tarantool-client 0.1.0 → 0.3.0

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -1
  3. data/README.md +96 -1
  4. data/lib/tarantool/client/batch_delete.rb +97 -0
  5. data/lib/tarantool/client/configuration.rb +20 -3
  6. data/lib/tarantool/client/diagnostics.rb +20 -0
  7. data/lib/tarantool/client/errors.rb +8 -1
  8. data/lib/tarantool/client/fork_safety.rb +11 -2
  9. data/lib/tarantool/client/instrumentation/log_instrumenter.rb +35 -0
  10. data/lib/tarantool/client/instrumentation/log_lines.rb +71 -0
  11. data/lib/tarantool/client/instrumentation/request_payload.rb +42 -0
  12. data/lib/tarantool/client/instrumentation.rb +51 -0
  13. data/lib/tarantool/client/instrumented.rb +42 -0
  14. data/lib/tarantool/client/named_requests.rb +67 -0
  15. data/lib/tarantool/client/operations.rb +58 -22
  16. data/lib/tarantool/client/page.rb +18 -0
  17. data/lib/tarantool/client/pool/member.rb +4 -9
  18. data/lib/tarantool/client/pool/member_fork_safety.rb +10 -5
  19. data/lib/tarantool/client/pool/member_role.rb +36 -0
  20. data/lib/tarantool/client/protocol/constants.rb +3 -0
  21. data/lib/tarantool/client/protocol/requests/select.rb +22 -5
  22. data/lib/tarantool/client/protocol/response.rb +1 -0
  23. data/lib/tarantool/client/row.rb +8 -0
  24. data/lib/tarantool/client/schema.rb +4 -3
  25. data/lib/tarantool/client/space_info.rb +2 -0
  26. data/lib/tarantool/client/stream.rb +20 -10
  27. data/lib/tarantool/client/transaction_schema.rb +12 -8
  28. data/lib/tarantool/client/transport/connection.rb +10 -0
  29. data/lib/tarantool/client/transport/link.rb +22 -27
  30. data/lib/tarantool/client/transport/link_draining.rb +35 -0
  31. data/lib/tarantool/client/transport/link_events.rb +48 -0
  32. data/lib/tarantool/client/transport/link_state.rb +39 -5
  33. data/lib/tarantool/client/transport/pending_requests.rb +28 -1
  34. data/lib/tarantool/client/tuple_mapping.rb +65 -0
  35. data/lib/tarantool/client/tuple_shape.rb +25 -0
  36. data/lib/tarantool/client/version.rb +1 -1
  37. data/lib/tarantool/client.rb +25 -48
  38. metadata +17 -2
@@ -5,21 +5,24 @@ module Tarantool
5
5
  module Transport
6
6
  # Keeps one instance connected. Owns the current Connection; once it is lost, a background thread
7
7
  # opens new ones with backoff while requests wait for one to become ready.
8
- # States: connecting -> ready -> reconnecting -> closed.
8
+ # States: connecting -> ready -> draining | reconnecting -> closed.
9
9
  # @api private
10
10
  class Link
11
+ include LinkEvents
12
+ include LinkDraining
13
+
11
14
  attr_writer :event_handler
12
15
 
13
- # on_connect runs on each new connection before requests can use it and may raise to fail the
14
- # attempt; on_ready runs once the connection is current; on_lost runs when a reconnect starts,
15
- # before its first attempt.
16
- def initialize(config, on_connect:, on_ready:, on_lost: nil, backoff: nil)
16
+ # on_connect runs on each new connection before requests can use it and may raise to fail the attempt;
17
+ # on_ready runs once the connection is current; on_lost runs when a reconnect starts, before its first attempt.
18
+ def initialize(config, on_connect:, on_ready:, on_lost: nil, backoff: nil, instrumentation: nil)
17
19
  @config = config
18
20
  @on_connect = on_connect
19
21
  @on_ready = on_ready
20
22
  @on_lost = on_lost
21
23
  @backoff = backoff || Backoff.new(base: config.reconnect_delay, max: config.reconnect_max_delay)
22
24
  @state = LinkState.new(config.address, reconnect: config.reconnect)
25
+ @instrumentation = instrumentation
23
26
  end
24
27
 
25
28
  def connect
@@ -49,14 +52,16 @@ module Tarantool
49
52
  def greeting = @state.connection.greeting
50
53
  def connected? = @state.connected?
51
54
 
52
- # Closing the attempt first fails a handshake or on_connect blocked on it, so the join is short.
55
+ # Closing the attempt first fails a handshake or on_connect blocked on it, so the join is short. Closing
56
+ # the connection wakes the drain thread, so it is joined last.
53
57
  def close
54
- reconnector, attempt = @state.close
58
+ reconnector, attempt, drainer = @state.close
55
59
  attempt&.close
56
60
  begin
57
61
  ThreadJoin.join(reconnector)
58
62
  ensure
59
63
  @state.connection&.close
64
+ ThreadJoin.join(drainer)
60
65
  end
61
66
 
62
67
  nil
@@ -93,25 +98,28 @@ module Tarantool
93
98
  end
94
99
 
95
100
  def lost(connection)
96
- return unless @config.reconnect
101
+ return report_lost(connection) unless @config.reconnect
97
102
 
98
103
  @state.start_reconnect(connection) do |previous|
99
- Thread.new { reconnect(previous) }.tap { |thread| thread.name = "tarantool-reconnect #{@config.address}" }
104
+ Thread.new { reconnect(previous, connection) }.tap do |thread|
105
+ thread.name = "tarantool-reconnect #{@config.address}"
106
+ end
100
107
  end
101
108
  rescue ThreadError => e
102
- @state.give_up(ConnectionError.new("cannot reconnect to #{@config.address}: #{e.message}"))
109
+ fail_reconnect(ConnectionError.new("cannot reconnect to #{@config.address}: #{e.message}"))
103
110
  end
104
111
 
105
112
  # Joining the previous thread first lets close join only the newest one. Nothing may escape: a dead
106
113
  # thread would leave the link reconnecting forever, and its exception would resurface in every join.
107
- def reconnect(previous)
114
+ def reconnect(previous, connection)
108
115
  ThreadJoin.join(previous)
116
+ emit('disconnect.tarantool', reason: connection.failure&.message)
109
117
  run_hook('on_lost') { @on_lost&.call }
110
118
 
111
119
  reconnect_with_backoff
112
120
  rescue StandardError => e
113
- warn(e.full_message)
114
- @state.give_up(ConnectionError.new("reconnect to #{@config.address} failed: #{e.class}: #{e.message}"))
121
+ Diagnostics.warn(@config.logger, e.full_message)
122
+ fail_reconnect(ConnectionError.new("reconnect to #{@config.address} failed: #{e.class}: #{e.message}"))
115
123
  end
116
124
 
117
125
  def reconnect_with_backoff
@@ -119,7 +127,7 @@ module Tarantool
119
127
 
120
128
  while @state.pause(@backoff.delay(attempt))
121
129
  result = attempt_connect
122
- return run_hook('on_ready') { install(result) } if result.is_a?(Connection)
130
+ return reconnected(result, attempt + 1) if result.is_a?(Connection)
123
131
 
124
132
  attempt += 1
125
133
  return give_up(attempt, result) if attempt >= @config.reconnect_attempts
@@ -131,19 +139,6 @@ module Tarantool
131
139
  rescue StandardError => e
132
140
  e
133
141
  end
134
-
135
- # A failing hook is the caller's bug, not a reason to stop reconnecting or to drop the new connection.
136
- def run_hook(name)
137
- yield
138
- rescue StandardError => e
139
- warn("#{name} failed for #{@config.address}: #{e.full_message}")
140
- end
141
-
142
- def give_up(attempts, error)
143
- @state.give_up(
144
- ConnectionError.new("cannot reconnect to #{@config.address} after #{attempts} attempts: #{error.message}")
145
- )
146
- end
147
142
  end
148
143
  end
149
144
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tarantool
4
+ class Client
5
+ module Transport
6
+ # Graceful shutdown of a Link's connection. Includers set @config and @state, and provide #emit.
7
+ # @api private
8
+ module LinkDraining
9
+ # The server announced its shutdown and waits for this connection to close: new requests wait for the
10
+ # next connection, in-flight ones finish, then the connection is closed and its loss handled as usual.
11
+ def drain
12
+ connection = @state.connection
13
+
14
+ @state.start_draining(connection) do
15
+ Thread.new { drain_connection(connection) }.tap do |thread|
16
+ thread.name = "tarantool-drain #{@config.address}"
17
+ end
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ # Without reconnect the drained connection becomes current again so requests fail with its reason.
24
+ def drain_connection(connection)
25
+ emit('shutdown.tarantool', pending: connection.in_flight)
26
+ connection.wait_idle(@config.request_timeout)
27
+ @state.finish_draining(connection) unless @config.reconnect
28
+ connection.shut_down("server #{@config.address} is shutting down")
29
+ rescue StandardError => e
30
+ Diagnostics.warn(@config.logger, "drain failed for #{@config.address}: #{e.full_message}")
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tarantool
4
+ class Client
5
+ module Transport
6
+ # Lifecycle events and hooks of a Link. Includers set @config, @state and @instrumentation, and provide
7
+ # #install.
8
+ # @api private
9
+ module LinkEvents
10
+ private
11
+
12
+ # A failing hook is the caller's bug, not a reason to stop reconnecting or to drop the new connection.
13
+ def run_hook(name)
14
+ yield
15
+ rescue StandardError => e
16
+ Diagnostics.warn(@config.logger, "#{name} failed for #{@config.address}: #{e.full_message}")
17
+ end
18
+
19
+ # Without reconnect the link keeps the lost connection, so the loss of the current one is all to
20
+ # report. The reader thread's disconnect handler and install's own loss check can both reach this
21
+ # for the same connection, so LinkState claims the loss once.
22
+ def report_lost(connection)
23
+ emit('disconnect.tarantool', reason: connection.failure&.message) if @state.claim_loss(connection)
24
+ end
25
+
26
+ # A connection the link was closed under, or that broke during on_ready, is no reconnect to report.
27
+ def reconnected(connection, attempts)
28
+ run_hook('on_ready') { install(connection) }
29
+ emit('reconnect.tarantool', attempts:) unless connection.closed?
30
+ end
31
+
32
+ def give_up(attempts, error)
33
+ message = "cannot reconnect to #{@config.address} after #{attempts} attempts: #{error.message}"
34
+
35
+ fail_reconnect(ConnectionError.new(message), attempts:)
36
+ end
37
+
38
+ def fail_reconnect(error, **payload)
39
+ @state.give_up(error)
40
+ emit('reconnect_failed.tarantool', **payload, **Instrumentation.exception_keys(error))
41
+ end
42
+
43
+ # Link already has a public #notify for WATCH/UNWATCH requests, so events use a different name.
44
+ def emit(name, **payload) = @instrumentation&.notify(name, payload)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -20,6 +20,16 @@ module Tarantool
20
20
  @lock.synchronize { @state == :ready && !@connection.closed? }
21
21
  end
22
22
 
23
+ # Without reconnect the link stays on the lost connection; the first caller reports its loss.
24
+ def claim_loss(connection)
25
+ @lock.synchronize do
26
+ next false unless @connection.equal?(connection) && !@lost.equal?(connection)
27
+
28
+ @lost = connection
29
+ true
30
+ end
31
+ end
32
+
23
33
  # Records the connection being opened so that close can abort it; raises once the link is closed.
24
34
  def attempting(connection)
25
35
  @lock.synchronize do
@@ -42,11 +52,34 @@ module Tarantool
42
52
  end
43
53
  end
44
54
 
45
- # Leaves ready only for the current connection, so a loss is handled once. The block gets the previous
46
- # reconnect thread and returns the new one; the state flips only once that thread exists.
55
+ # The server announced its shutdown: requests wait as while reconnecting, in-flight ones finish on the
56
+ # connection. The block returns the drain thread; only the current connection drains, once.
57
+ def start_draining(connection)
58
+ @lock.synchronize do
59
+ next false unless @state == :ready && @connection.equal?(connection)
60
+
61
+ @drainer = yield
62
+ @state = :draining
63
+ @changed.broadcast
64
+ true
65
+ end
66
+ end
67
+
68
+ # Without reconnect the drained connection stays current, failing requests with the shutdown reason.
69
+ def finish_draining(connection)
70
+ @lock.synchronize do
71
+ next unless @state == :draining && @connection.equal?(connection)
72
+
73
+ @state = :ready
74
+ @changed.broadcast
75
+ end
76
+ end
77
+
78
+ # Leaves ready or draining only for the current connection, so a loss is handled once. The block gets
79
+ # the previous reconnect thread and returns the new one; the state flips only once that thread exists.
47
80
  def start_reconnect(connection)
48
81
  @lock.synchronize do
49
- next unless @state == :ready && @connection.equal?(connection)
82
+ next unless %i[ready draining].include?(@state) && @connection.equal?(connection)
50
83
 
51
84
  @reconnector = yield @reconnector
52
85
  @state = :reconnecting
@@ -54,7 +87,8 @@ module Tarantool
54
87
  end
55
88
  end
56
89
 
57
- # Returns the reconnect thread and the connection it is opening, for the caller to abort and join.
90
+ # Returns the reconnect thread, the connection it is opening and the drain thread, for the caller to
91
+ # abort and join.
58
92
  def close
59
93
  @lock.synchronize do
60
94
  @error = ClientClosedError.new("connection to #{@address} closed")
@@ -62,7 +96,7 @@ module Tarantool
62
96
  @state = :closed
63
97
  @changed.broadcast
64
98
  end
65
- [@reconnector, @attempt]
99
+ [@reconnector, @attempt, @drainer]
66
100
  end
67
101
  end
68
102
 
@@ -8,6 +8,7 @@ module Tarantool
8
8
  class PendingRequests
9
9
  def initialize
10
10
  @lock = Mutex.new
11
+ @emptied = ConditionVariable.new
11
12
  @futures = {}
12
13
  @last_sync = 0
13
14
  @failure = nil
@@ -31,7 +32,32 @@ module Tarantool
31
32
  end
32
33
 
33
34
  def delete(sync)
34
- @lock.synchronize { @futures.delete(sync) }
35
+ @lock.synchronize do
36
+ future = @futures.delete(sync)
37
+ @emptied.broadcast if @futures.empty?
38
+
39
+ future
40
+ end
41
+ end
42
+
43
+ def size
44
+ @lock.synchronize { @futures.size }
45
+ end
46
+
47
+ # True once no request is in flight, false at the deadline.
48
+ def wait_empty(timeout)
49
+ deadline = Deadline.after(timeout)
50
+
51
+ @lock.synchronize do
52
+ until @futures.empty?
53
+ remaining = Deadline.remaining(deadline)
54
+ return false if remaining&.zero?
55
+
56
+ @emptied.wait(@lock, remaining)
57
+ end
58
+
59
+ true
60
+ end
35
61
  end
36
62
 
37
63
  def failure
@@ -43,6 +69,7 @@ module Tarantool
43
69
  first, failure, futures = @lock.synchronize do
44
70
  first = @failure.nil?
45
71
  @failure ||= error
72
+ @emptied.broadcast
46
73
  [first, @failure, @futures.values.tap { @futures.clear }]
47
74
  end
48
75
  futures.each { |future| future.reject(failure) }
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tarantool
4
+ class Client
5
+ # Tuple <-> Hash by the space format, for SpaceInfo. Fields beyond the format are keyed by their 0-based position.
6
+ # @api private
7
+ module TupleMapping
8
+ # A position this far past the format would otherwise let a huge Integer key allocate a giant Array.
9
+ MAX_POSITIONS_BEYOND_FORMAT = 1024
10
+
11
+ def to_hash(tuple)
12
+ hash = fields.each_with_index.to_h { |field, position| [field.name.to_sym, tuple[position]] }
13
+ (fields.size...tuple.size).each { |position| hash[position] = tuple[position] }
14
+
15
+ hash
16
+ end
17
+
18
+ # A missing field goes out as nil; on 3.x the server applies the field default to nil as well.
19
+ def to_tuple(hash)
20
+ positions = fields.each_with_index.to_h { |field, position| [field.name, position] }
21
+ named_positions = {}
22
+
23
+ hash.each_with_object(Array.new(fields.size)) do |(key, value), tuple|
24
+ position = position_of(key, positions)
25
+ check_not_duplicate(key, position, named_positions)
26
+ tuple[position] = value
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def position_of(key, positions)
33
+ case key
34
+ when Symbol, String
35
+ positions.fetch(key.to_s) { raise UnknownFieldError, "space #{name} has no field '#{key}'" }
36
+ when Integer then position_beyond_format(key)
37
+ else raise ArgumentError, "unsupported key #{key.inspect} for space #{name}"
38
+ end
39
+ end
40
+
41
+ # `:a` and 'a' name the same field; the later one would otherwise win silently.
42
+ def check_not_duplicate(key, position, named_positions)
43
+ return unless key.is_a?(Symbol) || key.is_a?(String)
44
+ raise ArgumentError, "space #{name} field '#{fields[position].name}' given twice" if named_positions[position]
45
+
46
+ named_positions[position] = true
47
+ end
48
+
49
+ def position_beyond_format(position)
50
+ raise ArgumentError, "position #{position} is negative for space #{name}" if position.negative?
51
+
52
+ if position < fields.size
53
+ raise ArgumentError, "position #{position} is inside the format of space #{name}; use the field name"
54
+ end
55
+
56
+ if position >= fields.size + MAX_POSITIONS_BEYOND_FORMAT
57
+ raise ArgumentError, "position #{position} is more than #{MAX_POSITIONS_BEYOND_FORMAT} past the format " \
58
+ "of space #{name}"
59
+ end
60
+
61
+ position
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tarantool
4
+ class Client
5
+ # What data operations return for each tuple, chosen with `as:`.
6
+ # @api private
7
+ module TupleShape
8
+ SHAPES = %i[tuple hash row].freeze
9
+
10
+ def self.check(shape)
11
+ return if SHAPES.include?(shape)
12
+
13
+ raise ArgumentError, "unknown as: #{shape.inspect}, expected one of #{SHAPES.join(', ')}"
14
+ end
15
+
16
+ def self.apply(shape, tuple, space_info)
17
+ case shape
18
+ when :hash then space_info.to_hash(tuple)
19
+ when :row then Row.new(tuple:, attributes: space_info.to_hash(tuple))
20
+ else tuple
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Tarantool
4
4
  class Client
5
- VERSION = '0.1.0'
5
+ VERSION = '0.3.0'
6
6
  end
7
7
  end
@@ -8,6 +8,7 @@ require_relative 'client/errors'
8
8
  require_relative 'client/configuration'
9
9
  require_relative 'client/backoff'
10
10
  require_relative 'client/thread_join'
11
+ require_relative 'client/diagnostics'
11
12
  require_relative 'client/protocol/constants'
12
13
  require_relative 'client/box_error'
13
14
  require_relative 'client/uuid'
@@ -40,6 +41,12 @@ require_relative 'client/protocol/requests/auth'
40
41
  require_relative 'client/protocol/requests/id'
41
42
  require_relative 'client/protocol/requests/watch'
42
43
  require_relative 'client/protocol/requests/unwatch'
44
+ require_relative 'client/instrumentation'
45
+ require_relative 'client/instrumentation/request_payload'
46
+ require_relative 'client/instrumentation/log_lines'
47
+ require_relative 'client/instrumentation/log_instrumenter'
48
+ require_relative 'client/instrumented'
49
+ require_relative 'client/named_requests'
43
50
  require_relative 'client/transport/future'
44
51
  require_relative 'client/transport/pending_requests'
45
52
  require_relative 'client/transport/stream_ids'
@@ -50,14 +57,21 @@ require_relative 'client/transport/frame_writer'
50
57
  require_relative 'client/transport/handshake'
51
58
  require_relative 'client/transport/connection'
52
59
  require_relative 'client/transport/link_state'
60
+ require_relative 'client/transport/link_events'
61
+ require_relative 'client/transport/link_draining'
53
62
  require_relative 'client/transport/link'
54
63
  require_relative 'client/index_info'
64
+ require_relative 'client/tuple_mapping'
55
65
  require_relative 'client/space_info'
56
66
  require_relative 'client/schema'
57
67
  require_relative 'client/schema_cache'
58
68
  require_relative 'client/sql_result'
59
69
  require_relative 'client/prepared_statement'
70
+ require_relative 'client/batch_delete'
60
71
  require_relative 'client/transaction_schema'
72
+ require_relative 'client/row'
73
+ require_relative 'client/page'
74
+ require_relative 'client/tuple_shape'
61
75
  require_relative 'client/operations'
62
76
  require_relative 'client/transaction_scope'
63
77
  require_relative 'client/stream'
@@ -68,6 +82,7 @@ require_relative 'client/watchers/dispatcher'
68
82
  require_relative 'client/fork_safety'
69
83
  require_relative 'client/pool'
70
84
  require_relative 'client/pool/member_fork_safety'
85
+ require_relative 'client/pool/member_role'
71
86
  require_relative 'client/pool/member'
72
87
 
73
88
  module Tarantool
@@ -75,6 +90,8 @@ module Tarantool
75
90
  class Client
76
91
  include Operations
77
92
  include ForkSafety
93
+ include Instrumented
94
+ include NamedRequests
78
95
 
79
96
  attr_reader :config
80
97
 
@@ -85,6 +102,7 @@ module Tarantool
85
102
 
86
103
  def initialize(uri = nil, **)
87
104
  @config = Configuration.build(uri, **)
105
+ @instrumentation = Instrumentation.build(@config)
88
106
  @pid = Process.pid
89
107
  @reinit_lock = Mutex.new
90
108
  open_link
@@ -107,7 +125,7 @@ module Tarantool
107
125
  TransactionScope.enter(self) do
108
126
  stream = Stream.new(
109
127
  connection: @link.connection, schema_cache: @schema_cache, id: @stream_ids.next,
110
- request_timeout: @config.request_timeout
128
+ request_timeout: @config.request_timeout, instrumentation: @instrumentation
111
129
  )
112
130
  stream.transaction(isolation:, timeout:, &block)
113
131
  end
@@ -139,7 +157,7 @@ module Tarantool
139
157
 
140
158
  def link_hooks = { on_connect: method(:load_schema), on_ready: method(:resubscribe), on_lost: method(:lost) }
141
159
 
142
- def perform(request, timeout: nil, schema_version: nil, on_push: nil)
160
+ def transmit(request, timeout: nil, schema_version: nil, on_push: nil)
143
161
  ensure_fork_safe
144
162
  @link.perform(request, timeout: timeout || @config.request_timeout, schema_version:, on_push:)
145
163
  end
@@ -164,53 +182,12 @@ module Tarantool
164
182
 
165
183
  # Name-resolved requests carry the schema version they were resolved against, so the
166
184
  # server rejects them (ER_WRONG_SCHEMA_VERSION) instead of running them on a stale id.
167
- def perform_on_space(space, index, timeout: nil, &)
168
- return perform(yield(space, index), timeout:) if space.is_a?(Integer) && index.is_a?(Integer)
185
+ # A request that needs the space format is resolved the same way even by ids, so the
186
+ # format and the version it carries come from one schema.
187
+ def transmit_on_space(space, index, timeout: nil, format: false, payload: nil, &)
188
+ return transmit(yield(space, index), timeout:) if !format && space.is_a?(Integer) && index.is_a?(Integer)
169
189
 
170
- perform_by_name(space, index, timeout || @config.request_timeout, &)
171
- end
172
-
173
- # Every schema fetch and attempt draws on one deadline, so the whole call stays within timeout.
174
- # Waiting for a connection first means the cache already holds its schema or a newer one.
175
- def perform_by_name(space, index, timeout, &)
176
- ensure_fork_safe
177
- deadline = Transport::Deadline.after(timeout)
178
- @link.connection(deadline)
179
-
180
- perform_with_schema_check(space, index, deadline, &)
181
- rescue TimeoutError
182
- raise TimeoutError, "no response within #{timeout}s"
183
- end
184
-
185
- def lookup_space(space, timeout)
186
- ensure_fork_safe
187
- timeout ||= @config.request_timeout
188
- deadline = Transport::Deadline.after(timeout)
189
- @link.connection(deadline)
190
-
191
- @schema_cache.space_info(space, deadline)
192
- rescue TimeoutError
193
- raise TimeoutError, "no response within #{timeout}s"
194
- end
195
-
196
- def perform_with_schema_check(space, index, deadline)
197
- schema, ids = @schema_cache.resolve(space, index, deadline)
198
-
199
- perform_resolved(schema, yield(*ids), deadline)
200
- rescue ServerError => e
201
- raise unless e.code == Protocol::Constants::ER_WRONG_SCHEMA_VERSION
202
-
203
- fresh = @schema_cache.reload(schema, timeout: Transport::Deadline.remaining(deadline))
204
- schema, ids = @schema_cache.resolve(space, index, deadline, fresh)
205
- perform_resolved(schema, yield(*ids), deadline)
206
- end
207
-
208
- # Only the connection the schema came from: once it is lost, another instance may reuse the
209
- # schema version with other ids, so the request fails with it rather than wait for a reconnect.
210
- def perform_resolved(schema, request, deadline)
211
- timeout = Transport::Deadline.remaining(deadline)
212
-
213
- schema.connection.perform(request, timeout:, schema_version: schema.version)
190
+ perform_by_name(space, index, timeout || @config.request_timeout, payload, &)
214
191
  end
215
192
  end
216
193
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tarantool-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Starlexxx
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-10-16 00:00:00.000000000 Z
11
+ date: 2026-10-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bigdecimal
@@ -52,17 +52,27 @@ files:
52
52
  - README.md
53
53
  - lib/tarantool/client.rb
54
54
  - lib/tarantool/client/backoff.rb
55
+ - lib/tarantool/client/batch_delete.rb
55
56
  - lib/tarantool/client/box_error.rb
56
57
  - lib/tarantool/client/callback_guard.rb
57
58
  - lib/tarantool/client/configuration.rb
59
+ - lib/tarantool/client/diagnostics.rb
58
60
  - lib/tarantool/client/errors.rb
59
61
  - lib/tarantool/client/fork_safety.rb
60
62
  - lib/tarantool/client/index_info.rb
63
+ - lib/tarantool/client/instrumentation.rb
64
+ - lib/tarantool/client/instrumentation/log_instrumenter.rb
65
+ - lib/tarantool/client/instrumentation/log_lines.rb
66
+ - lib/tarantool/client/instrumentation/request_payload.rb
67
+ - lib/tarantool/client/instrumented.rb
61
68
  - lib/tarantool/client/interval.rb
69
+ - lib/tarantool/client/named_requests.rb
62
70
  - lib/tarantool/client/operations.rb
71
+ - lib/tarantool/client/page.rb
63
72
  - lib/tarantool/client/pool.rb
64
73
  - lib/tarantool/client/pool/member.rb
65
74
  - lib/tarantool/client/pool/member_fork_safety.rb
75
+ - lib/tarantool/client/pool/member_role.rb
66
76
  - lib/tarantool/client/prepared_statement.rb
67
77
  - lib/tarantool/client/protocol/auth/chap_sha1.rb
68
78
  - lib/tarantool/client/protocol/codec.rb
@@ -93,6 +103,7 @@ files:
93
103
  - lib/tarantool/client/protocol/requests/upsert.rb
94
104
  - lib/tarantool/client/protocol/requests/watch.rb
95
105
  - lib/tarantool/client/protocol/response.rb
106
+ - lib/tarantool/client/row.rb
96
107
  - lib/tarantool/client/schema.rb
97
108
  - lib/tarantool/client/schema_cache.rb
98
109
  - lib/tarantool/client/space_info.rb
@@ -107,11 +118,15 @@ files:
107
118
  - lib/tarantool/client/transport/future.rb
108
119
  - lib/tarantool/client/transport/handshake.rb
109
120
  - lib/tarantool/client/transport/link.rb
121
+ - lib/tarantool/client/transport/link_draining.rb
122
+ - lib/tarantool/client/transport/link_events.rb
110
123
  - lib/tarantool/client/transport/link_state.rb
111
124
  - lib/tarantool/client/transport/pending_requests.rb
112
125
  - lib/tarantool/client/transport/stream_ids.rb
113
126
  - lib/tarantool/client/transport/timed_lock.rb
114
127
  - lib/tarantool/client/transport/timed_read.rb
128
+ - lib/tarantool/client/tuple_mapping.rb
129
+ - lib/tarantool/client/tuple_shape.rb
115
130
  - lib/tarantool/client/uuid.rb
116
131
  - lib/tarantool/client/version.rb
117
132
  - lib/tarantool/client/watcher.rb