redis 3.3.5 → 4.1.4
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 +5 -5
- data/CHANGELOG.md +84 -2
- data/README.md +131 -76
- data/lib/redis.rb +912 -200
- data/lib/redis/client.rb +71 -29
- data/lib/redis/cluster.rb +291 -0
- data/lib/redis/cluster/command.rb +81 -0
- data/lib/redis/cluster/command_loader.rb +34 -0
- data/lib/redis/cluster/key_slot_converter.rb +72 -0
- data/lib/redis/cluster/node.rb +104 -0
- data/lib/redis/cluster/node_key.rb +31 -0
- data/lib/redis/cluster/node_loader.rb +37 -0
- data/lib/redis/cluster/option.rb +87 -0
- data/lib/redis/cluster/slot.rb +72 -0
- data/lib/redis/cluster/slot_loader.rb +50 -0
- data/lib/redis/connection.rb +3 -2
- data/lib/redis/connection/command_helper.rb +3 -8
- data/lib/redis/connection/hiredis.rb +3 -2
- data/lib/redis/connection/registry.rb +1 -0
- data/lib/redis/connection/ruby.rb +48 -32
- data/lib/redis/connection/synchrony.rb +13 -4
- data/lib/redis/distributed.rb +39 -15
- data/lib/redis/errors.rb +47 -0
- data/lib/redis/hash_ring.rb +21 -64
- data/lib/redis/pipeline.rb +54 -12
- data/lib/redis/subscribe.rb +1 -0
- data/lib/redis/version.rb +2 -1
- metadata +40 -198
- data/.gitignore +0 -16
- data/.travis.yml +0 -89
- data/.travis/Gemfile +0 -11
- data/.yardopts +0 -3
- data/Gemfile +0 -4
- data/Rakefile +0 -87
- data/benchmarking/logging.rb +0 -71
- data/benchmarking/pipeline.rb +0 -51
- data/benchmarking/speed.rb +0 -21
- data/benchmarking/suite.rb +0 -24
- data/benchmarking/worker.rb +0 -71
- data/examples/basic.rb +0 -15
- data/examples/consistency.rb +0 -114
- data/examples/dist_redis.rb +0 -43
- data/examples/incr-decr.rb +0 -17
- data/examples/list.rb +0 -26
- data/examples/pubsub.rb +0 -37
- data/examples/sentinel.rb +0 -41
- data/examples/sentinel/sentinel.conf +0 -9
- data/examples/sentinel/start +0 -49
- data/examples/sets.rb +0 -36
- data/examples/unicorn/config.ru +0 -3
- data/examples/unicorn/unicorn.rb +0 -20
- data/redis.gemspec +0 -44
- data/test/bitpos_test.rb +0 -69
- data/test/blocking_commands_test.rb +0 -42
- data/test/client_test.rb +0 -59
- data/test/command_map_test.rb +0 -30
- data/test/commands_on_hashes_test.rb +0 -21
- data/test/commands_on_hyper_log_log_test.rb +0 -21
- data/test/commands_on_lists_test.rb +0 -20
- data/test/commands_on_sets_test.rb +0 -77
- data/test/commands_on_sorted_sets_test.rb +0 -137
- data/test/commands_on_strings_test.rb +0 -101
- data/test/commands_on_value_types_test.rb +0 -133
- data/test/connection_handling_test.rb +0 -277
- data/test/connection_test.rb +0 -57
- data/test/db/.gitkeep +0 -0
- data/test/distributed_blocking_commands_test.rb +0 -46
- data/test/distributed_commands_on_hashes_test.rb +0 -10
- data/test/distributed_commands_on_hyper_log_log_test.rb +0 -33
- data/test/distributed_commands_on_lists_test.rb +0 -22
- data/test/distributed_commands_on_sets_test.rb +0 -83
- data/test/distributed_commands_on_sorted_sets_test.rb +0 -18
- data/test/distributed_commands_on_strings_test.rb +0 -59
- data/test/distributed_commands_on_value_types_test.rb +0 -95
- data/test/distributed_commands_requiring_clustering_test.rb +0 -164
- data/test/distributed_connection_handling_test.rb +0 -23
- data/test/distributed_internals_test.rb +0 -79
- data/test/distributed_key_tags_test.rb +0 -52
- data/test/distributed_persistence_control_commands_test.rb +0 -26
- data/test/distributed_publish_subscribe_test.rb +0 -92
- data/test/distributed_remote_server_control_commands_test.rb +0 -66
- data/test/distributed_scripting_test.rb +0 -102
- data/test/distributed_sorting_test.rb +0 -20
- data/test/distributed_test.rb +0 -58
- data/test/distributed_transactions_test.rb +0 -32
- data/test/encoding_test.rb +0 -18
- data/test/error_replies_test.rb +0 -59
- data/test/fork_safety_test.rb +0 -65
- data/test/helper.rb +0 -232
- data/test/helper_test.rb +0 -24
- data/test/internals_test.rb +0 -417
- data/test/lint/blocking_commands.rb +0 -150
- data/test/lint/hashes.rb +0 -162
- data/test/lint/hyper_log_log.rb +0 -60
- data/test/lint/lists.rb +0 -143
- data/test/lint/sets.rb +0 -140
- data/test/lint/sorted_sets.rb +0 -316
- data/test/lint/strings.rb +0 -260
- data/test/lint/value_types.rb +0 -122
- data/test/persistence_control_commands_test.rb +0 -26
- data/test/pipelining_commands_test.rb +0 -242
- data/test/publish_subscribe_test.rb +0 -282
- data/test/remote_server_control_commands_test.rb +0 -118
- data/test/scanning_test.rb +0 -413
- data/test/scripting_test.rb +0 -78
- data/test/sentinel_command_test.rb +0 -80
- data/test/sentinel_test.rb +0 -255
- data/test/sorting_test.rb +0 -59
- data/test/ssl_test.rb +0 -73
- data/test/support/connection/hiredis.rb +0 -1
- data/test/support/connection/ruby.rb +0 -1
- data/test/support/connection/synchrony.rb +0 -17
- data/test/support/redis_mock.rb +0 -130
- data/test/support/ssl/gen_certs.sh +0 -31
- data/test/support/ssl/trusted-ca.crt +0 -25
- data/test/support/ssl/trusted-ca.key +0 -27
- data/test/support/ssl/trusted-cert.crt +0 -81
- data/test/support/ssl/trusted-cert.key +0 -28
- data/test/support/ssl/untrusted-ca.crt +0 -26
- data/test/support/ssl/untrusted-ca.key +0 -27
- data/test/support/ssl/untrusted-cert.crt +0 -82
- data/test/support/ssl/untrusted-cert.key +0 -28
- data/test/support/wire/synchrony.rb +0 -24
- data/test/support/wire/thread.rb +0 -5
- data/test/synchrony_driver.rb +0 -88
- data/test/test.conf.erb +0 -9
- data/test/thread_safety_test.rb +0 -62
- data/test/transactions_test.rb +0 -264
- data/test/unknown_commands_test.rb +0 -14
- data/test/url_param_test.rb +0 -138
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
class Redis
|
6
|
+
class Cluster
|
7
|
+
# Keep slot and node key map for Redis Cluster Client
|
8
|
+
class Slot
|
9
|
+
ROLE_SLAVE = 'slave'
|
10
|
+
|
11
|
+
def initialize(available_slots, node_flags = {}, with_replica = false)
|
12
|
+
@with_replica = with_replica
|
13
|
+
@node_flags = node_flags
|
14
|
+
@map = build_slot_node_key_map(available_slots)
|
15
|
+
end
|
16
|
+
|
17
|
+
def exists?(slot)
|
18
|
+
@map.key?(slot)
|
19
|
+
end
|
20
|
+
|
21
|
+
def find_node_key_of_master(slot)
|
22
|
+
return nil unless exists?(slot)
|
23
|
+
|
24
|
+
@map[slot][:master]
|
25
|
+
end
|
26
|
+
|
27
|
+
def find_node_key_of_slave(slot)
|
28
|
+
return nil unless exists?(slot)
|
29
|
+
return find_node_key_of_master(slot) if replica_disabled?
|
30
|
+
|
31
|
+
@map[slot][:slaves].to_a.sample
|
32
|
+
end
|
33
|
+
|
34
|
+
def put(slot, node_key)
|
35
|
+
assign_node_key(@map, slot, node_key)
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def replica_disabled?
|
42
|
+
!@with_replica
|
43
|
+
end
|
44
|
+
|
45
|
+
def master?(node_key)
|
46
|
+
!slave?(node_key)
|
47
|
+
end
|
48
|
+
|
49
|
+
def slave?(node_key)
|
50
|
+
@node_flags[node_key] == ROLE_SLAVE
|
51
|
+
end
|
52
|
+
|
53
|
+
# available_slots is mapping of node_key to list of slot ranges
|
54
|
+
def build_slot_node_key_map(available_slots)
|
55
|
+
available_slots.each_with_object({}) do |(node_key, slots_arr), acc|
|
56
|
+
slots_arr.each do |slots|
|
57
|
+
slots.each { |slot| assign_node_key(acc, slot, node_key) }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def assign_node_key(mappings, slot, node_key)
|
63
|
+
mappings[slot] ||= { master: nil, slaves: ::Set.new }
|
64
|
+
if master?(node_key)
|
65
|
+
mappings[slot][:master] = node_key
|
66
|
+
else
|
67
|
+
mappings[slot][:slaves].add(node_key)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../errors'
|
4
|
+
require_relative 'node_key'
|
5
|
+
|
6
|
+
class Redis
|
7
|
+
class Cluster
|
8
|
+
# Load and hashify slot info for Redis Cluster Client
|
9
|
+
module SlotLoader
|
10
|
+
module_function
|
11
|
+
|
12
|
+
def load(nodes)
|
13
|
+
info = {}
|
14
|
+
|
15
|
+
nodes.each do |node|
|
16
|
+
info = fetch_slot_info(node)
|
17
|
+
info.empty? ? next : break
|
18
|
+
end
|
19
|
+
|
20
|
+
return info unless info.empty?
|
21
|
+
|
22
|
+
raise CannotConnectError, 'Redis client could not connect to any cluster nodes'
|
23
|
+
end
|
24
|
+
|
25
|
+
def fetch_slot_info(node)
|
26
|
+
hash_with_default_arr = Hash.new { |h, k| h[k] = [] }
|
27
|
+
node.call(%i[cluster slots])
|
28
|
+
.flat_map { |arr| parse_slot_info(arr, default_ip: node.host) }
|
29
|
+
.each_with_object(hash_with_default_arr) { |arr, h| h[arr[0]] << arr[1] }
|
30
|
+
|
31
|
+
rescue CannotConnectError, ConnectionError, CommandError
|
32
|
+
{} # can retry on another node
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse_slot_info(arr, default_ip:)
|
36
|
+
first_slot, last_slot = arr[0..1]
|
37
|
+
slot_range = (first_slot..last_slot).freeze
|
38
|
+
arr[2..-1].map { |addr| [stringify_node_key(addr, default_ip), slot_range] }
|
39
|
+
end
|
40
|
+
|
41
|
+
def stringify_node_key(arr, default_ip)
|
42
|
+
ip, port = arr
|
43
|
+
ip = default_ip if ip.empty? # When cluster is down
|
44
|
+
NodeKey.build_from_host_port(ip, port)
|
45
|
+
end
|
46
|
+
|
47
|
+
private_class_method :fetch_slot_info, :parse_slot_info, :stringify_node_key
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/redis/connection.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "connection/registry"
|
2
3
|
|
3
4
|
# If a connection driver was required before this file, the array
|
4
5
|
# Redis::Connection.drivers will contain one or more classes. The last driver
|
@@ -6,4 +7,4 @@ require "redis/connection/registry"
|
|
6
7
|
# the plain Ruby driver as our default. Another driver can be required at a
|
7
8
|
# later point in time, causing it to be the last element of the #drivers array
|
8
9
|
# and therefore be chosen by default.
|
9
|
-
|
10
|
+
require_relative "connection/ruby" if Redis::Connection.drivers.empty?
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
class Redis
|
2
3
|
module Connection
|
3
4
|
module CommandHelper
|
@@ -30,14 +31,8 @@ class Redis
|
|
30
31
|
|
31
32
|
protected
|
32
33
|
|
33
|
-
|
34
|
-
|
35
|
-
string.force_encoding(Encoding::default_external)
|
36
|
-
end
|
37
|
-
else
|
38
|
-
def encode(string)
|
39
|
-
string
|
40
|
-
end
|
34
|
+
def encode(string)
|
35
|
+
string.force_encoding(Encoding.default_external)
|
41
36
|
end
|
42
37
|
end
|
43
38
|
end
|
@@ -1,6 +1,7 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "registry"
|
3
|
+
require_relative "command_helper"
|
4
|
+
require_relative "../errors"
|
4
5
|
require "socket"
|
5
6
|
require "timeout"
|
6
7
|
|
@@ -10,36 +11,17 @@ rescue LoadError
|
|
10
11
|
# Not all systems have OpenSSL support
|
11
12
|
end
|
12
13
|
|
13
|
-
if RUBY_VERSION < "1.9.3"
|
14
|
-
class String
|
15
|
-
# Ruby 1.8.7 does not have byteslice, but it handles encodings differently anyway.
|
16
|
-
# We can simply slice the string, which is a byte array there.
|
17
|
-
def byteslice(*args)
|
18
|
-
slice(*args)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
14
|
class Redis
|
24
15
|
module Connection
|
25
16
|
module SocketMixin
|
26
17
|
|
27
18
|
CRLF = "\r\n".freeze
|
28
19
|
|
29
|
-
# Exceptions raised during non-blocking I/O ops that require retrying the op
|
30
|
-
if RUBY_VERSION >= "1.9.3"
|
31
|
-
NBIO_READ_EXCEPTIONS = [IO::WaitReadable]
|
32
|
-
NBIO_WRITE_EXCEPTIONS = [IO::WaitWritable]
|
33
|
-
else
|
34
|
-
NBIO_READ_EXCEPTIONS = [Errno::EWOULDBLOCK, Errno::EAGAIN]
|
35
|
-
NBIO_WRITE_EXCEPTIONS = [Errno::EWOULDBLOCK, Errno::EAGAIN]
|
36
|
-
end
|
37
|
-
|
38
20
|
def initialize(*args)
|
39
21
|
super(*args)
|
40
22
|
|
41
23
|
@timeout = @write_timeout = nil
|
42
|
-
@buffer = ""
|
24
|
+
@buffer = "".dup
|
43
25
|
end
|
44
26
|
|
45
27
|
def timeout=(timeout)
|
@@ -72,7 +54,7 @@ class Redis
|
|
72
54
|
crlf = nil
|
73
55
|
|
74
56
|
while (crlf = @buffer.index(CRLF)) == nil
|
75
|
-
@buffer << _read_from_socket(
|
57
|
+
@buffer << _read_from_socket(16384)
|
76
58
|
end
|
77
59
|
|
78
60
|
@buffer.slice!(0, crlf + CRLF.bytesize)
|
@@ -83,13 +65,13 @@ class Redis
|
|
83
65
|
begin
|
84
66
|
read_nonblock(nbytes)
|
85
67
|
|
86
|
-
rescue
|
68
|
+
rescue IO::WaitReadable
|
87
69
|
if IO.select([self], nil, nil, @timeout)
|
88
70
|
retry
|
89
71
|
else
|
90
72
|
raise Redis::TimeoutError
|
91
73
|
end
|
92
|
-
rescue
|
74
|
+
rescue IO::WaitWritable
|
93
75
|
if IO.select(nil, [self], nil, @timeout)
|
94
76
|
retry
|
95
77
|
else
|
@@ -105,13 +87,13 @@ class Redis
|
|
105
87
|
begin
|
106
88
|
write_nonblock(data)
|
107
89
|
|
108
|
-
rescue
|
90
|
+
rescue IO::WaitWritable
|
109
91
|
if IO.select(nil, [self], nil, @write_timeout)
|
110
92
|
retry
|
111
93
|
else
|
112
94
|
raise Redis::TimeoutError
|
113
95
|
end
|
114
|
-
rescue
|
96
|
+
rescue IO::WaitReadable
|
115
97
|
if IO.select([self], nil, nil, @write_timeout)
|
116
98
|
retry
|
117
99
|
else
|
@@ -285,8 +267,32 @@ class Redis
|
|
285
267
|
|
286
268
|
ssl_sock = new(tcp_sock, ctx)
|
287
269
|
ssl_sock.hostname = host
|
288
|
-
|
289
|
-
|
270
|
+
|
271
|
+
begin
|
272
|
+
# Initiate the socket connection in the background. If it doesn't fail
|
273
|
+
# immediately it will raise an IO::WaitWritable (Errno::EINPROGRESS)
|
274
|
+
# indicating the connection is in progress.
|
275
|
+
# Unlike waiting for a tcp socket to connect, you can't time out ssl socket
|
276
|
+
# connections during the connect phase properly, because IO.select only partially works.
|
277
|
+
# Instead, you have to retry.
|
278
|
+
ssl_sock.connect_nonblock
|
279
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
|
280
|
+
if IO.select([ssl_sock], nil, nil, timeout)
|
281
|
+
retry
|
282
|
+
else
|
283
|
+
raise TimeoutError
|
284
|
+
end
|
285
|
+
rescue IO::WaitWritable
|
286
|
+
if IO.select(nil, [ssl_sock], nil, timeout)
|
287
|
+
retry
|
288
|
+
else
|
289
|
+
raise TimeoutError
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
unless ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE || (ctx.respond_to?(:verify_hostname) && !ctx.verify_hostname)
|
294
|
+
ssl_sock.post_connection_check(host)
|
295
|
+
end
|
290
296
|
|
291
297
|
ssl_sock
|
292
298
|
end
|
@@ -307,16 +313,16 @@ class Redis
|
|
307
313
|
raise ArgumentError, "SSL incompatible with unix sockets" if config[:ssl]
|
308
314
|
sock = UNIXSocket.connect(config[:path], config[:connect_timeout])
|
309
315
|
elsif config[:scheme] == "rediss" || config[:ssl]
|
310
|
-
raise ArgumentError, "This library does not support SSL on Ruby < 1.9" if RUBY_VERSION < "1.9.3"
|
311
316
|
sock = SSLSocket.connect(config[:host], config[:port], config[:connect_timeout], config[:ssl_params])
|
312
317
|
else
|
313
318
|
sock = TCPSocket.connect(config[:host], config[:port], config[:connect_timeout])
|
314
319
|
end
|
315
320
|
|
316
321
|
instance = new(sock)
|
317
|
-
instance.timeout = config[:
|
322
|
+
instance.timeout = config[:read_timeout]
|
318
323
|
instance.write_timeout = config[:write_timeout]
|
319
324
|
instance.set_tcp_keepalive config[:tcp_keepalive]
|
325
|
+
instance.set_tcp_nodelay if sock.is_a? TCPSocket
|
320
326
|
instance
|
321
327
|
end
|
322
328
|
|
@@ -347,6 +353,16 @@ class Redis
|
|
347
353
|
end
|
348
354
|
end
|
349
355
|
|
356
|
+
# disables Nagle's Algorithm, prevents multiple round trips with MULTI
|
357
|
+
if [:IPPROTO_TCP, :TCP_NODELAY].all?{|c| Socket.const_defined? c}
|
358
|
+
def set_tcp_nodelay
|
359
|
+
@sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
360
|
+
end
|
361
|
+
else
|
362
|
+
def set_tcp_nodelay
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
350
366
|
def initialize(sock)
|
351
367
|
@sock = sock
|
352
368
|
end
|
@@ -1,6 +1,7 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "command_helper"
|
3
|
+
require_relative "registry"
|
4
|
+
require_relative "../errors"
|
4
5
|
require "em-synchrony"
|
5
6
|
require "hiredis/reader"
|
6
7
|
|
@@ -72,7 +73,15 @@ class Redis
|
|
72
73
|
|
73
74
|
def self.connect(config)
|
74
75
|
if config[:scheme] == "unix"
|
75
|
-
|
76
|
+
begin
|
77
|
+
conn = EventMachine.connect_unix_domain(config[:path], RedisClient)
|
78
|
+
rescue RuntimeError => e
|
79
|
+
if e.message == "no connection"
|
80
|
+
raise Errno::ECONNREFUSED
|
81
|
+
else
|
82
|
+
raise e
|
83
|
+
end
|
84
|
+
end
|
76
85
|
elsif config[:scheme] == "rediss" || config[:ssl]
|
77
86
|
raise NotImplementedError, "SSL not supported by synchrony driver"
|
78
87
|
else
|
data/lib/redis/distributed.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "hash_ring"
|
2
3
|
|
3
4
|
class Redis
|
4
5
|
class Distributed
|
@@ -144,8 +145,8 @@ class Redis
|
|
144
145
|
end
|
145
146
|
|
146
147
|
# Create a key using the serialized value, previously obtained using DUMP.
|
147
|
-
def restore(key, ttl, serialized_value)
|
148
|
-
node_for(key).restore(key, ttl, serialized_value)
|
148
|
+
def restore(key, ttl, serialized_value, options = {})
|
149
|
+
node_for(key).restore(key, ttl, serialized_value, options)
|
149
150
|
end
|
150
151
|
|
151
152
|
# Transfer a key from the connected instance to another instance.
|
@@ -161,6 +162,14 @@ class Redis
|
|
161
162
|
end
|
162
163
|
end
|
163
164
|
|
165
|
+
# Unlink keys.
|
166
|
+
def unlink(*args)
|
167
|
+
keys_per_node = args.group_by { |key| node_for(key) }
|
168
|
+
keys_per_node.inject(0) do |sum, (node, keys)|
|
169
|
+
sum + node.unlink(*keys)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
164
173
|
# Determine if a key exists.
|
165
174
|
def exists(key)
|
166
175
|
node_for(key).exists(key)
|
@@ -277,13 +286,16 @@ class Redis
|
|
277
286
|
node_for(key).get(key)
|
278
287
|
end
|
279
288
|
|
280
|
-
# Get the values of all the given keys.
|
289
|
+
# Get the values of all the given keys as an Array.
|
281
290
|
def mget(*keys)
|
282
|
-
|
291
|
+
mapped_mget(*keys).values_at(*keys)
|
283
292
|
end
|
284
293
|
|
294
|
+
# Get the values of all the given keys as a Hash.
|
285
295
|
def mapped_mget(*keys)
|
286
|
-
|
296
|
+
keys.group_by { |k| node_for k }.inject({}) do |results, (node, subkeys)|
|
297
|
+
results.merge! node.mapped_mget(*subkeys)
|
298
|
+
end
|
287
299
|
end
|
288
300
|
|
289
301
|
# Overwrite part of a string at key starting at the specified offset.
|
@@ -390,14 +402,12 @@ class Redis
|
|
390
402
|
end
|
391
403
|
|
392
404
|
def _bpop(cmd, args)
|
393
|
-
|
394
|
-
|
395
|
-
case args.last
|
396
|
-
when Hash
|
405
|
+
timeout = if args.last.is_a?(Hash)
|
397
406
|
options = args.pop
|
398
|
-
|
407
|
+
options[:timeout]
|
408
|
+
elsif args.last.respond_to?(:to_int)
|
399
409
|
# Issue deprecation notice in obnoxious mode...
|
400
|
-
|
410
|
+
args.pop.to_int
|
401
411
|
end
|
402
412
|
|
403
413
|
if args.size > 1
|
@@ -407,7 +417,11 @@ class Redis
|
|
407
417
|
keys = args.flatten
|
408
418
|
|
409
419
|
ensure_same_node(cmd, keys) do |node|
|
410
|
-
|
420
|
+
if timeout
|
421
|
+
node.__send__(cmd, keys, timeout: timeout)
|
422
|
+
else
|
423
|
+
node.__send__(cmd, keys)
|
424
|
+
end
|
411
425
|
end
|
412
426
|
end
|
413
427
|
|
@@ -509,6 +523,16 @@ class Redis
|
|
509
523
|
node_for(key).smembers(key)
|
510
524
|
end
|
511
525
|
|
526
|
+
# Scan a set
|
527
|
+
def sscan(key, cursor, options={})
|
528
|
+
node_for(key).sscan(key, cursor, options)
|
529
|
+
end
|
530
|
+
|
531
|
+
# Scan a set and return an enumerator
|
532
|
+
def sscan_each(key, options={}, &block)
|
533
|
+
node_for(key).sscan_each(key, options, &block)
|
534
|
+
end
|
535
|
+
|
512
536
|
# Subtract multiple sets.
|
513
537
|
def sdiff(*keys)
|
514
538
|
ensure_same_node(:sdiff, keys) do |node|
|
@@ -679,8 +703,8 @@ class Redis
|
|
679
703
|
end
|
680
704
|
|
681
705
|
# Delete one or more hash fields.
|
682
|
-
def hdel(key,
|
683
|
-
node_for(key).hdel(key,
|
706
|
+
def hdel(key, *fields)
|
707
|
+
node_for(key).hdel(key, *fields)
|
684
708
|
end
|
685
709
|
|
686
710
|
# Determine if a hash field exists.
|