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.
- 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
|