tarantool 0.3.0.7 → 0.4.2.1

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