odba 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|