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
@@ -1,78 +1,377 @@
1
- class Tarantool
2
- class Request
1
+ require 'tarantool/util'
2
+ require 'tarantool/serializers'
3
3
 
4
- class << self
5
- def request_type(name = nil)
6
- if name
7
- @request_type = Tarantool::Requests::REQUEST_TYPES[name] || raise(UndefinedRequestType)
4
+ module Tarantool
5
+ module Request
6
+ include Util::Packer
7
+ include Util::TailGetter
8
+ include Serializers
9
+ INT32 = 'V'.freeze
10
+ INT64 = 'Q<'.freeze
11
+ SELECT_HEADER = 'VVVVV'.freeze
12
+ INSERT_HEADER = 'VV'.freeze
13
+ UPDATE_HEADER = 'VV'.freeze
14
+ DELETE_HEADER = 'VV'.freeze
15
+ CALL_HEADER = 'Vwa*'.freeze
16
+ INT32_0 = "\x00\x00\x00\x00".freeze
17
+ INT32_1 = "\x01\x00\x00\x00".freeze
18
+ BER4 = "\x04".freeze
19
+ BER8 = "\x08".freeze
20
+ ZERO = "\x00".freeze
21
+ EMPTY = "".freeze
22
+ PACK_STRING = 'wa*'.freeze
23
+ LEST_INT32 = -(2**31)
24
+ GREATEST_INT32 = 2**32
25
+ TYPES_AUTO = [:auto].freeze
26
+ TYPES_FALLBACK = [:str].freeze
27
+ TYPES_STR_STR = [:str, :str].freeze
28
+ TYPES_STR_AUTO = [:str, :auto].freeze
29
+
30
+ REQUEST_SELECT = 17
31
+ REQUEST_INSERT = 13
32
+ REQUEST_UPDATE = 19
33
+ REQUEST_DELETE = 21
34
+ REQUEST_CALL = 22
35
+ REQUEST_PING = 65280
36
+
37
+ BOX_RETURN_TUPLE = 0x01
38
+ BOX_ADD = 0x02
39
+ BOX_REPLACE = 0x04
40
+
41
+ UPDATE_OPS = {
42
+ :"=" => 0, :+ => 1, :& => 2, :^ => 3, :| => 4, :[] => 5,
43
+ :set => 0, :add => 1, :and => 2, :xor => 3, :or => 4, :splice => 5,
44
+ :delete => 6, :insert => 7,
45
+ :del => 6, :ins => 7,
46
+ '=' => 0, '+' => 1, '&' => 2, '^' => 3, '|' => 4, '[]' => 5,
47
+ ':' => 5,
48
+ 'set'=> 0, 'add'=> 1, 'and'=> 2, 'xor'=> 3, 'or'=> 4, 'splice'=> 5,
49
+ '#' => 6, '!' => 7,
50
+ 'delete'=> 6, 'insert'=> 7,
51
+ 'del' => 6, 'ins' => 7,
52
+ 0 => 0, 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, 6 => 6, 7 => 7
53
+ }
54
+ UPDATE_FIELDNO_OP = 'VC'.freeze
55
+
56
+ def _select(space_no, index_no, offset, limit, keys, cb, fields, index_fields, translators = [])
57
+ get_tuples = limit == :first ? (limit = 1; :first) : :all
58
+ keys = [*keys]
59
+ body = [space_no, index_no, offset, limit, keys.size].pack(SELECT_HEADER)
60
+
61
+ for key in keys
62
+ pack_tuple(body, key, index_fields, index_no)
63
+ end
64
+ cb = Response.new(cb, get_tuples, fields, translators)
65
+ _send_request(REQUEST_SELECT, body, cb)
66
+ end
67
+
68
+ class IndexIndexError < StandardError; end
69
+ def pack_tuple(body, key, types, index_no = 0)
70
+ if Integer === types.last
71
+ *types, tail = types
72
+ else
73
+ tail = 1
74
+ end
75
+ case key
76
+ when Array
77
+ if index_no != :space && nili = key.index(nil)
78
+ key = key.slice(0, nili)
79
+ end
80
+ body << [key_size = key.size].pack(INT32)
81
+ i = 0
82
+ while i < key_size
83
+ field = types[i] || get_tail_item(types, i, tail)
84
+ pack_field(body, field, key[i])
85
+ i += 1
86
+ end
87
+ when nil
88
+ body << INT32_0
89
+ else
90
+ body << INT32_1
91
+ pack_field(body, types[0], key)
92
+ end
93
+ rescue IndexIndexError => e
94
+ raise ArgumentError, "tuple #{key} has more entries than index #{index_no}"
95
+ end
96
+
97
+ MAX_BYTE_SIZE = 1024 * 1024
98
+ def pack_field(body, field_kind, value)
99
+ if value.nil?
100
+ body << ZERO
101
+ return
102
+ end
103
+ case field_kind
104
+ when :int, :integer
105
+ value = value.to_i
106
+ if LEST_INT32 <= value && value < GREATEST_INT32
107
+ body << BER4 << [value].pack(INT32)
8
108
  else
9
- @request_type
109
+ body << BER8 << [value].pack(INT64)
10
110
  end
111
+ when :str, :string, :bytes
112
+ value = value.to_s
113
+ raise StringTooLong if value.bytesize > MAX_BYTE_SIZE
114
+ body << [value.bytesize, value].pack(PACK_STRING)
115
+ when :error
116
+ raise IndexIndexError
117
+ when :auto
118
+ case value
119
+ when Integer
120
+ pack_field(body, :int, value)
121
+ when String
122
+ pack_field(body, :str, value)
123
+ when Util::AutoType
124
+ pack_field(body, :str, value.data)
125
+ else
126
+ raise ArgumentError, "Could auto detect only Integer and String"
127
+ end
128
+ else
129
+ value = get_serializer(field_kind).encode(value).to_s
130
+ raise StringTooLong if value.bytesize > MAX_BYTE_SIZE
131
+ body << [value.bytesize, value].pack(PACK_STRING)
132
+ end
133
+ end
134
+
135
+ def _modify_request(type, body, fields, ret_tuple, cb, translators)
136
+ cb = Response.new(cb, ret_tuple && (ret_tuple != :all ? :first : :all),
137
+ fields, translators)
138
+ _send_request(type, body, cb)
139
+ end
140
+
141
+ def _insert(space_no, flags, tuple, fields, cb, ret_tuple, translators = [])
142
+ flags |= BOX_RETURN_TUPLE if ret_tuple
143
+ fields = [*fields]
144
+
145
+ tuple = [*tuple]
146
+ tuple_size = tuple.size
147
+ body = [space_no, flags].pack(INSERT_HEADER)
148
+ pack_tuple(body, tuple, fields, :space)
149
+
150
+ _modify_request(REQUEST_INSERT, body, fields, ret_tuple, cb, translators)
151
+ end
152
+
153
+ def _update(space_no, pk, operations, fields, pk_fields, cb, ret_tuple, translators = [])
154
+ flags = ret_tuple ? BOX_RETURN_TUPLE : 0
155
+
156
+ if Array === operations && !(Array === operations.first)
157
+ operations = [operations]
11
158
  end
12
159
 
13
- def pack_tuple(*values)
14
- [values.size].pack('L') + values.map { |v| pack_field(v) }.join
160
+ body = [space_no, flags].pack(UPDATE_HEADER)
161
+ pack_tuple(body, pk, pk_fields, 0)
162
+ body << [operations.size].pack(INT32)
163
+
164
+ _pack_operations(body, operations, fields)
165
+
166
+ _modify_request(REQUEST_UPDATE, body, fields, ret_tuple, cb, translators)
167
+ end
168
+
169
+ def _pack_operations(body, operations, fields)
170
+ if Integer === fields.last
171
+ *fields, tail = fields
172
+ else
173
+ tail = 1
15
174
  end
175
+ for operation in operations
176
+ if Array === operation[0]
177
+ operation = operation[0] + operation.drop(1)
178
+ elsif Array === operation[1]
179
+ if operation.size == 2
180
+ operation = [operation[0]] + operation[1]
181
+ else
182
+ raise ArgumentError, "Could not understand operation #{operation}"
183
+ end
184
+ end
16
185
 
17
- def pack_field(value)
18
- if String === value
19
- raise StringTooLong.new if value.bytesize > 1024 * 1024
20
- [value.bytesize, value].pack('wa*')
21
- elsif Integer === value
22
- if value < 4294967296 # 2 ^ 32
23
- [4, value].pack('wL')
186
+ field_no = operation[0]
187
+ if operation.size == 2
188
+ if (Integer === field_no || field_no =~ /\A\d/)
189
+ unless Symbol === operation[1] && UPDATE_OPS[operation[1]] == 6
190
+ body << [field_no, 0].pack(UPDATE_FIELDNO_OP)
191
+ type = fields[field_no] || get_tail_item(fields, field_no, tail) ||
192
+ _detect_type(operation[1])
193
+ pack_field(body, type, operation[1])
194
+ next
195
+ end
196
+ elsif String === field_no
197
+ operation.insert(1, field_no.slice(0, 1))
198
+ field_no = field_no.slice(1..-1).to_i
24
199
  else
25
- [8, value].pack('wQ')
200
+ raise ArgumentError, "Could not understand field number #{field_no.inspect}"
26
201
  end
27
- elsif value.is_a?(Tarantool::Field)
28
- [value.data.bytesize].pack('w') + value.data
29
- else
30
- raise ArgumentError.new("Field should be integer or string")
31
202
  end
32
- end
33
- end
34
203
 
35
- attr_reader :space, :params, :args
36
- attr_reader :space_no
37
- def initialize(space, *args)
38
- @space = space
39
- @args = args
40
- @params = if args.last.is_a? Hash
41
- args.pop
42
- else
43
- {}
204
+ op = operation[1]
205
+ op = UPDATE_OPS[op] unless Integer === op
206
+ raise ArgumentError, "Unknown operation #{operation[1]}" unless op
207
+ body << [field_no, op].pack(UPDATE_FIELDNO_OP)
208
+ case op
209
+ when 0
210
+ if (type = fields[field_no]).nil?
211
+ if operation.size == 4 && Symbol === operation.last
212
+ *operation, type = operation
213
+ else
214
+ type = get_tail_item(fields, field_no, tail) || _detect_type(operation[2])
215
+ end
216
+ end
217
+ unless operation.size == 3
218
+ raise ArgumentError, "wrong arguments for set or insert operation #{operation.inspect}"
219
+ end
220
+ pack_field(body, type, operation[2])
221
+ when 1, 2, 3, 4
222
+ unless operation.size == 3 && !operation[2].nil?
223
+ raise ArgumentError, "wrong arguments for integer operation #{operation.inspect}"
224
+ end
225
+ pack_field(body, :int, operation[2])
226
+ when 5
227
+ unless operation.size == 5 && !operation[2].nil? && !operation[3].nil?
228
+ raise ArgumentError, "wrong arguments for slice operation #{operation.inspect}"
229
+ end
230
+
231
+ str = operation[4].to_s
232
+ 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)
236
+ when 7
237
+ old_field_no = field_no +
238
+ (inserted ||= []).count{|i| i <= field_no} -
239
+ (deleted ||= []).count{|i| i <= field_no}
240
+ inserted << field_no
241
+ if (type = fields[old_field_no]).nil?
242
+ if operation.size == 4 && Symbol === operation.last
243
+ *operation, type = operation
244
+ else
245
+ type = get_tail_item(fields, old_field_no, tail)
246
+ end
247
+ end
248
+ unless operation.size == 3
249
+ raise ArgumentError, "wrong arguments for set or insert operation #{operation.inspect}"
250
+ end
251
+ pack_field(body, type, operation[2])
252
+ when 6
253
+ body << ZERO
254
+ # pass
255
+ end
44
256
  end
45
- @space_no = params.delete(:space_no) || space.space_no || raise(UndefinedSpace.new)
46
- parse_args
47
257
  end
48
258
 
49
- def perform
50
- data = connection.send_request self.class.request_type, make_body
51
- make_response data
259
+ def _delete(space_no, pk, fields, pk_fields, cb, ret_tuple, translators = [])
260
+ flags = ret_tuple ? BOX_RETURN_TUPLE : 0
261
+
262
+ body = [space_no, flags].pack(DELETE_HEADER)
263
+ pack_tuple(body, pk, pk_fields, 0)
264
+
265
+ _modify_request(REQUEST_DELETE, body, fields, ret_tuple, cb, translators)
266
+ end
267
+
268
+ def _space_call_fix_values(values, space_no, opts)
269
+ opts = opts.dup
270
+ space_no = opts[:space_no] if opts.has_key?(:space_no)
271
+ if space_no
272
+ values = [space_no].concat([*values])
273
+ if opts[:types]
274
+ opts[:types] = [:str].concat([*opts[:types]]) # cause lua could convert it to integer by itself
275
+ else
276
+ opts[:types] = TYPES_STR_STR
277
+ end
278
+ end
279
+ [values, opts]
52
280
  end
53
281
 
54
- def parse_args
282
+ def _call(func_name, values, cb, opts={})
283
+ return_tuple = opts[:return_tuple] && :all
284
+ flags = return_tuple ? BOX_RETURN_TUPLE : 0
55
285
 
286
+ values = [*values]
287
+ value_types = opts[:types] ? [*opts[:types]] :
288
+ _detect_types(values)
289
+ return_types = [*opts[:returns] || TYPES_AUTO]
290
+
291
+ func_name = func_name.to_s
292
+ body = [flags, func_name.size, func_name].pack(CALL_HEADER)
293
+ pack_tuple(body, values, value_types, :func_call)
294
+
295
+ _modify_request(REQUEST_CALL, body, return_types, return_tuple, cb, opts[:translators] || [])
56
296
  end
57
297
 
58
- def make_response(data)
59
- return_code, = data[0,4].unpack('L')
60
- if return_code == 0
61
- Response.new(data[4, data.size], response_params)
62
- else
63
- msg = data[4, data.size].unpack('A*')
64
- raise BadReturnCode.new("Error code #{return_code}: #{msg}")
65
- end
298
+ def _ping(cb)
299
+ _send_request(REQUEST_PING, EMPTY, cb)
66
300
  end
301
+ alias ping_cb _ping
67
302
 
68
- def response_params
69
- res = {}
70
- res[:return_tuple] = true if params[:return_tuple]
303
+ def _detect_types(values)
304
+ values.map{|v| Integer === v ? :int : :str}
305
+ end
306
+
307
+ def _detect_type(value)
308
+ Integer === v ? :int :
309
+ Util::AutoType === v ? :auto :
310
+ :str
311
+ end
312
+
313
+ def _parse_hash_definition(returns)
314
+ field_names = []
315
+ field_types = []
316
+ returns.each{|name, type|
317
+ field_names << name
318
+ field_types << type
319
+ }
320
+ field_types << if field_names.include?(:_tail)
321
+ unless field_names.last == :_tail
322
+ raise ArgumentError, "_tail should be de declared last"
323
+ end
324
+ field_names.pop
325
+ [*field_types.last].size
326
+ else
327
+ 1
328
+ end
329
+ field_types.flatten!
330
+ [field_types, TranslateToHash.new(field_names, field_types.last)]
331
+ end
332
+
333
+ def _raise_or_return(res)
334
+ raise res if Exception === res
71
335
  res
72
336
  end
337
+ end
338
+
339
+ module CommonSpaceBlockMethods
340
+ def all_by_pks_blk(keys, opts={}, &block)
341
+ all_by_pks_cb(keys, block, opts)
342
+ end
73
343
 
74
- def connection
75
- space.connection
344
+ def by_pk_blk(key_array, &block)
345
+ by_pk_cb(key_array, block)
346
+ end
347
+
348
+ def insert_blk(tuple, opts={}, &block)
349
+ insert_cb(tuple, block, opts)
350
+ end
351
+
352
+ def replace_blk(tuple, opts={}, &block)
353
+ replace_cb(tuple, block, opts)
354
+ end
355
+
356
+ def update_blk(pk, operations, opts={}, &block)
357
+ update_cb(pk, operations, block, opts)
358
+ end
359
+
360
+ def delete_blk(pk, opts={}, &block)
361
+ delete_cb(pk, block, opts)
362
+ end
363
+
364
+ def invoke_blk(func_name, values = [], opts={}, &block)
365
+ invoke_cb(func_name, values, block, opts)
366
+ end
367
+
368
+ def call_blk(func_name, values = [], opts={}, &block)
369
+ call_cb(func_name, values, block, opts)
370
+ end
371
+
372
+ def ping_blk(&block)
373
+ ping_cb(block)
76
374
  end
77
375
  end
78
- end
376
+
377
+ end
@@ -1,61 +1,124 @@
1
- class Tarantool
2
- class Field
3
- attr_reader :data
4
- def initialize(data)
5
- @data = data
6
- end
1
+ require 'tarantool/util'
2
+ require 'tarantool/exceptions'
3
+ require 'tarantool/serializers'
7
4
 
8
- def to_i
9
- case data.bytesize
10
- when 8
11
- data.unpack('Q')[0]
12
- when 4
13
- data.unpack('L')[0]
14
- when 2
15
- data.unpack('S')[0]
5
+ module Tarantool
6
+ class Response < Struct.new(:cb, :get_tuples, :fields, :translators)
7
+ include Util::Packer
8
+ include Util::TailGetter
9
+ include Serializers
10
+ def call(data)
11
+ if Exception === data
12
+ cb.call(data)
16
13
  else
17
- raise ValueError.new("Unable to cast field to int: length must be 2, 4 or 8 bytes, field length is #{data.size}")
14
+ if (ret = return_code(data)) == 0
15
+ cb.call parse_response(data)
16
+ else
17
+ data.gsub!("\x00", "")
18
+ cb.call CODE_TO_EXCEPTION[ret].new(ret, data)
19
+ end
18
20
  end
19
21
  end
20
22
 
21
- def to_s
22
- data.dup.force_encoding('utf-8')
23
+ def translators
24
+ super || (self.translators = [])
23
25
  end
24
- end
25
- class Response
26
- attr_reader :tuples_affected, :offset, :tuples
27
- def initialize(data, params = {})
28
- @offset = 0
29
- @tuples_affected, = data[0, 4].unpack('L')
30
- @offset += 4
31
- if params[:return_tuple]
32
- @tuples = (1..tuples_affected).map do
33
- unpack_tuple(data)
34
- end
26
+
27
+ def parse_response(data)
28
+ unless get_tuples
29
+ unpack_int32(data)
35
30
  else
36
- tuples_affected
31
+ tuples = unpack_tuples(data)
32
+ if translators
33
+ translators.each{|trans|
34
+ tuples.map!{|tuple| trans.call(tuple)}
35
+ }
36
+ end
37
+ get_tuples == :first ? tuples.first : tuples
37
38
  end
38
39
  end
39
40
 
40
- # Only select request can return many tuples
41
- def tuple
42
- tuples.first
43
- end
41
+ def unpack_tuples(data)
42
+ tuples_affected = unpack_int32!(data)
43
+ tuples = []
44
+ fields = fields()
45
+ if Integer === fields.last
46
+ *fields, tail = fields
47
+ else
48
+ tail = 1
49
+ end
44
50
 
45
- def unpack_tuple(data)
46
- byte_size, cardinality = data[offset, 8].unpack("LL")
47
- @offset += 8
48
- tuple_data = data[offset, byte_size]
49
- @offset += byte_size
50
- (1..cardinality).map do
51
- Field.new unpack_field(tuple_data)
51
+ while tuples_affected > 0
52
+ byte_size = unpack_int32!(data)
53
+ fields_num = unpack_int32!(data)
54
+ tuple_str = data.slice!(0, byte_size)
55
+ i = 0
56
+ tuple = []
57
+ while i < fields_num
58
+ field_size = unpack_ber!(tuple_str)
59
+
60
+ field = fields[i] || get_tail_item(fields, i, tail)
61
+
62
+ tuple << (field_size == 0 ? nil :
63
+ case field
64
+ when :int, :integer
65
+ case field_size
66
+ when 8
67
+ unpack_int64!(tuple_str)
68
+ when 4
69
+ unpack_int32!(tuple_str)
70
+ when 2
71
+ unpack_int16!(tuple_str)
72
+ else
73
+ raise ValueError, "Bad field size #{field_size} for integer field ##{i}"
74
+ end
75
+ when :str, :string
76
+ tuple_str.slice!(0, field_size).force_encoding('utf-8')
77
+ when :bytes
78
+ tuple_str.slice!(0, field_size)
79
+ when :auto
80
+ str = tuple_str.slice!(0, field_size).force_encoding('utf-8')
81
+ case field_size
82
+ when 8, 4, 2
83
+ Util::AutoType.new(str)
84
+ else
85
+ str
86
+ end
87
+ else
88
+ get_serializer(field).decode(tuple_str.slice!(0, field_size))
89
+ end)
90
+ i += 1
91
+ end
92
+ tuples << tuple
93
+ tuples_affected -= 1
52
94
  end
95
+ tuples
96
+ end
97
+
98
+ def return_code(data)
99
+ unpack_int32!(data)
53
100
  end
101
+ end
54
102
 
55
- def unpack_field(data)
56
- byte_size, = data.unpack('w')
57
- data.slice!(0, [byte_size].pack('w').bytesize) # ololo
58
- data.slice!(0, byte_size)
103
+ # note that :_tail should not be in field_names
104
+ class TranslateToHash < Struct.new(:field_names, :tail_size)
105
+ def call(tuple)
106
+ i = 0
107
+ hash = {}
108
+ tuple_size = tuple.size
109
+ names = field_names
110
+ while i < tuple_size
111
+ if name = names[i]
112
+ hash[name] = tuple[i]
113
+ else
114
+ tail = tuple.slice(i..-1)
115
+ hash[:_tail] = tail_size == 1 ? tail :
116
+ tail.each_slice(tail_size).to_a
117
+ break
118
+ end
119
+ i += 1
120
+ end
121
+ hash
59
122
  end
60
123
  end
61
- end
124
+ end
@@ -1,5 +1,5 @@
1
1
  require 'bson'
2
- class Tarantool
2
+ module Tarantool
3
3
  module Serializers
4
4
  class BSON
5
5
  Serializers::MAP[:bson] = self
@@ -12,4 +12,4 @@ class Tarantool
12
12
  end
13
13
  end
14
14
  end
15
- end
15
+ end
@@ -1,9 +1,37 @@
1
- class Tarantool
1
+ module Tarantool
2
2
  module Serializers
3
3
  MAP = {}
4
- %w{string integer}.each do |v|
5
- require "tarantool/serializers/#{v}"
4
+
5
+ def check_type(type)
6
+ type = type.to_sym if String === type
7
+ return :int if Integer == type
8
+ return :str if String == type
9
+
10
+ case type
11
+ when :int, :integer, :str, :string, :bytes, :auto
12
+ # pass
13
+ when Symbol
14
+ unless MAP.include?(type)
15
+ raise ArgumentError, "Unknown type name #{type.inspect}"
16
+ end
17
+ else
18
+ unless type.respond_to?(:encode) && type.respond_to?(:decode)
19
+ raise ArgumentError, "Wrong serializer object #{type.inspect} (must respond to #encode and #decode)"
20
+ end
21
+ end
22
+ type
23
+ end
24
+
25
+ def get_serializer(type)
26
+ if Symbol === type
27
+ MAP.fetch(type){ raise ArgumentError, "Unknown type name #{type.inspect}" }
28
+ elsif type.respond_to?(:encode) && type.respond_to?(:decode)
29
+ type
30
+ else
31
+ raise ArgumentError, "Wrong serializer object #{type.inspect} (must respond to #encode and #decode)"
32
+ end
6
33
  end
7
34
 
35
+ extend self
8
36
  end
9
- end
37
+ end