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
@@ -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 = {})