oklahoma_mixer 0.2.0 → 0.3.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.
Files changed (52) hide show
  1. data/CHANGELOG.rdoc +7 -0
  2. data/README.rdoc +4 -0
  3. data/Rakefile +1 -1
  4. data/TODO.rdoc +2 -3
  5. data/lib/oklahoma_mixer.rb +7 -20
  6. data/lib/oklahoma_mixer/array_list.rb +21 -7
  7. data/lib/oklahoma_mixer/array_list/c.rb +9 -1
  8. data/lib/oklahoma_mixer/b_tree_database.rb +25 -9
  9. data/lib/oklahoma_mixer/b_tree_database/c.rb +5 -0
  10. data/lib/oklahoma_mixer/cursor.rb +3 -0
  11. data/lib/oklahoma_mixer/cursor/c.rb +2 -0
  12. data/lib/oklahoma_mixer/error.rb +2 -0
  13. data/lib/oklahoma_mixer/extensible_string.rb +4 -2
  14. data/lib/oklahoma_mixer/extensible_string/c.rb +2 -0
  15. data/lib/oklahoma_mixer/fixed_length_database.rb +11 -3
  16. data/lib/oklahoma_mixer/fixed_length_database/c.rb +2 -0
  17. data/lib/oklahoma_mixer/hash_database.rb +24 -7
  18. data/lib/oklahoma_mixer/hash_database/c.rb +2 -0
  19. data/lib/oklahoma_mixer/hash_map.rb +42 -0
  20. data/lib/oklahoma_mixer/hash_map/c.rb +27 -0
  21. data/lib/oklahoma_mixer/query.rb +73 -0
  22. data/lib/oklahoma_mixer/query/c.rb +51 -0
  23. data/lib/oklahoma_mixer/table_database.rb +402 -0
  24. data/lib/oklahoma_mixer/table_database/c.rb +104 -0
  25. data/lib/oklahoma_mixer/utilities.rb +26 -1
  26. data/test/{b_tree_binary_data_test.rb → b_tree_database/b_tree_binary_data_test.rb} +2 -2
  27. data/test/{b_tree_tuning_test.rb → b_tree_database/b_tree_tuning_test.rb} +2 -2
  28. data/test/{cursor_based_iteration_test.rb → b_tree_database/cursor_based_iteration_test.rb} +2 -2
  29. data/test/{duplicate_storage_test.rb → b_tree_database/duplicate_storage_test.rb} +18 -12
  30. data/test/{binary_data_test.rb → core_database/binary_data_test.rb} +2 -2
  31. data/test/{file_system_test.rb → core_database/file_system_test.rb} +0 -0
  32. data/test/{getting_and_setting_keys_test.rb → core_database/getting_and_setting_keys_test.rb} +31 -60
  33. data/test/{iteration_test.rb → core_database/iteration_test.rb} +2 -2
  34. data/test/{transactions_test.rb → core_database/transactions_test.rb} +0 -0
  35. data/test/core_database/tuning_test.rb +31 -0
  36. data/test/{fixed_length_tuning_test.rb → fixed_length_database/fixed_length_tuning_test.rb} +0 -0
  37. data/test/{getting_and_setting_by_id_test.rb → fixed_length_database/getting_and_setting_by_id_test.rb} +8 -0
  38. data/test/{shared_binary_data.rb → shared/binary_data_tests.rb} +1 -1
  39. data/test/{tuning_test.rb → shared/hash_tuning_tests.rb} +18 -42
  40. data/test/{shared_iteration.rb → shared/iteration_tests.rb} +8 -7
  41. data/test/{key_range_test.rb → shared/key_range_test.rb} +0 -0
  42. data/test/{order_test.rb → shared/order_test.rb} +0 -0
  43. data/test/shared/storage_tests.rb +65 -0
  44. data/test/{top_level_interface_test.rb → shared/top_level_interface_test.rb} +39 -2
  45. data/test/{shared_tuning.rb → shared/tuning_tests.rb} +1 -1
  46. data/test/table_database/document_iteration_test.rb +22 -0
  47. data/test/table_database/document_storage_test.rb +225 -0
  48. data/test/table_database/index_test.rb +57 -0
  49. data/test/table_database/query_test.rb +866 -0
  50. data/test/table_database/table_tuning_test.rb +56 -0
  51. data/test/test_helper.rb +27 -0
  52. metadata +35 -36
@@ -1,3 +1,5 @@
1
+ require "oklahoma_mixer/utilities"
2
+
1
3
  module OklahomaMixer
2
4
  class HashDatabase
3
5
  module C # :nodoc:
@@ -0,0 +1,42 @@
1
+ require "oklahoma_mixer/hash_map/c"
2
+
3
+ module OklahomaMixer
4
+ class HashMap # :nodoc:
5
+ def initialize(pointer = C.new)
6
+ @pointer = pointer
7
+ end
8
+
9
+ attr_reader :pointer
10
+
11
+ def update(pairs)
12
+ pairs.each do |key, value|
13
+ C.put(@pointer, *[yield(key), yield(value)].flatten)
14
+ end
15
+ end
16
+
17
+ def each
18
+ C.iterinit(@pointer)
19
+ loop do
20
+ return self unless key = C.read_from_func(:iternext, :no_free, @pointer)
21
+ yield [key, C.read_from_func(:get, :no_free, @pointer, key, key.size)]
22
+ end
23
+ end
24
+
25
+ def to_hash
26
+ hash = { }
27
+ each do |key, value|
28
+ hash[yield(key)] = yield(value)
29
+ end
30
+ hash
31
+ end
32
+
33
+ def replace(pairs, &cast)
34
+ C.clear(@pointer)
35
+ update(pairs, &cast)
36
+ end
37
+
38
+ def free
39
+ C.del(@pointer)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,27 @@
1
+ require "oklahoma_mixer/utilities"
2
+
3
+ module OklahomaMixer
4
+ class HashMap
5
+ module C # :nodoc:
6
+ extend OklahomaMixer::Utilities::FFIDSL
7
+
8
+ prefix :tcmap
9
+
10
+ def_new_and_del_funcs
11
+
12
+ func :name => :put,
13
+ :args => [:pointer, :pointer, :int, :pointer, :int]
14
+ func :name => :get,
15
+ :args => [:pointer, :pointer, :int, :pointer],
16
+ :returns => :pointer
17
+ func :name => :clear,
18
+ :args => :pointer
19
+
20
+ func :name => :iterinit,
21
+ :args => :pointer
22
+ func :name => :iternext,
23
+ :args => [:pointer, :pointer],
24
+ :returns => :pointer
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,73 @@
1
+ require "oklahoma_mixer/error"
2
+ require "oklahoma_mixer/query/c"
3
+
4
+ module OklahomaMixer
5
+ class Query # :nodoc:
6
+ def initialize(table_pointer)
7
+ @pointer = C.new(table_pointer)
8
+ end
9
+
10
+ attr_reader :pointer
11
+
12
+ def condition(column, operator, expression, no_index = false)
13
+ operator = operator.to_s
14
+ negate = operator.sub!(/\A(?:!_?|not_)/, "")
15
+ operator = case operator
16
+ when /\A(?:=?=|eq(?:ua)?ls?\??)\z/
17
+ if expression.is_a? Numeric then :TDBQCNUMEQ
18
+ else :TDBQCSTREQ
19
+ end
20
+ when /\Astr(?:ing)?_eq(?:ua)?ls?\??\z/ then :TDBQCSTREQ
21
+ when /\Ainclude?s?\??\z/ then :TDBQCSTRINC
22
+ when /\Astarts?_with\??\z/ then :TDBQCSTRBW
23
+ when /\Aends?_with\??\z/ then :TDBQCSTREW
24
+ when /\Aincludes?_all(?:_tokens?)?\??\z/ then :TDBQCSTRAND
25
+ when /\Aincludes?_any(?:_tokens?)?\??\z/ then :TDBQCSTROR
26
+ when /\Aeq(?:ua)?ls?_any(?:_tokens?)?\??\z/ then :TDBQCSTROREQ
27
+ when /\A(?:~|=~|match(?:es)?\??)\z/ then :TDBQCSTRRX
28
+ when /\Anum(?:ber)?_eq(?:ua)?ls?\??\z/ then :TDBQCNUMEQ
29
+ when ">" then :TDBQCNUMGT
30
+ when ">=" then :TDBQCNUMGE
31
+ when "<" then :TDBQCNUMLT
32
+ when "<=" then :TDBQCNUMLE
33
+ when /\Abetween\??\z/ then :TDBQCNUMBT
34
+ when /\Aany_num(?:ber)?\??\z/ then :TDBQCNUMOREQ
35
+ when /\Aphrase_search\??\z/ then :TDBQCFTSPH
36
+ when /\Aall_tokens?_search\??\z/ then :TDBQCFTSAND
37
+ when /\Aany_tokens?_search\??\z/ then :TDBQCFTSOR
38
+ when /\Aexpression_search\??\z/ then :TDBQCFTSEX
39
+ else
40
+ fail Error::QueryError, "unknown condition operator"
41
+ end
42
+ operator = C::CONDITIONS[operator]
43
+ operator |= C::CONDITIONS[:TDBQCNEGATE] if negate
44
+ operator |= C::CONDITIONS[:TDBQCNOIDX] if no_index
45
+ C.addcond( @pointer,
46
+ yield(column),
47
+ operator,
48
+ yield( expression.respond_to?(:source) ?
49
+ expression.source :
50
+ expression ) )
51
+ end
52
+
53
+ def order(column, str_num_asc_desc = nil)
54
+ str_num_asc_desc = case str_num_asc_desc.to_s
55
+ when /\A(?:STR_)?ASC\z/i, "" then :TDBQOSTRASC
56
+ when /\A(?:STR_)?DESC\z/i then :TDBQOSTRDESC
57
+ when /\ANUM_ASC\z/i then :TDBQONUMASC
58
+ when /\ANUM_DESC\z/i then :TDBQONUMDESC
59
+ else
60
+ fail Error::QueryError, "unknown order type"
61
+ end
62
+ C.setorder(@pointer, yield(column), C::ORDERS[str_num_asc_desc])
63
+ end
64
+
65
+ def limit(max, offset = nil)
66
+ C.setlimit(@pointer, max.to_i, offset.to_i)
67
+ end
68
+
69
+ def free
70
+ C.del(@pointer)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,51 @@
1
+ require "oklahoma_mixer/utilities"
2
+
3
+ module OklahomaMixer
4
+ class Query
5
+ module C # :nodoc:
6
+ extend OklahomaMixer::Utilities::FFIDSL
7
+
8
+ prefix :tctdbqry
9
+
10
+ CONDITIONS = enum :TDBQCSTREQ, 0,
11
+ :TDBQCSTRINC, 1,
12
+ :TDBQCSTRBW, 2,
13
+ :TDBQCSTREW, 3,
14
+ :TDBQCSTRAND, 4,
15
+ :TDBQCSTROR, 5,
16
+ :TDBQCSTROREQ, 6,
17
+ :TDBQCSTRRX, 7,
18
+ :TDBQCNUMEQ, 8,
19
+ :TDBQCNUMGT, 9,
20
+ :TDBQCNUMGE, 10,
21
+ :TDBQCNUMLT, 11,
22
+ :TDBQCNUMLE, 12,
23
+ :TDBQCNUMBT, 13,
24
+ :TDBQCNUMOREQ, 14,
25
+ :TDBQCFTSPH, 15,
26
+ :TDBQCFTSAND, 16,
27
+ :TDBQCFTSOR, 17,
28
+ :TDBQCFTSEX, 18,
29
+ :TDBQCNEGATE, 1 << 24,
30
+ :TDBQCNOIDX, 1 << 25
31
+ ORDERS = enum :TDBQOSTRASC, 0,
32
+ :TDBQOSTRDESC, 1,
33
+ :TDBQONUMASC, 2,
34
+ :TDBQONUMDESC, 3
35
+
36
+
37
+ func :name => :new,
38
+ :args => :pointer,
39
+ :returns => :pointer
40
+ func :name => :del,
41
+ :args => :pointer
42
+
43
+ func :name => :addcond,
44
+ :args => [:pointer, :string, CONDITIONS, :string]
45
+ func :name => :setorder,
46
+ :args => [:pointer, :string, ORDERS]
47
+ func :name => :setlimit,
48
+ :args => [:pointer, :int, :int]
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,402 @@
1
+ require "oklahoma_mixer/error"
2
+ require "oklahoma_mixer/array_list"
3
+ require "oklahoma_mixer/hash_map"
4
+ require "oklahoma_mixer/query"
5
+ require "oklahoma_mixer/hash_database"
6
+ require "oklahoma_mixer/table_database/c"
7
+
8
+ module OklahomaMixer
9
+ class TableDatabase < HashDatabase
10
+ module Paginated
11
+ attr_accessor :current_page, :per_page, :total_entries
12
+
13
+ def total_pages
14
+ (total_entries / per_page.to_f).ceil
15
+ end
16
+
17
+ def out_of_bounds?
18
+ current_page > total_pages
19
+ end
20
+
21
+ def offset
22
+ (current_page - 1) * per_page
23
+ end
24
+
25
+ def previous_page
26
+ current_page > 1 ? (current_page - 1) : nil
27
+ end
28
+
29
+ def next_page
30
+ current_page < total_pages ? (current_page + 1) : nil
31
+ end
32
+ end
33
+
34
+ ################################
35
+ ### Getting and Setting Keys ###
36
+ ################################
37
+
38
+ def store(key, value, mode = nil, &dup_handler)
39
+ if mode == :add and dup_handler.nil?
40
+ super
41
+ elsif dup_handler
42
+ warn "block supersedes mode argument" unless mode.nil?
43
+ k = cast_key_in(key)
44
+ callback = lambda { |old_value_pointer, old_size, returned_size, _|
45
+ old_value = cast_from_null_terminated_colums(
46
+ *old_value_pointer.get_bytes(0, old_size)
47
+ )
48
+ replacement, size = cast_to_null_terminated_colums( yield( key,
49
+ old_value,
50
+ value ) )
51
+ returned_size.put_int(0, size)
52
+ pointer = Utilities.malloc(size)
53
+ pointer.put_bytes(0, replacement) unless pointer.address.zero?
54
+ pointer
55
+ }
56
+ try(:putproc, k, cast_to_null_terminated_colums(value), callback, nil)
57
+ value
58
+ else
59
+ Utilities.temp_map do |map|
60
+ map.update(value) { |string|
61
+ cast_to_bytes_and_length(string)
62
+ }
63
+ result = super(key, map, mode, &dup_handler)
64
+ result == map ? value : result
65
+ end
66
+ end
67
+ end
68
+ alias_method :[]=, :store
69
+
70
+ def fetch(key, *default)
71
+ if value = try( :get, cast_key_in(key),
72
+ :failure => lambda { |value| value.address.zero? },
73
+ :no_error => {22 => nil} )
74
+ cast_value_out(value)
75
+ else
76
+ if block_given?
77
+ warn "block supersedes default value argument" unless default.empty?
78
+ yield key
79
+ elsif not default.empty?
80
+ default.first
81
+ else
82
+ fail IndexError, "key not found"
83
+ end
84
+ end
85
+ end
86
+
87
+ def generate_unique_id
88
+ try(:genuid, :failure => -1)
89
+ end
90
+ alias_method :uid, :generate_unique_id
91
+
92
+ #################
93
+ ### Iteration ###
94
+ #################
95
+
96
+ def each
97
+ try(:iterinit)
98
+ loop do
99
+ pointer = try( :iternext3,
100
+ :failure => lambda { |value| value.address.zero? },
101
+ :no_error => {22 => nil} )
102
+ return self unless pointer
103
+ value = cast_value_out(pointer)
104
+ key = value.delete("")
105
+ yield [key, value]
106
+ end
107
+ end
108
+ alias_method :each_pair, :each
109
+
110
+ ###############
111
+ ### Queries ###
112
+ ###############
113
+
114
+ def all(options = { }, &iterator)
115
+ query(options) do |q|
116
+ mode = results_mode(options)
117
+ if block_given?
118
+ results = self
119
+ callback = lambda { |key_pointer, key_size, doc_map, _|
120
+ if mode != :docs
121
+ key = cast_key_out(key_pointer.get_bytes(0, key_size))
122
+ end
123
+ if mode != :keys
124
+ map = HashMap.new(doc_map)
125
+ doc = map.to_hash { |string| cast_to_encoded_string(string) }
126
+ end
127
+ flags = case mode
128
+ when :keys then yield(key)
129
+ when :docs then yield(doc)
130
+ else yield(key, doc)
131
+ end
132
+ Array(flags).inject(0) { |returned_flags, flag|
133
+ returned_flags | case flag.to_s
134
+ when "update"
135
+ if mode != :keys
136
+ map.replace(doc) { |key_or_value|
137
+ cast_to_bytes_and_length(key_or_value)
138
+ }
139
+ end
140
+ lib::FLAGS[:TDBQPPUT]
141
+ when "delete" then lib::FLAGS[:TDBQPOUT]
142
+ when "break" then lib::FLAGS[:TDBQPSTOP]
143
+ else 0
144
+ end
145
+ }
146
+ }
147
+ else
148
+ results = mode != :hoh ? [ ] : { }
149
+ callback = lambda { |key_pointer, key_size, doc_map, _|
150
+ if mode == :docs
151
+ results << cast_value_out(doc_map, :no_free)
152
+ else
153
+ key = cast_key_out(key_pointer.get_bytes(0, key_size))
154
+ case mode
155
+ when :keys
156
+ results << key
157
+ when :hoh
158
+ results[key] = cast_value_out(doc_map, :no_free)
159
+ when :aoh
160
+ results << cast_value_out(doc_map, :no_free).
161
+ merge(:primary_key => key)
162
+ else
163
+ results << [key, cast_value_out(doc_map, :no_free)]
164
+ end
165
+ end
166
+ 0
167
+ }
168
+ end
169
+ lib.qryproc(q.pointer, callback, nil)
170
+ results
171
+ end
172
+ end
173
+
174
+ def first(options = { })
175
+ all(options.merge(:limit => 1)).first
176
+ end
177
+
178
+ def count(options = { })
179
+ count = 0
180
+ all(options) { count += 1 }
181
+ count
182
+ end
183
+
184
+ def paginate(options)
185
+ mode = results_mode(options)
186
+ results = (mode != :hoh ? [ ] : { }).extend(Paginated)
187
+ fail Error::QueryError, ":page argument required" \
188
+ unless options.include? :page
189
+ results.current_page = (options[:page] || 1).to_i
190
+ fail Error::QueryError, ":page must be >= 1" if results.current_page < 1
191
+ results.per_page = (options[:per_page] || 30).to_i
192
+ fail Error::QueryError, ":per_page must be >= 1" if results.per_page < 1
193
+ results.total_entries = 0
194
+ all( options.merge( :select => :keys_and_docs,
195
+ :limit => nil ) ) { |key, value|
196
+ if results.total_entries >= results.offset and
197
+ results.size < results.per_page
198
+ case mode
199
+ when :keys
200
+ results << key
201
+ when :docs
202
+ results << value
203
+ when :hoh
204
+ results[key] = value
205
+ when :aoh
206
+ results << value.merge(:primary_key => key)
207
+ else
208
+ results << [key, value]
209
+ end
210
+ end
211
+ results.total_entries += 1
212
+ }
213
+ results
214
+ end
215
+
216
+ def union(q, *queries)
217
+ search([q] + queries, lib::SEARCHES[:TDBMSUNION])
218
+ end
219
+
220
+ def intersection(q, *queries)
221
+ search([q] + queries, lib::SEARCHES[:TDBMSISECT])
222
+ end
223
+ alias_method :isect, :intersection
224
+
225
+ def difference(q, *queries)
226
+ search([q] + queries, lib::SEARCHES[:TDBMSDIFF])
227
+ end
228
+ alias_method :diff, :difference
229
+
230
+ ###############
231
+ ### Indexes ###
232
+ ###############
233
+
234
+ def add_index(column, type, keep = false)
235
+ type = case type.to_s
236
+ when "lexical", "string" then lib::INDEXES[:TDBITLEXICAL]
237
+ when "decimal", "numeric" then lib::INDEXES[:TDBITDECIMAL]
238
+ when "token" then lib::INDEXES[:TDBITTOKEN]
239
+ when "qgram" then lib::INDEXES[:TDBITQGRAM]
240
+ else
241
+ fail Error::IndexError, "unknown index type"
242
+ end
243
+ type |= lib::INDEXES[:TDBITKEEP] if keep
244
+ try( :setindex,
245
+ cast_to_bytes_and_length(column_name(column)).first,
246
+ type,
247
+ :no_error => {21 => false} )
248
+ end
249
+
250
+ def remove_index(column)
251
+ try( :setindex,
252
+ cast_to_bytes_and_length(column_name(column)).first,
253
+ lib::INDEXES[:TDBITVOID],
254
+ :no_error => {2 => false} )
255
+ end
256
+
257
+ def optimize_index(column)
258
+ try( :setindex,
259
+ cast_to_bytes_and_length(column_name(column)).first,
260
+ lib::INDEXES[:TDBITOPT],
261
+ :no_error => {2 => false} )
262
+ end
263
+ alias_method :defrag_index, :optimize_index
264
+
265
+ #######
266
+ private
267
+ #######
268
+
269
+ def tune(options)
270
+ super
271
+ if options.values_at(:bnum, :apow, :fpow, :opts).any?
272
+ optimize(options.merge(:tune => true))
273
+ end
274
+ if options.values_at(:rcnum, :lcnum, :ncnum).any?
275
+ setcache(options)
276
+ end
277
+ end
278
+
279
+ def setcache(options)
280
+ try( :setcache,
281
+ options.fetch(:rcnum, 0).to_i,
282
+ options.fetch(:lcnum, 0).to_i,
283
+ options.fetch(:ncnum, 0).to_i )
284
+ end
285
+
286
+ def cast_value_in(value)
287
+ value.pointer
288
+ end
289
+
290
+ def cast_value_out(pointer, no_free = false)
291
+ map = HashMap.new(pointer)
292
+ map.to_hash { |string| cast_to_encoded_string(string) }
293
+ ensure
294
+ map.free if map and not no_free
295
+ end
296
+
297
+ def cast_from_null_terminated_colums(string)
298
+ Hash[*string.split("\0").map { |s| cast_to_encoded_string(s) }]
299
+ end
300
+
301
+ def cast_to_null_terminated_colums(hash)
302
+ cast_to_bytes_and_length(hash.to_a.flatten.join("\0"))
303
+ end
304
+
305
+ def column_name(column)
306
+ case column
307
+ when :primary_key, :pk then ""
308
+ else column
309
+ end
310
+ end
311
+
312
+ def query(options = { })
313
+ query = Query.new(@db)
314
+ conditions = Array(options[:conditions])
315
+ conditions = [conditions] unless conditions.empty? or
316
+ conditions.first.is_a? Array
317
+ conditions.each do |condition|
318
+ fail Error::QueryError,
319
+ "condition must be column, operator, and expression" \
320
+ unless condition.size.between? 3, 4
321
+ query.condition( column_name(condition.first),
322
+ *condition[1..-1] ) { |string|
323
+ cast_to_bytes_and_length(string).first
324
+ }
325
+ end
326
+ unless options[:order].nil?
327
+ order = options[:order] == "" ? [""] : Array(options[:order])
328
+ fail Error::QueryError, "order must have a field and can have a type" \
329
+ unless order.size.between? 1, 2
330
+ order[0] = column_name(order[0])
331
+ query.order(*order) { |string|
332
+ cast_to_bytes_and_length(string).first
333
+ }
334
+ end
335
+ unless options[:limit].nil?
336
+ query.limit(options[:limit], options[:offset])
337
+ end
338
+ if block_given?
339
+ yield query
340
+ else
341
+ query
342
+ end
343
+ ensure
344
+ query.free if query and block_given?
345
+ end
346
+
347
+ def results_mode(options)
348
+ case options[:select].to_s
349
+ when /\A(?:primary_)?keys?\z/i then :keys
350
+ when /\Adoc(?:ument)?s?\z/i then :docs
351
+ else
352
+ case options[:return].to_s
353
+ when /\Ah(?:ash_)?o(?:f_)?h(?:ash)?e?s?\z/i then :hoh
354
+ when /\Aa(?:rray_)?o(?:f_)?a(?:rray)?s?\z/i then :aoa
355
+ when /\Aa(?:rray_)?o(?:f_)?h(?:ash)?e?s?\z/i then :aoh
356
+ else
357
+ if RUBY_VERSION < "1.9" and not options[:order].nil? then :aoa
358
+ else :hoh
359
+ end
360
+ end
361
+ end
362
+ end
363
+
364
+ def search(queries, operation)
365
+ mode = results_mode(queries.first)
366
+ qs = queries.map { |q| query(q) }
367
+ keys = ArrayList.new( Utilities.temp_pointer(qs.size) do |pointer|
368
+ pointer.write_array_of_pointer(qs.map { |q| q.pointer })
369
+ lib.metasearch(pointer, qs.size, operation)
370
+ end )
371
+ case mode
372
+ when :keys
373
+ keys.map { |key| cast_key_out(key) }
374
+ when :docs
375
+ keys.map { |key| self[cast_key_out(key)] }
376
+ when :hoh
377
+ results = { }
378
+ while key = keys.shift { |k| cast_key_out(k) }
379
+ results[key] = self[key]
380
+ end
381
+ results
382
+ when :aoh
383
+ keys.map { |key|
384
+ key = cast_key_out(key)
385
+ self[key].merge(:primary_key => key)
386
+ }
387
+ else
388
+ keys.map { |key|
389
+ key = cast_key_out(key)
390
+ [key, self[key]]
391
+ }
392
+ end
393
+ ensure
394
+ if qs
395
+ qs.each do |q|
396
+ q.free
397
+ end
398
+ end
399
+ keys.free if keys
400
+ end
401
+ end
402
+ end