redis-client 0.2.0 → 0.4.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/Gemfile +1 -2
- data/Gemfile.lock +2 -3
- data/README.md +66 -3
- data/Rakefile +43 -23
- data/lib/redis_client/command_builder.rb +83 -0
- data/lib/redis_client/config.rb +9 -48
- data/lib/redis_client/connection_mixin.rb +38 -0
- data/lib/redis_client/decorator.rb +84 -0
- data/lib/redis_client/pooled.rb +38 -30
- data/lib/redis_client/ruby_connection/buffered_io.rb +153 -0
- data/lib/redis_client/{resp3.rb → ruby_connection/resp3.rb} +0 -26
- data/lib/redis_client/{connection.rb → ruby_connection.rb} +26 -31
- data/lib/redis_client/version.rb +1 -1
- data/lib/redis_client.rb +162 -36
- data/redis-client.gemspec +2 -4
- metadata +12 -59
- data/.rubocop.yml +0 -190
- data/ext/redis_client/hiredis/export.clang +0 -2
- data/ext/redis_client/hiredis/export.gcc +0 -7
- data/ext/redis_client/hiredis/extconf.rb +0 -62
- data/ext/redis_client/hiredis/hiredis_connection.c +0 -708
- data/ext/redis_client/hiredis/vendor/.gitignore +0 -9
- data/ext/redis_client/hiredis/vendor/.travis.yml +0 -131
- data/ext/redis_client/hiredis/vendor/CHANGELOG.md +0 -364
- data/ext/redis_client/hiredis/vendor/CMakeLists.txt +0 -165
- data/ext/redis_client/hiredis/vendor/COPYING +0 -29
- data/ext/redis_client/hiredis/vendor/Makefile +0 -308
- data/ext/redis_client/hiredis/vendor/README.md +0 -664
- data/ext/redis_client/hiredis/vendor/adapters/ae.h +0 -130
- data/ext/redis_client/hiredis/vendor/adapters/glib.h +0 -156
- data/ext/redis_client/hiredis/vendor/adapters/ivykis.h +0 -84
- data/ext/redis_client/hiredis/vendor/adapters/libev.h +0 -179
- data/ext/redis_client/hiredis/vendor/adapters/libevent.h +0 -175
- data/ext/redis_client/hiredis/vendor/adapters/libuv.h +0 -117
- data/ext/redis_client/hiredis/vendor/adapters/macosx.h +0 -115
- data/ext/redis_client/hiredis/vendor/adapters/qt.h +0 -135
- data/ext/redis_client/hiredis/vendor/alloc.c +0 -86
- data/ext/redis_client/hiredis/vendor/alloc.h +0 -91
- data/ext/redis_client/hiredis/vendor/appveyor.yml +0 -24
- data/ext/redis_client/hiredis/vendor/async.c +0 -887
- data/ext/redis_client/hiredis/vendor/async.h +0 -147
- data/ext/redis_client/hiredis/vendor/async_private.h +0 -75
- data/ext/redis_client/hiredis/vendor/dict.c +0 -352
- data/ext/redis_client/hiredis/vendor/dict.h +0 -126
- data/ext/redis_client/hiredis/vendor/fmacros.h +0 -12
- data/ext/redis_client/hiredis/vendor/hiredis-config.cmake.in +0 -13
- data/ext/redis_client/hiredis/vendor/hiredis.c +0 -1174
- data/ext/redis_client/hiredis/vendor/hiredis.h +0 -336
- data/ext/redis_client/hiredis/vendor/hiredis.pc.in +0 -12
- data/ext/redis_client/hiredis/vendor/hiredis_ssl-config.cmake.in +0 -13
- data/ext/redis_client/hiredis/vendor/hiredis_ssl.h +0 -157
- data/ext/redis_client/hiredis/vendor/hiredis_ssl.pc.in +0 -12
- data/ext/redis_client/hiredis/vendor/net.c +0 -612
- data/ext/redis_client/hiredis/vendor/net.h +0 -56
- data/ext/redis_client/hiredis/vendor/read.c +0 -739
- data/ext/redis_client/hiredis/vendor/read.h +0 -129
- data/ext/redis_client/hiredis/vendor/sds.c +0 -1289
- data/ext/redis_client/hiredis/vendor/sds.h +0 -278
- data/ext/redis_client/hiredis/vendor/sdsalloc.h +0 -44
- data/ext/redis_client/hiredis/vendor/sockcompat.c +0 -248
- data/ext/redis_client/hiredis/vendor/sockcompat.h +0 -92
- data/ext/redis_client/hiredis/vendor/ssl.c +0 -544
- data/ext/redis_client/hiredis/vendor/test.c +0 -1401
- data/ext/redis_client/hiredis/vendor/test.sh +0 -78
- data/ext/redis_client/hiredis/vendor/win32.h +0 -56
- data/lib/redis_client/buffered_io.rb +0 -151
- 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/
|
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
|
9
|
-
|
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
|
-
|
21
|
-
|
13
|
+
class << self
|
14
|
+
def ssl_context(ssl_params)
|
15
|
+
params = ssl_params.dup || {}
|
22
16
|
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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.
|
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)
|
data/lib/redis_client/version.rb
CHANGED
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 =
|
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 =
|
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 =
|
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
|
-
|
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(
|
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
|
-
|
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
|
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
|
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|
|
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
|