tarantool 0.2.5 → 0.3.0.7

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 (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