redis-client 0.2.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/Gemfile +1 -2
  4. data/Gemfile.lock +2 -3
  5. data/README.md +66 -3
  6. data/Rakefile +43 -23
  7. data/lib/redis_client/command_builder.rb +83 -0
  8. data/lib/redis_client/config.rb +9 -48
  9. data/lib/redis_client/connection_mixin.rb +38 -0
  10. data/lib/redis_client/decorator.rb +84 -0
  11. data/lib/redis_client/pooled.rb +38 -30
  12. data/lib/redis_client/ruby_connection/buffered_io.rb +153 -0
  13. data/lib/redis_client/{resp3.rb → ruby_connection/resp3.rb} +0 -26
  14. data/lib/redis_client/{connection.rb → ruby_connection.rb} +26 -31
  15. data/lib/redis_client/version.rb +1 -1
  16. data/lib/redis_client.rb +162 -36
  17. data/redis-client.gemspec +2 -4
  18. metadata +12 -59
  19. data/.rubocop.yml +0 -190
  20. data/ext/redis_client/hiredis/export.clang +0 -2
  21. data/ext/redis_client/hiredis/export.gcc +0 -7
  22. data/ext/redis_client/hiredis/extconf.rb +0 -62
  23. data/ext/redis_client/hiredis/hiredis_connection.c +0 -708
  24. data/ext/redis_client/hiredis/vendor/.gitignore +0 -9
  25. data/ext/redis_client/hiredis/vendor/.travis.yml +0 -131
  26. data/ext/redis_client/hiredis/vendor/CHANGELOG.md +0 -364
  27. data/ext/redis_client/hiredis/vendor/CMakeLists.txt +0 -165
  28. data/ext/redis_client/hiredis/vendor/COPYING +0 -29
  29. data/ext/redis_client/hiredis/vendor/Makefile +0 -308
  30. data/ext/redis_client/hiredis/vendor/README.md +0 -664
  31. data/ext/redis_client/hiredis/vendor/adapters/ae.h +0 -130
  32. data/ext/redis_client/hiredis/vendor/adapters/glib.h +0 -156
  33. data/ext/redis_client/hiredis/vendor/adapters/ivykis.h +0 -84
  34. data/ext/redis_client/hiredis/vendor/adapters/libev.h +0 -179
  35. data/ext/redis_client/hiredis/vendor/adapters/libevent.h +0 -175
  36. data/ext/redis_client/hiredis/vendor/adapters/libuv.h +0 -117
  37. data/ext/redis_client/hiredis/vendor/adapters/macosx.h +0 -115
  38. data/ext/redis_client/hiredis/vendor/adapters/qt.h +0 -135
  39. data/ext/redis_client/hiredis/vendor/alloc.c +0 -86
  40. data/ext/redis_client/hiredis/vendor/alloc.h +0 -91
  41. data/ext/redis_client/hiredis/vendor/appveyor.yml +0 -24
  42. data/ext/redis_client/hiredis/vendor/async.c +0 -887
  43. data/ext/redis_client/hiredis/vendor/async.h +0 -147
  44. data/ext/redis_client/hiredis/vendor/async_private.h +0 -75
  45. data/ext/redis_client/hiredis/vendor/dict.c +0 -352
  46. data/ext/redis_client/hiredis/vendor/dict.h +0 -126
  47. data/ext/redis_client/hiredis/vendor/fmacros.h +0 -12
  48. data/ext/redis_client/hiredis/vendor/hiredis-config.cmake.in +0 -13
  49. data/ext/redis_client/hiredis/vendor/hiredis.c +0 -1174
  50. data/ext/redis_client/hiredis/vendor/hiredis.h +0 -336
  51. data/ext/redis_client/hiredis/vendor/hiredis.pc.in +0 -12
  52. data/ext/redis_client/hiredis/vendor/hiredis_ssl-config.cmake.in +0 -13
  53. data/ext/redis_client/hiredis/vendor/hiredis_ssl.h +0 -157
  54. data/ext/redis_client/hiredis/vendor/hiredis_ssl.pc.in +0 -12
  55. data/ext/redis_client/hiredis/vendor/net.c +0 -612
  56. data/ext/redis_client/hiredis/vendor/net.h +0 -56
  57. data/ext/redis_client/hiredis/vendor/read.c +0 -739
  58. data/ext/redis_client/hiredis/vendor/read.h +0 -129
  59. data/ext/redis_client/hiredis/vendor/sds.c +0 -1289
  60. data/ext/redis_client/hiredis/vendor/sds.h +0 -278
  61. data/ext/redis_client/hiredis/vendor/sdsalloc.h +0 -44
  62. data/ext/redis_client/hiredis/vendor/sockcompat.c +0 -248
  63. data/ext/redis_client/hiredis/vendor/sockcompat.h +0 -92
  64. data/ext/redis_client/hiredis/vendor/ssl.c +0 -544
  65. data/ext/redis_client/hiredis/vendor/test.c +0 -1401
  66. data/ext/redis_client/hiredis/vendor/test.sh +0 -78
  67. data/ext/redis_client/hiredis/vendor/win32.h +0 -56
  68. data/lib/redis_client/buffered_io.rb +0 -151
  69. data/lib/redis_client/hiredis_connection.rb +0 -80
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "io/wait" unless IO.method_defined?(:wait_readable) && IO.method_defined?(:wait_writable)
4
+
5
+ class RedisClient
6
+ class RubyConnection
7
+ class BufferedIO
8
+ EOL = "\r\n".b.freeze
9
+ EOL_SIZE = EOL.bytesize
10
+
11
+ attr_accessor :read_timeout, :write_timeout
12
+
13
+ def initialize(io, read_timeout:, write_timeout:, chunk_size: 4096)
14
+ @io = io
15
+ @buffer = "".b
16
+ @offset = 0
17
+ @chunk_size = chunk_size
18
+ @read_timeout = read_timeout
19
+ @write_timeout = write_timeout
20
+ @blocking_reads = false
21
+ end
22
+
23
+ def close
24
+ @io.to_io.close
25
+ end
26
+
27
+ def closed?
28
+ @io.to_io.closed?
29
+ end
30
+
31
+ def eof?
32
+ @offset >= @buffer.bytesize && @io.eof?
33
+ end
34
+
35
+ def with_timeout(new_timeout)
36
+ new_timeout = false if new_timeout == 0
37
+
38
+ previous_read_timeout = @read_timeout
39
+ previous_blocking_reads = @blocking_reads
40
+
41
+ if new_timeout
42
+ @read_timeout = new_timeout
43
+ else
44
+ @blocking_reads = true
45
+ end
46
+
47
+ begin
48
+ yield
49
+ ensure
50
+ @read_timeout = previous_read_timeout
51
+ @blocking_reads = previous_blocking_reads
52
+ end
53
+ end
54
+
55
+ def skip(offset)
56
+ ensure_remaining(offset)
57
+ @offset += offset
58
+ nil
59
+ end
60
+
61
+ def write(string)
62
+ total = remaining = string.bytesize
63
+ loop do
64
+ case bytes_written = @io.write_nonblock(string, exception: false)
65
+ when Integer
66
+ remaining -= bytes_written
67
+ if remaining > 0
68
+ string = string.byteslice(bytes_written..-1)
69
+ else
70
+ return total
71
+ end
72
+ when :wait_readable
73
+ @io.to_io.wait_readable(@read_timeout) or raise ReadTimeoutError
74
+ when :wait_writable
75
+ @io.to_io.wait_writable(@write_timeout) or raise WriteTimeoutError
76
+ when nil
77
+ raise Errno::ECONNRESET
78
+ else
79
+ raise "Unexpected `write_nonblock` return: #{bytes.inspect}"
80
+ end
81
+ end
82
+ end
83
+
84
+ def getbyte
85
+ ensure_remaining(1)
86
+ byte = @buffer.getbyte(@offset)
87
+ @offset += 1
88
+ byte
89
+ end
90
+
91
+ def gets_chomp
92
+ fill_buffer(false) if @offset >= @buffer.bytesize
93
+ until eol_index = @buffer.index(EOL, @offset)
94
+ fill_buffer(false)
95
+ end
96
+
97
+ line = @buffer.byteslice(@offset, eol_index - @offset)
98
+ @offset = eol_index + EOL_SIZE
99
+ line
100
+ end
101
+
102
+ def read_chomp(bytes)
103
+ ensure_remaining(bytes + EOL_SIZE)
104
+ str = @buffer.byteslice(@offset, bytes)
105
+ @offset += bytes + EOL_SIZE
106
+ str
107
+ end
108
+
109
+ private
110
+
111
+ def ensure_remaining(bytes)
112
+ needed = bytes - (@buffer.bytesize - @offset)
113
+ if needed > 0
114
+ fill_buffer(true, needed)
115
+ end
116
+ end
117
+
118
+ def fill_buffer(strict, size = @chunk_size)
119
+ remaining = size
120
+ empty_buffer = @offset >= @buffer.bytesize
121
+
122
+ loop do
123
+ bytes = if empty_buffer
124
+ @io.read_nonblock([remaining, @chunk_size].max, @buffer, exception: false)
125
+ else
126
+ @io.read_nonblock([remaining, @chunk_size].max, exception: false)
127
+ end
128
+ case bytes
129
+ when String
130
+ if empty_buffer
131
+ @offset = 0
132
+ empty_buffer = false
133
+ else
134
+ @buffer << bytes
135
+ end
136
+ remaining -= bytes.bytesize
137
+ return if !strict || remaining <= 0
138
+ when :wait_readable
139
+ unless @io.to_io.wait_readable(@read_timeout)
140
+ raise ReadTimeoutError unless @blocking_reads
141
+ end
142
+ when :wait_writable
143
+ @io.to_io.wait_writable(@write_timeout) or raise WriteTimeoutError
144
+ when nil
145
+ raise Errno::ECONNRESET
146
+ else
147
+ raise "Unexpected `read_nonblock` return: #{bytes.inspect}"
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
@@ -58,32 +58,6 @@ class RedisClient
58
58
  String.new(encoding: Encoding::BINARY, capacity: 128)
59
59
  end
60
60
 
61
- def coerce_command!(command)
62
- command = command.flat_map do |element|
63
- case element
64
- when Hash
65
- element.flatten
66
- when Set
67
- element.to_a
68
- else
69
- element
70
- end
71
- end
72
-
73
- command.map! do |element|
74
- case element
75
- when String
76
- element
77
- when Integer, Float, Symbol
78
- element.to_s
79
- else
80
- raise TypeError, "Unsupported command argument type: #{element.class}"
81
- end
82
- end
83
-
84
- command
85
- end
86
-
87
61
  def dump_any(object, buffer)
88
62
  method = DUMP_TYPES.fetch(object.class) do
89
63
  raise TypeError, "Unsupported command argument type: #{object.class}"
@@ -2,47 +2,42 @@
2
2
 
3
3
  require "socket"
4
4
  require "openssl"
5
- require "redis_client/buffered_io"
5
+ require "redis_client/connection_mixin"
6
+ require "redis_client/ruby_connection/buffered_io"
7
+ require "redis_client/ruby_connection/resp3"
6
8
 
7
9
  class RedisClient
8
- class Connection
9
- module Common
10
- def call(command, timeout)
11
- write(command)
12
- result = read(timeout)
13
- if result.is_a?(CommandError)
14
- raise result
15
- else
16
- result
17
- end
18
- end
10
+ class RubyConnection
11
+ include ConnectionMixin
19
12
 
20
- def call_pipelined(commands, timeouts)
21
- exception = nil
13
+ class << self
14
+ def ssl_context(ssl_params)
15
+ params = ssl_params.dup || {}
22
16
 
23
- size = commands.size
24
- results = Array.new(commands.size)
25
- write_multi(commands)
17
+ cert = params[:cert]
18
+ if cert.is_a?(String)
19
+ cert = File.read(cert) if File.exist?(cert)
20
+ params[:cert] = OpenSSL::X509::Certificate.new(cert)
21
+ end
26
22
 
27
- size.times do |index|
28
- timeout = timeouts && timeouts[index]
29
- result = read(timeout)
30
- if result.is_a?(CommandError)
31
- exception ||= result
32
- end
33
- results[index] = result
23
+ key = params[:key]
24
+ if key.is_a?(String)
25
+ key = File.read(key) if File.exist?(key)
26
+ params[:key] = OpenSSL::PKey.read(key)
34
27
  end
35
28
 
36
- if exception
37
- raise exception
38
- else
39
- results
29
+ context = OpenSSL::SSL::SSLContext.new
30
+ context.set_params(params)
31
+ if context.verify_mode != OpenSSL::SSL::VERIFY_NONE
32
+ if context.respond_to?(:verify_hostname) # Missing on JRuby
33
+ context.verify_hostname
34
+ end
40
35
  end
36
+
37
+ context
41
38
  end
42
39
  end
43
40
 
44
- include Common
45
-
46
41
  SUPPORTS_RESOLV_TIMEOUT = Socket.method(:tcp).parameters.any? { |p| p.last == :resolv_timeout }
47
42
 
48
43
  def initialize(config, connect_timeout:, read_timeout:, write_timeout:)
@@ -60,7 +55,7 @@ class RedisClient
60
55
  end
61
56
 
62
57
  if config.ssl
63
- socket = OpenSSL::SSL::SSLSocket.new(socket, config.openssl_context)
58
+ socket = OpenSSL::SSL::SSLSocket.new(socket, config.ssl_context)
64
59
  socket.hostname = config.host
65
60
  loop do
66
61
  case status = socket.connect_nonblock(exception: false)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class RedisClient
4
- VERSION = "0.2.0"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/redis_client.rb CHANGED
@@ -1,12 +1,57 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "set"
4
+
3
5
  require "redis_client/version"
6
+ require "redis_client/command_builder"
4
7
  require "redis_client/config"
5
8
  require "redis_client/sentinel_config"
6
- require "redis_client/connection"
7
9
  require "redis_client/middlewares"
8
10
 
9
11
  class RedisClient
12
+ @driver_definitions = {}
13
+ @drivers = {}
14
+
15
+ @default_driver = nil
16
+
17
+ class << self
18
+ def register_driver(name, &block)
19
+ @driver_definitions[name] = block
20
+ end
21
+
22
+ def driver(name)
23
+ return name if name.is_a?(Class)
24
+
25
+ name = name.to_sym
26
+ unless @driver_definitions.key?(name)
27
+ raise ArgumentError, "Unknown driver #{name.inspect}, expected one of: `#{@driver_definitions.keys.inspect}`"
28
+ end
29
+
30
+ @drivers[name] ||= @driver_definitions[name]&.call
31
+ end
32
+
33
+ def default_driver
34
+ unless @default_driver
35
+ @driver_definitions.each_key do |name|
36
+ if @default_driver = driver(name)
37
+ break
38
+ end
39
+ rescue LoadError
40
+ end
41
+ end
42
+ @default_driver
43
+ end
44
+
45
+ def default_driver=(name)
46
+ @default_driver = driver(name)
47
+ end
48
+ end
49
+
50
+ register_driver :ruby do
51
+ require "redis_client/ruby_connection"
52
+ RubyConnection
53
+ end
54
+
10
55
  module Common
11
56
  attr_reader :config, :id
12
57
  attr_accessor :connect_timeout, :read_timeout, :write_timeout
@@ -23,6 +68,7 @@ class RedisClient
23
68
  @connect_timeout = connect_timeout
24
69
  @read_timeout = read_timeout
25
70
  @write_timeout = write_timeout
71
+ @command_builder = config.command_builder
26
72
  end
27
73
 
28
74
  def timeout=(timeout)
@@ -54,10 +100,14 @@ class RedisClient
54
100
 
55
101
  AuthenticationError = Class.new(CommandError)
56
102
  PermissionError = Class.new(CommandError)
103
+ ReadOnlyError = Class.new(CommandError)
104
+ WrongTypeError = Class.new(CommandError)
57
105
 
58
106
  CommandError::ERRORS = {
59
107
  "WRONGPASS" => AuthenticationError,
60
108
  "NOPERM" => PermissionError,
109
+ "READONLY" => ReadOnlyError,
110
+ "WRONGTYPE" => WrongTypeError,
61
111
  }.freeze
62
112
 
63
113
  class << self
@@ -115,67 +165,89 @@ class RedisClient
115
165
  end
116
166
 
117
167
  def pubsub
118
- sub = PubSub.new(ensure_connected)
168
+ sub = PubSub.new(ensure_connected, @command_builder)
119
169
  @raw_connection = nil
120
170
  sub
121
171
  end
122
172
 
123
- def call(*command)
124
- command = RESP3.coerce_command!(command)
125
- ensure_connected do |connection|
173
+ def call(*command, **kwargs)
174
+ command = @command_builder.generate!(command, kwargs)
175
+ result = ensure_connected do |connection|
126
176
  Middlewares.call(command, config) do
127
177
  connection.call(command, nil)
128
178
  end
129
179
  end
180
+
181
+ if block_given?
182
+ yield result
183
+ else
184
+ result
185
+ end
130
186
  end
131
187
 
132
- def call_once(*command)
133
- command = RESP3.coerce_command!(command)
134
- ensure_connected(retryable: false) do |connection|
188
+ def call_once(*command, **kwargs)
189
+ command = @command_builder.generate!(command, kwargs)
190
+ result = ensure_connected(retryable: false) do |connection|
135
191
  Middlewares.call(command, config) do
136
192
  connection.call(command, nil)
137
193
  end
138
194
  end
195
+
196
+ if block_given?
197
+ yield result
198
+ else
199
+ result
200
+ end
139
201
  end
140
202
 
141
- def blocking_call(timeout, *command)
142
- command = RESP3.coerce_command!(command)
143
- ensure_connected do |connection|
203
+ def blocking_call(timeout, *command, **kwargs)
204
+ command = @command_builder.generate!(command, kwargs)
205
+ result = ensure_connected do |connection|
144
206
  Middlewares.call(command, config) do
145
207
  connection.call(command, timeout)
146
208
  end
147
209
  end
210
+
211
+ if block_given?
212
+ yield result
213
+ else
214
+ result
215
+ end
148
216
  end
149
217
 
150
- def scan(*args, &block)
218
+ def scan(*args, **kwargs, &block)
151
219
  unless block_given?
152
- return to_enum(__callee__, *args)
220
+ return to_enum(__callee__, *args, **kwargs)
153
221
  end
154
222
 
223
+ args = @command_builder.generate!(args, kwargs)
155
224
  scan_list(1, ["SCAN", 0, *args], &block)
156
225
  end
157
226
 
158
- def sscan(key, *args, &block)
227
+ def sscan(key, *args, **kwargs, &block)
159
228
  unless block_given?
160
- return to_enum(__callee__, key, *args)
229
+ return to_enum(__callee__, key, *args, **kwargs)
161
230
  end
162
231
 
232
+ args = @command_builder.generate!(args, kwargs)
163
233
  scan_list(2, ["SSCAN", key, 0, *args], &block)
164
234
  end
165
235
 
166
- def hscan(key, *args, &block)
236
+ def hscan(key, *args, **kwargs, &block)
167
237
  unless block_given?
168
- return to_enum(__callee__, key, *args)
238
+ return to_enum(__callee__, key, *args, **kwargs)
169
239
  end
170
240
 
241
+ args = @command_builder.generate!(args, kwargs)
171
242
  scan_pairs(2, ["HSCAN", key, 0, *args], &block)
172
243
  end
173
244
 
174
- def zscan(key, *args, &block)
245
+ def zscan(key, *args, **kwargs, &block)
175
246
  unless block_given?
176
- return to_enum(__callee__, key, *args)
247
+ return to_enum(__callee__, key, *args, **kwargs)
177
248
  end
178
249
 
250
+ args = @command_builder.generate!(args, kwargs)
179
251
  scan_pairs(2, ["ZSCAN", key, 0, *args], &block)
180
252
  end
181
253
 
@@ -190,23 +262,27 @@ class RedisClient
190
262
  end
191
263
 
192
264
  def pipelined
193
- pipeline = Pipeline.new
265
+ pipeline = Pipeline.new(@command_builder)
194
266
  yield pipeline
195
267
 
196
268
  if pipeline._size == 0
197
269
  []
198
270
  else
199
- ensure_connected(retryable: pipeline._retryable?) do |connection|
271
+ results = ensure_connected(retryable: pipeline._retryable?) do |connection|
200
272
  commands = pipeline._commands
201
273
  Middlewares.call_pipelined(commands, config) do
202
274
  connection.call_pipelined(commands, pipeline._timeouts)
203
275
  end
204
276
  end
277
+
278
+ pipeline._coerce!(results)
205
279
  end
206
280
  end
207
281
 
208
282
  def multi(watch: nil, &block)
209
- if watch
283
+ transaction = nil
284
+
285
+ results = if watch
210
286
  # WATCH is stateful, so we can't reconnect if it's used, the whole transaction
211
287
  # has to be redone.
212
288
  ensure_connected(retryable: false) do |connection|
@@ -214,7 +290,7 @@ class RedisClient
214
290
  begin
215
291
  if transaction = build_transaction(&block)
216
292
  commands = transaction._commands
217
- Middlewares.call_pipelined(commands, config) do
293
+ results = Middlewares.call_pipelined(commands, config) do
218
294
  connection.call_pipelined(commands, nil)
219
295
  end.last
220
296
  else
@@ -239,15 +315,22 @@ class RedisClient
239
315
  end
240
316
  end
241
317
  end
318
+
319
+ if transaction
320
+ transaction._coerce!(results)
321
+ else
322
+ results
323
+ end
242
324
  end
243
325
 
244
326
  class PubSub
245
- def initialize(raw_connection)
327
+ def initialize(raw_connection, command_builder)
246
328
  @raw_connection = raw_connection
329
+ @command_builder = command_builder
247
330
  end
248
331
 
249
- def call(*command)
250
- raw_connection.write(RESP3.coerce_command!(command))
332
+ def call(*command, **kwargs)
333
+ raw_connection.write(@command_builder.generate!(command, kwargs))
251
334
  nil
252
335
  end
253
336
 
@@ -273,20 +356,26 @@ class RedisClient
273
356
  end
274
357
 
275
358
  class Multi
276
- def initialize
359
+ def initialize(command_builder)
360
+ @command_builder = command_builder
277
361
  @size = 0
278
362
  @commands = []
363
+ @blocks = nil
279
364
  @retryable = true
280
365
  end
281
366
 
282
- def call(*command)
283
- @commands << RESP3.coerce_command!(command)
367
+ def call(*command, **kwargs, &block)
368
+ command = @command_builder.generate!(command, kwargs)
369
+ (@blocks ||= [])[@commands.size] = block if block_given?
370
+ @commands << command
284
371
  nil
285
372
  end
286
373
 
287
- def call_once(*command)
374
+ def call_once(*command, **kwargs)
375
+ command = @command_builder.generate!(command, kwargs)
288
376
  @retryable = false
289
- @commands << RESP3.coerce_command!(command)
377
+ (@blocks ||= [])[@commands.size] = block if block_given?
378
+ @commands << command
290
379
  nil
291
380
  end
292
381
 
@@ -294,6 +383,10 @@ class RedisClient
294
383
  @commands
295
384
  end
296
385
 
386
+ def _blocks
387
+ @blocks
388
+ end
389
+
297
390
  def _size
298
391
  @commands.size
299
392
  end
@@ -309,18 +402,38 @@ class RedisClient
309
402
  def _retryable?
310
403
  @retryable
311
404
  end
405
+
406
+ def _coerce!(results)
407
+ if results
408
+ results.each do |result|
409
+ if result.is_a?(CommandError)
410
+ raise result
411
+ end
412
+ end
413
+
414
+ @blocks&.each_with_index do |block, index|
415
+ if block
416
+ results[index - 1] = block.call(results[index - 1])
417
+ end
418
+ end
419
+ end
420
+
421
+ results
422
+ end
312
423
  end
313
424
 
314
425
  class Pipeline < Multi
315
- def initialize
426
+ def initialize(_command_builder)
316
427
  super
317
428
  @timeouts = nil
318
429
  end
319
430
 
320
- def blocking_call(timeout, *command)
431
+ def blocking_call(timeout, *command, **kwargs)
432
+ command = @command_builder.generate!(command, kwargs)
321
433
  @timeouts ||= []
322
434
  @timeouts[@commands.size] = timeout
323
- @commands << RESP3.coerce_command!(command)
435
+ (@blocks ||= [])[@commands.size] = block if block_given?
436
+ @commands << command
324
437
  nil
325
438
  end
326
439
 
@@ -331,12 +444,24 @@ class RedisClient
331
444
  def _empty?
332
445
  @commands.empty?
333
446
  end
447
+
448
+ def _coerce!(results)
449
+ return results unless results
450
+
451
+ @blocks&.each_with_index do |block, index|
452
+ if block
453
+ results[index] = block.call(results[index])
454
+ end
455
+ end
456
+
457
+ results
458
+ end
334
459
  end
335
460
 
336
461
  private
337
462
 
338
463
  def build_transaction
339
- transaction = Multi.new
464
+ transaction = Multi.new(@command_builder)
340
465
  transaction.call("MULTI")
341
466
  yield transaction
342
467
  transaction.call("EXEC")
@@ -436,5 +561,6 @@ class RedisClient
436
561
  end
437
562
  end
438
563
 
439
- require "redis_client/resp3"
440
564
  require "redis_client/pooled"
565
+
566
+ RedisClient.default_driver
data/redis-client.gemspec CHANGED
@@ -10,6 +10,7 @@ Gem::Specification.new do |spec|
10
10
 
11
11
  spec.summary = "Simple low-level client for Redis 6+"
12
12
  spec.homepage = "https://github.com/redis-rb/redis-client"
13
+ spec.license = "MIT"
13
14
  spec.required_ruby_version = ">= 2.5.0"
14
15
 
15
16
  spec.metadata["allowed_push_host"] = "https://rubygems.org"
@@ -22,13 +23,10 @@ Gem::Specification.new do |spec|
22
23
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
24
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
25
  `git ls-files -z`.split("\x0").reject do |f|
25
- (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features|benchmark)/|\.(?:git|travis|circleci)|appveyor)})
26
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|hiredis-client|test|spec|features|benchmark)/|\.(?:git|rubocop))})
26
27
  end
27
28
  end
28
- spec.bindir = "exe"
29
- spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
29
  spec.require_paths = ["lib"]
31
- spec.extensions = ["ext/redis_client/hiredis/extconf.rb"]
32
30
 
33
31
  spec.add_runtime_dependency "connection_pool"
34
32
  end