odba 1.1.8 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/devenv.yml +30 -0
- data/.github/workflows/ruby.yml +8 -7
- data/.gitignore +11 -0
- data/{History.txt → History.md} +43 -19
- data/README.md +79 -2
- data/Rakefile +3 -15
- data/devenv.lock +171 -0
- data/devenv.nix +53 -0
- data/devenv.yaml +8 -0
- data/lib/odba/cache.rb +401 -344
- data/lib/odba/cache_entry.rb +42 -31
- data/lib/odba/connection_pool.rb +99 -72
- data/lib/odba/drbwrapper.rb +26 -18
- data/lib/odba/id_server.rb +20 -20
- data/lib/odba/index.rb +249 -215
- data/lib/odba/index_definition.rb +17 -17
- data/lib/odba/marshal.rb +15 -14
- data/lib/odba/odba.rb +40 -32
- data/lib/odba/odba_error.rb +4 -2
- data/lib/odba/persistable.rb +460 -414
- data/lib/odba/storage.rb +328 -277
- data/lib/odba/stub.rb +166 -153
- data/lib/odba/version.rb +1 -1
- data/lib/odba.rb +9 -9
- data/odba.gemspec +8 -3
- data/test/example.rb +47 -0
- data/test/helper.rb +6 -0
- data/test/suite.rb +7 -0
- data/test/test_array.rb +38 -33
- data/test/test_cache.rb +657 -622
- data/test/test_cache_entry.rb +51 -71
- data/test/test_connection_pool.rb +70 -25
- data/test/test_drbwrapper.rb +24 -19
- data/test/test_hash.rb +98 -89
- data/test/test_id_server.rb +30 -32
- data/test/test_index.rb +191 -158
- data/test/test_marshal.rb +15 -25
- data/test/test_persistable.rb +378 -369
- data/test/test_storage.rb +510 -471
- data/test/test_stub.rb +147 -135
- metadata +63 -20
- data/Guide.txt +0 -36
- data/Manifest.txt +0 -38
- data/install.rb +0 -1098
- data/lib/odba/18_19_loading_compatibility.rb +0 -77
data/lib/odba/cache_entry.rb
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
# ODBA::CacheEntry -- odba -- 09.01.2012 -- mhatakeyama@ywesee.com
|
|
4
4
|
# ODBA::CacheEntry -- odba -- 29.04.2004 -- hwyss@ywesee.com mwalder@ywesee.com
|
|
5
5
|
|
|
6
6
|
module ODBA
|
|
7
|
-
|
|
7
|
+
class CacheEntry # :nodoc: all
|
|
8
8
|
@@id_table = {}
|
|
9
9
|
@@finalizer = proc { |object_id|
|
|
10
|
-
if(odba_id = @@id_table.delete(object_id))
|
|
10
|
+
if (odba_id = @@id_table.delete(object_id))
|
|
11
11
|
ODBA.cache.invalidate odba_id
|
|
12
12
|
end
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
attr_accessor :last_access
|
|
15
|
+
attr_reader :accessed_by, :odba_id, :odba_object_id
|
|
16
16
|
def initialize(obj)
|
|
17
17
|
@odba_id = obj.odba_id
|
|
18
18
|
update obj
|
|
@@ -20,6 +20,7 @@ module ODBA
|
|
|
20
20
|
@odba_observers = obj.odba_observers
|
|
21
21
|
@cache_entry_mutex = Mutex.new
|
|
22
22
|
end
|
|
23
|
+
|
|
23
24
|
def update obj
|
|
24
25
|
@last_access = Time.now
|
|
25
26
|
@odba_object = obj
|
|
@@ -32,59 +33,68 @@ module ODBA
|
|
|
32
33
|
ObjectSpace.define_finalizer obj, @@finalizer
|
|
33
34
|
end
|
|
34
35
|
end
|
|
36
|
+
|
|
35
37
|
def object_id2ref(object_id, odba_id)
|
|
36
38
|
if (obj = ObjectSpace._id2ref(object_id)) \
|
|
37
39
|
&& obj.is_a?(Persistable) && !obj.odba_unsaved? \
|
|
38
40
|
&& obj.odba_id == odba_id
|
|
39
41
|
obj
|
|
40
42
|
end
|
|
41
|
-
rescue RangeError, NoMethodError
|
|
43
|
+
rescue RangeError, NoMethodError
|
|
42
44
|
nil
|
|
43
45
|
end
|
|
46
|
+
|
|
44
47
|
def odba_id2ref(odba_id)
|
|
45
48
|
odba_id && ODBA.cache.include?(odba_id) && ODBA.cache.fetch(odba_id)
|
|
46
49
|
end
|
|
47
|
-
|
|
50
|
+
|
|
51
|
+
def odba_add_reference(object)
|
|
48
52
|
return unless object.respond_to?(:odba_id)
|
|
49
53
|
@cache_entry_mutex.synchronize do
|
|
50
54
|
@accessed_by.store(object.object_id, object.odba_id)
|
|
51
55
|
end
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
object
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def odba_cut_connections!
|
|
55
60
|
@cache_entry_mutex.synchronize do
|
|
56
61
|
@accessed_by.each { |object_id, odba_id|
|
|
57
|
-
if(
|
|
58
|
-
&& item.respond_to?(:odba_cut_connection)
|
|
62
|
+
if (item = odba_id2ref(odba_id) || object_id2ref(object_id, odba_id)) \
|
|
63
|
+
&& item.respond_to?(:odba_cut_connection)
|
|
59
64
|
item.odba_cut_connection(_odba_object)
|
|
60
65
|
end
|
|
61
66
|
}
|
|
62
67
|
end
|
|
63
|
-
|
|
68
|
+
end
|
|
69
|
+
|
|
64
70
|
def odba_notify_observers(*args)
|
|
65
71
|
@odba_observers.each { |obs| obs.odba_update(*args) }
|
|
66
72
|
end
|
|
67
|
-
|
|
68
|
-
|
|
73
|
+
|
|
74
|
+
def odba_object
|
|
75
|
+
@last_access = Time.now
|
|
69
76
|
@odba_object = _odba_object
|
|
70
77
|
@odba_object || ODBA.cache.fetch(@odba_id)
|
|
71
78
|
end
|
|
79
|
+
|
|
72
80
|
def _odba_object
|
|
73
81
|
@odba_object || object_id2ref(@odba_object_id, @odba_id)
|
|
74
|
-
|
|
75
|
-
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def odba_old?(retire_horizon = Time.now - ODBA.cache.retire_age)
|
|
76
85
|
!_odba_object.odba_unsaved? \
|
|
77
86
|
&& (retire_horizon > @last_access)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def odba_retire opts = {}
|
|
90
|
+
# replace with stubs in accessed_by
|
|
81
91
|
instance = _odba_object
|
|
82
92
|
if opts[:force]
|
|
83
93
|
@cache_entry_mutex.synchronize do
|
|
84
94
|
@accessed_by.each do |object_id, odba_id|
|
|
85
95
|
if item = odba_id2ref(odba_id)
|
|
86
96
|
item.odba_stubize instance, opts
|
|
87
|
-
elsif(item = object_id2ref(object_id, odba_id))
|
|
97
|
+
elsif (item = object_id2ref(object_id, odba_id))
|
|
88
98
|
if item.is_a?(Persistable) && !item.is_a?(Stub)
|
|
89
99
|
item.odba_stubize instance, opts
|
|
90
100
|
end
|
|
@@ -95,9 +105,9 @@ module ODBA
|
|
|
95
105
|
@odba_object = nil
|
|
96
106
|
else
|
|
97
107
|
@accessed_by.delete_if { |object_id, odba_id|
|
|
98
|
-
if(item = odba_id2ref(odba_id))
|
|
108
|
+
if (item = odba_id2ref(odba_id))
|
|
99
109
|
item.odba_stubize instance
|
|
100
|
-
elsif(item = object_id2ref(object_id, odba_id))
|
|
110
|
+
elsif (item = object_id2ref(object_id, odba_id))
|
|
101
111
|
case item
|
|
102
112
|
when Stub
|
|
103
113
|
true
|
|
@@ -116,19 +126,20 @@ module ODBA
|
|
|
116
126
|
@odba_object = nil
|
|
117
127
|
end
|
|
118
128
|
end
|
|
119
|
-
|
|
120
|
-
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def odba_replace!(obj)
|
|
121
132
|
oldhash = _odba_object.hash
|
|
122
|
-
|
|
123
|
-
if
|
|
133
|
+
_odba_object.odba_replace!(obj)
|
|
134
|
+
if _odba_object.hash != oldhash
|
|
124
135
|
@cache_entry_mutex.synchronize do
|
|
125
136
|
@accessed_by.each { |object_id, odba_id|
|
|
126
|
-
if(item = odba_id2ref(odba_id) || object_id2ref(object_id, odba_id))
|
|
127
|
-
item.rehash if
|
|
137
|
+
if (item = odba_id2ref(odba_id) || object_id2ref(object_id, odba_id))
|
|
138
|
+
item.rehash if item.respond_to? :rehash
|
|
128
139
|
end
|
|
129
140
|
}
|
|
130
141
|
end
|
|
131
142
|
end
|
|
132
|
-
|
|
133
|
-
|
|
143
|
+
end
|
|
144
|
+
end
|
|
134
145
|
end
|
data/lib/odba/connection_pool.rb
CHANGED
|
@@ -1,63 +1,87 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
# ODBA::ConnectionPool -- odba -- 08.12.2011 -- mhatakeyama@ywesee.com
|
|
4
4
|
# ODBA::ConnectionPool -- odba -- 08.03.2005 -- hwyss@ywesee.com
|
|
5
5
|
|
|
6
|
-
require
|
|
7
|
-
require 'thread'
|
|
6
|
+
require "dbi"
|
|
8
7
|
|
|
9
8
|
module ODBA
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
9
|
+
class ConnectionPool
|
|
10
|
+
POOL_SIZE = 5
|
|
11
|
+
SETUP_RETRIES = 3
|
|
12
|
+
# ydbd-pg maps every PG::Error - including connection level failures -
|
|
13
|
+
# onto DBI::ProgrammingError, so for that class the message is the only
|
|
14
|
+
# thing that tells a dead connection apart from a malformed statement.
|
|
15
|
+
# Without this list a restart of the database server leaves every pooled
|
|
16
|
+
# connection permanently broken, because the reconnect below never runs.
|
|
17
|
+
CONNECTION_LOST_MESSAGES = /
|
|
18
|
+
no\ connection\ to\ the\ server
|
|
19
|
+
| PQsocket
|
|
20
|
+
| server\ closed\ the\ connection
|
|
21
|
+
| connection\ not\ open
|
|
22
|
+
| terminating\ connection
|
|
23
|
+
| SSL\ connection\ has\ been\ closed
|
|
24
|
+
| could\ not\ connect\ to\ server
|
|
25
|
+
/xi
|
|
26
|
+
# attr_reader :connections
|
|
27
|
+
attr_reader :connections, :dbi_args
|
|
28
|
+
# All connections are delegated to DBI. The constructor simply records
|
|
29
|
+
# the DBI-arguments and reuses them to setup connections when needed.
|
|
30
|
+
def initialize(*dbi_args)
|
|
31
|
+
@dbi_args = dbi_args
|
|
32
|
+
@opts = @dbi_args.last.is_a?(Hash) ? @dbi_args.pop : {}
|
|
33
|
+
@connections = []
|
|
34
|
+
@mutex = Mutex.new
|
|
35
|
+
connect
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def next_connection # :nodoc:
|
|
39
|
+
conn = nil
|
|
40
|
+
@mutex.synchronize {
|
|
41
|
+
conn = @connections.shift
|
|
42
|
+
}
|
|
43
|
+
yield(conn)
|
|
44
|
+
ensure
|
|
45
|
+
@mutex.synchronize {
|
|
46
|
+
@connections.push(conn)
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def method_missing(method, *args, &block) # :nodoc:
|
|
51
|
+
tries = SETUP_RETRIES
|
|
52
|
+
begin
|
|
53
|
+
next_connection { |conn|
|
|
54
|
+
conn.send(method, *args, &block)
|
|
55
|
+
}
|
|
56
|
+
rescue NoMethodError, DBI::Error => e
|
|
42
57
|
warn e
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
if tries > 0 && retryable?(e)
|
|
59
|
+
sleep(SETUP_RETRIES - tries)
|
|
60
|
+
tries -= 1
|
|
61
|
+
reconnect
|
|
62
|
+
retry
|
|
63
|
+
else
|
|
64
|
+
raise
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# A statement that failed because the connection is gone is worth
|
|
70
|
+
# retrying on a fresh one. A statement that failed on its own merits is
|
|
71
|
+
# not - retrying it would only repeat the same error three times.
|
|
72
|
+
def retryable?(error) # :nodoc:
|
|
73
|
+
!error.is_a?(DBI::ProgrammingError) ||
|
|
74
|
+
CONNECTION_LOST_MESSAGES.match?(error.message.to_s)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def size
|
|
78
|
+
@connections.size
|
|
79
|
+
end
|
|
80
|
+
alias_method :pool_size, :size
|
|
81
|
+
def connect # :nodoc:
|
|
82
|
+
@mutex.synchronize { _connect }
|
|
83
|
+
end
|
|
84
|
+
|
|
61
85
|
def _connect # :nodoc:
|
|
62
86
|
POOL_SIZE.times {
|
|
63
87
|
conn = DBI.connect(*@dbi_args)
|
|
@@ -67,24 +91,27 @@ module ODBA
|
|
|
67
91
|
@connections.push(conn)
|
|
68
92
|
}
|
|
69
93
|
end
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
94
|
+
|
|
95
|
+
def disconnect # :nodoc:
|
|
96
|
+
@mutex.synchronize { _disconnect }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def _disconnect # :nodoc:
|
|
100
|
+
while (conn = @connections.shift)
|
|
101
|
+
begin
|
|
102
|
+
conn.disconnect
|
|
103
|
+
rescue DBI::InterfaceError, Exception
|
|
104
|
+
## we're not interested, since we are disconnecting anyway
|
|
79
105
|
nil
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def reconnect # :nodoc:
|
|
111
|
+
@mutex.synchronize {
|
|
112
|
+
_disconnect
|
|
113
|
+
_connect
|
|
114
|
+
}
|
|
115
|
+
end
|
|
116
|
+
end
|
|
90
117
|
end
|
data/lib/odba/drbwrapper.rb
CHANGED
|
@@ -1,59 +1,65 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# DRbWrapper -- ydim -- 11.01.2006 -- hwyss@ywesee.com
|
|
3
3
|
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
7
|
-
require
|
|
8
|
-
require
|
|
4
|
+
require "drb"
|
|
5
|
+
require "odba/persistable"
|
|
6
|
+
require "odba/stub"
|
|
7
|
+
require "odba/odba"
|
|
8
|
+
require "drb/timeridconv"
|
|
9
9
|
|
|
10
10
|
module ODBA
|
|
11
|
-
class DRbWrapper
|
|
12
|
-
instance_methods.each { |m|
|
|
13
|
-
undef_method(m) unless
|
|
11
|
+
class DRbWrapper
|
|
12
|
+
instance_methods.each { |m|
|
|
13
|
+
undef_method(m) unless /^(__)|(respond_to\?|object_id$)/.match?(m)
|
|
14
|
+
}
|
|
14
15
|
include DRb::DRbUndumped
|
|
15
16
|
def initialize(obj)
|
|
16
17
|
@obj = obj
|
|
17
18
|
end
|
|
19
|
+
|
|
18
20
|
def respond_to?(sym, *args)
|
|
19
21
|
super || @obj.respond_to?(sym, *args)
|
|
20
22
|
end
|
|
23
|
+
|
|
21
24
|
def method_missing(sym, *args)
|
|
22
|
-
if
|
|
25
|
+
if block_given?
|
|
23
26
|
res = @obj.__send__(sym, *args) { |*block_args|
|
|
24
|
-
yield
|
|
27
|
+
yield(*block_args.collect { |arg| __wrap(arg) })
|
|
25
28
|
}
|
|
26
29
|
__wrap(res)
|
|
27
30
|
else
|
|
28
31
|
res = @obj.__send__(sym, *args)
|
|
29
|
-
if
|
|
32
|
+
if res.is_a?(Array)
|
|
30
33
|
res.collect { |item| __wrap(item) }
|
|
31
|
-
elsif
|
|
32
|
-
res.
|
|
34
|
+
elsif res.is_a?(Hash)
|
|
35
|
+
res.each_with_object({}) { |(key, value), memo|
|
|
33
36
|
memo.store(__wrap(key), __wrap(value))
|
|
34
|
-
memo
|
|
35
37
|
}
|
|
36
38
|
else
|
|
37
39
|
__wrap(res)
|
|
38
40
|
end
|
|
39
41
|
end
|
|
40
42
|
end
|
|
43
|
+
|
|
41
44
|
def __wrap(obj)
|
|
42
|
-
if
|
|
45
|
+
if obj.is_a?(ODBA::Persistable)
|
|
43
46
|
DRbWrapper.new(obj.odba_instance)
|
|
44
47
|
else
|
|
45
48
|
obj
|
|
46
49
|
end
|
|
47
50
|
end
|
|
51
|
+
|
|
48
52
|
def __wrappee
|
|
49
53
|
@obj
|
|
50
54
|
end
|
|
51
55
|
end
|
|
56
|
+
|
|
52
57
|
class DRbIdConv < DRb::DRbIdConv
|
|
53
58
|
def initialize(*args)
|
|
54
59
|
super
|
|
55
60
|
@unsaved = {}
|
|
56
61
|
end
|
|
62
|
+
|
|
57
63
|
def odba_update(key, odba_id, object_id)
|
|
58
64
|
case key
|
|
59
65
|
when :store
|
|
@@ -62,9 +68,10 @@ module ODBA
|
|
|
62
68
|
@unsaved.delete(object_id)
|
|
63
69
|
end
|
|
64
70
|
end
|
|
71
|
+
|
|
65
72
|
def to_obj(ref)
|
|
66
73
|
test = ref
|
|
67
|
-
if
|
|
74
|
+
if test.is_a?(String) || (test = @unsaved[ref])
|
|
68
75
|
DRbWrapper.new(ODBA.cache.fetch(test.to_i))
|
|
69
76
|
else
|
|
70
77
|
super
|
|
@@ -72,9 +79,10 @@ module ODBA
|
|
|
72
79
|
rescue RuntimeError => e
|
|
73
80
|
raise RangeError, e.message
|
|
74
81
|
end
|
|
82
|
+
|
|
75
83
|
def to_id(obj)
|
|
76
|
-
if
|
|
77
|
-
if
|
|
84
|
+
if obj.is_a?(ODBA::Persistable)
|
|
85
|
+
if obj.odba_unsaved?
|
|
78
86
|
obj.odba_add_observer(self)
|
|
79
87
|
super
|
|
80
88
|
else
|
data/lib/odba/id_server.rb
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
#-- IdServer -- odba -- 10.11.2004 -- hwyss@ywesee.com
|
|
3
3
|
|
|
4
|
-
require
|
|
5
|
-
require 'thread'
|
|
4
|
+
require "odba/persistable"
|
|
6
5
|
|
|
7
6
|
module ODBA
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
7
|
+
class IdServer
|
|
8
|
+
include Persistable
|
|
9
|
+
ODBA_SERIALIZABLE = ["@ids"]
|
|
10
|
+
ODBA_EXCLUDE_VARS = ["@mutex"]
|
|
11
|
+
def initialize
|
|
12
|
+
@ids = {}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def next_id(key, startval = 1)
|
|
16
|
+
@mutex ||= Mutex.new
|
|
17
|
+
res = nil
|
|
18
|
+
@mutex.synchronize {
|
|
19
|
+
@ids[key] ||= (startval - 1)
|
|
20
|
+
res = @ids[key] += 1
|
|
21
|
+
}
|
|
22
|
+
odba_store
|
|
23
|
+
res
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
26
|
end
|