tarantool 0.2.5 → 0.3.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. data/Gemfile +14 -12
  2. data/LICENSE +4 -4
  3. data/README.md +15 -9
  4. data/Rakefile +5 -129
  5. data/lib/tarantool/base_record.rb +288 -0
  6. data/lib/tarantool/block_db.rb +135 -0
  7. data/lib/tarantool/callback_db.rb +47 -0
  8. data/lib/tarantool/core-ext.rb +12 -0
  9. data/lib/tarantool/em_db.rb +37 -0
  10. data/lib/tarantool/exceptions.rb +52 -7
  11. data/lib/tarantool/fiber_db.rb +152 -0
  12. data/lib/tarantool/light_record.rb +68 -0
  13. data/lib/tarantool/query.rb +127 -0
  14. data/lib/tarantool/record/select.rb +59 -0
  15. data/lib/tarantool/record.rb +93 -282
  16. data/lib/tarantool/request.rb +351 -52
  17. data/lib/tarantool/response.rb +108 -45
  18. data/lib/tarantool/serializers/bson.rb +2 -2
  19. data/lib/tarantool/serializers.rb +32 -4
  20. data/lib/tarantool/space_array.rb +153 -0
  21. data/lib/tarantool/space_hash.rb +262 -0
  22. data/lib/tarantool/util.rb +182 -0
  23. data/lib/tarantool/version.rb +3 -0
  24. data/lib/tarantool.rb +79 -29
  25. data/test/box.pid +1 -0
  26. data/test/helper.rb +164 -0
  27. data/test/run_all.rb +3 -0
  28. data/test/shared_query.rb +73 -0
  29. data/test/shared_record.rb +474 -0
  30. data/test/shared_space_array.rb +284 -0
  31. data/test/shared_space_hash.rb +239 -0
  32. data/test/tarant/init.lua +22 -0
  33. data/test/tarantool.cfg +62 -0
  34. data/test/tarantool.log +6 -0
  35. data/{spec/tarantool.cfg → test/tarantool_repl.cfg} +11 -5
  36. data/test/test_light_record.rb +48 -0
  37. data/test/test_query_block.rb +6 -0
  38. data/test/test_query_fiber.rb +7 -0
  39. data/test/test_record.rb +88 -0
  40. data/{spec/tarantool/composite_primary_key_spec.rb → test/test_record_composite_pk.rb} +5 -7
  41. data/test/test_space_array_block.rb +6 -0
  42. data/test/test_space_array_callback.rb +255 -0
  43. data/test/test_space_array_callback_nodef.rb +190 -0
  44. data/test/test_space_array_fiber.rb +7 -0
  45. data/test/test_space_hash_block.rb +6 -0
  46. data/test/test_space_hash_fiber.rb +7 -0
  47. metadata +78 -55
  48. data/Gemfile.lock +0 -54
  49. data/examples/em_simple.rb +0 -16
  50. data/examples/record.rb +0 -68
  51. data/examples/simple.rb +0 -13
  52. data/lib/tarantool/requests/call.rb +0 -20
  53. data/lib/tarantool/requests/delete.rb +0 -18
  54. data/lib/tarantool/requests/insert.rb +0 -19
  55. data/lib/tarantool/requests/ping.rb +0 -16
  56. data/lib/tarantool/requests/select.rb +0 -22
  57. data/lib/tarantool/requests/update.rb +0 -35
  58. data/lib/tarantool/requests.rb +0 -19
  59. data/lib/tarantool/serializers/integer.rb +0 -14
  60. data/lib/tarantool/serializers/string.rb +0 -14
  61. data/lib/tarantool/space.rb +0 -39
  62. data/spec/helpers/let.rb +0 -11
  63. data/spec/helpers/truncate.rb +0 -12
  64. data/spec/spec_helper.rb +0 -21
  65. data/spec/tarantool/em_spec.rb +0 -22
  66. data/spec/tarantool/record_spec.rb +0 -316
  67. data/spec/tarantool/request_spec.rb +0 -103
  68. data/tarantool.gemspec +0 -69
@@ -0,0 +1,474 @@
1
+ require File.expand_path('../helper.rb', __FILE__)
2
+ require 'yajl'
3
+ require 'tarantool/serializers/bson'
4
+
5
+ shared_examples_for :record do
6
+ DB = Tarantool.new(TCONFIG.merge(type: :block))
7
+ before{ truncate }
8
+
9
+ let(:user_class) do
10
+ Class.new(base_class) do
11
+ set_tarantool DB
12
+ set_space_no 0
13
+
14
+ def self.name # For naming
15
+ "User"
16
+ end
17
+
18
+ field :login, :string
19
+ field :name, :string
20
+ field :email, :string
21
+ field :apples_count, :integer, default: 0
22
+ index :name, :email
23
+ end
24
+ end
25
+
26
+ let(:second_class) do
27
+ Class.new(base_class) do
28
+ set_tarantool DB
29
+ set_space_no 1
30
+
31
+ def self.name # For naming
32
+ "Second"
33
+ end
34
+
35
+ field :id, :int
36
+ field :count1, :int
37
+ field :count2, :int
38
+ _tail :str, :int
39
+ end
40
+ end
41
+
42
+ let(:user) { user_class.new }
43
+
44
+ it "should set and get attributes" do
45
+ user.name = 'Andrew'
46
+ user.name.must_equal 'Andrew'
47
+ end
48
+
49
+ describe "inheritance" do
50
+ let(:author_class) do
51
+ Class.new(user_class) do
52
+ field :best_book, Integer
53
+ end
54
+ end
55
+ let(:artist_class) do
56
+ Class.new(user_class) do
57
+ field :imdb_id, Integer
58
+ end
59
+ end
60
+
61
+ describe "Artist from User" do
62
+ it "should has only itself field" do
63
+ artist_class.fields.keys.must_equal [:login, :name, :email, :apples_count, :imdb_id]
64
+ end
65
+ end
66
+
67
+ it "should has same space no as parent" do
68
+ user_class.space_no = 1
69
+ artist_class.space_no.must_equal 1
70
+ end
71
+
72
+ it "should has different space no to parent if setted" do
73
+ artist_class.space_no = 1
74
+ user_class.space_no.must_equal 0
75
+ artist_class.space_no.must_equal 1
76
+ end
77
+ end
78
+
79
+ describe "save" do
80
+ it "should save and select record" do
81
+ u = user_class.new login: 'prepor', name: 'Andrew', email: 'ceo@prepor.ru'
82
+ u.save
83
+ u = user_class.find 'prepor'
84
+ u.id.must_equal 'prepor'
85
+ u.email.must_equal 'ceo@prepor.ru'
86
+ u.name.must_equal 'Andrew'
87
+ u.apples_count.must_equal 0
88
+ end
89
+
90
+ it "should update dirty attributes" do
91
+ u = user_class.create login: 'prepor', name: 'Andrew', email: 'ceo@prepor.ru'
92
+ u.name = 'Petr'
93
+ u.save
94
+ u = user_class.find 'prepor'
95
+ u.email.must_equal 'ceo@prepor.ru'
96
+ u.name.must_equal 'Petr'
97
+ end
98
+
99
+ describe "with nils" do
100
+ before do
101
+ user_class.field :score, :integer
102
+ user_class.field :info, Tarantool::Serializers::BSON #:bson
103
+ end
104
+ it "should work properly with nils values" do
105
+ u = user_class.create login: 'prepor', name: 'Andrew', email: 'ceo@prepor.ru', apples_count: 1
106
+ u.info.must_be_nil
107
+ u.score.must_be_nil
108
+ u.reload
109
+ u.info.must_be_nil
110
+ u.score.must_be_nil
111
+ u.info = {'bio' => 'hi!'}
112
+ u.score = 1
113
+ u.save
114
+ u.reload
115
+ u.info.must_equal({ 'bio' => 'hi!' })
116
+ u.score.must_equal 1
117
+ end
118
+ end
119
+ end
120
+
121
+ describe "reload" do
122
+ it "should reload current record" do
123
+ u = user_class.create login: 'prepor', name: 'Andrew', email: 'ceo@prepor.ru'
124
+ u.name = 'Petr'
125
+ u.reload.name.must_equal 'Andrew'
126
+ end
127
+ end
128
+
129
+ describe "update" do
130
+ let(:user) { user_class.create login: 'prepor', name: 'Andrew', email: 'ceo@prepor.ru' }
131
+ it "should update fields" do
132
+ user.update(name: "Andrey", email: [":", 0, 3, "prepor"], apples_count: [:+, 7])
133
+ user.name.must_equal "Andrey"
134
+ user.email.must_equal "prepor@prepor.ru"
135
+ user.apples_count.must_equal 7
136
+
137
+ user.update(apples_count: [:^, 12])
138
+ user.apples_count.must_equal 11
139
+ end
140
+
141
+ describe "increment" do
142
+ it "should increment apples count by 1" do
143
+ user.increment :apples_count
144
+ user.apples_count.must_equal 1
145
+ end
146
+
147
+ it "should increment apples count by 3" do
148
+ user.increment :apples_count, 3
149
+ user.apples_count.must_equal 3
150
+ end
151
+ end
152
+ end
153
+
154
+ describe "tail" do
155
+ let(:attrs){ {:id => 100, :count1 => 1, :count2 => 2,
156
+ :_tail => [['hello', 23], ['world', 42]]} }
157
+ it "should insert with tail" do
158
+ obj = second_class.insert(attrs, true)
159
+ obj.attributes.must_equal attrs
160
+ second_class.by_pk(100).attributes.must_equal attrs
161
+ end
162
+
163
+ it "should create with tail" do
164
+ obj = second_class.create(attrs)
165
+ obj.attributes.must_equal attrs
166
+ second_class.by_pk(100).attributes.must_equal attrs
167
+ end
168
+
169
+ it "should save tail" do
170
+ iron_tail = [['i',1],['am',2],['irontail',3]]
171
+ obj = second_class.create(attrs)
172
+ obj._tail = iron_tail
173
+ obj.save
174
+ obj = second_class.by_pk(100)
175
+ obj.attributes.must_equal attrs.merge(:_tail => iron_tail)
176
+ obj._tail.must_equal iron_tail
177
+ end
178
+ end
179
+
180
+ describe "dismissed record" do
181
+ before {
182
+ @user = user_class.create login: 'prapor', name: 'Crocos', email: 'focus@poc.us'
183
+ user_class.delete 'prapor'
184
+ }
185
+
186
+ it "should raise error on save" do
187
+ proc{
188
+ @user.apples_count += 1
189
+ @user.save
190
+ }.must_raise Tarantool::TupleDoesntExists
191
+ end
192
+
193
+ it "should raise error on update" do
194
+ proc{
195
+ @user.update(:apples_count => [:+, 1])
196
+ }.must_raise Tarantool::TupleDoesntExists
197
+ end
198
+
199
+ it "should raise error on reload" do
200
+ proc{
201
+ @user.reload
202
+ }.must_raise Tarantool::TupleDoesntExists
203
+ end
204
+ end
205
+
206
+ describe "destroy" do
207
+ it "should destroy record" do
208
+ u = user_class.create login: 'prepor', name: 'Andrew', email: 'ceo@prepor.ru'
209
+ u.destroy
210
+ user_class.find('prepor').must_equal nil
211
+ end
212
+ end
213
+
214
+ describe "select" do
215
+ describe "by name Andrew" do
216
+ let(:select) { user_class.where(name: 'Andrew') }
217
+ before do
218
+ user_class.create login: 'prepor', name: 'Andrew', email: 'ceo@prepor.ru'
219
+ user_class.create login: 'petro', name: 'Petr', email: 'petro@gmail.com'
220
+ user_class.create login: 'ruden', name: 'Andrew', email: 'rudenkoco@gmail.com'
221
+ end
222
+ it "should select all records with name == 'Andrew'" do
223
+ select.all.map(&:login).must_equal ['prepor', 'ruden']
224
+ end
225
+
226
+ it "should select first record with name == 'Andrew'" do
227
+ select.first.login.must_equal 'prepor'
228
+ end
229
+
230
+ it "should select 1 record by name and email" do
231
+ user_class.where(name: 'Andrew', email: 'rudenkoco@gmail.com').map(&:login).must_equal ['ruden']
232
+ end
233
+
234
+ it "should select 2 record by name and email" do
235
+ user_class.where(name: ['Andrew', 'Andrew'], email: ['ceo@prepor.ru', 'rudenkoco@gmail.com']).map(&:login).must_equal ['prepor', 'ruden']
236
+ end
237
+
238
+ it "should select 3 record by names" do
239
+ user_class.where(name: ['Andrew', 'Petr']).map(&:login).must_equal ['prepor', 'ruden', 'petro']
240
+ end
241
+
242
+ it "should raise an error for wrong index" do
243
+ proc{ user_class.where(namee: "Andrew").all }.must_raise Tarantool::ArgumentError
244
+ end
245
+
246
+ describe "with limit 1" do
247
+ let(:select_limit) { select.limit(1) }
248
+ it "should select first record with name == 'Andrew'" do
249
+ select_limit.map(&:login).must_equal ['prepor']
250
+ end
251
+
252
+ describe "with offset 1" do
253
+ let(:select_offset) { select_limit.offset(1) }
254
+ it "should select last record with name == 'Andrew'" do
255
+ select_offset.map(&:login).must_equal ['ruden']
256
+ end
257
+ end
258
+ end
259
+ end
260
+
261
+ describe "==" do
262
+ before do
263
+ user_class.create login: 'prepor', name: 'Andrew', email: 'ceo@prepor.ru'
264
+ user_class.create login: 'petro', name: 'Petr', email: 'petro@gmail.com'
265
+ end
266
+
267
+ it "should return equality as true" do
268
+ u1 = user_class.where(login: "prepor").first
269
+ u2 = user_class.where(login: "prepor").first
270
+ (u1 == u2).must_equal true
271
+ end
272
+
273
+ it "should not return equality as true" do
274
+ u1 = user_class.where(login: "prepor")
275
+ u2 = user_class.where(login: "petro")
276
+ (u1 == u2).must_equal false
277
+ end
278
+ end
279
+
280
+
281
+ describe "call" do
282
+ let(:res) { user_class.select.call('box.select_range', 0, 2)}
283
+
284
+ it "should select 2 records and return UserClass instances" do
285
+ user_class.create login: 'prepor', name: 'Andrew', email: 'ceo@prepor.ru'
286
+ user_class.create login: 'petro', name: 'Petr', email: 'petro@gmail.com'
287
+ user_class.create login: 'ruden', name: 'Andrew', email: 'rudenkoco@gmail.com'
288
+ res.size.must_equal 2
289
+ res.any? { |v| v.is_a? user_class }
290
+ end
291
+ end
292
+ end
293
+
294
+ describe "light api" do
295
+ let(:prepor){ {login: 'prepor', name: 'Andrew', email: 'ceo@prepor.ru'} }
296
+ let(:petro){ {login: 'petro', name: 'Petr', email: 'petro@gmail.com'} }
297
+ let(:ruden){ {login: 'ruden', name: 'Andrew', email: 'rudenkoco@gmail.com'} }
298
+ before do
299
+ user_class.create prepor
300
+ user_class.create petro
301
+ user_class.create ruden
302
+ end
303
+
304
+ it "should search by_pk" do
305
+ user_class.by_pk('prepor').email.must_equal 'ceo@prepor.ru'
306
+ user_class.by_pk(['petro']).email.must_equal 'petro@gmail.com'
307
+ user_class.by_pk(login: 'ruden').email.must_equal 'rudenkoco@gmail.com'
308
+ end
309
+
310
+ it "should search by_pks" do
311
+ user_class.by_pks(['prepor', ['petro'], {login: 'ruden'}]).
312
+ map(&:email).must_equal %w{ceo@prepor.ru petro@gmail.com rudenkoco@gmail.com}
313
+ end
314
+
315
+ it "should find" do
316
+ user_class.find('prepor').email.must_equal 'ceo@prepor.ru'
317
+ user_class.find(['petro']).email.must_equal 'petro@gmail.com'
318
+ user_class.find(login: 'ruden').email.must_equal 'rudenkoco@gmail.com'
319
+ user_class.find('prepor', ['petro'], {login: 'ruden'}).
320
+ map(&:email).must_equal %w{ceo@prepor.ru petro@gmail.com rudenkoco@gmail.com}
321
+ end
322
+
323
+ it "should find first" do
324
+ user_class.first(name: 'Petr', email: 'petro@gmail.com').login.must_equal 'petro'
325
+ ['prepor', 'ruden'].must_include user_class.first(name: 'Andrew').login
326
+ end
327
+
328
+ it "should find all" do
329
+ user_class.select(name: 'Andrew').map(&:login).sort.must_equal %w{prepor ruden}
330
+
331
+ limited = user_class.select({name: 'Andrew'}, limit: 1).map(&:login)
332
+ limited.size.must_equal 1
333
+ limited.must_equal limited & %w{prepor ruden}
334
+
335
+ offsetted = user_class.all({name: 'Andrew'}, offset: 1).map(&:login)
336
+ offsetted.size.must_equal 1
337
+ offsetted.must_equal offsetted & %w{prepor ruden}
338
+ limited.wont_equal offsetted
339
+ (limited+offsetted).sort.must_equal %w{prepor ruden}
340
+
341
+ user_class.all(name: 'Andrew', email: 'ceo@prepor.ru').map(&:login).must_equal %w{prepor}
342
+ end
343
+
344
+ let(:zuma){ {login: 'zuma', name: 'Zuma', email: 'a@b.c', apples_count: 0} }
345
+ let(:vova){ {login: 'vova', name: 'Vova', email: 'a@b.c'} }
346
+ let(:_lila){ {login: 'lila', name: 'lila', email: 'l@i.la', apples_count: 1} }
347
+
348
+ it "should insert" do
349
+ user_class.insert(zuma).must_equal 1
350
+ proc {
351
+ user_class.insert(zuma)
352
+ }.must_raise Tarantool::TupleExists
353
+ user = user_class.insert(_lila, true)
354
+ user.must_be_instance_of user_class
355
+ user.attributes.must_equal _lila
356
+ user.attributes.wont_be_same_as _lila
357
+ end
358
+
359
+ it "should insert with nil" do
360
+ second_class.insert(id: 100).must_equal 1
361
+ obj = second_class.by_pk(100)
362
+ obj.attributes.must_equal({id: 100, count1: nil, count2: nil})
363
+ tuple = DB.space(1, [:int, :int, :int], keys: 0).by_pk(100)
364
+ tuple.must_equal([100, nil, nil])
365
+
366
+ second_class.insert(id: 101, count2: 102).must_equal 1
367
+ obj = second_class.by_pk(101)
368
+ obj.attributes.must_equal({id: 101, count1: nil, count2: 102})
369
+ tuple = DB.space(1, [:int, :int, :int], keys: 0).by_pk(101)
370
+ tuple.must_equal([101, nil, 102])
371
+ end
372
+
373
+ it "should replace" do
374
+ proc {
375
+ user_class.replace(vova)
376
+ }.must_raise Tarantool::TupleDoesntExists
377
+
378
+ user_class.replace(ruden.merge(apples_count: 10)).must_equal 1
379
+ user_class.by_pk('ruden').apples_count.must_equal 10
380
+
381
+ user_class.replace(petro.merge(apples_count: 11), true).
382
+ attributes.must_equal petro.merge(apples_count: 11)
383
+ end
384
+
385
+ it "should update" do
386
+ user_class.update('ruden', {apples_count: [:+, 2]})
387
+ user_class.by_pk('ruden').apples_count.must_equal 2
388
+
389
+ user_class.update('prepor', {apples_count: [:+, 2]}, true).
390
+ attributes.must_equal prepor.merge(apples_count: 2)
391
+
392
+ user_class.update('fdsaf', {name: 'no'}).must_equal 0
393
+ user_class.update('fdsaf', {name: 'no'}, true).must_be_nil
394
+ end
395
+
396
+ it "should delete" do
397
+ user_class.delete(login: 'ruden').must_equal 1
398
+ user_class.by_pk('ruden').must_be_nil
399
+ end
400
+
401
+ it "should invoke" do
402
+ user_class.by_pk('ruden').attributes.must_equal ruden.merge(apples_count: 0)
403
+ user_class.invoke('box.delete', 'ruden').must_equal 1
404
+ user_class.by_pk('ruden').must_be_nil
405
+
406
+ user_class.by_pk('petro').attributes.must_equal petro.merge(apples_count: 0)
407
+ user_class.invoke('box.delete', user_class.space_no, 'petro', space_no: nil).must_equal 1
408
+ user_class.by_pk('petro').must_be_nil
409
+ end
410
+
411
+ it "should call" do
412
+ user_class.by_pk('ruden').attributes.must_equal ruden.merge(apples_count: 0)
413
+ user_class.call('box.delete', 'ruden')[0].attributes.must_equal ruden.merge(apples_count: 0)
414
+ user_class.by_pk('ruden').must_be_nil
415
+
416
+ user_class.by_pk('petro').attributes.must_equal petro.merge(apples_count: 0)
417
+ user_class.call('box.delete', user_class.space_no, 'petro', space_no: nil)[0].
418
+ attributes.must_equal petro.merge(apples_count: 0)
419
+ user_class.by_pk('petro').must_be_nil
420
+ end
421
+
422
+ it "should call with custom returns" do
423
+ v = user_class.call('box.delete', 'ruden', returns: [:str, :str, :str, :int])
424
+ v.must_equal [ruden.values + [0]]
425
+ v = user_class.call('box.delete', 'prepor', returns: {a: :str, b: :str, c: :str, d: :int})
426
+ v.must_equal [{a: 'prepor', b: 'Andrew', c: 'ceo@prepor.ru', d: 0}]
427
+ end
428
+
429
+ describe "auto_space" do
430
+ let(:auto_space){ user_class.auto_space }
431
+ it "should return records on search" do
432
+ p = auto_space.by_pk('prepor')
433
+ p.must_be_instance_of user_class
434
+ p.attributes.must_equal prepor.merge(apples_count: 0)
435
+
436
+ p = auto_space.all_by_pks(['petro', {login: 'prepor'}])
437
+ p[0].must_be_instance_of user_class
438
+ p[1].must_be_instance_of user_class
439
+ p.map(&:email).sort.must_equal %w[ceo@prepor.ru petro@gmail.com]
440
+
441
+ p = auto_space.select({name: 'Andrew', email: 'rudenkoco@gmail.com'}, 0, 1)
442
+ p[0].must_be_instance_of user_class
443
+ p[0].attributes.must_equal ruden.merge(apples_count: 0)
444
+
445
+ p = auto_space.call('box.select_range', [0, 1000])
446
+ p.each{|o| o.must_be_instance_of user_class}
447
+ p.map(&:login).sort.must_equal %w{petro prepor ruden}
448
+
449
+ p = auto_space.insert({name: 'a', login: 'b', email: 'r', apples_count: 100}, return_tuple: true)
450
+ p.must_be_instance_of user_class
451
+ p.name.must_equal 'a'
452
+ p.login.must_equal 'b'
453
+ p.email.must_equal 'r'
454
+ p.apples_count.must_equal 100
455
+ end
456
+ end
457
+ end
458
+
459
+ describe "fields serializers" do
460
+ before do
461
+ user_class.field :info, :bson
462
+ end
463
+
464
+ it "should serialise and deserialize info field" do
465
+ info = { 'bio' => "hi!", 'age' => 23, 'hobbies' => ['mufa', 'tuka'] }
466
+ u = user_class.create login: 'prepor', name: 'Andrew', email: 'ceo@prepor.ru', info: info
467
+ u.info['hobbies'].must_equal ['mufa', 'tuka']
468
+ u.reload
469
+ u.info['hobbies'].must_equal ['mufa', 'tuka']
470
+ u = user_class.find u.login
471
+ u.info['hobbies'].must_equal ['mufa', 'tuka']
472
+ end
473
+ end
474
+ end