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
data/lib/odba/storage.rb
ADDED
@@ -0,0 +1,628 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#-- Storage -- odba -- 29.04.2004 -- hwyss@ywesee.com rwaltert@ywesee.com mwalder@ywesee.com
|
3
|
+
|
4
|
+
require 'thread'
|
5
|
+
require 'singleton'
|
6
|
+
require 'dbi'
|
7
|
+
|
8
|
+
module ODBA
|
9
|
+
class Storage # :nodoc: all
|
10
|
+
include Singleton
|
11
|
+
attr_writer :dbi
|
12
|
+
BULK_FETCH_STEP = 2500
|
13
|
+
TABLES = [
|
14
|
+
# in table 'object', the isolated dumps of all objects are stored
|
15
|
+
['object', <<-'SQL'],
|
16
|
+
CREATE TABLE object (
|
17
|
+
odba_id INTEGER NOT NULL, content TEXT,
|
18
|
+
name TEXT, prefetchable BOOLEAN, extent TEXT,
|
19
|
+
PRIMARY KEY(odba_id), UNIQUE(name)
|
20
|
+
);
|
21
|
+
SQL
|
22
|
+
['prefetchable_index', <<-SQL],
|
23
|
+
CREATE INDEX prefetchable_index ON object(prefetchable);
|
24
|
+
SQL
|
25
|
+
['extent_index', <<-SQL],
|
26
|
+
CREATE INDEX extent_index ON object(extent);
|
27
|
+
SQL
|
28
|
+
# helper table 'object_connection'
|
29
|
+
['object_connection', <<-'SQL'],
|
30
|
+
CREATE TABLE object_connection (
|
31
|
+
origin_id integer, target_id integer,
|
32
|
+
PRIMARY KEY(origin_id, target_id)
|
33
|
+
);
|
34
|
+
SQL
|
35
|
+
['target_id_index', <<-SQL],
|
36
|
+
CREATE INDEX target_id_index ON object_connection(target_id);
|
37
|
+
SQL
|
38
|
+
# helper table 'collection'
|
39
|
+
['collection', <<-'SQL'],
|
40
|
+
CREATE TABLE collection (
|
41
|
+
odba_id integer NOT NULL, key text, value text,
|
42
|
+
PRIMARY KEY(odba_id, key)
|
43
|
+
);
|
44
|
+
SQL
|
45
|
+
]
|
46
|
+
def initialize
|
47
|
+
@id_mutex = Mutex.new
|
48
|
+
end
|
49
|
+
def bulk_restore(bulk_fetch_ids)
|
50
|
+
if(bulk_fetch_ids.empty?)
|
51
|
+
[]
|
52
|
+
else
|
53
|
+
bulk_fetch_ids = bulk_fetch_ids.uniq
|
54
|
+
rows = []
|
55
|
+
while(!(ids = bulk_fetch_ids.slice!(0, BULK_FETCH_STEP)).empty?)
|
56
|
+
sql = <<-SQL
|
57
|
+
SELECT odba_id, content FROM object
|
58
|
+
WHERE odba_id IN (#{ids.join(',')})
|
59
|
+
SQL
|
60
|
+
rows.concat(self.dbi.select_all(sql))
|
61
|
+
end
|
62
|
+
rows
|
63
|
+
end
|
64
|
+
end
|
65
|
+
def collection_fetch(odba_id, key_dump)
|
66
|
+
sql = <<-SQL
|
67
|
+
SELECT value FROM collection
|
68
|
+
WHERE odba_id = ? AND key = ?
|
69
|
+
SQL
|
70
|
+
row = self.dbi.select_one(sql, odba_id, key_dump)
|
71
|
+
row.first unless row.nil?
|
72
|
+
end
|
73
|
+
def collection_remove(odba_id, key_dump)
|
74
|
+
self.dbi.do <<-SQL, odba_id, key_dump
|
75
|
+
DELETE FROM collection
|
76
|
+
WHERE odba_id = ? AND key = ?
|
77
|
+
SQL
|
78
|
+
end
|
79
|
+
def collection_store(odba_id, key_dump, value_dump)
|
80
|
+
self.dbi.do <<-SQL, odba_id, key_dump, value_dump
|
81
|
+
INSERT INTO collection (odba_id, key, value)
|
82
|
+
VALUES (?, ?, ?)
|
83
|
+
SQL
|
84
|
+
end
|
85
|
+
def condition_index_delete(index_name, origin_id,
|
86
|
+
search_terms, target_id=nil)
|
87
|
+
values = []
|
88
|
+
sql = "DELETE FROM #{index_name}"
|
89
|
+
if(origin_id)
|
90
|
+
sql << " WHERE origin_id = ?"
|
91
|
+
else
|
92
|
+
sql << " WHERE origin_id IS ?"
|
93
|
+
end
|
94
|
+
search_terms.each { |key, value|
|
95
|
+
sql << " AND %s = ?" % key
|
96
|
+
values << value
|
97
|
+
}
|
98
|
+
if(target_id)
|
99
|
+
sql << " AND target_id = ?"
|
100
|
+
values << target_id
|
101
|
+
end
|
102
|
+
self.dbi.do sql, origin_id, *values
|
103
|
+
end
|
104
|
+
def condition_index_ids(index_name, id, id_name)
|
105
|
+
sql = <<-SQL
|
106
|
+
SELECT DISTINCT *
|
107
|
+
FROM #{index_name}
|
108
|
+
WHERE #{id_name}=?
|
109
|
+
SQL
|
110
|
+
self.dbi.select_all(sql, id)
|
111
|
+
end
|
112
|
+
def create_dictionary_map(language)
|
113
|
+
%w{lhword lpart_hword lword}.each { |token|
|
114
|
+
self.dbi.do <<-SQL
|
115
|
+
INSERT INTO pg_ts_cfgmap (ts_name, tok_alias, dict_name)
|
116
|
+
VALUES ('default_#{language}', '#{token}',
|
117
|
+
'{#{language}_ispell,#{language}_stem}')
|
118
|
+
SQL
|
119
|
+
}
|
120
|
+
[ 'url', 'host', 'sfloat', 'uri', 'int', 'float', 'email',
|
121
|
+
'word', 'hword', 'nlword', 'nlpart_hword', 'part_hword',
|
122
|
+
'nlhword', 'file', 'uint', 'version'
|
123
|
+
].each { |token|
|
124
|
+
self.dbi.do <<-SQL
|
125
|
+
INSERT INTO pg_ts_cfgmap (ts_name, tok_alias, dict_name)
|
126
|
+
VALUES ('default_#{language}', '#{token}', '{simple}')
|
127
|
+
SQL
|
128
|
+
}
|
129
|
+
end
|
130
|
+
def create_condition_index(table_name, definition)
|
131
|
+
self.dbi.do <<-SQL
|
132
|
+
CREATE TABLE #{table_name} (
|
133
|
+
origin_id INTEGER,
|
134
|
+
#{definition.collect { |*pair| pair.join(' ') }.join(",\n ") },
|
135
|
+
target_id INTEGER
|
136
|
+
);
|
137
|
+
SQL
|
138
|
+
#index origin_id
|
139
|
+
self.dbi.do <<-SQL
|
140
|
+
CREATE INDEX origin_id_#{table_name} ON #{table_name}(origin_id);
|
141
|
+
SQL
|
142
|
+
#index search_term
|
143
|
+
definition.each { |name, datatype|
|
144
|
+
self.dbi.do <<-SQL
|
145
|
+
CREATE INDEX #{name}_#{table_name} ON #{table_name}(#{name});
|
146
|
+
SQL
|
147
|
+
}
|
148
|
+
#index target_id
|
149
|
+
self.dbi.do <<-SQL
|
150
|
+
CREATE INDEX target_id_#{table_name} ON #{table_name}(target_id);
|
151
|
+
SQL
|
152
|
+
end
|
153
|
+
def create_fulltext_index(table_name)
|
154
|
+
self.dbi.do <<-SQL
|
155
|
+
CREATE TABLE #{table_name} (
|
156
|
+
origin_id INTEGER,
|
157
|
+
search_term tsvector,
|
158
|
+
target_id INTEGER
|
159
|
+
);
|
160
|
+
SQL
|
161
|
+
#index origin_id
|
162
|
+
self.dbi.do <<-SQL
|
163
|
+
CREATE INDEX origin_id_#{table_name} ON #{table_name}(origin_id);
|
164
|
+
SQL
|
165
|
+
#index search_term
|
166
|
+
self.dbi.do <<-SQL
|
167
|
+
CREATE INDEX search_term_#{table_name}
|
168
|
+
ON #{table_name} USING gist(search_term);
|
169
|
+
SQL
|
170
|
+
#index target_id
|
171
|
+
self.dbi.do <<-SQL
|
172
|
+
CREATE INDEX target_id_#{table_name} ON #{table_name}(target_id);
|
173
|
+
SQL
|
174
|
+
end
|
175
|
+
def create_index(table_name)
|
176
|
+
self.dbi.do <<-SQL
|
177
|
+
CREATE TABLE #{table_name} (
|
178
|
+
origin_id INTEGER,
|
179
|
+
search_term TEXT,
|
180
|
+
target_id INTEGER
|
181
|
+
);
|
182
|
+
SQL
|
183
|
+
#index origin_id
|
184
|
+
self.dbi.do <<-SQL
|
185
|
+
CREATE INDEX origin_id_#{table_name}
|
186
|
+
ON #{table_name}(origin_id)
|
187
|
+
SQL
|
188
|
+
#index search_term
|
189
|
+
self.dbi.do <<-SQL
|
190
|
+
CREATE INDEX search_term_#{table_name}
|
191
|
+
ON #{table_name}(search_term)
|
192
|
+
SQL
|
193
|
+
#index target_id
|
194
|
+
self.dbi.do <<-SQL
|
195
|
+
CREATE INDEX target_id_#{table_name}
|
196
|
+
ON #{table_name}(target_id)
|
197
|
+
SQL
|
198
|
+
end
|
199
|
+
def dbi
|
200
|
+
Thread.current[:txn] || @dbi
|
201
|
+
end
|
202
|
+
def drop_index(index_name)
|
203
|
+
self.dbi.do "DROP TABLE #{index_name}"
|
204
|
+
end
|
205
|
+
def delete_index_element(index_name, odba_id, id_name)
|
206
|
+
self.dbi.do <<-SQL, odba_id
|
207
|
+
DELETE FROM #{index_name} WHERE #{id_name} = ?
|
208
|
+
SQL
|
209
|
+
end
|
210
|
+
def delete_persistable(odba_id)
|
211
|
+
# delete origin from connections
|
212
|
+
self.dbi.do <<-SQL, odba_id
|
213
|
+
DELETE FROM object_connection WHERE origin_id = ?
|
214
|
+
SQL
|
215
|
+
# delete target from connections
|
216
|
+
self.dbi.do <<-SQL, odba_id
|
217
|
+
DELETE FROM object_connection WHERE target_id = ?
|
218
|
+
SQL
|
219
|
+
# delete from collections
|
220
|
+
self.dbi.do <<-SQL, odba_id
|
221
|
+
DELETE FROM collection WHERE odba_id = ?
|
222
|
+
SQL
|
223
|
+
# delete from objects
|
224
|
+
self.dbi.do <<-SQL, odba_id
|
225
|
+
DELETE FROM object WHERE odba_id = ?
|
226
|
+
SQL
|
227
|
+
end
|
228
|
+
def ensure_object_connections(origin_id, target_ids)
|
229
|
+
sql = <<-SQL
|
230
|
+
SELECT target_id FROM object_connection
|
231
|
+
WHERE origin_id = ?
|
232
|
+
SQL
|
233
|
+
target_ids.uniq!
|
234
|
+
update_ids = target_ids
|
235
|
+
old_ids = []
|
236
|
+
## use self.dbi instead of @dbi to get information about
|
237
|
+
## object_connections previously stored within this transaction
|
238
|
+
if(rows = self.dbi.select_all(sql, origin_id))
|
239
|
+
old_ids = rows.collect { |row| row[0] }
|
240
|
+
old_ids.uniq!
|
241
|
+
delete_ids = old_ids - target_ids
|
242
|
+
update_ids = target_ids - old_ids
|
243
|
+
unless(delete_ids.empty?)
|
244
|
+
while(!(ids = delete_ids.slice!(0, BULK_FETCH_STEP)).empty?)
|
245
|
+
self.dbi.do <<-SQL, origin_id
|
246
|
+
DELETE FROM object_connection
|
247
|
+
WHERE origin_id = ? AND target_id IN (#{ids.join(',')})
|
248
|
+
SQL
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
sth = self.dbi.prepare <<-SQL
|
253
|
+
INSERT INTO object_connection (origin_id, target_id)
|
254
|
+
VALUES (?, ?)
|
255
|
+
SQL
|
256
|
+
update_ids.each { |id|
|
257
|
+
sth.execute(origin_id, id)
|
258
|
+
}
|
259
|
+
sth.finish
|
260
|
+
end
|
261
|
+
def ensure_target_id_index(table_name)
|
262
|
+
#index target_id
|
263
|
+
self.dbi.do <<-SQL
|
264
|
+
CREATE INDEX target_id_#{table_name}
|
265
|
+
ON #{table_name}(target_id)
|
266
|
+
SQL
|
267
|
+
rescue
|
268
|
+
end
|
269
|
+
def extent_count(klass)
|
270
|
+
self.dbi.select_one(<<-EOQ, klass.to_s).first
|
271
|
+
SELECT COUNT(odba_id) FROM object WHERE extent = ?
|
272
|
+
EOQ
|
273
|
+
end
|
274
|
+
def extent_ids(klass)
|
275
|
+
self.dbi.select_all(<<-EOQ, klass.to_s).flatten
|
276
|
+
SELECT odba_id FROM object WHERE extent = ?
|
277
|
+
EOQ
|
278
|
+
end
|
279
|
+
def fulltext_index_delete(index_name, id, id_name)
|
280
|
+
self.dbi.do <<-SQL, id
|
281
|
+
DELETE FROM #{index_name}
|
282
|
+
WHERE #{id_name} = ?
|
283
|
+
SQL
|
284
|
+
end
|
285
|
+
def fulltext_index_target_ids(index_name, origin_id)
|
286
|
+
sql = <<-SQL
|
287
|
+
SELECT DISTINCT target_id
|
288
|
+
FROM #{index_name}
|
289
|
+
WHERE origin_id=?
|
290
|
+
SQL
|
291
|
+
self.dbi.select_all(sql, origin_id)
|
292
|
+
end
|
293
|
+
def generate_dictionary(language, locale, dict_dir)
|
294
|
+
# setup configuration
|
295
|
+
self.dbi.do <<-SQL
|
296
|
+
INSERT INTO pg_ts_cfg (ts_name, prs_name, locale)
|
297
|
+
VALUES ('default_#{language}', 'default', '#{locale}');
|
298
|
+
SQL
|
299
|
+
# insert path to dictionary
|
300
|
+
sql = <<-SQL
|
301
|
+
INSERT INTO pg_ts_dict (
|
302
|
+
SELECT '#{language}_ispell', dict_init, ?, dict_lexize
|
303
|
+
FROM pg_ts_dict
|
304
|
+
WHERE dict_name = 'ispell_template'
|
305
|
+
);
|
306
|
+
SQL
|
307
|
+
prepath = File.expand_path("fulltext", dict_dir)
|
308
|
+
path = %w{Aff Dict Stop}.collect { |type|
|
309
|
+
sprintf('%sFile="%s.%s"', type, prepath, type.downcase)
|
310
|
+
}.join(',')
|
311
|
+
sth.do sql, path
|
312
|
+
create_dictionary_map(language)
|
313
|
+
self.dbi.do <<-SQL
|
314
|
+
INSERT INTO pg_ts_dict (
|
315
|
+
dict_name, dict_init, dict_lexize
|
316
|
+
)
|
317
|
+
VALUES (
|
318
|
+
'#{language}_stem', 'dinit_#{language}(internal)',
|
319
|
+
'snb_lexize(internal, internal, int4)'
|
320
|
+
);
|
321
|
+
SQL
|
322
|
+
end
|
323
|
+
def index_delete_origin(index_name, odba_id, term)
|
324
|
+
self.dbi.do <<-SQL, odba_id, term
|
325
|
+
DELETE FROM #{index_name}
|
326
|
+
WHERE origin_id = ?
|
327
|
+
AND search_term = ?
|
328
|
+
SQL
|
329
|
+
end
|
330
|
+
def index_delete_target(index_name, origin_id, search_term, target_id)
|
331
|
+
self.dbi.do <<-SQL, origin_id, search_term, target_id
|
332
|
+
DELETE FROM #{index_name}
|
333
|
+
WHERE origin_id = ?
|
334
|
+
AND search_term = ?
|
335
|
+
AND target_id = ?
|
336
|
+
SQL
|
337
|
+
end
|
338
|
+
def index_fetch_keys(index_name, length=nil)
|
339
|
+
expr = if(length)
|
340
|
+
"substr(search_term, 1, #{length})"
|
341
|
+
else
|
342
|
+
"search_term"
|
343
|
+
end
|
344
|
+
sql = <<-SQL
|
345
|
+
SELECT DISTINCT #{expr} AS key
|
346
|
+
FROM #{index_name}
|
347
|
+
ORDER BY key
|
348
|
+
SQL
|
349
|
+
self.dbi.select_all(sql).flatten
|
350
|
+
end
|
351
|
+
def index_matches(index_name, substring, limit=nil, offset=0)
|
352
|
+
sql = <<-SQL
|
353
|
+
SELECT DISTINCT search_term AS key
|
354
|
+
FROM #{index_name}
|
355
|
+
WHERE search_term LIKE ?
|
356
|
+
ORDER BY key
|
357
|
+
SQL
|
358
|
+
if limit
|
359
|
+
sql << "LIMIT #{limit}\n"
|
360
|
+
end
|
361
|
+
if offset > 0
|
362
|
+
sql << "OFFSET #{offset}\n"
|
363
|
+
end
|
364
|
+
self.dbi.select_all(sql, substring + '%').flatten
|
365
|
+
end
|
366
|
+
def index_origin_ids(index_name, target_id)
|
367
|
+
sql = <<-SQL
|
368
|
+
SELECT DISTINCT origin_id, search_term
|
369
|
+
FROM #{index_name}
|
370
|
+
WHERE target_id=?
|
371
|
+
SQL
|
372
|
+
self.dbi.select_all(sql, target_id)
|
373
|
+
end
|
374
|
+
def index_target_ids(index_name, origin_id)
|
375
|
+
sql = <<-SQL
|
376
|
+
SELECT DISTINCT target_id, search_term
|
377
|
+
FROM #{index_name}
|
378
|
+
WHERE origin_id=?
|
379
|
+
SQL
|
380
|
+
self.dbi.select_all(sql, origin_id)
|
381
|
+
end
|
382
|
+
def max_id
|
383
|
+
@id_mutex.synchronize do
|
384
|
+
ensure_next_id_set
|
385
|
+
@next_id
|
386
|
+
end
|
387
|
+
end
|
388
|
+
def next_id
|
389
|
+
@id_mutex.synchronize do
|
390
|
+
ensure_next_id_set
|
391
|
+
@next_id += 1
|
392
|
+
end
|
393
|
+
end
|
394
|
+
def reserve_next_id(reserved_id)
|
395
|
+
@id_mutex.synchronize do
|
396
|
+
ensure_next_id_set
|
397
|
+
if @next_id < reserved_id
|
398
|
+
@next_id = reserved_id
|
399
|
+
else
|
400
|
+
raise OdbaDuplicateIdError,
|
401
|
+
"The id '#{reserved_id}' has already been assigned"
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|
405
|
+
def remove_dictionary(language)
|
406
|
+
# remove configuration
|
407
|
+
self.dbi.do <<-SQL
|
408
|
+
DELETE FROM pg_ts_cfg
|
409
|
+
WHERE ts_name='default_#{language}'
|
410
|
+
SQL
|
411
|
+
# remove dictionaries
|
412
|
+
self.dbi.do <<-SQL
|
413
|
+
DELETE FROM pg_ts_dict
|
414
|
+
WHERE dict_name IN ('#{language}_ispell', '#{language}_stem')
|
415
|
+
SQL
|
416
|
+
# remove tokens
|
417
|
+
self.dbi.do <<-SQL
|
418
|
+
DELETE FROM pg_ts_cfgmap
|
419
|
+
WHERE ts_name='default_#{language}'
|
420
|
+
SQL
|
421
|
+
end
|
422
|
+
def restore(odba_id)
|
423
|
+
row = self.dbi.select_one("SELECT content FROM object WHERE odba_id = ?", odba_id)
|
424
|
+
row.first unless row.nil?
|
425
|
+
end
|
426
|
+
def retrieve_connected_objects(target_id)
|
427
|
+
sql = <<-SQL
|
428
|
+
SELECT origin_id FROM object_connection
|
429
|
+
WHERE target_id = ?
|
430
|
+
SQL
|
431
|
+
self.dbi.select_all(sql, target_id)
|
432
|
+
end
|
433
|
+
def retrieve_from_condition_index(index_name, conditions, limit=nil)
|
434
|
+
sql = <<-EOQ
|
435
|
+
SELECT target_id, COUNT(target_id) AS relevance
|
436
|
+
FROM #{index_name}
|
437
|
+
WHERE TRUE
|
438
|
+
EOQ
|
439
|
+
values = []
|
440
|
+
lines = conditions.collect { |name, info|
|
441
|
+
val = nil
|
442
|
+
condition = nil
|
443
|
+
if(info.is_a?(Hash))
|
444
|
+
condition = info['condition']
|
445
|
+
if(val = info['value'])
|
446
|
+
if(/i?like/i.match(condition))
|
447
|
+
val += '%'
|
448
|
+
end
|
449
|
+
condition = "#{condition || '='} ?"
|
450
|
+
values.push(val.to_s)
|
451
|
+
end
|
452
|
+
elsif(info)
|
453
|
+
condition = "= ?"
|
454
|
+
values.push(info.to_s)
|
455
|
+
end
|
456
|
+
sql << <<-EOQ
|
457
|
+
AND #{name} #{condition || 'IS NULL'}
|
458
|
+
EOQ
|
459
|
+
}
|
460
|
+
sql << " GROUP BY target_id\n"
|
461
|
+
if(limit)
|
462
|
+
sql << " LIMIT #{limit}"
|
463
|
+
end
|
464
|
+
self.dbi.select_all(sql, *values)
|
465
|
+
end
|
466
|
+
def retrieve_from_fulltext_index(index_name, search_term, dict, limit=nil)
|
467
|
+
## this combination of gsub statements solves the problem of
|
468
|
+
# properly escaping strings of this form: "(2:1)" into
|
469
|
+
# '\(2\:1\)' (see test_retrieve_from_fulltext_index)
|
470
|
+
term = search_term.strip.gsub(/\s+/, '&').gsub(/&+/, '&')\
|
471
|
+
.gsub(/[():]/i, '\\ \\&').gsub(/\s/, '')
|
472
|
+
sql = <<-EOQ
|
473
|
+
SELECT target_id,
|
474
|
+
max(ts_rank(search_term, to_tsquery(?, ?))) AS relevance
|
475
|
+
FROM #{index_name}
|
476
|
+
WHERE search_term @@ to_tsquery(?, ?)
|
477
|
+
GROUP BY target_id
|
478
|
+
ORDER BY relevance DESC
|
479
|
+
EOQ
|
480
|
+
if(limit)
|
481
|
+
sql << " LIMIT #{limit}"
|
482
|
+
end
|
483
|
+
self.dbi.select_all(sql, dict, term, dict, term)
|
484
|
+
rescue DBI::ProgrammingError => e
|
485
|
+
warn("ODBA::Storage.retrieve_from_fulltext_index rescued a DBI::ProgrammingError(#{e.message}). Query:")
|
486
|
+
warn("self.dbi.select_all(#{sql}, #{dict}, #{term}, #{dict}, #{term})")
|
487
|
+
warn("returning empty result")
|
488
|
+
[]
|
489
|
+
end
|
490
|
+
def retrieve_from_index(index_name, search_term,
|
491
|
+
exact=nil, limit=nil)
|
492
|
+
unless(exact)
|
493
|
+
search_term = search_term + "%"
|
494
|
+
end
|
495
|
+
sql = <<-EOQ
|
496
|
+
SELECT target_id, COUNT(target_id) AS relevance
|
497
|
+
FROM #{index_name}
|
498
|
+
WHERE search_term LIKE ?
|
499
|
+
GROUP BY target_id
|
500
|
+
EOQ
|
501
|
+
if(limit)
|
502
|
+
sql << " LIMIT #{limit}"
|
503
|
+
end
|
504
|
+
self.dbi.select_all(sql, search_term)
|
505
|
+
end
|
506
|
+
def restore_collection(odba_id)
|
507
|
+
self.dbi.select_all <<-EOQ
|
508
|
+
SELECT key, value FROM collection WHERE odba_id = #{odba_id}
|
509
|
+
EOQ
|
510
|
+
end
|
511
|
+
def restore_named(name)
|
512
|
+
row = self.dbi.select_one("SELECT content FROM object WHERE name = ?",
|
513
|
+
name)
|
514
|
+
row.first unless row.nil?
|
515
|
+
end
|
516
|
+
def restore_prefetchable
|
517
|
+
self.dbi.select_all <<-EOQ
|
518
|
+
SELECT odba_id, content FROM object WHERE prefetchable = true
|
519
|
+
EOQ
|
520
|
+
end
|
521
|
+
def setup
|
522
|
+
TABLES.each { |name, definition|
|
523
|
+
self.dbi.do(definition) rescue DBI::ProgrammingError
|
524
|
+
}
|
525
|
+
unless(self.dbi.columns('object').any? { |col| col.name == 'extent' })
|
526
|
+
self.dbi.do <<-EOS
|
527
|
+
ALTER TABLE object ADD COLUMN extent TEXT;
|
528
|
+
CREATE INDEX extent_index ON object(extent);
|
529
|
+
EOS
|
530
|
+
end
|
531
|
+
end
|
532
|
+
def store(odba_id, dump, name, prefetchable, klass)
|
533
|
+
sql = "SELECT name FROM object WHERE odba_id = ?"
|
534
|
+
if(row = self.dbi.select_one(sql, odba_id))
|
535
|
+
name ||= row['name']
|
536
|
+
self.dbi.do <<-SQL, dump, name, prefetchable, klass.to_s, odba_id
|
537
|
+
UPDATE object SET
|
538
|
+
content = ?,
|
539
|
+
name = ?,
|
540
|
+
prefetchable = ?,
|
541
|
+
extent = ?
|
542
|
+
WHERE odba_id = ?
|
543
|
+
SQL
|
544
|
+
else
|
545
|
+
self.dbi.do <<-SQL, odba_id, dump, name, prefetchable, klass.to_s
|
546
|
+
INSERT INTO object (odba_id, content, name, prefetchable, extent)
|
547
|
+
VALUES (?, ?, ?, ?, ?)
|
548
|
+
SQL
|
549
|
+
end
|
550
|
+
end
|
551
|
+
def transaction(&block)
|
552
|
+
dbi = nil
|
553
|
+
retval = nil
|
554
|
+
@dbi.transaction { |dbi|
|
555
|
+
## this should not be necessary anymore:
|
556
|
+
#dbi['AutoCommit'] = false
|
557
|
+
Thread.current[:txn] = dbi
|
558
|
+
retval = block.call
|
559
|
+
}
|
560
|
+
retval
|
561
|
+
ensure
|
562
|
+
## this should not be necessary anymore:
|
563
|
+
#dbi['AutoCommit'] = true
|
564
|
+
Thread.current[:txn] = nil
|
565
|
+
end
|
566
|
+
def update_condition_index(index_name, origin_id, search_terms, target_id)
|
567
|
+
keys = []
|
568
|
+
vals = []
|
569
|
+
search_terms.each { |key, val|
|
570
|
+
keys.push(key)
|
571
|
+
vals.push(val)
|
572
|
+
}
|
573
|
+
if(target_id)
|
574
|
+
self.dbi.do <<-SQL, origin_id, target_id, *vals
|
575
|
+
INSERT INTO #{index_name} (origin_id, target_id, #{keys.join(', ')})
|
576
|
+
VALUES (?, ?#{', ?' * keys.size})
|
577
|
+
SQL
|
578
|
+
else
|
579
|
+
key_str = keys.collect { |key| "#{key}=?" }.join(', ')
|
580
|
+
self.dbi.do <<-SQL, *(vals.push(origin_id))
|
581
|
+
UPDATE #{index_name} SET #{key_str}
|
582
|
+
WHERE origin_id = ?
|
583
|
+
SQL
|
584
|
+
end
|
585
|
+
end
|
586
|
+
def update_fulltext_index(index_name, origin_id, search_term, target_id, dict)
|
587
|
+
search_term = search_term.gsub(/\s+/, ' ').strip
|
588
|
+
if(target_id)
|
589
|
+
self.dbi.do <<-SQL, origin_id, dict, search_term, target_id
|
590
|
+
INSERT INTO #{index_name} (origin_id, search_term, target_id)
|
591
|
+
VALUES (?, to_tsvector(?, ?), ?)
|
592
|
+
SQL
|
593
|
+
else
|
594
|
+
self.dbi.do <<-SQL, dict, search_term, origin_id
|
595
|
+
UPDATE #{index_name} SET search_term=to_tsvector(?, ?)
|
596
|
+
WHERE origin_id=?
|
597
|
+
SQL
|
598
|
+
end
|
599
|
+
end
|
600
|
+
def update_index(index_name, origin_id, search_term, target_id)
|
601
|
+
if(target_id)
|
602
|
+
self.dbi.do <<-SQL, origin_id, search_term, target_id
|
603
|
+
INSERT INTO #{index_name} (origin_id, search_term, target_id)
|
604
|
+
VALUES (?, ?, ?)
|
605
|
+
SQL
|
606
|
+
else
|
607
|
+
self.dbi.do <<-SQL, search_term, origin_id
|
608
|
+
UPDATE #{index_name} SET search_term=?
|
609
|
+
WHERE origin_id=?
|
610
|
+
SQL
|
611
|
+
end
|
612
|
+
end
|
613
|
+
private
|
614
|
+
def ensure_next_id_set
|
615
|
+
if(@next_id.nil?)
|
616
|
+
@next_id = restore_max_id
|
617
|
+
end
|
618
|
+
end
|
619
|
+
def restore_max_id
|
620
|
+
row = self.dbi.select_one("SELECT odba_id FROM object ORDER BY odba_id DESC LIMIT 1")
|
621
|
+
unless(row.nil? || row.first.nil?)
|
622
|
+
row.first
|
623
|
+
else
|
624
|
+
0
|
625
|
+
end
|
626
|
+
end
|
627
|
+
end
|
628
|
+
end
|