rufus-tokyo 1.0.3 → 1.0.4

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