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
@@ -1,8 +1,12 @@
1
- require 'iproto/core-ext'
1
+ require 'tarantool/core-ext'
2
2
  require 'tarantool/em_db'
3
3
 
4
4
  module Tarantool
5
5
  class FiberDB < EMDB
6
+ def primary_interface
7
+ :synchronous
8
+ end
9
+
6
10
  module CommonSpaceFiberMethods
7
11
  def all_by_pks(pks, opts={})
8
12
  all_by_pks_cb(pks, ::Fiber.current, opts)
@@ -24,6 +28,11 @@ module Tarantool
24
28
  _raise_or_return ::Fiber.yield
25
29
  end
26
30
 
31
+ def store(tuple, opts={})
32
+ store_cb(tuple, ::Fiber.current, opts)
33
+ _raise_or_return ::Fiber.yield
34
+ end
35
+
27
36
  def update(pk, operations, opts={})
28
37
  update_cb(pk, operations, ::Fiber.current, opts)
29
38
  _raise_or_return ::Fiber.yield
@@ -123,6 +132,11 @@ module Tarantool
123
132
  _raise_or_return ::Fiber.yield
124
133
  end
125
134
 
135
+ def store(space_no, tuple, opts={})
136
+ store_cb(space_no, tuple, ::Fiber.current, opts)
137
+ _raise_or_return ::Fiber.yield
138
+ end
139
+
126
140
  def update(space_no, pk, operation, opts={})
127
141
  update_cb(space_no, pk, operation, ::Fiber.current, opts)
128
142
  _raise_or_return ::Fiber.yield
@@ -22,20 +22,37 @@ module Tarantool
22
22
  end
23
23
 
24
24
  def save
25
+ return false unless before_save
25
26
  if @__new_record
27
+ return false unless before_create
26
28
  self.class.insert(@attributes)
27
29
  @__new_record = false
30
+ after_create
28
31
  else
32
+ return false unless before_update
29
33
  self.class.replace(@attributes)
34
+ after_update
30
35
  end
36
+ after_save
31
37
  self
32
38
  end
33
39
 
34
40
  def destroy
41
+ return false unless before_destroy
35
42
  self.class.delete id
43
+ after_destroy
36
44
  true
37
45
  end
38
46
 
47
+ def before_save; true end
48
+ def before_create; true end
49
+ def before_update; true end
50
+ def after_create; end
51
+ def after_update; end
52
+ def after_save; end
53
+ def before_destroy; true end
54
+ def after_destroy; end
55
+
39
56
  class << self
40
57
  def generated_attribute_methods
41
58
  @generated_attribute_methods ||= begin
@@ -6,10 +6,7 @@ module Tarantool
6
6
  include Request
7
7
  def initialize(tarantool)
8
8
  @tarantool = tarantool
9
- end
10
-
11
- def _send_request(type, body, cb)
12
- @tarantool._send_request(type, body, cb)
9
+ _init_shard_vars(nil, false)
13
10
  end
14
11
 
15
12
  def select_cb(space_no, index_no, keys, offset, limit, cb, opts={})
@@ -20,7 +17,7 @@ module Tarantool
20
17
  returns, *translators = _parse_hash_definition(returns)
21
18
  end
22
19
  _select(space_no, index_no, offset, limit, keys, cb, returns,
23
- types, translators)
20
+ types, all_shards, translators)
24
21
  end
25
22
 
26
23
  def all_cb(space_no, index_no, keys, cb, opts={})
@@ -35,12 +32,17 @@ module Tarantool
35
32
 
36
33
  def insert_cb(space_no, tuple, cb, opts={})
37
34
  types = opts[:types] || _detect_types(tuple)
38
- _insert(space_no, BOX_ADD, tuple, types, cb, opts[:return_tuple])
35
+ _insert(space_no, BOX_ADD, tuple, types, cb, opts[:return_tuple], all_shards)
39
36
  end
40
37
 
41
38
  def replace_cb(space_no, tuple, cb, opts={})
42
39
  types = opts[:types] || _detect_types(tuple)
43
- _insert(space_no, BOX_REPLACE, tuple, types, cb, opts[:return_tuple])
40
+ _insert(space_no, BOX_REPLACE, tuple, types, cb, opts[:return_tuple], all_shards)
41
+ end
42
+
43
+ def store_cb(space_no, tuple, cb, opts={})
44
+ types = opts[:types] || _detect_types(tuple)
45
+ _insert(space_no, 0, tuple, types, cb, opts[:return_tuple], all_shards)
44
46
  end
45
47
 
46
48
  def update_cb(space_no, pk, operations, cb, opts={})
@@ -51,7 +53,7 @@ module Tarantool
51
53
  returns, *translators = _parse_hash_definition(returns)
52
54
  end
53
55
  _update(space_no, pk, operations, returns, pk_types, cb,
54
- opts[:return_tuple], translators)
56
+ opts[:return_tuple], all_shards, translators)
55
57
  end
56
58
 
57
59
  def delete_cb(space_no, pk, cb, opts={})
@@ -62,7 +64,7 @@ module Tarantool
62
64
  returns, *translators = _parse_hash_definition(returns)
63
65
  end
64
66
  _delete(space_no, pk, returns, pk_types, cb,
65
- opts[:return_tuple], translators)
67
+ opts[:return_tuple], all_shards, translators)
66
68
  end
67
69
 
68
70
  def invoke_cb(func_name, values, cb, opts={})
@@ -104,6 +106,10 @@ module Tarantool
104
106
  replace_cb(space_no, tuple, block, opts)
105
107
  end
106
108
 
109
+ def store_blk(space_no, tuple, opts={}, &block)
110
+ store_cb(space_no, tuple, block, opts)
111
+ end
112
+
107
113
  def update_blk(space_no, pk, operation, opts={}, &block)
108
114
  update_cb(space_no, pk, operation, block, opts={})
109
115
  end
@@ -24,6 +24,11 @@ module Tarantool
24
24
  end
25
25
  end
26
26
 
27
+ def reset!
28
+ @results = nil
29
+ self
30
+ end
31
+
27
32
  def each
28
33
  return to_enum unless block_given?
29
34
  results.each{|a| yield a}
@@ -45,14 +50,27 @@ module Tarantool
45
50
  self.class.new(@record, @params.merge(where: params))
46
51
  end
47
52
 
53
+ def shard(params)
54
+ self.class.new(@record, @params.merge(shard: params))
55
+ end
56
+
57
+ def auto_shard
58
+ params = @params.dup
59
+ params.delte :shard
60
+ self.class.new(@record, params)
61
+ end
62
+
48
63
  def all
49
64
  results.dup
50
65
  end
51
66
 
52
67
  def first
53
- @record.auto_space.
54
- select(@params[:where], @params[:offset] || 0, 1).
55
- first
68
+ space.select(@params[:where], @params[:offset] || 0, 1).first
69
+ end
70
+
71
+ def space
72
+ space = @record.auto_space
73
+ @params[:shard] ? space.shard(@params[:shard]) : space
56
74
  end
57
75
  end
58
76
  end
@@ -1,13 +1,16 @@
1
1
  require 'tarantool/util'
2
+ require 'tarantool/shards_support'
2
3
  require 'tarantool/serializers'
3
4
 
4
5
  module Tarantool
6
+ module CommonSpace
7
+ attr_reader :tarantool, :space_no
8
+ end
9
+
5
10
  module Request
6
11
  include Util::Packer
7
12
  include Util::TailGetter
8
13
  include Serializers
9
- INT32 = 'V'.freeze
10
- INT64 = 'Q<'.freeze
11
14
  SELECT_HEADER = 'VVVVV'.freeze
12
15
  INSERT_HEADER = 'VV'.freeze
13
16
  UPDATE_HEADER = 'VV'.freeze
@@ -15,17 +18,16 @@ module Tarantool
15
18
  CALL_HEADER = 'Vwa*'.freeze
16
19
  INT32_0 = "\x00\x00\x00\x00".freeze
17
20
  INT32_1 = "\x01\x00\x00\x00".freeze
18
- BER4 = "\x04".freeze
19
- BER8 = "\x08".freeze
20
21
  ZERO = "\x00".freeze
22
+ ONE = "\x01".freeze
21
23
  EMPTY = "".freeze
22
24
  PACK_STRING = 'wa*'.freeze
23
25
  LEST_INT32 = -(2**31)
24
- GREATEST_INT32 = 2**32
26
+
25
27
  TYPES_AUTO = [:auto].freeze
26
- TYPES_FALLBACK = [:str].freeze
27
- TYPES_STR_STR = [:str, :str].freeze
28
- TYPES_STR_AUTO = [:str, :auto].freeze
28
+ TYPES_FALLBACK = [:string].freeze
29
+ TYPES_STR_STR = [:string, :string].freeze
30
+ TYPES_STR_AUTO = [:string, :auto].freeze
29
31
 
30
32
  REQUEST_SELECT = 17
31
33
  REQUEST_INSERT = 13
@@ -53,7 +55,11 @@ module Tarantool
53
55
  }
54
56
  UPDATE_FIELDNO_OP = 'VC'.freeze
55
57
 
56
- def _select(space_no, index_no, offset, limit, keys, cb, fields, index_fields, translators = [])
58
+ def _send_request(shard_numbers, read_write, cb)
59
+ @tarantool._send_request(shard_numbers, read_write, cb)
60
+ end
61
+
62
+ def _select(space_no, index_no, offset, limit, keys, cb, fields, index_fields, shard_nums, translators = [])
57
63
  get_tuples = limit == :first ? (limit = 1; :first) : :all
58
64
  keys = [*keys]
59
65
  body = [space_no, index_no, offset, limit, keys.size].pack(SELECT_HEADER)
@@ -61,8 +67,8 @@ module Tarantool
61
67
  for key in keys
62
68
  pack_tuple(body, key, index_fields, index_no)
63
69
  end
64
- cb = Response.new(cb, get_tuples, fields, translators)
65
- _send_request(REQUEST_SELECT, body, cb)
70
+ response = Response.new(cb, REQUEST_SELECT, body, get_tuples, fields, translators)
71
+ _send_request(shard_nums, :read, response)
66
72
  end
67
73
 
68
74
  class IndexIndexError < StandardError; end
@@ -90,7 +96,7 @@ module Tarantool
90
96
  body << INT32_1
91
97
  pack_field(body, types[0], key)
92
98
  end
93
- rescue IndexIndexError => e
99
+ rescue IndexIndexError
94
100
  raise ArgumentError, "tuple #{key} has more entries than index #{index_no}"
95
101
  end
96
102
 
@@ -103,25 +109,62 @@ module Tarantool
103
109
  case field_kind
104
110
  when :int, :integer
105
111
  value = value.to_i
106
- if LEST_INT32 <= value && value < GREATEST_INT32
107
- body << BER4 << [value].pack(INT32)
108
- else
109
- body << BER8 << [value].pack(INT64)
110
- end
111
- when :str, :string, :bytes
112
+ _raise_integer_overflow(value, MIN_INT, MAX_INT32) if value > MAX_INT32 or value < 0
113
+ append_ber_int32!(body, value)
114
+ when :string, :bytes, :str
112
115
  value = value.to_s
113
- raise StringTooLong if value.bytesize > MAX_BYTE_SIZE
116
+ value = ZERO + value if value < ONE
117
+ raise StringTooLong if value.bytesize >= MAX_BYTE_SIZE
114
118
  body << [value.bytesize, value].pack(PACK_STRING)
119
+ when :bytes
120
+ value = value.to_s
121
+ raise StringTooLong if value.bytesize >= MAX_BYTE_SIZE
122
+ body << [value.bytesize, value].pack(PACK_STRING)
123
+ when :int64
124
+ value = value.to_i
125
+ _raise_integer_overflow(value, MIN_INT, MAX_INT64) if value > MAX_INT64 or value < 0
126
+ append_ber_int64!(body, value)
127
+ when :int16
128
+ value = value.to_i
129
+ _raise_integer_overflow(value, MIN_INT, MAX_INT16) if value > MAX_INT16 or value < 0
130
+ append_ber_int16!(body, value)
131
+ when :int8
132
+ value = value.to_i
133
+ _raise_integer_overflow(value, MIN_INT, MAX_INT8) if value > MAX_INT8 or value < 0
134
+ append_ber_int8!(body, value)
135
+ when :sint
136
+ value = value.to_i
137
+ _raise_integer_overflow(value, MIN_SINT32, MAX_SINT32) if value > MAX_SINT32 or value < MIN_SINT32
138
+ append_ber_sint32!(body, value)
139
+ when :sint64
140
+ value = value.to_i
141
+ _raise_integer_overflow(value, MIN_SINT64, MAX_SINT64) if value > MAX_SINT64 or value < MIN_SINT64
142
+ append_ber_sint64!(body, value)
143
+ when :sint16
144
+ value = value.to_i
145
+ _raise_integer_overflow(value, MIN_SINT16, MAX_SINT16) if value > MAX_SINT16 or value < MIN_SINT16
146
+ append_ber_sint16!(body, value)
147
+ when :sint8
148
+ value = value.to_i
149
+ _raise_integer_overflow(value, MIN_SINT8, MAX_SINT8) if value > MAX_SINT8 or value < MIN_SINT8
150
+ append_ber_sint8!(body, value)
151
+ when :varint
152
+ value = value.to_i
153
+ if 0 <= value && value < MAX_INT32
154
+ append_ber_int32!(body, value)
155
+ else
156
+ append_ber_sint64!(body, value)
157
+ end
115
158
  when :error
116
159
  raise IndexIndexError
117
160
  when :auto
118
161
  case value
119
162
  when Integer
120
- pack_field(body, :int, value)
163
+ pack_field(body, :varint, value)
121
164
  when String
122
- pack_field(body, :str, value)
165
+ pack_field(body, :bytes, value)
123
166
  when Util::AutoType
124
- pack_field(body, :str, value.data)
167
+ pack_field(body, :bytes, value.data)
125
168
  else
126
169
  raise ArgumentError, "Could auto detect only Integer and String"
127
170
  end
@@ -132,25 +175,29 @@ module Tarantool
132
175
  end
133
176
  end
134
177
 
135
- def _modify_request(type, body, fields, ret_tuple, cb, translators)
136
- cb = Response.new(cb, ret_tuple && (ret_tuple != :all ? :first : :all),
178
+ def _raise_integer_overflow(value, min, max)
179
+ raise IntegerFieldOverflow, "#{value} not in (#{min}..#{max})"
180
+ end
181
+
182
+ def _modify_request(type, body, fields, ret_tuple, cb, shard_nums, read_write, translators)
183
+ response = Response.new(cb, type, body, ret_tuple && (ret_tuple != :all ? :first : :all),
137
184
  fields, translators)
138
- _send_request(type, body, cb)
185
+ _send_request(shard_nums, read_write, response)
139
186
  end
140
187
 
141
- def _insert(space_no, flags, tuple, fields, cb, ret_tuple, translators = [])
188
+ def _insert(space_no, flags, tuple, fields, cb, ret_tuple, shard_nums, in_any_shard = nil, translators = [])
142
189
  flags |= BOX_RETURN_TUPLE if ret_tuple
143
190
  fields = [*fields]
144
191
 
145
192
  tuple = [*tuple]
146
- tuple_size = tuple.size
147
193
  body = [space_no, flags].pack(INSERT_HEADER)
148
194
  pack_tuple(body, tuple, fields, :space)
149
195
 
150
- _modify_request(REQUEST_INSERT, body, fields, ret_tuple, cb, translators)
196
+ _modify_request(REQUEST_INSERT, body, fields, ret_tuple, cb, shard_nums,
197
+ in_any_shard ? :replace : :write, translators)
151
198
  end
152
199
 
153
- def _update(space_no, pk, operations, fields, pk_fields, cb, ret_tuple, translators = [])
200
+ def _update(space_no, pk, operations, fields, pk_fields, cb, ret_tuple, shard_nums, translators = [])
154
201
  flags = ret_tuple ? BOX_RETURN_TUPLE : 0
155
202
 
156
203
  if Array === operations && !(Array === operations.first)
@@ -159,11 +206,11 @@ module Tarantool
159
206
 
160
207
  body = [space_no, flags].pack(UPDATE_HEADER)
161
208
  pack_tuple(body, pk, pk_fields, 0)
162
- body << [operations.size].pack(INT32)
209
+ append_int32!(body, operations.size)
163
210
 
164
211
  _pack_operations(body, operations, fields)
165
212
 
166
- _modify_request(REQUEST_UPDATE, body, fields, ret_tuple, cb, translators)
213
+ _modify_request(REQUEST_UPDATE, body, fields, ret_tuple, cb, shard_nums, :write, translators)
167
214
  end
168
215
 
169
216
  def _pack_operations(body, operations, fields)
@@ -222,7 +269,7 @@ module Tarantool
222
269
  unless operation.size == 3 && !operation[2].nil?
223
270
  raise ArgumentError, "wrong arguments for integer operation #{operation.inspect}"
224
271
  end
225
- pack_field(body, :int, operation[2])
272
+ pack_field(body, :sint, operation[2])
226
273
  when 5
227
274
  unless operation.size == 5 && !operation[2].nil? && !operation[3].nil?
228
275
  raise ArgumentError, "wrong arguments for slice operation #{operation.inspect}"
@@ -230,9 +277,9 @@ module Tarantool
230
277
 
231
278
  str = operation[4].to_s
232
279
  body << [ 10 + ber_size(str.bytesize) + str.bytesize ].pack('w')
233
- pack_field(body, :int, operation[2])
234
- pack_field(body, :int, operation[3])
235
- pack_field(body, :str, str)
280
+ append_ber_sint32!(body, operation[2].to_i)
281
+ append_ber_sint32!(body, operation[3].to_i)
282
+ body << [str.bytesize, str.to_s].pack(PACK_STRING)
236
283
  when 7
237
284
  old_field_no = field_no +
238
285
  (inserted ||= []).count{|i| i <= field_no} -
@@ -256,13 +303,13 @@ module Tarantool
256
303
  end
257
304
  end
258
305
 
259
- def _delete(space_no, pk, fields, pk_fields, cb, ret_tuple, translators = [])
306
+ def _delete(space_no, pk, fields, pk_fields, cb, ret_tuple, shard_nums, translators = [])
260
307
  flags = ret_tuple ? BOX_RETURN_TUPLE : 0
261
308
 
262
309
  body = [space_no, flags].pack(DELETE_HEADER)
263
310
  pack_tuple(body, pk, pk_fields, 0)
264
311
 
265
- _modify_request(REQUEST_DELETE, body, fields, ret_tuple, cb, translators)
312
+ _modify_request(REQUEST_DELETE, body, fields, ret_tuple, cb, shard_nums, :write, translators)
266
313
  end
267
314
 
268
315
  def _space_call_fix_values(values, space_no, opts)
@@ -271,11 +318,24 @@ module Tarantool
271
318
  if space_no
272
319
  values = [space_no].concat([*values])
273
320
  if opts[:types]
274
- opts[:types] = [:str].concat([*opts[:types]]) # cause lua could convert it to integer by itself
321
+ opts[:types] = [:string].concat([*opts[:types]]) # cause lua could convert it to integer by itself
275
322
  else
276
323
  opts[:types] = TYPES_STR_STR
277
324
  end
278
325
  end
326
+
327
+ # scheck for shards hints
328
+ opts[:shards] ||= _get_shard_nums do
329
+ if opts[:shard_for_insert]
330
+ opts[:shard_keys] ? _detect_shards_for_insert(opts[:shard_keys]) :
331
+ opts[:shard_key] ? _detect_shard_for_insert( opts[:shard_key]) :
332
+ _all_shards
333
+ else
334
+ opts[:shard_keys] ? _detect_shards(opts[:shard_keys]) :
335
+ opts[:shard_key] ? _detect_shard( opts[:shard_key]) :
336
+ _all_shards
337
+ end
338
+ end
279
339
  [values, opts]
280
340
  end
281
341
 
@@ -292,22 +352,45 @@ module Tarantool
292
352
  body = [flags, func_name.size, func_name].pack(CALL_HEADER)
293
353
  pack_tuple(body, values, value_types, :func_call)
294
354
 
295
- _modify_request(REQUEST_CALL, body, return_types, return_tuple, cb, opts[:translators] || [])
355
+ shard_nums = opts[:shards] || all_shards
356
+ read_write = case opts[:readonly]
357
+ when nil, false, :write
358
+ :write
359
+ when true, :read
360
+ :read
361
+ when :replace
362
+ :replace
363
+ else
364
+ raise ArgumentError, "space#call :readonly options accepts nil, false, :write, true, :read and :replace, but #{opts[:readonly].inspect} were sent"
365
+ end
366
+
367
+ _modify_request(REQUEST_CALL, body, return_types, return_tuple, cb, shard_nums, read_write, opts[:translators] || [])
296
368
  end
297
369
 
370
+ class WrapPing < Struct.new(:cb)
371
+ def call(data)
372
+ cb.call data
373
+ end
374
+ def call_callback(data)
375
+ cb.call data
376
+ end
377
+ def parse_response(data)
378
+ data
379
+ end
380
+ end
298
381
  def _ping(cb)
299
- _send_request(REQUEST_PING, EMPTY, cb)
382
+ _send_request(all_shards, :write, REQUEST_PING, EMPTY, WrapPing.new(cb))
300
383
  end
301
384
  alias ping_cb _ping
302
385
 
303
386
  def _detect_types(values)
304
- values.map{|v| Integer === v ? :int : :str}
387
+ values.map{|v| Integer === v ? :varint : :string}
305
388
  end
306
389
 
307
390
  def _detect_type(value)
308
- Integer === v ? :int :
391
+ Integer === v ? :varint :
309
392
  Util::AutoType === v ? :auto :
310
- :str
393
+ :string
311
394
  end
312
395
 
313
396
  def _parse_hash_definition(returns)
@@ -353,6 +436,10 @@ module Tarantool
353
436
  replace_cb(tuple, block, opts)
354
437
  end
355
438
 
439
+ def store_blk(tuple, opts={}, &block)
440
+ store_cb(tuple, block, opts)
441
+ end
442
+
356
443
  def update_blk(pk, operations, opts={}, &block)
357
444
  update_cb(pk, operations, block, opts)
358
445
  end