tarantool 0.3.0.7 → 0.4.2.1

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 (51) hide show
  1. data/Gemfile +2 -3
  2. data/README.md +90 -30
  3. data/Rakefile +6 -1
  4. data/lib/tarantool.rb +93 -18
  5. data/lib/tarantool/base_record.rb +97 -10
  6. data/lib/tarantool/block_db.rb +104 -6
  7. data/lib/tarantool/callback_db.rb +7 -0
  8. data/lib/tarantool/core-ext.rb +24 -8
  9. data/lib/tarantool/em_db.rb +189 -20
  10. data/lib/tarantool/exceptions.rb +4 -0
  11. data/lib/tarantool/fiber_db.rb +15 -1
  12. data/lib/tarantool/light_record.rb +17 -0
  13. data/lib/tarantool/query.rb +15 -9
  14. data/lib/tarantool/record/select.rb +21 -3
  15. data/lib/tarantool/request.rb +130 -43
  16. data/lib/tarantool/response.rb +70 -7
  17. data/lib/tarantool/serializers.rb +26 -5
  18. data/lib/tarantool/serializers/ber_array.rb +14 -0
  19. data/lib/tarantool/shards_support.rb +204 -0
  20. data/lib/tarantool/space_array.rb +38 -13
  21. data/lib/tarantool/space_hash.rb +49 -27
  22. data/lib/tarantool/util.rb +96 -10
  23. data/lib/tarantool/version.rb +2 -1
  24. data/test/helper.rb +154 -4
  25. data/test/{tarant/init.lua → init.lua} +0 -0
  26. data/test/run_all.rb +2 -2
  27. data/test/shared_record.rb +59 -0
  28. data/test/shared_replicated_shard.rb +1018 -0
  29. data/test/shared_reshard.rb +380 -0
  30. data/test/tarantool.cfg +2 -0
  31. data/test/test_light_record.rb +2 -0
  32. data/test/test_light_record_callback.rb +92 -0
  33. data/test/test_query_block.rb +1 -0
  34. data/test/test_query_fiber.rb +1 -0
  35. data/test/test_reshard_block.rb +7 -0
  36. data/test/test_reshard_fiber.rb +11 -0
  37. data/test/test_shard_replication_block.rb +7 -0
  38. data/test/test_shard_replication_fiber.rb +11 -0
  39. data/test/test_space_array_block.rb +1 -0
  40. data/test/test_space_array_callback.rb +50 -121
  41. data/test/test_space_array_callback_nodef.rb +39 -96
  42. data/test/test_space_array_fiber.rb +1 -0
  43. data/test/test_space_hash_block.rb +1 -0
  44. data/test/test_space_hash_fiber.rb +1 -0
  45. metadata +54 -17
  46. data/lib/tarantool/record.rb +0 -164
  47. data/test/box.pid +0 -1
  48. data/test/tarantool.log +0 -6
  49. data/test/tarantool_repl.cfg +0 -53
  50. data/test/test_record.rb +0 -88
  51. data/test/test_record_composite_pk.rb +0 -77
File without changes
data/test/run_all.rb CHANGED
@@ -1,3 +1,3 @@
1
- Dir.glob(File.expand_path('../test_*.rb', __FILE__)) do |f|
2
- require f
1
+ Dir.glob(File.expand_path('../test*.rb', __FILE__)) do |f|
2
+ require f unless f =~ /shard/
3
3
  end
@@ -471,4 +471,63 @@ shared_examples_for :record do
471
471
  u.info['hobbies'].must_equal ['mufa', 'tuka']
472
472
  end
473
473
  end
474
+
475
+ describe "composite primary key" do
476
+ let(:address_class) do
477
+ Class.new(base_class) do
478
+ set_tarantool DB
479
+ set_space_no 2
480
+
481
+ def self.name # For naming
482
+ "Adress"
483
+ end
484
+
485
+ field :city, :string
486
+ field :street, :string
487
+ field :index, :integer
488
+ field :name, :string
489
+ field :citizens, :integer, default: 1
490
+ index :city, :street, primary: true
491
+ index :index
492
+ end
493
+ end
494
+
495
+ before do
496
+ address_class.create city: "Moscow", street: "Leningradskii", index: 123, name: "Pedro"
497
+ address_class.create city: "Moscow", street: "Mohovaya", index: 123, name: "Pedro"
498
+ end
499
+
500
+ it "should return objects by second index" do
501
+ a1 = address_class.where(index: 123)
502
+ a1.all.size.must_equal 2
503
+ end
504
+
505
+ it "should work with increment" do
506
+ a1 = address_class.where(city: "Moscow", street: "Leningradskii").first
507
+ citizens = a1.citizens
508
+ a1.increment :citizens
509
+ a1.reload.citizens.must_equal(citizens+1)
510
+ end
511
+
512
+ it "should return all objects" do
513
+ a1 = address_class.where city: "Moscow"
514
+ a1.all.size.must_equal 2
515
+ a1.all.map(&:street).must_equal ["Leningradskii", "Mohovaya"]
516
+ end
517
+
518
+ it "should return right object" do
519
+ a1 = address_class.where city: "Moscow", street: "Leningradskii"
520
+ a1.first.street.must_equal "Leningradskii"
521
+ end
522
+
523
+ it "should destroy object" do
524
+ a1 = address_class.where city: "Moscow", street: "Leningradskii"
525
+ a1.first.destroy
526
+ end
527
+
528
+ it "should return id" do
529
+ a1 = address_class.where city: "Moscow", street: "Leningradskii"
530
+ a1.first.id.must_equal ["Moscow", "Leningradskii"]
531
+ end
532
+ end
474
533
  end
@@ -0,0 +1,1018 @@
1
+ require File.expand_path('../helper.rb', __FILE__)
2
+ require 'tarantool/light_record'
3
+
4
+ shared_examples_for 'replication and shards' do
5
+ before { TConf.reset_and_up_all }
6
+ after { TConf.clear_all }
7
+
8
+ let(:t_both) {
9
+ Tarantool.new(type: tarantool_type,
10
+ servers: [
11
+ [ TConf.conf(:master1), TConf.conf(:slave1) ],
12
+ [ TConf.conf(:master2), TConf.conf(:slave2) ]
13
+ ],
14
+ replica_strategy: replica_strategy
15
+ )
16
+ }
17
+
18
+ let(:t_first) {
19
+ Tarantool.new(type: tarantool_type,
20
+ servers: [ TConf.conf(:master1), TConf.conf(:slave1) ],
21
+ replica_strategy: replica_strategy
22
+ )
23
+ }
24
+
25
+ let(:t_second) {
26
+ Tarantool.new(type: tarantool_type,
27
+ servers: [ TConf.conf(:master2), TConf.conf(:slave2) ],
28
+ replica_strategy: replica_strategy
29
+ )
30
+ }
31
+ let(:replica_strategy) { :master_first }
32
+
33
+ HSPACE1_ = {
34
+ fields: {id: :int, name: :str, val: :int},
35
+ keys: :id
36
+ }
37
+
38
+ let(:space0_array_both) {
39
+ t_both.space(0, SPACE0[:types], keys: SPACE0[:keys],
40
+ shard_fields: shard_fields_array0, shard_proc: shard_proc0)
41
+ }
42
+ let(:space0_array_first) { t_first.space(0, SPACE0[:types], keys: SPACE0[:keys]) }
43
+ let(:space0_array_second) { t_second.space(0, SPACE0[:types], keys: SPACE0[:keys]) }
44
+ let(:space0_hash_both) {
45
+ t_both.space_hash(0, HSPACE0[:fields], keys: HSPACE0[:keys],
46
+ shard_fields: shard_fields_hash0, shard_proc: shard_proc0)
47
+ }
48
+ let(:space0_hash_first) { t_first.space_hash(0, HSPACE0[:fields], keys: HSPACE0[:keys]) }
49
+ let(:space0_hash_second) { t_second.space_hash(0, HSPACE0[:fields], keys: HSPACE0[:keys]) }
50
+
51
+ let(:space1_array_both) {
52
+ t_both.space(1, SPACE1[:types], keys: SPACE1[:keys],
53
+ shard_fields: shard_fields_array1, shard_proc: shard_proc1)
54
+ }
55
+ let(:space1_array_first) { t_first.space(1, SPACE1[:types], keys: SPACE1[:keys]) }
56
+ let(:space1_array_second) { t_second.space(1, SPACE1[:types], keys: SPACE1[:keys]) }
57
+ let(:space1_hash_both) {
58
+ t_both.space_hash(1, HSPACE1_[:fields], keys: HSPACE1_[:keys],
59
+ shard_fields: shard_fields_hash1, shard_proc: shard_proc1)
60
+ }
61
+ let(:space1_hash_first) { t_first.space_hash(1, HSPACE1_[:fields], keys: HSPACE1_[:keys]) }
62
+ let(:space1_hash_second) { t_second.space_hash(1, HSPACE1_[:fields], keys: HSPACE1_[:keys]) }
63
+
64
+ let(:space2_array_both) {
65
+ t_both.space(2, SPACE2[:types], keys: SPACE2[:keys],
66
+ shard_fields: shard_fields_array2, shard_proc: shard_proc2)
67
+ }
68
+ let(:space2_array_first) { t_first.space(2, SPACE2[:types], keys: SPACE2[:keys]) }
69
+ let(:space2_array_second) { t_second.space(2, SPACE2[:types], keys: SPACE2[:keys]) }
70
+ let(:space2_hash_both) {
71
+ t_both.space_hash(2, HSPACE2[:fields], keys: HSPACE2[:keys],
72
+ shard_fields: shard_fields_hash2, shard_proc: shard_proc2)
73
+ }
74
+ let(:space2_hash_first) { t_first.space_hash(2, HSPACE2[:fields], keys: HSPACE2[:keys]) }
75
+ let(:space2_hash_second) { t_second.space_hash(2, HSPACE2[:fields], keys: HSPACE2[:keys]) }
76
+
77
+ let(:shard_fields_array0) { nil }
78
+ let(:shard_fields_array1) { nil }
79
+ let(:shard_fields_array2) { nil }
80
+ let(:shard_fields_hash0) { nil }
81
+ let(:shard_fields_hash1) { nil }
82
+ let(:shard_fields_hash2) { nil }
83
+ let(:shard_proc0) { nil }
84
+ let(:shard_proc1) { nil }
85
+ let(:shard_proc2) { nil }
86
+
87
+ shared_examples_for "array space with simple shard" do
88
+ let(:space_both) { space1_array_both }
89
+ let(:space_first) { space1_array_first }
90
+ let(:space_second){ space1_array_second }
91
+ before {
92
+ blockrun {
93
+ 100.times{|i|
94
+ space_both.insert([i, "#{i+1}", i+2])
95
+ }
96
+ }
97
+ }
98
+ it "should spread distribution over" do
99
+ all_pks = 100.times.map{|i| [i]}
100
+ results = blockrun{[
101
+ 100.times.map{|i| space_both.by_pk(i) },
102
+ space_first.all_by_pks(all_pks),
103
+ space_second.all_by_pks(all_pks),
104
+ space_both.all_by_pks(all_pks),
105
+ ]}
106
+ results[1].size.must_equal 50
107
+ results[2].size.must_equal 50
108
+ results[1].include?([50, '51', 52]).must_equal results[2].include?([51, '52', 53])
109
+ (results[1] + results[2]).sort.must_equal results[0]
110
+ results[3].sort.must_equal results[0]
111
+ end
112
+
113
+ it "should delete" do
114
+ results = blockrun{[
115
+ space_both.delete(50, return_tuple: true),
116
+ space_both.by_pk(50),
117
+ space_both.delete(51, return_tuple: true),
118
+ space_both.by_pk(51),
119
+ ]}
120
+ results[0].must_equal [50, '51', 52]
121
+ results[1].must_be_nil
122
+ results[2].must_equal [51, '52', 53]
123
+ results[3].must_be_nil
124
+ end
125
+
126
+ it "should update" do
127
+ results = blockrun{[
128
+ space_both.update(50, {1 => '--'}, return_tuple: true),
129
+ space_both.update(51, [[1, :set, '++']], return_tuple: true),
130
+ ]}
131
+ results[0].must_equal [50, '--', 52]
132
+ results[1].must_equal [51, '++', 53]
133
+ end
134
+ end
135
+
136
+ describe "array space with modulo shard" do
137
+ let(:shard_proc1) { :modulo }
138
+ it_behaves_like "array space with simple shard"
139
+ end
140
+
141
+ shared_examples_for "hash space with simple shard" do
142
+ let(:space_both) { space1_hash_both }
143
+ let(:space_first) { space1_hash_first }
144
+ let(:space_second){ space1_hash_second }
145
+ before {
146
+ blockrun {
147
+ 100.times{|i|
148
+ space_both.insert({id: i, name: "#{i+1}", val: i+2})
149
+ }
150
+ }
151
+ }
152
+ it "should spread distribution over" do
153
+ all_pks = 100.times.map{|i| [i]}
154
+ results = blockrun{[
155
+ 100.times.map{|i| space_both.by_pk(i) },
156
+ space_first.all_by_pks(all_pks),
157
+ space_second.all_by_pks(all_pks),
158
+ space_both.all_by_pks(all_pks),
159
+ ]}
160
+ results[1].size.must_equal 50
161
+ results[2].size.must_equal 50
162
+ results[1].include?({id:50, name:'51', val:52}).must_equal results[2].include?({id:51, name:'52', val:53})
163
+ (results[1] + results[2]).sort_by{|v| v[:id]}.must_equal results[0]
164
+ results[3].sort_by{|v| v[:id]}.must_equal results[0]
165
+ end
166
+
167
+ it "should delete" do
168
+ results = blockrun{[
169
+ space_both.delete(50, return_tuple: true),
170
+ space_both.by_pk(50),
171
+ space_both.delete(51, return_tuple: true),
172
+ space_both.by_pk(51),
173
+ ]}
174
+ results[0].must_equal({id: 50, name:'51', val:52})
175
+ results[1].must_be_nil
176
+ results[2].must_equal({id: 51, name:'52', val:53})
177
+ results[3].must_be_nil
178
+ end
179
+
180
+ it "should update" do
181
+ results = blockrun{[
182
+ space_both.update(50, {name: '--'}, return_tuple: true),
183
+ space_both.update(51, [[:name, :set, '++']], return_tuple: true),
184
+ ]}
185
+ results[0].must_equal({id: 50, name: '--', val: 52})
186
+ results[1].must_equal({id: 51, name: '++', val: 53})
187
+ end
188
+ end
189
+
190
+ describe "hash space with modulo shard" do
191
+ let(:shard_proc1) { :modulo }
192
+ it_behaves_like "hash space with simple shard"
193
+ end
194
+
195
+ shared_examples_for "array space shard with composit pk" do
196
+ def iii(pk) [*pk, pk.hash & 0xffffff] end
197
+ let(:space_both){ space2_array_both }
198
+ let(:space_first){ space2_array_first }
199
+ let(:space_second){ space2_array_second }
200
+ let(:pks) {
201
+ (1..10).to_a.product((1..10).to_a).map{|i,j| [i.to_s, j.to_s]}
202
+ }
203
+ let(:pk_first) {
204
+ pks.detect{|pk| space_both._detect_shards_for_key(pk, 0) == 0}
205
+ }
206
+ let(:pk_second) {
207
+ pks.detect{|pk| space_both._detect_shards_for_key(pk, 0) == 1}
208
+ }
209
+ before {
210
+ blockrun{
211
+ pks.each{|pk| space_both.insert(iii(pk))}
212
+ }
213
+ }
214
+
215
+ it "should spread distribution over" do
216
+ results = blockrun{[
217
+ pks.map{|pk| space_both.by_pk(pk)},
218
+ pks.flat_map{|pk| space_first.all_by_pks([pk])},
219
+ pks.flat_map{|pk| space_second.all_by_pks([pk])},
220
+ space_first.by_pk(pk_first),
221
+ space_second.by_pk(pk_second),
222
+ space_first.by_pk(pk_second),
223
+ space_second.by_pk(pk_first),
224
+ ]}
225
+ results[0].compact.size.must_equal pks.size
226
+ (results[1] + results[2]).sort.must_equal results[0].sort
227
+ results[1].size.must_be_close_to pks.size/2, pks.size/5
228
+ results[2].size.must_be_close_to pks.size/2, pks.size/5
229
+ results[3].must_equal iii(pk_first)
230
+ results[4].must_equal iii(pk_second)
231
+ results[5].must_equal nil
232
+ results[6].must_equal nil
233
+ end
234
+
235
+ it "should delete" do
236
+ results = blockrun{[
237
+ space_both.delete(pk_first, return_tuple:true),
238
+ space_both.by_pk(pk_first),
239
+ space_both.delete(pk_second, return_tuple:true),
240
+ space_both.by_pk(pk_second),
241
+ ]}
242
+ results[0].must_equal iii(pk_first)
243
+ results[1].must_be_nil
244
+ results[2].must_equal iii(pk_second)
245
+ results[3].must_be_nil
246
+ end
247
+
248
+ it "should update" do
249
+ results = blockrun{[
250
+ space_both.update(pk_first, {2=> [:+, 1]}, return_tuple:true),
251
+ space_both.update(pk_second, {2=> [:+, 1]}, return_tuple:true),
252
+ ]}
253
+ results[0][2].must_equal iii(pk_first)[2]+1
254
+ results[1][2].must_equal iii(pk_second)[2]+1
255
+ end
256
+
257
+ it "should search by keys half" do
258
+ results = blockrun{[
259
+ pks.map{|pk| space_both.by_pk(pk)},
260
+ (1..10).flat_map{|i| space_both.select(0, [i.to_s])},
261
+ space_both.select(0, (1..10).map{|i| [i.to_s]})
262
+ ]}
263
+ results[1].sort.must_equal results[0].sort
264
+ results[2].sort.must_equal results[0].sort
265
+ end
266
+ end
267
+
268
+ describe "array space default shard with composit pk" do
269
+ it_behaves_like "array space shard with composit pk"
270
+ end
271
+
272
+ describe "array space custom shard with composit pk" do
273
+ class ShardProc2
274
+ attr :count
275
+ def initialize
276
+ @count = 0
277
+ end
278
+ def call(shard_values, shards_count, this)
279
+ @count += 1
280
+ shard_values[0] && shard_values[0].to_i % shards_count
281
+ end
282
+ end
283
+ let(:shard_proc2){ ShardProc2.new }
284
+
285
+ it_behaves_like "array space shard with composit pk"
286
+
287
+ it "should call custom shard proc" do
288
+ shard_proc2.count.must_equal pks.size
289
+ end
290
+ end
291
+
292
+ shared_examples_for "hash space shard with composit pk" do
293
+ def iii(pk) {first: pk[0], second: pk[1], third: pk.hash & 0xffffff} end
294
+ let(:space_both){ space2_hash_both }
295
+ let(:space_first){ space2_hash_first }
296
+ let(:space_second){ space2_hash_second }
297
+ let(:pks) {
298
+ (1..10).to_a.product((1..10).to_a).map{|i,j| [i.to_s, j.to_s]}
299
+ }
300
+ let(:pk_first) {
301
+ pks.detect{|pk| space_both._detect_shards_for_key(pk, 0) == 0}
302
+ }
303
+ let(:pk_second) {
304
+ pks.detect{|pk| space_both._detect_shards_for_key(pk, 0) == 1}
305
+ }
306
+ before {
307
+ blockrun{
308
+ pks.each{|pk| space_both.insert(iii(pk))}
309
+ }
310
+ }
311
+
312
+ it "should spread distribution over" do
313
+ results = blockrun{[
314
+ pks.map{|pk| space_both.by_pk(pk)},
315
+ pks.flat_map{|pk| space_first.all_by_pks([pk])},
316
+ pks.flat_map{|pk| space_second.all_by_pks([pk])},
317
+ space_first.by_pk(pk_first),
318
+ space_second.by_pk(pk_second),
319
+ space_first.by_pk(pk_second),
320
+ space_second.by_pk(pk_first),
321
+ ]}
322
+ results[0].compact.size.must_equal pks.size
323
+ (results[1] + results[2]).sort_by(&:values).must_equal results[0].sort_by(&:values)
324
+ results[1].size.must_be_close_to pks.size/2, pks.size/5
325
+ results[2].size.must_be_close_to pks.size/2, pks.size/5
326
+ results[3].must_equal iii(pk_first)
327
+ results[4].must_equal iii(pk_second)
328
+ results[5].must_equal nil
329
+ results[6].must_equal nil
330
+ end
331
+
332
+ it "should delete" do
333
+ results = blockrun{[
334
+ space_both.delete(pk_first, return_tuple:true),
335
+ space_both.by_pk(pk_first),
336
+ space_both.delete(pk_second, return_tuple:true),
337
+ space_both.by_pk(pk_second),
338
+ ]}
339
+ results[0].must_equal iii(pk_first)
340
+ results[1].must_be_nil
341
+ results[2].must_equal iii(pk_second)
342
+ results[3].must_be_nil
343
+ end
344
+
345
+ it "should update" do
346
+ results = blockrun{[
347
+ space_both.update(pk_first, {third: [:+, 1]}, return_tuple:true),
348
+ space_both.update(pk_second, {third: [:+, 1]}, return_tuple:true),
349
+ ]}
350
+ results[0][:third].must_equal iii(pk_first)[:third]+1
351
+ results[1][:third].must_equal iii(pk_second)[:third]+1
352
+ end
353
+
354
+ it "should search by keys half" do
355
+ results = blockrun{[
356
+ pks.map{|pk| space_both.by_pk(pk)},
357
+ (1..10).flat_map{|i| space_both.select({first: i.to_s})},
358
+ space_both.select((1..10).map{|i| {first: i.to_s}})
359
+ ]}
360
+ results[1].sort_by(&:values).must_equal results[0].sort_by(&:values)
361
+ results[2].sort_by(&:values).must_equal results[0].sort_by(&:values)
362
+ end
363
+ end
364
+
365
+ describe "hash space default shard with composit pk" do
366
+ it_behaves_like "hash space shard with composit pk"
367
+ end
368
+
369
+ describe "array space with shard on not pk" do
370
+ let(:shard_proc0) { :modulo }
371
+ let(:shard_fields_array0) { [3] }
372
+ let(:space_both){ space0_array_both }
373
+ let(:space_first){ space0_array_first }
374
+ let(:space_second){ space0_array_second }
375
+ let(:pks){ 100.times.to_a }
376
+
377
+ before {
378
+ blockrun{
379
+ pks.each{|i| space_both.insert([i, i+1, i+2, i/10]) }
380
+ }
381
+ }
382
+
383
+ it "should spread distribution over" do
384
+ results = blockrun{[
385
+ pks.flat_map{|i| space_both.all_by_pks([i])},
386
+ pks.flat_map{|i| space_first.all_by_pks([i])},
387
+ pks.flat_map{|i| space_second.all_by_pks([i])},
388
+ (0..9).flat_map{|i| space_both.select(2, i)},
389
+ (0..9).flat_map{|i| space_first.select([3], i)},
390
+ (0..9).flat_map{|i| space_second.select(2, i)},
391
+ space_both.all_by_pks(pks.map{|pk| [pk]}),
392
+ space_both.select(2, (0..9).map{|i| [i]})
393
+ ]}
394
+ results[0].size.must_equal pks.size
395
+ results[1].size.must_equal pks.size/2
396
+ results[2].size.must_equal pks.size/2
397
+ (results[1]+results[2]).sort_by{|v|v[0].to_i}.must_equal results[0]
398
+
399
+ results[3].size.must_equal pks.size
400
+ results[3].sort_by{|v|v[0].to_i}.must_equal results[0]
401
+ results[4].size.must_equal pks.size/2
402
+ results[5].size.must_equal pks.size/2
403
+ (results[4]+results[5]).sort_by{|v|v[0].to_i}.must_equal results[0]
404
+
405
+ results[4].include?(['21','22','23',2]).must_equal(
406
+ results[5].include?(['31','32','33',3]))
407
+
408
+ results[6].sort_by{|v|v[0].to_i}.must_equal results[0]
409
+ results[7].sort_by{|v|v[0].to_i}.must_equal results[0]
410
+ end
411
+
412
+ it "should delete" do
413
+ results = blockrun{[
414
+ space_both.delete('21', return_tuple:true),
415
+ space_both.by_pk('21'),
416
+ space_both.delete('31', return_tuple:true),
417
+ space_both.by_pk('31')
418
+ ]}
419
+ results[0].must_equal ['21','22','23',2]
420
+ results[1].must_be_nil
421
+ results[2].must_equal ['31','32','33',3]
422
+ results[3].must_be_nil
423
+ end
424
+
425
+ it "should update" do
426
+ results = blockrun{[
427
+ space_both.update('21', {1=> [:splice,3,0,'!']}, return_tuple:true),
428
+ space_both.update('31', {1=> [:splice,3,0,'!']}, return_tuple:true),
429
+ ]}
430
+ results[0].must_equal ['21','22!','23',2]
431
+ results[1].must_equal ['31','32!','33',3]
432
+ end
433
+ end
434
+
435
+ describe "hash space with shard on not pk" do
436
+ let(:shard_proc0) { :modulo }
437
+ let(:shard_fields_hash0) { [:score] }
438
+ let(:space_both){ space0_hash_both }
439
+ let(:space_first){ space0_hash_first }
440
+ let(:space_second){ space0_hash_second }
441
+ let(:pks){ 100.times.to_a }
442
+
443
+ before {
444
+ blockrun{
445
+ pks.each{|i|
446
+ space_both.insert(name: i, surname: i+1, email: i+2, score: i/10)
447
+ }
448
+ }
449
+ }
450
+ let(:twenty_one){ {name:'21',surname:'22',email:'23',score: 2} }
451
+ let(:thirty_one){ {name:'31',surname:'32',email:'33',score: 3} }
452
+
453
+ it "should spread distribution over" do
454
+ results = blockrun{[
455
+ pks.flat_map{|i| space_both.all_by_pks([i])},
456
+ pks.flat_map{|i| space_first.all_by_pks([i])},
457
+ pks.flat_map{|i| space_second.all_by_pks([i])},
458
+ (0..9).flat_map{|i| space_both.select(score: i)},
459
+ (0..9).flat_map{|i| space_first.select(score: i)},
460
+ (0..9).flat_map{|i| space_second.select(score: i)},
461
+ space_both.all_by_pks(pks.map{|pk| [pk]}),
462
+ space_both.select(score: (0..9).to_a)
463
+ ]}
464
+ results[0].size.must_equal pks.size
465
+ results[1].size.must_equal pks.size/2
466
+ results[2].size.must_equal pks.size/2
467
+ (results[1]+results[2]).sort_by{|v|v[:name].to_i}.must_equal results[0]
468
+
469
+ results[3].size.must_equal pks.size
470
+ results[3].sort_by{|v|v[:name].to_i}.must_equal results[0]
471
+ results[4].size.must_equal pks.size/2
472
+ results[5].size.must_equal pks.size/2
473
+ (results[4]+results[5]).sort_by{|v|v[:name].to_i}.must_equal results[0]
474
+
475
+ results[4].include?(twenty_one).must_equal results[5].include?(thirty_one)
476
+
477
+ results[6].sort_by{|v|v[:name].to_i}.must_equal results[0]
478
+ results[7].sort_by{|v|v[:name].to_i}.must_equal results[0]
479
+ end
480
+
481
+ it "should delete" do
482
+ results = blockrun{[
483
+ space_both.delete('21', return_tuple:true),
484
+ space_both.by_pk('21'),
485
+ space_both.delete('31', return_tuple:true),
486
+ space_both.by_pk('31')
487
+ ]}
488
+ results[0].must_equal twenty_one
489
+ results[1].must_be_nil
490
+ results[2].must_equal thirty_one
491
+ results[3].must_be_nil
492
+ end
493
+
494
+ it "should update" do
495
+ results = blockrun{[
496
+ space_both.update('21', {surname: [:splice,3,0,'!']}, return_tuple:true),
497
+ space_both.update('31', {surname: [:splice,3,0,'!']}, return_tuple:true),
498
+ ]}
499
+ results[0].must_equal twenty_one.merge(surname: '22!')
500
+ results[1].must_equal thirty_one.merge(surname: '32!')
501
+ end
502
+ end
503
+
504
+ shared_examples_for "space test call" do
505
+ before {
506
+ blockrun {
507
+ space_both.insert(one)
508
+ space_both.insert(two)
509
+ }
510
+ }
511
+
512
+ it "should call on both" do
513
+ result = blockrun{ space_both.call('box.select_range', [0, 100]) }
514
+ result.sort_by{|t| get_id(t)}.must_equal [one, two]
515
+ end
516
+
517
+ it "should call on specified" do
518
+ results = blockrun{[
519
+ space_both.call('box.select_range', [0, 100], shard_key: 1),
520
+ space_both.call('box.select_range', [0, 100], shard_keys: [2])
521
+ ]}
522
+ results[0].must_equal [one]
523
+ results[1].must_equal [two]
524
+ end
525
+ end
526
+
527
+ describe "space array test call" do
528
+ let(:space_both){ space1_array_both }
529
+ let(:space_first){ space1_array_first }
530
+ let(:space_second){ space1_array_second }
531
+
532
+ let(:one) { [1, 'a', 1] }
533
+ let(:two) { [2, 'b', 2] }
534
+ def get_id(tuple) tuple[0] end
535
+
536
+ it_behaves_like "space test call"
537
+ end
538
+
539
+ describe "space hash test call" do
540
+ let(:space_both){ space1_hash_both }
541
+ let(:space_first){ space1_hash_first }
542
+ let(:space_second){ space1_hash_second }
543
+
544
+ let(:one) { {id: 1, name: 'a', val: 1} }
545
+ let(:two) { {id: 2, name: 'b', val: 2} }
546
+ def get_id(tuple) tuple[:id] end
547
+
548
+ it_behaves_like "space test call"
549
+ end
550
+
551
+ shared_examples_for "explicit shard number" do
552
+ before {
553
+ blockrun {
554
+ space_both.shard(0).insert(one)
555
+ space_both.shard(1).insert(two)
556
+ }
557
+ }
558
+
559
+ it "should not find under implicit shard" do
560
+ blockrun{[
561
+ space_both.by_pk(1),
562
+ space_both.by_pk(2),
563
+ ]}.must_equal [nil, nil]
564
+ end
565
+
566
+ it "should not find under explicit but not same shard" do
567
+ blockrun{[
568
+ space_both.shard(1).by_pk(1),
569
+ space_both.shard(0).by_pk(2),
570
+ ]}.must_equal [nil, nil]
571
+ end
572
+
573
+ it "should find under explict shard" do
574
+ blockrun{[
575
+ space_both.shard(0).by_pk(1),
576
+ space_both.shard(1).by_pk(2)
577
+ ]}.must_equal [one, two]
578
+ end
579
+
580
+ it "should call on both" do
581
+ result = blockrun{ space_both.call('box.select_range', [0, 100]) }
582
+ result.sort_by{|t| get_id(t)}.must_equal [one, two]
583
+ end
584
+
585
+ it "should call on specified" do
586
+ results = blockrun{[
587
+ space_both.shard(1).call('box.select_range', [0, 100]),
588
+ space_both.shard(0).call('box.select_range', [0, 100])
589
+ ]}
590
+ results[0].must_equal [two]
591
+ results[1].must_equal [one]
592
+ end
593
+ end
594
+
595
+ describe "space array explicit shard number" do
596
+ let(:space_both){ space1_array_both }
597
+ let(:space_first){ space1_array_first }
598
+ let(:space_second){ space1_array_second }
599
+
600
+ let(:one) { [1, 'a', 1] }
601
+ let(:two) { [2, 'b', 2] }
602
+ def get_id(tuple) tuple[0] end
603
+
604
+ it_behaves_like "explicit shard number"
605
+ end
606
+
607
+ describe "space hash explicit shard number" do
608
+ let(:space_both){ space1_hash_both }
609
+ let(:space_first){ space1_hash_first }
610
+ let(:space_second){ space1_hash_second }
611
+
612
+ let(:one) { {id: 1, name: 'a', val: 1} }
613
+ let(:two) { {id: 2, name: 'b', val: 2} }
614
+ def get_id(tuple) tuple[:id] end
615
+
616
+ it_behaves_like "explicit shard number"
617
+ end
618
+
619
+ shared_examples_for "replication tests" do
620
+ before{
621
+ blockrun{
622
+ space.insert(record1)
623
+ space.insert(record2)
624
+ }
625
+ sleep 0.12
626
+ stop_masters
627
+ }
628
+
629
+ it "should read from slave" do
630
+ results = blockrun{[
631
+ space.by_pk(2),
632
+ space.by_pk(1),
633
+ space.call('box.select_range', [0, 100])
634
+ ]}
635
+ results[1].must_equal record1
636
+ results[0].must_equal record2
637
+ results[2].sort_by{|v| get_id(v)}.must_equal [record1, record2]
638
+ end
639
+
640
+ it "should raise exception when slave had not become master" do
641
+ blockrun{
642
+ [1,2].each do |i|
643
+ proc{
644
+ p space.delete(i)
645
+ }.must_raise Tarantool::NoMasterError
646
+ proc{
647
+ space.update(i, update_op)
648
+ }.must_raise Tarantool::NoMasterError
649
+ proc{
650
+ space.call('box.delete', [0, i])
651
+ }.must_raise Tarantool::NoMasterError
652
+ end
653
+ }
654
+ end
655
+
656
+ it "should perform write when slave became master" do
657
+ make_masters
658
+ results = blockrun{[
659
+ space.call('box.select_range', [0, 100]),
660
+ space.update(1, update_op, return_tuple: true),
661
+ space.delete(1, return_tuple: true),
662
+ space.update(2, update_op, return_tuple: true),
663
+ space.delete(2, return_tuple: true),
664
+ ]}
665
+ results[0].sort_by{|v| get_id(v)}.must_equal [record1, record2]
666
+ results[1].must_equal updated1
667
+ results[2].must_equal updated1
668
+ results[3].must_equal updated2
669
+ results[4].must_equal updated2
670
+ end
671
+
672
+ it "should perform read from previous masters when slaves (which became masters) fails" do
673
+ make_masters
674
+ make_slaves
675
+ sleep 0.15
676
+ stop_masters :slaves
677
+
678
+ results = blockrun{[
679
+ space.by_pk(1),
680
+ space.by_pk(2),
681
+ space.call('box.select_range', [0, 100])
682
+ ]}
683
+ results[0].must_equal record1
684
+ results[1].must_equal record2
685
+ results[2].sort_by{|v| get_id(v)}.must_equal [record1, record2]
686
+ end
687
+
688
+ it "should perform write to previous masters, which become masters again when slaves fails" do
689
+ make_masters
690
+ make_slaves
691
+ sleep 0.15
692
+ stop_masters :slaves
693
+ make_masters :masters
694
+
695
+ results = blockrun{[
696
+ space.call('box.select_range', [0, 100]),
697
+ space.update(1, update_op, return_tuple: true),
698
+ space.delete(1, return_tuple: true),
699
+ space.update(2, update_op, return_tuple: true),
700
+ space.delete(2, return_tuple: true),
701
+ ]}
702
+ results[0].sort_by{|v| get_id(v)}.must_equal [record1, record2]
703
+ results[1].must_equal updated1
704
+ results[2].must_equal updated1
705
+ results[3].must_equal updated2
706
+ results[4].must_equal updated2
707
+ end
708
+
709
+ it "should fail when masters and slaves both down" do
710
+ stop_masters(:slaves)
711
+ blockrun{
712
+ proc {
713
+ space.by_pk(1)
714
+ }.must_raise ::Tarantool::ConnectionError
715
+ }
716
+ end
717
+ end
718
+
719
+ shared_examples_for "replication tests 2" do
720
+ def prepare_replica
721
+ space.insert(record1)
722
+ space.insert(record2)
723
+ bsleep 0.12
724
+ stop_masters
725
+ end
726
+
727
+ it "should read from slave" do
728
+ results = blockrun{
729
+ prepare_replica
730
+ [
731
+ space.by_pk(2),
732
+ space.by_pk(1),
733
+ space.call('box.select_range', [0, 100])
734
+ ]}
735
+ results[1].must_equal record1
736
+ results[0].must_equal record2
737
+ results[2].sort_by{|v| get_id(v)}.must_equal [record1, record2]
738
+ end
739
+
740
+ it "should raise exception when slave had not become master" do
741
+ blockrun{
742
+ prepare_replica
743
+ [1,2].each do |i|
744
+ proc{
745
+ p space.delete(i)
746
+ }.must_raise Tarantool::NoMasterError
747
+ proc{
748
+ space.update(i, update_op)
749
+ }.must_raise Tarantool::NoMasterError
750
+ proc{
751
+ space.call('box.delete', [0, i])
752
+ }.must_raise Tarantool::NoMasterError
753
+ end
754
+ }
755
+ end
756
+
757
+ it "should perform write when slave became master" do
758
+ results = blockrun{
759
+ prepare_replica
760
+ make_masters
761
+ [
762
+ space.call('box.select_range', [0, 100]),
763
+ space.update(1, update_op, return_tuple: true),
764
+ space.delete(1, return_tuple: true),
765
+ space.update(2, update_op, return_tuple: true),
766
+ space.delete(2, return_tuple: true),
767
+ ]}
768
+ results[0].sort_by{|v| get_id(v)}.must_equal [record1, record2]
769
+ results[1].must_equal updated1
770
+ results[2].must_equal updated1
771
+ results[3].must_equal updated2
772
+ results[4].must_equal updated2
773
+ end
774
+
775
+ it "should perform read from previous masters when slaves (which became masters) fails" do
776
+ results = blockrun{
777
+ prepare_replica
778
+ make_masters
779
+ make_slaves
780
+ bsleep 0.15
781
+ stop_masters :slaves
782
+ [
783
+ space.by_pk(1),
784
+ space.by_pk(2),
785
+ space.call('box.select_range', [0, 100])
786
+ ]}
787
+ results[0].must_equal record1
788
+ results[1].must_equal record2
789
+ results[2].sort_by{|v| get_id(v)}.must_equal [record1, record2]
790
+ end
791
+
792
+ it "should perform write to previous masters, which become masters again when slaves fails" do
793
+
794
+ results = blockrun{
795
+ prepare_replica
796
+ make_masters
797
+ make_slaves
798
+ bsleep 0.15
799
+ stop_masters :slaves
800
+ make_masters :masters
801
+ [
802
+ space.call('box.select_range', [0, 100]),
803
+ space.update(1, update_op, return_tuple: true),
804
+ space.delete(1, return_tuple: true),
805
+ space.update(2, update_op, return_tuple: true),
806
+ space.delete(2, return_tuple: true),
807
+ ]}
808
+ results[0].sort_by{|v| get_id(v)}.must_equal [record1, record2]
809
+ results[1].must_equal updated1
810
+ results[2].must_equal updated1
811
+ results[3].must_equal updated2
812
+ results[4].must_equal updated2
813
+ end
814
+
815
+
816
+ it "should fail when masters and slaves both down" do
817
+ blockrun{
818
+ prepare_replica
819
+ stop_masters(:slaves)
820
+ proc {
821
+ space.by_pk(1)
822
+ }.must_raise ::Tarantool::ConnectionError
823
+ }
824
+ end
825
+ end
826
+
827
+ shared_examples_for "space array replication" do
828
+ let(:record1){ [1, 'a', 1] }
829
+ let(:record2){ [2, 'a', 2] }
830
+ let(:update_op){ {1 => 'b'} }
831
+ let(:updated1){ [1, 'b', 1] }
832
+ let(:updated2){ [2, 'b', 2] }
833
+ def get_id(v) v[0] end
834
+ end
835
+
836
+ shared_examples_for "space hash replication" do
837
+ let(:record1){ {id: 1, name: 'a', val: 1} }
838
+ let(:record2){ {id: 2, name: 'a', val: 2} }
839
+ let(:update_op){ {name: 'b'} }
840
+ let(:updated1){ {id: 1, name: 'b', val: 1} }
841
+ let(:updated2){ {id: 2, name: 'b', val: 2} }
842
+ def get_id(v) v[:id] end
843
+ end
844
+
845
+ shared_examples_for "single replication" do
846
+ def stop_masters(which=:masters)
847
+ if which == :masters
848
+ TConf.stop(:master1)
849
+ else
850
+ TConf.stop(:slave1)
851
+ end
852
+ end
853
+ def make_masters(which=:slaves)
854
+ if which == :slaves
855
+ TConf.promote_to_master(:slave1)
856
+ else
857
+ TConf.promote_to_master(:master1)
858
+ end
859
+ end
860
+ def make_slaves
861
+ TConf.promote_to_slave(:master1, :slave1)
862
+ end
863
+ end
864
+
865
+ shared_examples_for "shard and replication" do
866
+ def stop_masters(which=:masters)
867
+ if which == :masters
868
+ TConf.stop(:master1)
869
+ TConf.stop(:master2)
870
+ else
871
+ TConf.stop(:slave1)
872
+ TConf.stop(:slave2)
873
+ end
874
+ end
875
+ def make_masters(which=:slaves)
876
+ if which == :slaves
877
+ TConf.promote_to_master(:slave1)
878
+ TConf.promote_to_master(:slave2)
879
+ else
880
+ TConf.promote_to_master(:master1)
881
+ TConf.promote_to_master(:master2)
882
+ end
883
+ end
884
+ def make_slaves
885
+ TConf.promote_to_slave(:master1, :slave1)
886
+ TConf.promote_to_slave(:master2, :slave2)
887
+ end
888
+ end
889
+
890
+ describe "space array single replication" do
891
+ let(:space){ space1_array_first }
892
+
893
+ it_behaves_like "space array replication"
894
+ it_behaves_like "single replication"
895
+ it_behaves_like "replication tests"
896
+ end
897
+
898
+ describe "space hash single replication" do
899
+ let(:space){ space1_hash_first }
900
+
901
+ it_behaves_like "space hash replication"
902
+ it_behaves_like "single replication"
903
+ it_behaves_like "replication tests"
904
+ end
905
+
906
+ describe "space hash shard and replication" do
907
+ let(:space){ space1_hash_both }
908
+
909
+ it_behaves_like "space hash replication"
910
+ it_behaves_like "shard and replication"
911
+ it_behaves_like "replication tests"
912
+ end
913
+
914
+ describe "space array shard and replication" do
915
+ let(:space){ space1_array_both }
916
+
917
+ it_behaves_like "space array replication"
918
+ it_behaves_like "shard and replication"
919
+ it_behaves_like "replication tests"
920
+ end
921
+
922
+ describe "space array single replication 2" do
923
+ let(:space){ space1_array_first }
924
+
925
+ it_behaves_like "space array replication"
926
+ it_behaves_like "single replication"
927
+ it_behaves_like "replication tests 2"
928
+ end
929
+
930
+ describe "space hash single replication 2" do
931
+ let(:space){ space1_hash_first }
932
+
933
+ it_behaves_like "space hash replication"
934
+ it_behaves_like "single replication"
935
+ it_behaves_like "replication tests 2"
936
+ end
937
+
938
+ describe "space hash shard and replication 2" do
939
+ let(:space){ space1_hash_both }
940
+
941
+ it_behaves_like "space hash replication"
942
+ it_behaves_like "shard and replication"
943
+ it_behaves_like "replication tests 2"
944
+ end
945
+
946
+ describe "space array shard and replication 2" do
947
+ let(:space){ space1_array_both }
948
+
949
+ it_behaves_like "space array replication"
950
+ it_behaves_like "shard and replication"
951
+ it_behaves_like "replication tests 2"
952
+ end
953
+
954
+ describe "record api" do
955
+ let(:klass) do
956
+ t_both = t_both()
957
+ Class.new(Tarantool::LightRecord) do
958
+ set_tarantool t_both
959
+ set_space_no 0
960
+
961
+ field :name, :str
962
+ field :surname, :str
963
+ field :email, :str
964
+ field :score, :int
965
+
966
+ index :surname, :email
967
+ index :score
968
+
969
+ shard_fields :score
970
+ shard_proc do |fields, shard_num|
971
+ fields[0] && ((fields[0].to_i + 1) / 2 + 1) % shard_num
972
+ end
973
+ end
974
+ end
975
+
976
+ before do
977
+ blockrun {
978
+ klass.create(name: 'a', surname: 'a', email: 'a', score: 1)
979
+ klass.create(name: 'b', surname: 'b', email: 'b', score: 2)
980
+ klass.create(name: 'c', surname: 'c', email: 'c', score: 3)
981
+ klass.create(name: 'd', surname: 'd', email: 'd', score: 4)
982
+ }
983
+ end
984
+
985
+ it "should spread distribution" do
986
+ results = blockrun {[
987
+ space0_hash_first.all_by_pks(%w{a b c d}),
988
+ space0_hash_second.all_by_pks(%w{a b c d})
989
+ ]}
990
+ results[0].map{|v| v[:name]}.sort.must_equal %w{a b}
991
+ results[0].map{|v| v[:score]}.sort.must_equal [1, 2]
992
+ results[1].map{|v| v[:name]}.sort.must_equal %w{c d}
993
+ results[1].map{|v| v[:score]}.sort.must_equal [3, 4]
994
+ end
995
+
996
+ it "should find by pk" do
997
+ results = blockrun {[
998
+ klass.find('a'),
999
+ klass.find('b'),
1000
+ klass.find('c'),
1001
+ klass.find('d')
1002
+ ]}
1003
+ results.map(&:name).must_equal %w{a b c d}
1004
+ results.map(&:score).must_equal [1,2,3,4]
1005
+ end
1006
+
1007
+ it "should find by shard key" do
1008
+ results = blockrun {[
1009
+ klass.first(score: 1),
1010
+ klass.first(score: 2),
1011
+ klass.first(score: 3),
1012
+ klass.first(score: 4),
1013
+ ]}
1014
+ results.map(&:name).must_equal %w{a b c d}
1015
+ results.map(&:score).must_equal [1,2,3,4]
1016
+ end
1017
+ end
1018
+ end