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.
- data/Gemfile +2 -3
- data/README.md +90 -30
- data/Rakefile +6 -1
- data/lib/tarantool.rb +93 -18
- data/lib/tarantool/base_record.rb +97 -10
- data/lib/tarantool/block_db.rb +104 -6
- data/lib/tarantool/callback_db.rb +7 -0
- data/lib/tarantool/core-ext.rb +24 -8
- data/lib/tarantool/em_db.rb +189 -20
- data/lib/tarantool/exceptions.rb +4 -0
- data/lib/tarantool/fiber_db.rb +15 -1
- data/lib/tarantool/light_record.rb +17 -0
- data/lib/tarantool/query.rb +15 -9
- data/lib/tarantool/record/select.rb +21 -3
- data/lib/tarantool/request.rb +130 -43
- data/lib/tarantool/response.rb +70 -7
- data/lib/tarantool/serializers.rb +26 -5
- data/lib/tarantool/serializers/ber_array.rb +14 -0
- data/lib/tarantool/shards_support.rb +204 -0
- data/lib/tarantool/space_array.rb +38 -13
- data/lib/tarantool/space_hash.rb +49 -27
- data/lib/tarantool/util.rb +96 -10
- data/lib/tarantool/version.rb +2 -1
- data/test/helper.rb +154 -4
- data/test/{tarant/init.lua → init.lua} +0 -0
- data/test/run_all.rb +2 -2
- data/test/shared_record.rb +59 -0
- data/test/shared_replicated_shard.rb +1018 -0
- data/test/shared_reshard.rb +380 -0
- data/test/tarantool.cfg +2 -0
- data/test/test_light_record.rb +2 -0
- data/test/test_light_record_callback.rb +92 -0
- data/test/test_query_block.rb +1 -0
- data/test/test_query_fiber.rb +1 -0
- data/test/test_reshard_block.rb +7 -0
- data/test/test_reshard_fiber.rb +11 -0
- data/test/test_shard_replication_block.rb +7 -0
- data/test/test_shard_replication_fiber.rb +11 -0
- data/test/test_space_array_block.rb +1 -0
- data/test/test_space_array_callback.rb +50 -121
- data/test/test_space_array_callback_nodef.rb +39 -96
- data/test/test_space_array_fiber.rb +1 -0
- data/test/test_space_hash_block.rb +1 -0
- data/test/test_space_hash_fiber.rb +1 -0
- metadata +54 -17
- data/lib/tarantool/record.rb +0 -164
- data/test/box.pid +0 -1
- data/test/tarantool.log +0 -6
- data/test/tarantool_repl.cfg +0 -53
- data/test/test_record.rb +0 -88
- data/test/test_record_composite_pk.rb +0 -77
data/lib/tarantool/block_db.rb
CHANGED
@@ -1,15 +1,101 @@
|
|
1
1
|
module Tarantool
|
2
2
|
class BlockDB < DB
|
3
|
-
|
4
|
-
|
3
|
+
IPROTO_CONNECTION_TYPE = :block
|
4
|
+
|
5
|
+
include ParseIProto
|
6
|
+
def _send_request(shard_numbers, read_write, response)
|
7
|
+
if @closed
|
8
|
+
response.cb.call ::IProto::Disconnected.new("Tarantool is closed")
|
9
|
+
else
|
10
|
+
response.call_callback begin
|
11
|
+
shard_numbers = shard_numbers[0] if Array === shard_numbers && shard_numbers.size == 1
|
12
|
+
if Array === shard_numbers
|
13
|
+
_send_to_several_shards(shard_numbers, read_write, response)
|
14
|
+
else
|
15
|
+
_send_to_one_shard(shard_numbers, read_write, response)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def _send_to_one_shard(shard_number, read_write, response)
|
22
|
+
response.parse_response(
|
23
|
+
if (replicas = _shard(shard_number)).size == 1
|
24
|
+
_parse_iproto(replicas[0].send_request(response.request_type, response.body))
|
25
|
+
elsif read_write == :read
|
26
|
+
replicas = replicas.shuffle if @replica_strategy == :round_robin
|
27
|
+
_one_shard_read(replicas, response.request_type, response.body)
|
28
|
+
else
|
29
|
+
_one_shard_write(replicas, response.request_type, response.body)
|
30
|
+
end
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def _one_shard_read(replicas, request_type, body)
|
35
|
+
for conn in replicas
|
36
|
+
if conn.could_be_connected?
|
37
|
+
begin
|
38
|
+
res = _parse_iproto(conn.send_request(request_type, body))
|
39
|
+
raise res if Exception === res
|
40
|
+
return res
|
41
|
+
rescue ::IProto::ConnectionError
|
42
|
+
# pass
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
raise ConnectionError, "no available connections"
|
47
|
+
end
|
48
|
+
|
49
|
+
def _one_shard_write(replicas, request_type, body)
|
50
|
+
i = replicas.size
|
51
|
+
while i > 0
|
52
|
+
conn = replicas[0]
|
53
|
+
if conn.could_be_connected?
|
54
|
+
begin
|
55
|
+
res = _parse_iproto(conn.send_request(request_type, body))
|
56
|
+
raise res if Exception === res
|
57
|
+
return res
|
58
|
+
rescue ::IProto::ConnectionError, ::Tarantool::NonMaster
|
59
|
+
# pass
|
60
|
+
end
|
61
|
+
end
|
62
|
+
replicas.rotate!
|
63
|
+
i -= 1
|
64
|
+
end
|
65
|
+
raise NoMasterError, "no available master connections"
|
5
66
|
end
|
6
67
|
|
7
|
-
def
|
8
|
-
|
68
|
+
def _send_to_several_shards(shard_numbers, read_write, response)
|
69
|
+
results = []
|
70
|
+
unless read_write == :replace
|
71
|
+
for shard in shard_numbers
|
72
|
+
res = _send_to_one_shard(shard, read_write, response)
|
73
|
+
Array === res ? results.concat(res) : results << res
|
74
|
+
end
|
75
|
+
else
|
76
|
+
for shard in shard_numbers
|
77
|
+
begin
|
78
|
+
res = _send_to_one_shard(shard, read_write, response)
|
79
|
+
Array === res ? results.concat(res) : results << res
|
80
|
+
rescue ::Tarantool::TupleDoesntExists => e
|
81
|
+
results << e
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
if results.all?{|r| ::Tarantool::TupleDoesntExists === r}
|
86
|
+
raise results.first
|
87
|
+
else
|
88
|
+
results.delete_if{|r| ::Tarantool::TupleDoesntExists === r}
|
89
|
+
end
|
90
|
+
end
|
91
|
+
if Integer === results.first
|
92
|
+
results = results.inject(0){|s, i| s + i}
|
93
|
+
end
|
94
|
+
results
|
9
95
|
end
|
10
96
|
|
11
|
-
def
|
12
|
-
|
97
|
+
def primary_interface
|
98
|
+
:synchronous
|
13
99
|
end
|
14
100
|
|
15
101
|
module CommonSpaceBlockingMethods
|
@@ -33,6 +119,10 @@ module Tarantool
|
|
33
119
|
replace_cb(tuple, _block_cb, opts)
|
34
120
|
end
|
35
121
|
|
122
|
+
def store(tuple, opts={})
|
123
|
+
store_cb(tuple, _block_cb, opts)
|
124
|
+
end
|
125
|
+
|
36
126
|
def update(pk, operations, opts={})
|
37
127
|
update_cb(pk, operations, _block_cb, opts)
|
38
128
|
end
|
@@ -91,6 +181,10 @@ module Tarantool
|
|
91
181
|
end
|
92
182
|
|
93
183
|
class Query < ::Tarantool::Query
|
184
|
+
def _block_cb
|
185
|
+
@_block_cb ||= method(:_raise_or_return)
|
186
|
+
end
|
187
|
+
|
94
188
|
def select(space_no, index_no, keys, offset, limit, opts={})
|
95
189
|
select_cb(space_no, index_no, keys, offset, limit, _block_cb, opts)
|
96
190
|
end
|
@@ -111,6 +205,10 @@ module Tarantool
|
|
111
205
|
replace_cb(space_no, tuple, _block_cb, opts)
|
112
206
|
end
|
113
207
|
|
208
|
+
def store(space_no, tuple, opts={})
|
209
|
+
store_cb(space_no, tuple, _block_cb, opts)
|
210
|
+
end
|
211
|
+
|
114
212
|
def update(space_no, pk, operation, opts={})
|
115
213
|
update_cb(space_no, pk, operation, _block_cb, opts)
|
116
214
|
end
|
@@ -2,6 +2,10 @@ require 'tarantool/em_db'
|
|
2
2
|
|
3
3
|
module Tarantool
|
4
4
|
class CallbackDB < EMDB
|
5
|
+
def primary_interface
|
6
|
+
:callback
|
7
|
+
end
|
8
|
+
|
5
9
|
class SpaceArray < ::Tarantool::SpaceArray
|
6
10
|
alias by_pk by_pk_blk
|
7
11
|
alias all_by_key all_by_key_blk
|
@@ -10,6 +14,7 @@ module Tarantool
|
|
10
14
|
alias select select_blk
|
11
15
|
alias insert insert_blk
|
12
16
|
alias replace replace_blk
|
17
|
+
alias store store_blk
|
13
18
|
alias update update_blk
|
14
19
|
alias delete delete_blk
|
15
20
|
alias invoke invoke_blk
|
@@ -24,6 +29,7 @@ module Tarantool
|
|
24
29
|
alias select select_blk
|
25
30
|
alias insert insert_blk
|
26
31
|
alias replace replace_blk
|
32
|
+
alias store store_blk
|
27
33
|
alias update update_blk
|
28
34
|
alias delete delete_blk
|
29
35
|
alias invoke invoke_blk
|
@@ -37,6 +43,7 @@ module Tarantool
|
|
37
43
|
alias first first_blk
|
38
44
|
alias insert insert_blk
|
39
45
|
alias replace replace_blk
|
46
|
+
alias store store_blk
|
40
47
|
alias update update_blk
|
41
48
|
alias delete delete_blk
|
42
49
|
alias invoke invoke_blk
|
data/lib/tarantool/core-ext.rb
CHANGED
@@ -1,12 +1,28 @@
|
|
1
|
-
require '
|
1
|
+
require 'iproto/core-ext'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
module Tarantool
|
4
|
+
module ClassAttribute
|
5
|
+
# spinoff from ActiveSupport class attribute
|
6
|
+
def t_class_attribute(*attrs)
|
7
|
+
attrs.each do |name|
|
8
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
9
|
+
def self.#{name}() nil end
|
10
|
+
def self.#{name}?() !!#{name} end
|
6
11
|
|
7
|
-
|
8
|
-
|
9
|
-
|
12
|
+
def self.#{name}=(val)
|
13
|
+
singleton_class.class_eval do
|
14
|
+
begin
|
15
|
+
if method_defined?(:"#{name}") || private_method_defined?(:"#{name}")
|
16
|
+
remove_method(:#{name})
|
17
|
+
end
|
18
|
+
rescue NameError
|
19
|
+
end
|
20
|
+
define_method(:#{name}) { val }
|
21
|
+
end
|
22
|
+
val
|
23
|
+
end
|
24
|
+
RUBY
|
25
|
+
end
|
26
|
+
end
|
10
27
|
end
|
11
|
-
alias fyield fiber_yield
|
12
28
|
end
|
data/lib/tarantool/em_db.rb
CHANGED
@@ -1,36 +1,205 @@
|
|
1
1
|
module Tarantool
|
2
2
|
class EMDB < DB
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
IPROTO_CONNECTION_TYPE = :em_callback
|
4
|
+
INITIAL = Object.new.freeze
|
5
|
+
|
6
|
+
class Curry1 < Struct.new(:obj, :arg)
|
7
|
+
def call
|
8
|
+
obj.call arg
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class FeedResponse < Struct.new(:response)
|
13
|
+
def call(result)
|
14
|
+
if Exception === result
|
15
|
+
response.cb.call result
|
16
|
+
else
|
17
|
+
response.call_callback(result)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def _send_request(shard_numbers, read_write, response)
|
23
|
+
if @closed
|
24
|
+
exc = ::IProto::Disconnected.new("Tarantool is closed")
|
25
|
+
if EM.reactor_running?
|
26
|
+
EM.next_tick Curry1.new(response.cb, exc)
|
27
|
+
else
|
28
|
+
response.cb.call exc
|
29
|
+
end
|
30
|
+
else
|
31
|
+
feed = FeedResponse.new(response)
|
32
|
+
shard_numbers = shard_numbers[0] if Array === shard_numbers && shard_numbers.size == 1
|
33
|
+
if Array === shard_numbers
|
34
|
+
_send_to_several_shards(shard_numbers, read_write, response, feed)
|
35
|
+
else
|
36
|
+
_send_to_one_shard(shard_numbers, read_write, response, feed)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def _send_to_one_shard(shard_number, read_write, response, feed)
|
42
|
+
if (replicas = _shard(shard_number)).size == 1
|
43
|
+
replicas[0].send_request(response.request_type, response.body, OneReplica.new(response, feed))
|
44
|
+
elsif read_write == :read
|
45
|
+
replicas = replicas.shuffle if @replica_strategy == :round_robin
|
46
|
+
EM.next_tick OneShardRead.new(replicas, response, feed)
|
47
|
+
else
|
48
|
+
EM.next_tick OneShardWrite.new(replicas, response, feed)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class OneReplica
|
53
|
+
include ParseIProto
|
54
|
+
def initialize(response, feed)
|
55
|
+
@response = response
|
56
|
+
@feed = feed
|
57
|
+
end
|
58
|
+
|
59
|
+
def call(result)
|
60
|
+
if Exception === (result = _parse_iproto(result))
|
61
|
+
@feed.call result
|
62
|
+
else
|
63
|
+
@feed.call @response.parse_response(result)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class OneShardRead
|
69
|
+
include ParseIProto
|
70
|
+
def initialize(replicas, response, feed)
|
71
|
+
@replicas = replicas
|
72
|
+
@i = -1
|
73
|
+
@response = response
|
74
|
+
@feed = feed
|
75
|
+
end
|
76
|
+
|
77
|
+
def call(result=INITIAL)
|
78
|
+
result = _parse_iproto(result) unless result == INITIAL
|
79
|
+
case result
|
80
|
+
when INITIAL, ::IProto::ConnectionError
|
81
|
+
begin
|
82
|
+
if (@i += 1) >= @replicas.size
|
83
|
+
EM.next_tick Curry1.new(@feed, ConnectionError.new("no available connections"))
|
84
|
+
return
|
85
|
+
end
|
86
|
+
end until (repl = @replicas[@i]).could_be_connected?
|
87
|
+
repl.send_request(@response.request_type, @response.body, self)
|
88
|
+
when Exception
|
89
|
+
@feed.call result
|
90
|
+
else
|
91
|
+
@feed.call @response.parse_response(result)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class OneShardWrite
|
97
|
+
include ParseIProto
|
98
|
+
def initialize(replicas, response, feed)
|
99
|
+
@replicas_origin = replicas
|
100
|
+
@replicas = replicas.dup
|
101
|
+
@i = replicas.size
|
102
|
+
@response = response
|
103
|
+
@feed = feed
|
104
|
+
end
|
105
|
+
|
106
|
+
def rotate!
|
107
|
+
if @i > 0
|
108
|
+
@i -= 1
|
109
|
+
@replicas.rotate!
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def call(result=INITIAL)
|
114
|
+
result = _parse_iproto(result) unless result == INITIAL
|
115
|
+
case result
|
116
|
+
when INITIAL, ::IProto::ConnectionError, ::Tarantool::NonMaster
|
117
|
+
rotate! if Exception === result
|
118
|
+
rotate! until @i <= 0 || (repl = @replicas[0]).could_be_connected?
|
119
|
+
if @i <= 0
|
120
|
+
EM.next_tick Curry1.new(@feed, NoMasterError.new("no available master connections"))
|
121
|
+
return
|
10
122
|
end
|
123
|
+
repl.send_request(@response.request_type, @response.body, self)
|
124
|
+
when Exception
|
125
|
+
@feed.call result
|
126
|
+
else
|
127
|
+
@replicas_origin.replace @replicas
|
128
|
+
@feed.call @response.parse_response(result)
|
11
129
|
end
|
12
130
|
end
|
13
131
|
end
|
14
132
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
133
|
+
class Concatter
|
134
|
+
def initialize(count, feed)
|
135
|
+
@result = []
|
136
|
+
@count = count
|
137
|
+
@feed = feed
|
138
|
+
end
|
139
|
+
def call(array)
|
140
|
+
if @count > 0
|
141
|
+
case array
|
142
|
+
when Array
|
143
|
+
@result.concat array
|
144
|
+
when Exception
|
145
|
+
@result = array
|
146
|
+
@count = 1
|
147
|
+
else
|
148
|
+
@result << array
|
149
|
+
end
|
150
|
+
if (@count -= 1) == 0
|
151
|
+
if Array === @result && Integer === @result.first
|
152
|
+
@feed.call @result.inject(0){|s, i| s + i}
|
153
|
+
else
|
154
|
+
@feed.call @result
|
155
|
+
end
|
156
|
+
end
|
20
157
|
end
|
21
|
-
|
22
|
-
|
23
|
-
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
class ConcatterReplace
|
162
|
+
def initialize(count, feed)
|
163
|
+
@result = []
|
164
|
+
@count = count
|
165
|
+
@feed = feed
|
166
|
+
end
|
167
|
+
def call(array)
|
168
|
+
if @count > 0
|
169
|
+
case array
|
170
|
+
when Array
|
171
|
+
@result.concat array
|
172
|
+
when ::Tarantool::TupleDoesntExists
|
173
|
+
@result << array
|
174
|
+
when Exception
|
175
|
+
@result = array
|
176
|
+
@count = 1
|
177
|
+
else
|
178
|
+
@result << array
|
179
|
+
end
|
180
|
+
if (@count -= 1) == 0
|
181
|
+
if Exception === @result
|
182
|
+
@feed.call @result
|
183
|
+
elsif @result.all?{|r| ::Tarantool::TupleDoesntExists === r}
|
184
|
+
@feed.call @result.first
|
185
|
+
else
|
186
|
+
@result.delete_if{|r| ::Tarantool::TupleDoesntExists === r}
|
187
|
+
if Integer === @result.first
|
188
|
+
@feed.call @result.inject(0){|s, i| s + i}
|
189
|
+
else
|
190
|
+
@feed.call @result
|
191
|
+
end
|
192
|
+
end
|
24
193
|
end
|
25
194
|
end
|
26
195
|
end
|
27
196
|
end
|
28
197
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
198
|
+
def _send_to_several_shards(shard_numbers, read_write, response, feed)
|
199
|
+
concat = read_write != :replace ? Concatter.new(shard_numbers.size, feed) :
|
200
|
+
ConcatterReplace.new(shard_numbers.size, feed)
|
201
|
+
for shard in shard_numbers
|
202
|
+
_send_to_one_shard(shard, read_write, response, concat)
|
34
203
|
end
|
35
204
|
end
|
36
205
|
end
|
data/lib/tarantool/exceptions.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
module Tarantool
|
2
|
+
class ConnectionError < ::IProto::Disconnected; end
|
3
|
+
class NoMasterError < ConnectionError; end
|
4
|
+
|
2
5
|
class ArgumentError < ::ArgumentError; end
|
3
6
|
class StringTooLong < ArgumentError; end
|
7
|
+
class IntegerFieldOverflow < ArgumentError; end
|
4
8
|
|
5
9
|
class TarantoolError < StandardError; end
|
6
10
|
class ValueError < TarantoolError; end
|