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
@@ -1,15 +1,101 @@
1
1
  module Tarantool
2
2
  class BlockDB < DB
3
- def establish_connection
4
- @connection = IProto.get_connection(@host, @port, :block)
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 close_connection
8
- @connection.close
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 _send_request(request_type, body, cb)
12
- cb.call @connection.send_request(request_type, body)
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
@@ -1,12 +1,28 @@
1
- require 'fiber'
1
+ require 'iproto/core-ext'
2
2
 
3
- class Fiber
4
- alias call resume
5
- end
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
- module Kernel
8
- def fiber_yield
9
- Fiber.yield
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
@@ -1,36 +1,205 @@
1
1
  module Tarantool
2
2
  class EMDB < DB
3
- def establish_connection
4
- @connection_waiters = []
5
- EM.schedule do
6
- unless @closed
7
- @connection = IProto.get_connection(@host, @port, :em_callback)
8
- while waiter = @connection_waiters.shift
9
- _send_request(*waiter)
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
- def close_connection
16
- EM.schedule do
17
- if @connection
18
- @connection.close
19
- @connection = nil
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
- unless @connection_waiters.empty?
22
- while waiter = @connection_waiters.shift
23
- waiter.last.call(::IProto::Disconnected)
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 _send_request(request_type, body, cb)
30
- if @connection
31
- @connection.send_request(request_type, body, cb)
32
- else
33
- @connection_waiters << [request_type, body, cb]
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
@@ -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