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
@@ -3,16 +3,32 @@ require 'tarantool/exceptions'
3
3
  require 'tarantool/serializers'
4
4
 
5
5
  module Tarantool
6
- class Response < Struct.new(:cb, :get_tuples, :fields, :translators)
6
+ module ParseIProto
7
+ include Util::Packer
8
+ def _parse_iproto(data)
9
+ if Exception === data || data == ''
10
+ data
11
+ elsif (ret = unpack_int32!(data)) == 0
12
+ data
13
+ else
14
+ data.gsub!("\x00", "")
15
+ CODE_TO_EXCEPTION[ret].new(ret, data)
16
+ end
17
+ end
18
+ end
19
+
20
+ class Response < Struct.new(:cb, :request_type, :body, :get_tuples, :fields, :translators)
7
21
  include Util::Packer
8
22
  include Util::TailGetter
9
23
  include Serializers
24
+ UTF8 = 'utf-8'.freeze
25
+
10
26
  def call(data)
11
27
  if Exception === data
12
28
  cb.call(data)
13
29
  else
14
30
  if (ret = return_code(data)) == 0
15
- cb.call parse_response(data)
31
+ call_callback parse_response(data)
16
32
  else
17
33
  data.gsub!("\x00", "")
18
34
  cb.call CODE_TO_EXCEPTION[ret].new(ret, data)
@@ -24,7 +40,12 @@ module Tarantool
24
40
  super || (self.translators = [])
25
41
  end
26
42
 
43
+ def call_callback(result)
44
+ cb.call(Exception === result || get_tuples != :first ? result : result.first)
45
+ end
46
+
27
47
  def parse_response(data)
48
+ return data if Exception === data
28
49
  unless get_tuples
29
50
  unpack_int32(data)
30
51
  else
@@ -34,7 +55,7 @@ module Tarantool
34
55
  tuples.map!{|tuple| trans.call(tuple)}
35
56
  }
36
57
  end
37
- get_tuples == :first ? tuples.first : tuples
58
+ tuples
38
59
  end
39
60
  end
40
61
 
@@ -62,6 +83,52 @@ module Tarantool
62
83
  tuple << (field_size == 0 ? nil :
63
84
  case field
64
85
  when :int, :integer
86
+ if field_size != 4
87
+ raise ValueError, "Bad field size #{field_size} for integer field ##{i}"
88
+ end
89
+ unpack_int32!(tuple_str)
90
+ when :string, :str
91
+ str = tuple_str.slice!(0, field_size)
92
+ str[0,1] = EMPTY if str < ONE
93
+ str.force_encoding(UTF8)
94
+ when :int64
95
+ if field_size != 8
96
+ raise ValueError, "Bad field size #{field_size} for 64bit integer field ##{i}"
97
+ end
98
+ unpack_int64!(tuple_str)
99
+ when :bytes
100
+ tuple_str.slice!(0, field_size)
101
+ when :int16
102
+ if field_size != 2
103
+ raise ValueError, "Bad field size #{field_size} for 16bit integer field ##{i}"
104
+ end
105
+ unpack_int16!(tuple_str)
106
+ when :int8
107
+ if field_size != 1
108
+ raise ValueError, "Bad field size #{field_size} for 8bit integer field ##{i}"
109
+ end
110
+ unpack_int8!(tuple_str)
111
+ when :sint
112
+ if field_size != 4
113
+ raise ValueError, "Bad field size #{field_size} for integer field ##{i}"
114
+ end
115
+ unpack_sint32!(tuple_str)
116
+ when :sint64
117
+ if field_size != 8
118
+ raise ValueError, "Bad field size #{field_size} for 64bit integer field ##{i}"
119
+ end
120
+ unpack_sint64!(tuple_str)
121
+ when :sint16
122
+ if field_size != 2
123
+ raise ValueError, "Bad field size #{field_size} for 16bit integer field ##{i}"
124
+ end
125
+ unpack_sint16!(tuple_str)
126
+ when :sint8
127
+ if field_size != 1
128
+ raise ValueError, "Bad field size #{field_size} for 8bit integer field ##{i}"
129
+ end
130
+ unpack_sint8!(tuple_str)
131
+ when :varint
65
132
  case field_size
66
133
  when 8
67
134
  unpack_int64!(tuple_str)
@@ -72,10 +139,6 @@ module Tarantool
72
139
  else
73
140
  raise ValueError, "Bad field size #{field_size} for integer field ##{i}"
74
141
  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
142
  when :auto
80
143
  str = tuple_str.slice!(0, field_size).force_encoding('utf-8')
81
144
  case field_size
@@ -4,22 +4,43 @@ module Tarantool
4
4
 
5
5
  def check_type(type)
6
6
  type = type.to_sym if String === type
7
- return :int if Integer == type
8
- return :str if String == type
7
+ return :varint if Integer == type
8
+ return :string if String == type
9
9
 
10
10
  case type
11
- when :int, :integer, :str, :string, :bytes, :auto
12
- # pass
11
+ when :int, :integer, :int32, :integer32
12
+ :int
13
+ when :int64, :integer64
14
+ :int64
15
+ when :int16, :integer16
16
+ :int16
17
+ when :int8, :integer8
18
+ :int8
19
+ when :sint, :sinteger, :sint32, :sinteger32
20
+ :sint
21
+ when :sint16, :sinteger16
22
+ :sint16
23
+ when :sint64, :sinteger64
24
+ :sint64
25
+ when :sint8, :sinteger8
26
+ :sint8
27
+ when :varint
28
+ :varint
29
+ when :str, :string
30
+ :string
31
+ when :bytes, :auto
32
+ type
13
33
  when Symbol
14
34
  unless MAP.include?(type)
15
35
  raise ArgumentError, "Unknown type name #{type.inspect}"
16
36
  end
37
+ type
17
38
  else
18
39
  unless type.respond_to?(:encode) && type.respond_to?(:decode)
19
40
  raise ArgumentError, "Wrong serializer object #{type.inspect} (must respond to #encode and #decode)"
20
41
  end
42
+ type
21
43
  end
22
- type
23
44
  end
24
45
 
25
46
  def get_serializer(type)
@@ -0,0 +1,14 @@
1
+ module Tarantool
2
+ module Serializers
3
+ class BerArray
4
+ Serializers::MAP[:ber_array] = self
5
+ def self.encode(value)
6
+ value.pack('w*')
7
+ end
8
+
9
+ def self.decode(value)
10
+ value.unpack('w*')
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,204 @@
1
+ require 'sumbur'
2
+ require 'murmurhash3'
3
+ module Tarantool
4
+ module Request
5
+ class SumburMurmur32
6
+ include Sumbur
7
+ include MurmurHash3::V32
8
+ end
9
+
10
+ class SumburMurmurFmix < SumburMurmur32
11
+ def call(shard_values, shards_count, this)
12
+ shard_values[0] &&
13
+ sumbur(murmur3_32_fmix(shard_values[0]), shards_count)
14
+ end
15
+ end
16
+
17
+ class SumburMurmurInt64 < SumburMurmur32
18
+ def call(shard_values, shards_count, this)
19
+ shard_values[0] &&
20
+ sumbur(murmur3_32_int64_hash(shard_values[0]), shards_count)
21
+ end
22
+ end
23
+
24
+ class SumburMurmurStr < SumburMurmur32
25
+ def call(shard_values, shards_count, this)
26
+ shard_values[0] &&
27
+ sumbur(murmur3_32_str_hash(shard_values[0]), shards_count)
28
+ end
29
+ end
30
+
31
+ class DefaultShardProc < SumburMurmur32
32
+ def call(shard_values, shards_count, this)
33
+ if shard_values.all?
34
+ hash = i = 0
35
+ size = shard_values.size
36
+ while i < size
37
+ hash = case value = shard_values[i]
38
+ when Integer
39
+ murmur3_32_int64_hash(value, hash)
40
+ when String
41
+ murmur3_32_str_hash(value, hash)
42
+ else
43
+ raise ValueError, "Default sharding proc could deal only with strings and integers"
44
+ end
45
+ i += 1
46
+ end
47
+ sumbur(hash, shards_count)
48
+ end
49
+ end
50
+ end
51
+
52
+ class ModuloShardProc
53
+ def call(shard_values, shards_count, this)
54
+ shard_values[0] && shard_values[0] % shards_count
55
+ end
56
+ end
57
+
58
+ attr_reader :shard_proc, :shards_count, :previous_shards_count
59
+ attr_reader :insert_with_shards_count
60
+
61
+ def _init_shard_vars(shard_proc, init_shard_for_index = true)
62
+ if init_shard_for_index
63
+ @shard_by_index = @index_fields.index{|index| index == @shard_fields}
64
+ @shard_for_index = @index_fields.map{|index|
65
+ if (pos = @shard_fields.map{|name| index.index(name)}).any?
66
+ pos.map{|i| i || 2**30}
67
+ end
68
+ }
69
+ @shard_proc = case shard_proc
70
+ when nil, :default
71
+ DefaultShardProc.new
72
+ when :sumbur_murmur_fmix
73
+ SumburMurmurFmix.new
74
+ when :sumbur_murmur_int64
75
+ SumburMurmurInt64.new
76
+ when :sumbur_murmur_str
77
+ SumburMurmurStr.new
78
+ when :modulo, :module
79
+ ModuloShardProc.new
80
+ else
81
+ unless shard_proc.respond_to?(:call)
82
+ raise ArgumentError, "Wrong sharding proc object #{shard_proc.inspect}"
83
+ end
84
+ shard_proc
85
+ end
86
+ end
87
+
88
+ @shards_count = @tarantool.shards_count
89
+ @previous_shards_count = @tarantool.previous_shards_count
90
+ @insert_with_shards_count = @tarantool.insert_with_shards_count
91
+ @default_shard = 0
92
+ end
93
+
94
+ def _detect_shards_for_keys(keys, index_no)
95
+ return _detect_shards_for_key(keys, index_no) unless Array === keys
96
+ if index_no == @shard_by_index && (
97
+ @index_fields.size == 1 ||
98
+ keys.all?{|key| Array === key && key.size == @index_fields.size}
99
+ )
100
+ _flat_uniq keys.map{|key| _detect_shard(key)}
101
+ elsif pos = @shard_for_index[index_no]
102
+ _flat_uniq keys.map{|key| _detect_shard([*key].values_at(*pos)) }
103
+ else
104
+ _all_shards
105
+ end
106
+ end
107
+
108
+ def _detect_shards_for_key(key, index_no)
109
+ if index_no == @shard_by_index
110
+ _detect_shard(key)
111
+ elsif pos = @shard_for_index[index_no]
112
+ _detect_shard(key.values_at(*pos))
113
+ else
114
+ _all_shards
115
+ end
116
+ end
117
+
118
+ def _detect_shards(keys)
119
+ _flat_uniq keys.map{|key| _detect_shard(key)}
120
+ end
121
+
122
+ def _detect_shards_for_insert(keys)
123
+ _flat_uniq keys.map{|key| _detect_shard_for_insert(key)}
124
+ end
125
+
126
+ def _flat_uniq(array)
127
+ hsh = {}
128
+ array.each do |v|
129
+ if v.respond_to?(:each)
130
+ v.each{|vv| hsh[vv] = true}
131
+ else
132
+ hsh[v] = true
133
+ end
134
+ end
135
+ hsh.keys
136
+ end
137
+
138
+ def _detect_shard_for_insert(shard_values)
139
+ shard_values = [shard_values] unless Array === shard_values
140
+ @shard_proc.call(shard_values, @insert_with_shards_count, self) || _all_shards
141
+ end
142
+
143
+ def _detect_shard(shard_values)
144
+ shard_values = [shard_values] unless Array === shard_values
145
+ shards = @shard_proc.call(shard_values, @shards_count, self) || _all_shards
146
+ if @previous_shards_count
147
+ prev_shards = @shard_proc.call(shard_values, @previous_shards_count, self) || _all_shards
148
+ shards = [*shards, *prev_shards].uniq
149
+ end
150
+ shards
151
+ end
152
+
153
+ def _get_shard_nums
154
+ @shards_count == 1 ? @default_shard : yield
155
+ end
156
+
157
+ def _all_shards
158
+ (0...@shards_count).to_a
159
+ end
160
+
161
+ def detect_shard(shard_values)
162
+ @shards_count == 1 ? @default_shard : _detect_shard(shard_values)
163
+ end
164
+
165
+ def detect_shard_for_insert(shard_values)
166
+ @shards_count == 1 ? @default_shard : _detect_shard_for_insert(shard_values)
167
+ end
168
+
169
+ def detect_shards(shard_values)
170
+ @shards_count == 1 ? @default_shard : _detect_shards(shard_values)
171
+ end
172
+
173
+ def detect_shards_for_insert(shard_values)
174
+ @shards_count == 1 ? @default_shard : _detect_shards_for_insert(shard_values)
175
+ end
176
+
177
+ def all_shards
178
+ @shards_count == 1 ? @default_shard : (0...@shards_count).to_a
179
+ end
180
+
181
+ def shard(shard_number)
182
+ case shard_number
183
+ when Integer
184
+ if shard_number >= @shards_count
185
+ raise ArgumentError, "There is no shard #{shard_number}, amount of shards is #{@shards_count}"
186
+ end
187
+ when Array
188
+ shard_number.each do|i|
189
+ if i >= @shards_count
190
+ raise ArgumentError, "There is no shard #{i}, amount of shards is #{@shards_count}"
191
+ end
192
+ end
193
+ else
194
+ raise ArgumentError, "Shard number should be integer or array of integers"
195
+ end
196
+ (@_fixed_shards ||= {})[shard_number] ||=
197
+ clone.instance_exec do
198
+ @shards_count = 1
199
+ @default_shard = shard_number
200
+ self
201
+ end
202
+ end
203
+ end
204
+ end
@@ -6,10 +6,11 @@ require 'tarantool/core-ext'
6
6
 
7
7
  module Tarantool
8
8
  class SpaceArray
9
+ include CommonSpace
9
10
  include Request
10
11
  include Util::Array
11
12
 
12
- def initialize(tarantool, space_no, fields, indexes)
13
+ def initialize(tarantool, space_no, fields, indexes, shard_fields = nil, shard_proc = nil)
13
14
  @tarantool = tarantool
14
15
  @space_no = space_no
15
16
 
@@ -24,9 +25,18 @@ module Tarantool
24
25
  @index_fields = indexes
25
26
  @indexes = _map_indexes(indexes)
26
27
  else
27
- @index_fields = nil
28
+ @index_fields = []
28
29
  @indexes = [TYPES_FALLBACK]
29
30
  end
31
+
32
+ shard_fields ||= @index_fields[0]
33
+ unless shard_fields || @tarantool.shards_count == 1
34
+ raise ArgumentError, "You could not use space without specifying primary key or shard fields when shards count is greater than 1"
35
+ else
36
+ @shard_fields = [*shard_fields]
37
+ @shard_positions = @shard_fields
38
+ _init_shard_vars(shard_proc)
39
+ end
30
40
  end
31
41
 
32
42
  def _map_indexes(indexes)
@@ -40,10 +50,6 @@ module Tarantool
40
50
  end.freeze
41
51
  end
42
52
 
43
- def _send_request(type, body, cb)
44
- @tarantool._send_request(type, body, cb)
45
- end
46
-
47
53
  def all_by_pks_cb(keys, cb, opts={})
48
54
  all_by_keys_cb(0, keys, cb, opts)
49
55
  end
@@ -76,7 +82,7 @@ module Tarantool
76
82
 
77
83
  def select_cb(index_no, keys, offset, limit, cb)
78
84
  if Array === index_no
79
- raise ArgumentError, "Has no defined indexes to search index #{index_no}" unless @index_fields
85
+ raise ArgumentError, "Has no defined indexes to search index #{index_no}" if @index_fields.empty?
80
86
  index_fields = index_no
81
87
  index_no = @index_fields.index{|fields| fields.take(index_fields.size) == index_fields}
82
88
  unless index_no || index_fields.size == 1
@@ -87,7 +93,7 @@ module Tarantool
87
93
  end
88
94
  end
89
95
  end
90
- if @index_fields
96
+ unless @index_fields.empty?
91
97
  unless index_types = @indexes[index_no]
92
98
  raise ArgumentError, "No index ##{index_no}"
93
99
  end
@@ -95,27 +101,46 @@ module Tarantool
95
101
  index_types = _detect_types([*[*keys][0]])
96
102
  end
97
103
 
98
- _select(@space_no, index_no, offset, limit, keys, cb, @fields, index_types)
104
+ shard_nums = _get_shard_nums { _detect_shards_for_keys(keys, index_no) }
105
+
106
+ _select(@space_no, index_no, offset, limit, keys, cb, @fields, index_types, shard_nums)
99
107
  end
100
108
 
101
109
  def insert_cb(tuple, cb, opts = {})
102
- _insert(@space_no, BOX_ADD, tuple, @fields, cb, opts[:return_tuple])
110
+ shard_nums = detect_shard_for_insert(tuple.values_at(*@shard_positions))
111
+ _insert(@space_no, BOX_ADD, tuple, @fields, cb, opts[:return_tuple], shard_nums)
103
112
  end
104
113
 
105
114
  def replace_cb(tuple, cb, opts = {})
106
- _insert(@space_no, BOX_REPLACE, tuple, @fields, cb, opts[:return_tuple])
115
+ shard_nums = detect_shard(tuple.values_at(*@shard_positions))
116
+ _insert(@space_no, BOX_REPLACE, tuple, @fields,
117
+ cb, opts[:return_tuple], shard_nums, opts.fetch(:in_any_shard, true))
118
+ end
119
+
120
+ def store_cb(tuple, cb, opts = {})
121
+ shard_nums = _get_shard_nums{
122
+ if opts.fetch(:to_insert_shard, true)
123
+ _detect_shard_for_insert(tuple.values_at(*@shard_positions))
124
+ else
125
+ _detect_shard(tuple.values_at(*@shard_positions))
126
+ end
127
+ }
128
+ _insert(@space_no, 0, tuple, @fields, cb, opts[:return_tuple], shard_nums)
107
129
  end
108
130
 
109
131
  def update_cb(pk, operations, cb, opts = {})
132
+ shard_nums = _get_shard_nums{ _detect_shards_for_key(pk, 0) }
110
133
  _update(@space_no, pk, operations, @fields,
111
134
  @indexes[0] || _detect_types([*pk]),
112
- cb, opts[:return_tuple])
135
+ cb, opts[:return_tuple],
136
+ shard_nums)
113
137
  end
114
138
 
115
139
  def delete_cb(pk, cb, opts = {})
140
+ shard_nums = _get_shard_nums{ _detect_shards_for_key(pk, 0) }
116
141
  _delete(@space_no, pk, @fields,
117
142
  @indexes[0] || _detect_types([*pk]),
118
- cb, opts[:return_tuple])
143
+ cb, opts[:return_tuple], shard_nums)
119
144
  end
120
145
 
121
146
  def invoke_cb(func_name, values, cb, opts = {})