odba 1.0.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.
- data/History.txt +5 -0
- data/LICENSE +459 -0
- data/Manifest.txt +38 -0
- data/README.txt +32 -0
- data/Rakefile +28 -0
- data/install.rb +1098 -0
- data/lib/odba.rb +72 -0
- data/lib/odba/18_19_loading_compatibility.rb +71 -0
- data/lib/odba/cache.rb +603 -0
- data/lib/odba/cache_entry.rb +122 -0
- data/lib/odba/connection_pool.rb +87 -0
- data/lib/odba/drbwrapper.rb +88 -0
- data/lib/odba/id_server.rb +26 -0
- data/lib/odba/index.rb +395 -0
- data/lib/odba/index_definition.rb +24 -0
- data/lib/odba/marshal.rb +18 -0
- data/lib/odba/odba.rb +45 -0
- data/lib/odba/odba_error.rb +12 -0
- data/lib/odba/persistable.rb +621 -0
- data/lib/odba/storage.rb +628 -0
- data/lib/odba/stub.rb +187 -0
- data/sql/collection.sql +6 -0
- data/sql/create_tables.sql +6 -0
- data/sql/object.sql +8 -0
- data/sql/object_connection.sql +7 -0
- data/test/suite.rb +8 -0
- data/test/test_array.rb +108 -0
- data/test/test_cache.rb +725 -0
- data/test/test_cache_entry.rb +109 -0
- data/test/test_connection_pool.rb +77 -0
- data/test/test_drbwrapper.rb +102 -0
- data/test/test_hash.rb +144 -0
- data/test/test_id_server.rb +43 -0
- data/test/test_index.rb +371 -0
- data/test/test_marshal.rb +28 -0
- data/test/test_persistable.rb +625 -0
- data/test/test_storage.rb +829 -0
- data/test/test_stub.rb +226 -0
- metadata +134 -0
@@ -0,0 +1,122 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#-- CacheEntry -- odba -- 29.04.2004 -- hwyss@ywesee.com mwalder@ywesee.com
|
3
|
+
|
4
|
+
module ODBA
|
5
|
+
class CacheEntry # :nodoc: all
|
6
|
+
@@id_table = {}
|
7
|
+
@@finalizer = proc { |object_id|
|
8
|
+
if(odba_id = @@id_table.delete(object_id))
|
9
|
+
ODBA.cache.invalidate odba_id
|
10
|
+
end
|
11
|
+
}
|
12
|
+
attr_accessor :last_access
|
13
|
+
attr_reader :accessed_by, :odba_id, :odba_object_id
|
14
|
+
def initialize(obj)
|
15
|
+
@odba_id = obj.odba_id
|
16
|
+
update obj
|
17
|
+
@accessed_by = {}
|
18
|
+
@odba_observers = obj.odba_observers
|
19
|
+
end
|
20
|
+
def update obj
|
21
|
+
@last_access = Time.now
|
22
|
+
@odba_object = obj
|
23
|
+
@odba_class = obj.class
|
24
|
+
@odba_id = obj.odba_id
|
25
|
+
unless @odba_object_id == obj.object_id
|
26
|
+
@@id_table.delete @odba_object_id
|
27
|
+
@odba_object_id = obj.object_id
|
28
|
+
@@id_table.store @odba_object_id, @odba_id
|
29
|
+
ObjectSpace.define_finalizer obj, @@finalizer
|
30
|
+
end
|
31
|
+
end
|
32
|
+
def object_id2ref(object_id, odba_id)
|
33
|
+
if (obj = ObjectSpace._id2ref(object_id)) \
|
34
|
+
&& obj.is_a?(Persistable) && !obj.odba_unsaved? \
|
35
|
+
&& obj.odba_id == odba_id
|
36
|
+
obj
|
37
|
+
end
|
38
|
+
rescue RangeError, NoMethodError => e
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
def odba_id2ref(odba_id)
|
42
|
+
odba_id && ODBA.cache.include?(odba_id) && ODBA.cache.fetch(odba_id)
|
43
|
+
end
|
44
|
+
def odba_add_reference(object)
|
45
|
+
@accessed_by.store(object.object_id, object.odba_id)
|
46
|
+
object
|
47
|
+
end
|
48
|
+
def odba_cut_connections!
|
49
|
+
@accessed_by.each { |object_id, odba_id|
|
50
|
+
if((item = odba_id2ref(odba_id) || object_id2ref(object_id, odba_id)) \
|
51
|
+
&& item.respond_to?(:odba_cut_connection))
|
52
|
+
item.odba_cut_connection(_odba_object)
|
53
|
+
end
|
54
|
+
}
|
55
|
+
end
|
56
|
+
def odba_notify_observers(*args)
|
57
|
+
@odba_observers.each { |obs| obs.odba_update(*args) }
|
58
|
+
end
|
59
|
+
def odba_object
|
60
|
+
@last_access = Time.now
|
61
|
+
@odba_object = _odba_object
|
62
|
+
@odba_object || ODBA.cache.fetch(@odba_id)
|
63
|
+
end
|
64
|
+
def _odba_object
|
65
|
+
@odba_object || object_id2ref(@odba_object_id, @odba_id)
|
66
|
+
end
|
67
|
+
def odba_old?(retire_horizon = Time.now - ODBA.cache.retire_age)
|
68
|
+
!_odba_object.odba_unsaved? \
|
69
|
+
&& (retire_horizon > @last_access)
|
70
|
+
end
|
71
|
+
def odba_retire opts={}
|
72
|
+
# replace with stubs in accessed_by
|
73
|
+
instance = _odba_object
|
74
|
+
if opts[:force]
|
75
|
+
@accessed_by.each do |object_id, odba_id|
|
76
|
+
if item = odba_id2ref(odba_id)
|
77
|
+
item.odba_stubize instance, opts
|
78
|
+
elsif(item = object_id2ref(object_id, odba_id))
|
79
|
+
if item.is_a?(Persistable) && !item.is_a?(Stub)
|
80
|
+
item.odba_stubize instance, opts
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
@accessed_by.clear
|
85
|
+
@odba_object = nil
|
86
|
+
else
|
87
|
+
@accessed_by.delete_if { |object_id, odba_id|
|
88
|
+
if(item = odba_id2ref(odba_id))
|
89
|
+
item.odba_stubize instance
|
90
|
+
elsif(item = object_id2ref(object_id, odba_id))
|
91
|
+
case item
|
92
|
+
when Stub
|
93
|
+
true
|
94
|
+
when Array, Hash
|
95
|
+
false
|
96
|
+
when Persistable
|
97
|
+
item.odba_stubize instance
|
98
|
+
else
|
99
|
+
true
|
100
|
+
end
|
101
|
+
else
|
102
|
+
true
|
103
|
+
end
|
104
|
+
}
|
105
|
+
if @accessed_by.empty?
|
106
|
+
@odba_object = nil
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
def odba_replace!(obj)
|
111
|
+
oldhash = _odba_object.hash
|
112
|
+
_odba_object.odba_replace!(obj)
|
113
|
+
if(_odba_object.hash != oldhash)
|
114
|
+
@accessed_by.each { |object_id, odba_id|
|
115
|
+
if(item = odba_id2ref(odba_id) || object_id2ref(object_id, odba_id))
|
116
|
+
item.rehash if(item.respond_to? :rehash)
|
117
|
+
end
|
118
|
+
}
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#-- ConnectionPool -- ODBA -- 08.03.2005 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'dbi'
|
5
|
+
require 'thread'
|
6
|
+
|
7
|
+
module ODBA
|
8
|
+
class ConnectionPool
|
9
|
+
POOL_SIZE = 5
|
10
|
+
SETUP_RETRIES = 3
|
11
|
+
attr_reader :connections
|
12
|
+
# All connections are delegated to DBI. The constructor simply records
|
13
|
+
# the DBI-arguments and reuses them to setup connections when needed.
|
14
|
+
def initialize(*dbi_args)
|
15
|
+
@dbi_args = dbi_args
|
16
|
+
@opts = @dbi_args.last.is_a?(Hash) ? @dbi_args.pop : Hash.new
|
17
|
+
@connections = []
|
18
|
+
@mutex = Mutex.new
|
19
|
+
connect
|
20
|
+
end
|
21
|
+
def next_connection # :nodoc:
|
22
|
+
conn = nil
|
23
|
+
@mutex.synchronize {
|
24
|
+
conn = @connections.shift
|
25
|
+
}
|
26
|
+
yield(conn)
|
27
|
+
ensure
|
28
|
+
@mutex.synchronize {
|
29
|
+
@connections.push(conn)
|
30
|
+
}
|
31
|
+
end
|
32
|
+
def method_missing(method, *args, &block) # :nodoc:
|
33
|
+
tries = SETUP_RETRIES
|
34
|
+
begin
|
35
|
+
next_connection { |conn|
|
36
|
+
conn.send(method, *args, &block)
|
37
|
+
}
|
38
|
+
rescue NoMethodError, DBI::Error => e
|
39
|
+
warn e
|
40
|
+
if(tries > 0 && (!e.is_a?(DBI::ProgrammingError) \
|
41
|
+
|| e.message == 'no connection to the server'))
|
42
|
+
sleep(SETUP_RETRIES - tries)
|
43
|
+
tries -= 1
|
44
|
+
reconnect
|
45
|
+
retry
|
46
|
+
else
|
47
|
+
raise
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
def size
|
52
|
+
@connections.size
|
53
|
+
end
|
54
|
+
alias :pool_size :size
|
55
|
+
def connect # :nodoc:
|
56
|
+
@mutex.synchronize { _connect }
|
57
|
+
end
|
58
|
+
def _connect # :nodoc:
|
59
|
+
POOL_SIZE.times {
|
60
|
+
conn = DBI.connect(*@dbi_args)
|
61
|
+
if encoding = @opts[:client_encoding]
|
62
|
+
conn.execute "SET CLIENT_ENCODING TO '#{encoding}'"
|
63
|
+
end
|
64
|
+
@connections.push(conn)
|
65
|
+
}
|
66
|
+
end
|
67
|
+
def disconnect # :nodoc:
|
68
|
+
@mutex.synchronize { _disconnect }
|
69
|
+
end
|
70
|
+
def _disconnect # :nodoc:
|
71
|
+
while(conn = @connections.shift)
|
72
|
+
begin
|
73
|
+
conn.disconnect
|
74
|
+
rescue DBI::InterfaceError, Exception
|
75
|
+
## we're not interested, since we are disconnecting anyway
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
def reconnect # :nodoc:
|
81
|
+
@mutex.synchronize {
|
82
|
+
_disconnect
|
83
|
+
_connect
|
84
|
+
}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# DRbWrapper -- ydim -- 11.01.2006 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'drb'
|
5
|
+
require 'odba/persistable'
|
6
|
+
require 'odba/stub'
|
7
|
+
require 'odba/odba'
|
8
|
+
require 'drb/timeridconv'
|
9
|
+
|
10
|
+
module ODBA
|
11
|
+
class DRbWrapper
|
12
|
+
instance_methods.each { |m|
|
13
|
+
undef_method(m) unless m =~ /^(__)|(respond_to\?|object_id$)/ }
|
14
|
+
include DRb::DRbUndumped
|
15
|
+
def initialize(obj)
|
16
|
+
@obj = obj
|
17
|
+
end
|
18
|
+
def respond_to?(sym, *args)
|
19
|
+
super || @obj.respond_to?(sym, *args)
|
20
|
+
end
|
21
|
+
def method_missing(sym, *args)
|
22
|
+
if(block_given?)
|
23
|
+
res = @obj.__send__(sym, *args) { |*block_args|
|
24
|
+
yield *block_args.collect { |arg| __wrap(arg) }
|
25
|
+
}
|
26
|
+
__wrap(res)
|
27
|
+
else
|
28
|
+
res = @obj.__send__(sym, *args)
|
29
|
+
if(res.is_a?(Array))
|
30
|
+
res.collect { |item| __wrap(item) }
|
31
|
+
elsif(res.is_a?(Hash))
|
32
|
+
res.inject({}) { |memo, (key, value)|
|
33
|
+
memo.store(__wrap(key), __wrap(value))
|
34
|
+
memo
|
35
|
+
}
|
36
|
+
else
|
37
|
+
__wrap(res)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
def __wrap(obj)
|
42
|
+
if(obj.is_a?(ODBA::Persistable))
|
43
|
+
DRbWrapper.new(obj.odba_instance)
|
44
|
+
else
|
45
|
+
obj
|
46
|
+
end
|
47
|
+
end
|
48
|
+
def __wrappee
|
49
|
+
@obj
|
50
|
+
end
|
51
|
+
end
|
52
|
+
class DRbIdConv < DRb::DRbIdConv
|
53
|
+
def initialize(*args)
|
54
|
+
super
|
55
|
+
@unsaved = {}
|
56
|
+
end
|
57
|
+
def odba_update(key, odba_id, object_id)
|
58
|
+
case key
|
59
|
+
when :store
|
60
|
+
@unsaved.store(object_id, odba_id)
|
61
|
+
when :clean, :delete
|
62
|
+
@unsaved.delete(object_id)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
def to_obj(ref)
|
66
|
+
test = ref
|
67
|
+
if(test.is_a?(String) || (test = @unsaved[ref]))
|
68
|
+
DRbWrapper.new(ODBA.cache.fetch(test.to_i))
|
69
|
+
else
|
70
|
+
super
|
71
|
+
end
|
72
|
+
rescue RuntimeError => e
|
73
|
+
raise RangeError, e.message
|
74
|
+
end
|
75
|
+
def to_id(obj)
|
76
|
+
if(obj.is_a?(ODBA::Persistable))
|
77
|
+
if(obj.odba_unsaved?)
|
78
|
+
obj.odba_add_observer(self)
|
79
|
+
super
|
80
|
+
else
|
81
|
+
obj.odba_id.to_s
|
82
|
+
end
|
83
|
+
else
|
84
|
+
super
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#-- IdServer -- odba -- 10.11.2004 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'odba/persistable'
|
5
|
+
require 'thread'
|
6
|
+
|
7
|
+
module ODBA
|
8
|
+
class IdServer
|
9
|
+
include Persistable
|
10
|
+
ODBA_SERIALIZABLE = ['@ids']
|
11
|
+
ODBA_EXCLUDE_VARS = ['@mutex']
|
12
|
+
def initialize
|
13
|
+
@ids = {}
|
14
|
+
end
|
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
|
+
end
|
data/lib/odba/index.rb
ADDED
@@ -0,0 +1,395 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#-- Index -- odba -- 13.05.2004 -- rwaltert@ywesee.com
|
3
|
+
|
4
|
+
require 'odba/persistable'
|
5
|
+
|
6
|
+
module ODBA
|
7
|
+
# Indices in ODBA are defined by origin and target (which may be identical)
|
8
|
+
# Any time a Persistable of class _target_klass_ or _origin_klass_ is stored,
|
9
|
+
# all corresponding indices are updated. To make this possible, we have to tell
|
10
|
+
# Index, how to navigate from _origin_ to _target_ and vice versa.
|
11
|
+
# This entails the Limitation that these paths must not change without
|
12
|
+
# _origin_ and/or _target_ being stored.
|
13
|
+
# Further, _search_term_ must be resolved in relation to _origin_.
|
14
|
+
class IndexCommon # :nodoc: all
|
15
|
+
include Persistable
|
16
|
+
if RUBY_VERSION >= '1.9'
|
17
|
+
ODBA_EXCLUDE_VARS = [
|
18
|
+
:@proc_origin, :@proc_target, :@proc_resolve_search_term
|
19
|
+
]
|
20
|
+
else
|
21
|
+
ODBA_EXCLUDE_VARS = [
|
22
|
+
'@proc_origin', '@proc_target', '@proc_resolve_search_term'
|
23
|
+
]
|
24
|
+
end
|
25
|
+
attr_accessor :origin_klass, :target_klass, :resolve_origin, :resolve_target,
|
26
|
+
:resolve_search_term, :index_name, :dictionary, :class_filter
|
27
|
+
def initialize(index_definition, origin_module)
|
28
|
+
@origin_klass = origin_module.instance_eval(index_definition.origin_klass.to_s)
|
29
|
+
@target_klass = origin_module.instance_eval(index_definition.target_klass.to_s)
|
30
|
+
@resolve_origin = index_definition.resolve_origin
|
31
|
+
@resolve_target = index_definition.resolve_target
|
32
|
+
@index_name = index_definition.index_name
|
33
|
+
@resolve_search_term = index_definition.resolve_search_term
|
34
|
+
@dictionary = index_definition.dictionary
|
35
|
+
@class_filter = index_definition.class_filter
|
36
|
+
end
|
37
|
+
def current_origin_ids(target_id) # :nodoc:
|
38
|
+
ODBA.storage.index_origin_ids(@index_name, target_id)
|
39
|
+
end
|
40
|
+
def current_target_ids(origin_id) # :nodoc:
|
41
|
+
ODBA.storage.index_target_ids(@index_name, origin_id)
|
42
|
+
end
|
43
|
+
def delete(object) # :nodoc:
|
44
|
+
if(object.is_a?(@origin_klass))
|
45
|
+
ODBA.storage.delete_index_element(@index_name, object.odba_id,
|
46
|
+
'origin_id')
|
47
|
+
end
|
48
|
+
if(object.is_a?(@target_klass))
|
49
|
+
ODBA.storage.delete_index_element(@index_name, object.odba_id,
|
50
|
+
'target_id')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
def delete_origin(origin_id, term) # :nodoc:
|
54
|
+
ODBA.storage.index_delete_origin(@index_name, origin_id, term)
|
55
|
+
end
|
56
|
+
def delete_target(origin_id, old_term, target_id) # :nodoc:
|
57
|
+
ODBA.storage.index_delete_target(@index_name, origin_id,
|
58
|
+
old_term, target_id)
|
59
|
+
end
|
60
|
+
def do_update_index(origin_id, term, target_id=nil) # :nodoc:
|
61
|
+
ODBA.storage.update_index(@index_name, origin_id, term, target_id)
|
62
|
+
end
|
63
|
+
def fill(targets)
|
64
|
+
@proc_origin = nil
|
65
|
+
rows = []
|
66
|
+
targets.flatten.each { |target|
|
67
|
+
target_id = target.odba_id
|
68
|
+
origins = proc_instance_origin.call(target)
|
69
|
+
origins.each { |origin|
|
70
|
+
search_terms(origin).each { |term|
|
71
|
+
do_update_index( origin.odba_id, term, target_id)
|
72
|
+
}
|
73
|
+
}
|
74
|
+
}
|
75
|
+
end
|
76
|
+
def keys(length=nil)
|
77
|
+
ODBA.storage.index_fetch_keys(@index_name, length).delete_if { |k|
|
78
|
+
k.empty? }
|
79
|
+
end
|
80
|
+
def matches(substring, limit=nil, offset=0)
|
81
|
+
ODBA.storage.index_matches @index_name, substring, limit, offset
|
82
|
+
end
|
83
|
+
def origin_class?(klass)
|
84
|
+
(@origin_klass == klass)
|
85
|
+
end
|
86
|
+
def proc_instance_origin # :nodoc:
|
87
|
+
if(@proc_origin.nil?)
|
88
|
+
if(@resolve_origin.to_s.empty?)
|
89
|
+
@proc_origin = Proc.new { |odba_item| [odba_item] }
|
90
|
+
else
|
91
|
+
src = <<-EOS
|
92
|
+
Proc.new { |odba_item|
|
93
|
+
res = [odba_item.#{@resolve_origin}]
|
94
|
+
res.flatten!
|
95
|
+
res.compact!
|
96
|
+
res
|
97
|
+
}
|
98
|
+
EOS
|
99
|
+
@proc_origin = eval(src)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
@proc_origin
|
103
|
+
end
|
104
|
+
def proc_instance_target # :nodoc:
|
105
|
+
if(@proc_target.nil?)
|
106
|
+
if(@resolve_target.to_s.empty?)
|
107
|
+
@proc_target = Proc.new { |odba_item| [odba_item] }
|
108
|
+
#elsif(@resolve_target == :odba_skip)
|
109
|
+
# @proc_target = Proc.new { [] }
|
110
|
+
else
|
111
|
+
src = <<-EOS
|
112
|
+
Proc.new { |odba_item|
|
113
|
+
res = [odba_item.#{@resolve_target}]
|
114
|
+
res.flatten!
|
115
|
+
res.compact!
|
116
|
+
res
|
117
|
+
}
|
118
|
+
EOS
|
119
|
+
@proc_target = eval(src)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
@proc_target
|
123
|
+
end
|
124
|
+
def proc_resolve_search_term # :nodoc:
|
125
|
+
if(@proc_resolve_search_term.nil?)
|
126
|
+
if(@resolve_search_term.to_s.empty?)
|
127
|
+
@proc_resolve_search_term = Proc.new { |origin|
|
128
|
+
origin.to_s.downcase
|
129
|
+
}
|
130
|
+
else
|
131
|
+
src = <<-EOS
|
132
|
+
Proc.new { |origin|
|
133
|
+
begin
|
134
|
+
origin.#{@resolve_search_term}
|
135
|
+
rescue NameError => e
|
136
|
+
nil
|
137
|
+
end
|
138
|
+
}
|
139
|
+
EOS
|
140
|
+
@proc_resolve_search_term = eval(src)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
@proc_resolve_search_term
|
144
|
+
end
|
145
|
+
def search_term(origin) # :nodoc:
|
146
|
+
proc_resolve_search_term.call(origin)
|
147
|
+
end
|
148
|
+
def search_terms(origin)
|
149
|
+
[search_term(origin)].flatten.compact.uniq
|
150
|
+
end
|
151
|
+
def set_relevance(meta, rows) # :nodoc:
|
152
|
+
if(meta.respond_to?(:set_relevance))
|
153
|
+
rows.each { |row|
|
154
|
+
meta.set_relevance(row.at(0), row.at(1))
|
155
|
+
}
|
156
|
+
end
|
157
|
+
end
|
158
|
+
def update(object)
|
159
|
+
@class_filter ||= :is_a?
|
160
|
+
if(object.send(@class_filter, @target_klass))
|
161
|
+
update_target(object)
|
162
|
+
elsif(object.send(@class_filter, @origin_klass))
|
163
|
+
update_origin(object)
|
164
|
+
end
|
165
|
+
rescue StandardError => err
|
166
|
+
warn <<-EOS
|
167
|
+
#{err.class}: #{err.message} when updating index '#{@index_name}' with a #{object.class}
|
168
|
+
#{err.backtrace[0,4]}
|
169
|
+
[...]
|
170
|
+
EOS
|
171
|
+
end
|
172
|
+
def update_origin(object) # :nodoc:
|
173
|
+
origin_id = object.odba_id
|
174
|
+
search_terms = search_terms(object)
|
175
|
+
current = current_target_ids(origin_id)
|
176
|
+
target_ids = []
|
177
|
+
current_terms = []
|
178
|
+
current.each { |row|
|
179
|
+
target_ids.push(row[0])
|
180
|
+
current_terms.push(row[1])
|
181
|
+
}
|
182
|
+
current_terms.uniq!
|
183
|
+
target_ids.uniq!
|
184
|
+
(current_terms - search_terms).each { |term|
|
185
|
+
delete_origin(origin_id, term)
|
186
|
+
}
|
187
|
+
new_terms = search_terms - current_terms
|
188
|
+
unless(new_terms.empty?)
|
189
|
+
target_ids.each { |target_id|
|
190
|
+
new_terms.each { |term|
|
191
|
+
do_update_index(origin_id, term, target_id)
|
192
|
+
}
|
193
|
+
}
|
194
|
+
end
|
195
|
+
end
|
196
|
+
def update_target(target) # :nodoc:
|
197
|
+
target_id = target.odba_id
|
198
|
+
current = current_origin_ids(target_id)
|
199
|
+
old_terms = current.collect { |row|
|
200
|
+
[row[0], row[1]]
|
201
|
+
}
|
202
|
+
origins = proc_instance_origin.call(target)
|
203
|
+
new_terms = []
|
204
|
+
origins.each { |origin|
|
205
|
+
origin_id = origin.odba_id
|
206
|
+
search_terms(origin).each { |term|
|
207
|
+
new_terms.push([origin_id, term])
|
208
|
+
}
|
209
|
+
}
|
210
|
+
(old_terms - new_terms).each { |origin_id, terms|
|
211
|
+
delete_target(origin_id, terms, target_id)
|
212
|
+
}
|
213
|
+
(new_terms - old_terms).each { |origin_id, terms|
|
214
|
+
do_update_index(origin_id, terms, target_id)
|
215
|
+
}
|
216
|
+
end
|
217
|
+
end
|
218
|
+
# Currently there are 3 predefined Index-classes
|
219
|
+
# For Sample Code see
|
220
|
+
# http://dev.ywesee.com/wiki.php/ODBA/SimpleIndex
|
221
|
+
# http://dev.ywesee.com/wiki.php/ODBA/ConditionIndex
|
222
|
+
# http://dev.ywesee.com/wiki.php/ODBA/FulltextIndex
|
223
|
+
class Index < IndexCommon # :nodoc: all
|
224
|
+
def initialize(index_definition, origin_module) # :nodoc:
|
225
|
+
super(index_definition, origin_module)
|
226
|
+
ODBA.storage.create_index(index_definition.index_name)
|
227
|
+
end
|
228
|
+
def fetch_ids(search_term, meta=nil) # :nodoc:
|
229
|
+
exact = meta.respond_to?(:exact) && meta.exact
|
230
|
+
limit = meta.respond_to?(:limit) && meta.limit
|
231
|
+
rows = ODBA.storage.retrieve_from_index(@index_name,
|
232
|
+
search_term.to_s.downcase,
|
233
|
+
exact, limit)
|
234
|
+
set_relevance(meta, rows)
|
235
|
+
rows.collect { |row| row.at(0) }
|
236
|
+
end
|
237
|
+
def update_origin(object) # :nodoc:
|
238
|
+
# Possible changes:
|
239
|
+
# - search_terms of origin have changed
|
240
|
+
# - targets have changed, except if @resolve_target == :none
|
241
|
+
# => we need a matrix of all current [term, target_id]
|
242
|
+
# and of all new [term, target_id]
|
243
|
+
origin_id = object.odba_id
|
244
|
+
search_terms = search_terms(object)
|
245
|
+
current = current_target_ids(origin_id)
|
246
|
+
target_ids = if @resolve_target == :none
|
247
|
+
current.dup
|
248
|
+
else
|
249
|
+
proc_instance_target.call(object).collect { |obj|
|
250
|
+
obj.odba_id }
|
251
|
+
end
|
252
|
+
target_ids.compact!
|
253
|
+
target_ids.uniq!
|
254
|
+
current_ids = []
|
255
|
+
current_terms = []
|
256
|
+
current.each { |row|
|
257
|
+
current_ids.push(row[0])
|
258
|
+
current_terms.push(row[1])
|
259
|
+
}
|
260
|
+
current_ids.uniq!
|
261
|
+
current_terms.uniq!
|
262
|
+
current_combinations = current_ids.inject([]) { |memo, id|
|
263
|
+
current_terms.each { |term| memo.push [term, id] }
|
264
|
+
memo
|
265
|
+
}
|
266
|
+
combinations = target_ids.inject([]) { |memo, id|
|
267
|
+
search_terms.each { |term| memo.push [term, id] }
|
268
|
+
memo
|
269
|
+
}
|
270
|
+
(current_combinations - combinations).each { |pair|
|
271
|
+
delete_target(origin_id, *pair)
|
272
|
+
}
|
273
|
+
(combinations - current_combinations).each { |pair|
|
274
|
+
do_update_index(origin_id, *pair)
|
275
|
+
}
|
276
|
+
end
|
277
|
+
def search_terms(origin)
|
278
|
+
super.collect { |term| term.to_s.downcase }.uniq
|
279
|
+
end
|
280
|
+
end
|
281
|
+
class ConditionIndex < IndexCommon # :nodoc: all
|
282
|
+
def initialize(index_definition, origin_module) # :nodoc:
|
283
|
+
super(index_definition, origin_module)
|
284
|
+
definition = {}
|
285
|
+
@resolve_search_term = {}
|
286
|
+
index_definition.resolve_search_term.each { |name, info|
|
287
|
+
if(info.is_a?(String))
|
288
|
+
info = { 'resolve' => info }
|
289
|
+
end
|
290
|
+
if(info['type'].nil?)
|
291
|
+
info['type'] = 'text'
|
292
|
+
end
|
293
|
+
@resolve_search_term.store(name, info)
|
294
|
+
definition.store(name, info['type'])
|
295
|
+
}
|
296
|
+
ODBA.storage.create_condition_index(@index_name, definition)
|
297
|
+
end
|
298
|
+
def current_ids(rows, id_name)
|
299
|
+
rows.collect { |row|
|
300
|
+
[
|
301
|
+
row[id_name],
|
302
|
+
@resolve_search_term.keys.collect { |key|
|
303
|
+
[key.to_s, row[key]] }.sort,
|
304
|
+
]
|
305
|
+
}
|
306
|
+
end
|
307
|
+
def current_origin_ids(target_id)
|
308
|
+
current_ids(ODBA.storage.condition_index_ids(@index_name,
|
309
|
+
target_id,
|
310
|
+
'target_id'),
|
311
|
+
'origin_id')
|
312
|
+
end
|
313
|
+
def current_target_ids(origin_id)
|
314
|
+
current_ids(ODBA.storage.condition_index_ids(@index_name,
|
315
|
+
origin_id,
|
316
|
+
'origin_id'),
|
317
|
+
'target_id')
|
318
|
+
end
|
319
|
+
def delete_origin(origin_id, search_terms)
|
320
|
+
ODBA.storage.condition_index_delete(@index_name, origin_id,
|
321
|
+
search_terms)
|
322
|
+
end
|
323
|
+
def delete_target(origin_id, search_terms, target_id)
|
324
|
+
ODBA.storage.condition_index_delete(@index_name, origin_id,
|
325
|
+
search_terms, target_id)
|
326
|
+
end
|
327
|
+
def do_update_index(origin_id, search_terms, target_id=nil) # :nodoc:
|
328
|
+
ODBA.storage.update_condition_index(@index_name, origin_id,
|
329
|
+
search_terms, target_id)
|
330
|
+
end
|
331
|
+
def fetch_ids(conditions, meta=nil) # :nodoc:
|
332
|
+
limit = meta.respond_to?(:limit) && meta.limit
|
333
|
+
rows = ODBA.storage.retrieve_from_condition_index(@index_name,
|
334
|
+
conditions,
|
335
|
+
limit)
|
336
|
+
set_relevance(meta, rows)
|
337
|
+
rows.collect { |row| row.at(0) }
|
338
|
+
end
|
339
|
+
def proc_resolve_search_term # :nodoc:
|
340
|
+
if(@proc_resolve_search_term.nil?)
|
341
|
+
src = <<-EOS
|
342
|
+
Proc.new { |origin|
|
343
|
+
values = {}
|
344
|
+
EOS
|
345
|
+
@resolve_search_term.each { |name, info|
|
346
|
+
src << <<-EOS
|
347
|
+
begin
|
348
|
+
values.store('#{name}', origin.#{info['resolve']})
|
349
|
+
rescue NameError
|
350
|
+
end
|
351
|
+
EOS
|
352
|
+
}
|
353
|
+
src << <<-EOS
|
354
|
+
values
|
355
|
+
}
|
356
|
+
EOS
|
357
|
+
@proc_resolve_search_term = eval(src)
|
358
|
+
end
|
359
|
+
@proc_resolve_search_term
|
360
|
+
end
|
361
|
+
def search_terms(origin)
|
362
|
+
super.collect { |data| data.to_a.sort }
|
363
|
+
end
|
364
|
+
end
|
365
|
+
class FulltextIndex < IndexCommon # :nodoc: all
|
366
|
+
def initialize(index_definition, origin_module) # :nodoc:
|
367
|
+
super(index_definition, origin_module)
|
368
|
+
ODBA.storage.create_fulltext_index(@index_name)
|
369
|
+
end
|
370
|
+
def current_origin_ids(target_id)
|
371
|
+
ODBA.storage.fulltext_index_delete(@index_name, target_id,
|
372
|
+
'target_id')
|
373
|
+
[]
|
374
|
+
end
|
375
|
+
def current_target_ids(origin_id)
|
376
|
+
ODBA.storage.fulltext_index_target_ids(@index_name, origin_id)
|
377
|
+
end
|
378
|
+
def delete_origin(origin_id, term)
|
379
|
+
ODBA.storage.fulltext_index_delete(@index_name, origin_id,
|
380
|
+
'origin_id')
|
381
|
+
end
|
382
|
+
def fetch_ids(search_term, meta=nil) # :nodoc:
|
383
|
+
limit = meta.respond_to?(:limit) && meta.limit
|
384
|
+
rows = ODBA.storage.retrieve_from_fulltext_index(@index_name,
|
385
|
+
search_term, @dictionary, limit)
|
386
|
+
set_relevance(meta, rows)
|
387
|
+
rows.collect { |row| row.at(0) }
|
388
|
+
end
|
389
|
+
def do_update_index(origin_id, search_text, target_id=nil) # :nodoc:
|
390
|
+
ODBA.storage.update_fulltext_index(@index_name, origin_id,
|
391
|
+
search_text, target_id,
|
392
|
+
@dictionary)
|
393
|
+
end
|
394
|
+
end
|
395
|
+
end
|