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