rufus-tokyo 1.0.3 → 1.0.4

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 (55) hide show
  1. data/.gitignore +4 -0
  2. data/CHANGELOG.txt +6 -0
  3. data/Rakefile +91 -0
  4. data/doc/decision_table.numbers +0 -0
  5. data/lib/rufus/edo/README.txt +101 -0
  6. data/lib/rufus/edo/tabcore.rb +1 -3
  7. data/lib/rufus/tokyo.rb +1 -2
  8. data/lib/rufus/tokyo/cabinet/lib.rb +4 -7
  9. data/lib/rufus/tokyo/cabinet/table.rb +10 -13
  10. data/lib/rufus/tokyo/cabinet/util.rb +4 -1
  11. data/lib/rufus/tokyo/hmethods.rb +4 -4
  12. data/lib/rufus/tokyo/outlen.rb +5 -1
  13. data/lib/rufus/tokyo/tyrant/abstract.rb +8 -0
  14. data/lib/rufus/tokyo/tyrant/lib.rb +6 -6
  15. data/lib/rufus/tokyo/tyrant/table.rb +9 -1
  16. data/lib/rufus/tokyo/version.rb +32 -0
  17. data/rufus-tokyo.gemspec +135 -0
  18. data/spec/cabinet_btree_spec.rb +92 -0
  19. data/spec/cabinet_fixed_spec.rb +33 -0
  20. data/spec/cabinet_spec.rb +291 -0
  21. data/spec/cabinetconfig_spec.rb +82 -0
  22. data/spec/dystopia_core_spec.rb +124 -0
  23. data/spec/edo_cabinet_btree_spec.rb +123 -0
  24. data/spec/edo_cabinet_fixed_spec.rb +42 -0
  25. data/spec/edo_cabinet_spec.rb +286 -0
  26. data/spec/edo_ntyrant_spec.rb +224 -0
  27. data/spec/edo_ntyrant_table_spec.rb +296 -0
  28. data/spec/edo_table_spec.rb +292 -0
  29. data/spec/hmethods_spec.rb +73 -0
  30. data/spec/incr.lua +23 -0
  31. data/spec/openable_spec.rb +51 -0
  32. data/spec/shared_abstract_spec.rb +426 -0
  33. data/spec/shared_table_spec.rb +675 -0
  34. data/spec/shared_tyrant_spec.rb +42 -0
  35. data/spec/spec_base.rb +23 -0
  36. data/spec/start_tyrants.sh +28 -0
  37. data/spec/stop_tyrants.sh +9 -0
  38. data/spec/table_spec.rb +267 -0
  39. data/spec/tyrant_spec.rb +218 -0
  40. data/spec/tyrant_table_spec.rb +298 -0
  41. data/spec/util_list_spec.rb +197 -0
  42. data/spec/util_map_spec.rb +130 -0
  43. data/tasks/dev.rb +70 -0
  44. data/test/bm0.rb +353 -0
  45. data/test/bm1_compression.rb +54 -0
  46. data/test/con0.rb +30 -0
  47. data/test/mem.rb +49 -0
  48. data/test/mem1.rb +44 -0
  49. data/test/readme0.rb +17 -0
  50. data/test/readme1.rb +21 -0
  51. data/test/readme2.rb +15 -0
  52. data/test/readme3.rb +24 -0
  53. data/test/readmes_test.sh +17 -0
  54. metadata +81 -21
  55. data/MIGRATED.txt +0 -1
@@ -0,0 +1,675 @@
1
+
2
+ #
3
+ # Specifying rufus-tokyo
4
+ #
5
+ # Tue Jul 21 13:02:53 JST 2009
6
+ #
7
+
8
+
9
+ shared 'table' do
10
+
11
+ it 'should generate unique ids' do
12
+
13
+ @t.genuid.should.satisfy { |i| i.to_s > '0' }
14
+ end
15
+
16
+ it 'should return nil for missing keys' do
17
+
18
+ @t['missing'].should.be.nil
19
+ end
20
+
21
+ it 'should accept Array and Hash input' do
22
+
23
+ @t.size.should.equal(0)
24
+
25
+ @t['pk0'] = [ 'name', 'toto', 'age', '30' ]
26
+ @t['pk1'] = { 'name' => 'fred', 'age' => '22' }
27
+
28
+ @t.size.should.equal(2)
29
+ @t['pk0'].should.equal({ 'name' => 'toto', 'age' => '30' })
30
+ end
31
+
32
+ it 'should return nil when deleting inexistent entries' do
33
+
34
+ @t.delete('I_do_not_exist').should.equal(nil)
35
+ end
36
+
37
+ it 'should delete the entry and return the value' do
38
+
39
+ @t['pk0'] = [ 'name', 'toto', 'age', '30' ]
40
+ @t.delete('pk0').should.equal({ 'name' => 'toto', 'age' => '30' })
41
+ @t.size.should.equal(0)
42
+ end
43
+
44
+ it 'should raise an ArgumentError on non map or hash input' do
45
+
46
+ lambda {
47
+ @t['pk0'] = 'bad thing here'
48
+ }.should.raise(ArgumentError)
49
+ end
50
+
51
+ #it 'should raise an ArgumentError on non-string column name' do
52
+ # lambda {
53
+ # @t['pk0'] = [ 1, 2 ]
54
+ # }.should.raise(ArgumentError)
55
+ # lambda {
56
+ # @t['pk0'] = { 1 => 2 }
57
+ # }.should.raise(ArgumentError)
58
+ #end
59
+ #it 'should raise an ArgumentError on non-string column value' do
60
+ # lambda {
61
+ # @t['pk0'] = { 'a' => 2 }
62
+ # }.should.raise(ArgumentError)
63
+ #end
64
+
65
+ it 'should store binary data \0' do
66
+ s = "toto#{0.chr}nada"
67
+ @t[s] = { s => s }
68
+ @t[s].should.equal({ s => s })
69
+ end
70
+
71
+ it 'should stringify primary key, keys, and values on read and write' do
72
+ @t[123] = {:num => 456}
73
+ @t["123".to_sym].should.equal("num" => "456")
74
+ end
75
+ end
76
+
77
+ shared 'table with transactions' do
78
+
79
+ it 'should correctly abort transactions' do
80
+
81
+ @t.transaction {
82
+ @t['pk0'] = { 'a' => 'A' }
83
+ @t.abort
84
+ }
85
+ @t.size.should.be.zero
86
+ end
87
+
88
+ it 'should rollback transactions with errors, and bubble exceptions' do
89
+
90
+ begin
91
+ @t.transaction {
92
+ @t['pk0'] = { 'a' => 'A' }
93
+ raise 'something goes wrong'
94
+ }
95
+ rescue RuntimeError
96
+ end
97
+ @t.size.should.be.zero
98
+ end
99
+
100
+ it 'should rollback transactions with Abort exceptions, and consume exceptions' do
101
+
102
+ @t.transaction {
103
+ @t['pk0'] = { 'a' => 'A' }
104
+ raise Rufus::Tokyo::Transactions::Abort
105
+ }
106
+ @t.size.should.be.zero
107
+ end
108
+
109
+ it 'should commit successful transactions' do
110
+
111
+ @t.transaction do
112
+ @t['pk0'] = { 'a' => 'A' }
113
+ end
114
+ @t['pk0'].should.equal({ 'a' => 'A' })
115
+ end
116
+
117
+ it 'should abort low level transactions' do
118
+
119
+ @t.tranbegin
120
+ @t['pk0'] = { 'a' => 'A' }
121
+ @t.tranabort
122
+ @t.size.should.be.zero
123
+ end
124
+
125
+ it 'should commit low level transactions' do
126
+
127
+ @t.tranbegin
128
+ @t['pk0'] = { 'a' => 'A' }
129
+ @t.trancommit
130
+ @t['pk0'].should.equal({ 'a' => 'A' })
131
+ end
132
+ end
133
+
134
+ shared 'table #keys' do
135
+
136
+ it 'should return a Ruby Array by default' do
137
+
138
+ @t.keys.class.should.equal(::Array)
139
+ end
140
+
141
+ if @t.class.name.match(/^Rufus::Tokyo/)
142
+
143
+ it 'should return a Cabinet List when :native => true' do
144
+
145
+ l = @t.keys(:native => true)
146
+ l.class.should.equal(Rufus::Tokyo::List)
147
+ l.size.should.equal(2 * @n + 1)
148
+ l.free
149
+ end
150
+ end
151
+
152
+ it 'should retrieve forward matching keys when :prefix => "prefix-"' do
153
+
154
+ @t.keys(:prefix => 'person').size.should.equal(@n)
155
+
156
+ #l = @t.keys(:prefix => 'animal', :native => true)
157
+ #l.size.should.equal(@n)
158
+ #l.free
159
+ l = @t.keys(:prefix => 'animal')
160
+ l.size.should.equal(@n)
161
+ end
162
+
163
+ it 'should retrieve keys that contain \0' do
164
+
165
+ @t.keys.include?("toto#{0.chr}5").should.be.true
166
+ end
167
+
168
+ it 'should retrieve forward matching keys when key contains \0' do
169
+
170
+ @t.keys(:prefix => 'toto').should.equal([ "toto#{0.chr}5" ])
171
+ end
172
+
173
+ it 'should return a limited number of keys when :limit is set' do
174
+
175
+ @t.keys(:limit => 20).size.should.equal(20)
176
+ end
177
+
178
+ it 'should delete_keys_with_prefix' do
179
+
180
+ @t.delete_keys_with_prefix('animal')
181
+ @t.size.should.equal(@n + 1)
182
+ @t.keys(:prefix => 'animal').size.should.equal(0)
183
+ end
184
+ end
185
+
186
+ shared 'table indexes' do
187
+
188
+ it 'should accept lexical indexes' do
189
+ @t.set_index('name', :lexical).should.equal(true)
190
+ end
191
+
192
+ it 'should accept decimal indexes' do
193
+ @t.set_index('age', :decimal).should.equal(true)
194
+ end
195
+
196
+ it 'should accept removal of indexes' do
197
+ @t.set_index('age', :decimal)
198
+ @t.set_index('age', :remove).should.equal(true)
199
+ end
200
+
201
+ it 'should accept indexes on the primary key (well...)' do
202
+ @t.set_index(:pk, :lexical).should.equal(true)
203
+ @t.set_index('', :lexical).should.equal(true)
204
+ end
205
+ end
206
+
207
+ shared 'table lget' do
208
+
209
+ it 'should return an empty hash for missing keys' do
210
+ @t.lget(%w{ pk97 pk98 }).should.equal({})
211
+ @t.mget(%w{ pk97 pk98 }).should.equal({})
212
+ end
213
+
214
+ it 'should return multiple records' do
215
+ @t.lget(%w{ pk0 pk1 }).should.equal({
216
+ 'pk0' => { 'name' => 'jim', 'age' => '25', 'lang' => 'ja,en' },
217
+ 'pk1' => { 'name' => 'jeff', 'age' => '32', 'lang' => 'en,es' }
218
+ })
219
+ @t.lget(*%w{ pk0 pk1 }).should.equal({
220
+ 'pk0' => { 'name' => 'jim', 'age' => '25', 'lang' => 'ja,en' },
221
+ 'pk1' => { 'name' => 'jeff', 'age' => '32', 'lang' => 'en,es' }
222
+ })
223
+ end
224
+ end
225
+
226
+ shared 'table like a hash' do
227
+
228
+ it 'should respond to #keys' do
229
+
230
+ @t.keys.should.equal([ 'pk0', 'pk1', 'pk2', 'pk3' ])
231
+ end
232
+
233
+ it 'should respond to #values' do
234
+
235
+ @t.values.should.equal([
236
+ { 'name' => 'jim', 'age' => '25', 'lang' => 'ja,en' },
237
+ { 'name' => 'jeff', 'age' => '32', 'lang' => 'en,es' },
238
+ { 'name' => 'jack', 'age' => '44', 'lang' => 'en' },
239
+ { 'name' => 'jake', 'age' => '45', 'lang' => 'en,li' }])
240
+ end
241
+
242
+ it 'should benefit from Enumerable' do
243
+
244
+ @t.find { |k, v|
245
+ v['name'] == 'jeff'
246
+ }.should.equal([
247
+ 'pk1', { 'name' => 'jeff', 'age' => '32', 'lang' => 'en,es' }])
248
+ end
249
+ end
250
+
251
+ shared 'table query' do
252
+
253
+ it 'can be executed' do
254
+
255
+ @t.query { |q|
256
+ q.add 'lang', :includes, 'en'
257
+ }.size.should.equal(4)
258
+ end
259
+
260
+ if @t.class.name.match(/^Rufus::Tokyo::/)
261
+
262
+ it 'can be prepared' do
263
+
264
+ @t.prepare_query { |q|
265
+ q.add 'lang', :includes, 'en'
266
+ }.should.satisfy { |q| q.class == Rufus::Tokyo::TableQuery }
267
+ end
268
+ end
269
+
270
+ it 'can be counted' do
271
+
272
+ q = @t.prepare_query { |qq| qq.add 'lang', :includes, 'en' }
273
+ q.run
274
+ q.count.should.equal(4)
275
+ end
276
+
277
+ it 'can be counted without being explicitly run' do
278
+
279
+ @t.prepare_query { |qq|
280
+ qq.add 'lang', :includes, 'en'
281
+ }.count.should.equal(4)
282
+ end
283
+
284
+ it 'can be counted immediately (qrycount table#query_count)' do
285
+
286
+ @t.query_count { |qq|
287
+ qq.add 'lang', :includes, 'en'
288
+ }.should.equal(4)
289
+ end
290
+
291
+ it 'can be limited' do
292
+
293
+ @t.query { |q|
294
+ q.add 'lang', :includes, 'en'
295
+ q.limit 2
296
+ }.size.should.equal(2)
297
+ end
298
+
299
+ it 'can leverage regex matches' do
300
+
301
+ @t.query { |q|
302
+ q.add 'name', :matches, '^j.+k'
303
+ }.to_a.should.equal([
304
+ {:pk => 'pk2', "name"=>"jack", "lang"=>"en", "age"=>"44"},
305
+ {:pk => 'pk3', "name"=>"jake", "lang"=>"en,li", "age"=>"45"}])
306
+ end
307
+
308
+ it 'can leverage numerical comparison (gt)' do
309
+
310
+ @t.query { |q|
311
+ q.add 'age', :gt, '40'
312
+ q.pk_only
313
+ }.to_a.should.equal([ 'pk2', 'pk3' ])
314
+ end
315
+
316
+ it 'can have negated conditions' do
317
+
318
+ @t.query { |q|
319
+ q.add 'age', :gt, '40', false
320
+ q.pk_only
321
+ }.to_a.should.equal([ 'pk0', 'pk1' ])
322
+ end
323
+
324
+ if (@t.respond_to?(:lib) && @t.lib.respond_to?(:qry_setlimit)) ||
325
+ (defined?(TokyoCabinet) && TokyoCabinet::TDBQRY.public_instance_methods.collect { |e| e.to_s }.include?('setlimit'))
326
+
327
+ it 'can be limited and have an offset' do
328
+
329
+ @t.query { |q|
330
+ q.add 'lang', :includes, 'en'
331
+ q.order_by 'name', :desc
332
+ q.limit 2, 0
333
+ }.collect { |e| e['name'] }.should.equal(%w{ jim jeff })
334
+ @t.query { |q|
335
+ q.add 'lang', :includes, 'en'
336
+ q.order_by 'name', :desc
337
+ q.limit 2, 2
338
+ }.collect { |e| e['name'] }.should.equal(%w{ jake jack })
339
+ end
340
+ end
341
+
342
+ it 'can be deleted (searchout : query#delete)' do
343
+
344
+ @t.prepare_query { |q|
345
+ q.add 'lang', :includes, 'es'
346
+ }.delete
347
+
348
+ @t.size.should.equal(3)
349
+ end
350
+
351
+ it 'can be deleted immediately (searchout table#query_delete)' do
352
+
353
+ @t.query_delete { |q|
354
+ q.add 'lang', :includes, 'es'
355
+ }
356
+
357
+ @t.size.should.equal(3)
358
+ end
359
+ end
360
+
361
+ shared 'table query (fts)' do
362
+
363
+ it 'can do full-text search' do
364
+
365
+ @t.query { |q|
366
+ q.add 'words', :ftsphrase, 'consul'
367
+ q.pk_only
368
+ }.to_a.should.equal(%w[ pk0 pk3 pk5 ])
369
+ end
370
+ end
371
+
372
+ shared 'table query #process' do
373
+
374
+ it 'can iterate over the matching records' do
375
+
376
+ keys, values = [], []
377
+
378
+ @t.prepare_query { |q|
379
+ q.add 'lang', :includes, 'en'
380
+ }.process { |k, v|
381
+ keys << k
382
+ values << v
383
+ }.free
384
+
385
+ keys.should.equal(%w[ pk0 pk1 pk2 pk3 ])
386
+ values.first.keys.sort.should.equal(%w[ age lang name ])
387
+ end
388
+
389
+ it 'can stop while iterating' do
390
+
391
+ seen = 0
392
+
393
+ @t.prepare_query { |q|
394
+ q.add 'lang', :includes, 'en'
395
+ }.process { |k, v|
396
+ seen = seen + 1
397
+ :stop
398
+ }.free
399
+
400
+ seen.should.equal(1)
401
+ end
402
+
403
+ it 'can delete while iterating' do
404
+
405
+ @t.prepare_query { |q|
406
+ q.add 'lang', :includes, 'en'
407
+ }.process { |k, v|
408
+ v['name'].match(/^ja/) ? :delete : nil
409
+ }.free
410
+
411
+ @t.keys.sort.should.equal(%w[ pk0 pk1 ])
412
+ end
413
+
414
+ it 'can update while iterating' do
415
+
416
+ @t.prepare_query { |q|
417
+ q.add 'lang', :includes, 'en'
418
+ }.process { |k, v|
419
+ v['name'].match(/^ja/) ? v.merge('special' => 'seen') : nil
420
+ }.free
421
+
422
+ @t.size.should.equal(4)
423
+
424
+ @t['pk2'].should.equal(
425
+ {'name'=>'jack', 'age'=>'44', 'lang'=>'en', 'special'=>'seen'})
426
+ @t['pk3'].should.equal(
427
+ {'name'=>'jake', 'age'=>'45', 'lang'=>'en,li', 'special'=>'seen'})
428
+ end
429
+
430
+ it 'can update, delete and stop' do
431
+
432
+ seen = []
433
+
434
+ @t.prepare_query { |q|
435
+ q.add 'lang', :includes, 'en'
436
+ q.order_by 'name', :desc
437
+ }.process { |k, v|
438
+ seen << v['name']
439
+ case v['name']
440
+ when 'jim' then nil
441
+ when 'jeff' then :delete
442
+ when 'jake' then [ :stop, v.merge('special' => 'nada') ]
443
+ end
444
+ }.free
445
+
446
+ seen.include?('jack').should.be.false
447
+
448
+ @t.size.should.equal(3)
449
+
450
+ @t['pk3'].should.equal(
451
+ {'name'=>'jake', 'age'=>'45', 'lang'=>'en,li', 'special'=>'nada'})
452
+ @t['pk1'].should.be.nil
453
+ end
454
+ end
455
+
456
+ shared 'table query results' do
457
+
458
+ it 'can come ordered (strdesc)' do
459
+
460
+ @t.query { |q|
461
+ q.add 'lang', :includes, 'en'
462
+ q.order_by 'name', :desc
463
+ q.limit 2
464
+ }.to_a.should.equal([
465
+ {:pk => 'pk0', "name"=>"jim", "lang"=>"ja,en", "age"=>"25"},
466
+ {:pk => 'pk1', "name"=>"jeff", "lang"=>"en,es", "age"=>"32"}])
467
+ end
468
+
469
+ it 'can come ordered (strasc)' do
470
+
471
+ @t.query { |q|
472
+ q.add 'lang', :includes, 'en'
473
+ q.order_by 'name', :asc
474
+ }.to_a.should.equal([
475
+ {:pk => 'pk2', "name"=>"jack", "lang"=>"en", "age"=>"44"},
476
+ {:pk => 'pk3', "name"=>"jake", "lang"=>"en,li", "age"=>"45"},
477
+ {:pk => 'pk1', "name"=>"jeff", "lang"=>"en,es", "age"=>"32"},
478
+ {:pk => 'pk0', "name"=>"jim", "lang"=>"ja,en", "age"=>"25"}])
479
+ end
480
+
481
+ it 'can come ordered (numasc)' do
482
+
483
+ @t.query { |q|
484
+ q.add 'lang', :includes, 'en'
485
+ q.order_by 'age', :numasc
486
+ }.to_a.should.equal([
487
+ {:pk => 'pk0', "name"=>"jim", "lang"=>"ja,en", "age"=>"25"},
488
+ {:pk => 'pk1', "name"=>"jeff", "lang"=>"en,es", "age"=>"32"},
489
+ {:pk => 'pk2', "name"=>"jack", "lang"=>"en", "age"=>"44"},
490
+ {:pk => 'pk3', "name"=>"jake", "lang"=>"en,li", "age"=>"45"}])
491
+ end
492
+
493
+ it 'can come without the primary keys (no_pk)' do
494
+
495
+ @t.query { |q|
496
+ q.add 'name', :matches, '^j.+k'
497
+ q.no_pk
498
+ }.to_a.should.equal([
499
+ {"name"=>"jack", "lang"=>"en", "age"=>"44"},
500
+ {"name"=>"jake", "lang"=>"en,li", "age"=>"45"}])
501
+ end
502
+
503
+ it 'can consist only of the primary keys (pk_only)' do
504
+
505
+ @t.query { |q|
506
+ q.add 'name', :matches, '^j.+k'
507
+ q.pk_only
508
+ }.to_a.should.equal(["pk2", "pk3"])
509
+ end
510
+ end
511
+
512
+ shared 'tyrant table with embedded lua' do
513
+
514
+ it 'should call Lua extensions' do
515
+ @t.ext(:hi).should.equal('Hi!')
516
+ end
517
+
518
+ it 'should return nil when function is missing' do
519
+ @t.ext(:missing, 'nada', 'forever').should.equal(nil)
520
+ end
521
+ end
522
+
523
+ shared 'a table structure flattening keys and values' do
524
+
525
+ it 'should to_s column names when #set_index' do
526
+
527
+ @t.set_index(:name, :lexical).should.equal(true)
528
+ end
529
+
530
+ it 'should to_s keys and values in the hash when #[]=' do
531
+
532
+ @t[:toto] = { :a => 1, :b => 2 }
533
+ @t['toto'].should.equal({ 'a' => '1', 'b' => '2' })
534
+ end
535
+
536
+ it 'should to_s keys when #delete' do
537
+
538
+ @t['toto'] = { 'a' => '1', 'b' => '2' }
539
+ @t.delete(:toto).should.equal({ 'a' => '1', 'b' => '2' })
540
+ end
541
+
542
+ it 'should to_s keys when #lget' do
543
+
544
+ (1..7).each { |i| @t["toto#{i}"] = { 'i' => i.to_s } }
545
+
546
+ @t.lget([ :toto1, :toto3, :toto4 ]).should.equal(
547
+ {"toto1"=>{"i"=>"1"}, "toto3"=>{"i"=>"3"}, "toto4"=>{"i"=>"4"}})
548
+ end
549
+ end
550
+
551
+ shared 'a table structure to_s-ing query stuff' do
552
+
553
+ it 'should accept symbols as column names' do
554
+
555
+ @t.query { |q|
556
+ q.add :lang, :includes, 'en'
557
+ }.size.should.equal(4)
558
+ end
559
+
560
+ it 'should accept non-strings as values' do
561
+
562
+ @t.query { |q|
563
+ q.add 'age', :equals, 44
564
+ }.to_a.should.equal(
565
+ [{"name"=>"jack", "lang"=>"en", :pk=>"pk2", "age"=>"44"}])
566
+ end
567
+
568
+ it 'should accept symbols as column names in #order_by' do
569
+
570
+ @t.query { |q|
571
+ q.add 'lang', :includes, 'en'
572
+ q.order_by :name, :desc
573
+ q.limit 2
574
+ }.to_a.should.equal([
575
+ {:pk => 'pk0', "name"=>"jim", "lang"=>"ja,en", "age"=>"25"},
576
+ {:pk => 'pk1', "name"=>"jeff", "lang"=>"en,es", "age"=>"32"}])
577
+ end
578
+ end
579
+
580
+ shared 'table query metasearch' do
581
+
582
+ it 'can do UNION on queries' do
583
+
584
+ @t.union(
585
+ @t.prepare_query { |q|
586
+ q.add 'lang', :includes, 'es'
587
+ },
588
+ @t.prepare_query { |q|
589
+ q.add 'lang', :includes, 'li'
590
+ },
591
+ false
592
+ ).should.equal([
593
+ 'pk1', 'pk3'
594
+ ])
595
+ end
596
+
597
+ it 'can do UNION on queries (and fetch the results)' do
598
+
599
+ @t.union(
600
+ @t.prepare_query { |q|
601
+ q.add 'lang', :includes, 'es'
602
+ },
603
+ @t.prepare_query { |q|
604
+ q.add 'lang', :includes, 'li'
605
+ }
606
+ ).should.equal(
607
+ {"pk1"=>{"name"=>"jeff", "lang"=>"en,es", "age"=>"32"}, "pk3"=>{"name"=>"jake", "lang"=>"en,li", "age"=>"45"}}
608
+ )
609
+ end
610
+
611
+ it 'can do INTERSECTION on queries' do
612
+
613
+ @t.intersection(
614
+ @t.prepare_query { |q|
615
+ q.add 'age', :gt, '30'
616
+ },
617
+ @t.prepare_query { |q|
618
+ q.add 'lang', :includes, 'li'
619
+ },
620
+ false
621
+ ).should.equal([
622
+ 'pk3'
623
+ ])
624
+ end
625
+
626
+ it 'can do DIFFERENCE on queries' do
627
+
628
+ @t.difference(
629
+ @t.prepare_query { |q|
630
+ q.add 'age', :gt, '30'
631
+ },
632
+ @t.prepare_query { |q|
633
+ q.add 'lang', :includes, 'li'
634
+ },
635
+ false
636
+ ).should.equal([
637
+ 'pk1', 'pk2'
638
+ ])
639
+ end
640
+
641
+ it 'can do meta with only one query' do
642
+
643
+ @t.difference(
644
+ @t.prepare_query { |q|
645
+ q.add 'age', :gt, '30'
646
+ },
647
+ false
648
+ ).should.equal([
649
+ 'pk1', 'pk2', 'pk3'
650
+ ])
651
+ end
652
+
653
+ it 'should complain when there is no query' do
654
+
655
+ lambda {
656
+ @t.difference(false)
657
+ }.should.raise(ArgumentError)
658
+ end
659
+
660
+ it 'can do metasearch a la ruby-tokyotyrant' do
661
+
662
+ @t.search(
663
+ :difference,
664
+ @t.prepare_query { |q|
665
+ q.add 'age', :gt, '30'
666
+ },
667
+ @t.prepare_query { |q|
668
+ q.add 'lang', :includes, 'li'
669
+ }
670
+ ).should.equal(
671
+ {"pk1"=>{"name"=>"jeff", "lang"=>"en,es", "age"=>"32"}, "pk2"=>{"name"=>"jack", "lang"=>"en", "age"=>"44"}}
672
+ )
673
+ end
674
+ end
675
+