redis 4.3.1 → 4.6.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 +80 -0
- data/README.md +10 -10
- data/lib/redis/client.rb +28 -11
- data/lib/redis/cluster/command.rb +4 -6
- data/lib/redis/cluster/command_loader.rb +6 -7
- data/lib/redis/cluster/node.rb +14 -1
- data/lib/redis/cluster.rb +32 -12
- data/lib/redis/commands/bitmaps.rb +63 -0
- data/lib/redis/commands/cluster.rb +45 -0
- data/lib/redis/commands/connection.rb +58 -0
- data/lib/redis/commands/geo.rb +84 -0
- data/lib/redis/commands/hashes.rb +251 -0
- data/lib/redis/commands/hyper_log_log.rb +37 -0
- data/lib/redis/commands/keys.rb +411 -0
- data/lib/redis/commands/lists.rb +289 -0
- data/lib/redis/commands/pubsub.rb +72 -0
- data/lib/redis/commands/scripting.rb +114 -0
- data/lib/redis/commands/server.rb +188 -0
- data/lib/redis/commands/sets.rb +207 -0
- data/lib/redis/commands/sorted_sets.rb +804 -0
- data/lib/redis/commands/streams.rb +382 -0
- data/lib/redis/commands/strings.rb +313 -0
- data/lib/redis/commands/transactions.rb +92 -0
- data/lib/redis/commands.rb +242 -0
- data/lib/redis/connection/command_helper.rb +2 -0
- data/lib/redis/connection/hiredis.rb +3 -2
- data/lib/redis/connection/ruby.rb +13 -9
- data/lib/redis/connection/synchrony.rb +10 -8
- data/lib/redis/connection.rb +1 -1
- data/lib/redis/distributed.rb +93 -9
- data/lib/redis/pipeline.rb +95 -2
- data/lib/redis/version.rb +1 -1
- data/lib/redis.rb +133 -3396
- metadata +22 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7e99f4e628112a227719d2000dc5b081893273cfbd51ae25bf00a8f6b6594061
|
|
4
|
+
data.tar.gz: 42a8e0cf75aebbc14cdf680b4bc1f7bbdec9e20b53f150c6443c01126960a959
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8bc57fe306c601f27d32df4940d5632e9e7d75529f6ac5d42732b21fe04452a4a1ab89567492c6fb7249ef4d69ebb2c0b7c985e18b0ef4f9ee6fb816c31bcbda
|
|
7
|
+
data.tar.gz: 42b2f8c584d6f96d0fc783d0b36af0fd9415d1dbd43ad8fe7a980688e52fbdb51645a07960d9cb3faacf6696c460cf38a013e290d0b58d4bbbb07626f18f15c7
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,85 @@
|
|
|
1
1
|
# Unreleased
|
|
2
2
|
|
|
3
|
+
# 4.6.0
|
|
4
|
+
|
|
5
|
+
* Deprecate `Redis.current`.
|
|
6
|
+
* Deprecate calling commands on `Redis` inside `Redis#pipelined`. See #1059.
|
|
7
|
+
```ruby
|
|
8
|
+
redis.pipelined do
|
|
9
|
+
redis.get("key")
|
|
10
|
+
end
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
should be replaced by:
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
redis.pipelined do |pipeline|
|
|
17
|
+
pipeline.get("key")
|
|
18
|
+
end
|
|
19
|
+
```
|
|
20
|
+
* Deprecate calling commands on `Redis` inside `Redis#multi`. See #1059.
|
|
21
|
+
```ruby
|
|
22
|
+
redis.multi do
|
|
23
|
+
redis.get("key")
|
|
24
|
+
end
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
should be replaced by:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
redis.multi do |transaction|
|
|
31
|
+
transaction.get("key")
|
|
32
|
+
end
|
|
33
|
+
```
|
|
34
|
+
* Deprecate `Redis#queue` and `Redis#commit`. See #1059.
|
|
35
|
+
|
|
36
|
+
* Fix `zpopmax` and `zpopmin` when called inside a pipeline. See #1055.
|
|
37
|
+
* `Redis#synchronize` is now private like it should always have been.
|
|
38
|
+
|
|
39
|
+
* Add `Redis.silence_deprecations=` to turn off deprecation warnings.
|
|
40
|
+
If you don't wish to see warnings yet, you can set `Redis.silence_deprecations = false`.
|
|
41
|
+
It is however heavily recommended to fix them instead when possible.
|
|
42
|
+
* Add `Redis.raise_deprecations=` to turn deprecation warnings into errors.
|
|
43
|
+
This makes it easier to identitify the source of deprecated APIs usage.
|
|
44
|
+
It is recommended to set `Redis.raise_deprecations = true` in development and test environments.
|
|
45
|
+
* Add new options to ZRANGE. See #1053.
|
|
46
|
+
* Add ZRANGESTORE command. See #1053.
|
|
47
|
+
* Add SCAN support for `Redis::Cluster`. See #1049.
|
|
48
|
+
* Add COPY command. See #1053. See #1048.
|
|
49
|
+
* Add ZDIFFSTORE command. See #1046.
|
|
50
|
+
* Add ZDIFF command. See #1044.
|
|
51
|
+
* Add ZUNION command. See #1042.
|
|
52
|
+
* Add HRANDFIELD command. See #1040.
|
|
53
|
+
|
|
54
|
+
# 4.5.1
|
|
55
|
+
|
|
56
|
+
* Restore the accidential auth behavior of redis-rb 4.3.0 with a warning. If provided with the `default` user's password, but a wrong username,
|
|
57
|
+
redis-rb will first try to connect as the provided user, but then will fallback to connect as the `default` user with the provided password.
|
|
58
|
+
This behavior is deprecated and will be removed in Redis 4.6.0. Fix #1038.
|
|
59
|
+
|
|
60
|
+
# 4.5.0
|
|
61
|
+
|
|
62
|
+
* Handle parts of the command using incompatible encodings. See #1037.
|
|
63
|
+
* Add GET option to SET command. See #1036.
|
|
64
|
+
* Add ZRANDMEMBER command. See #1035.
|
|
65
|
+
* Add LMOVE/BLMOVE commands. See #1034.
|
|
66
|
+
* Add ZMSCORE command. See #1032.
|
|
67
|
+
* Add LT/GT options to ZADD. See #1033.
|
|
68
|
+
* Add SMISMEMBER command. See #1031.
|
|
69
|
+
* Add EXAT/PXAT options to SET. See #1028.
|
|
70
|
+
* Add GETDEL/GETEX commands. See #1024.
|
|
71
|
+
* `Redis#exists` now returns an Integer by default, as warned since 4.2.0. The old behavior can be restored with `Redis.exists_returns_integer = false`.
|
|
72
|
+
* Fix Redis < 6 detection during connect. See #1025.
|
|
73
|
+
* Fix fetching command details in Redis cluster when the first node is unhealthy. See #1026.
|
|
74
|
+
|
|
75
|
+
# 4.4.0
|
|
76
|
+
|
|
77
|
+
* Redis cluster: fix cross-slot validation in pipelines. Fix ##1019.
|
|
78
|
+
* Add support for `XAUTOCLAIM`. See #1018.
|
|
79
|
+
* Properly issue `READONLY` when reconnecting to replicas. Fix #1017.
|
|
80
|
+
* Make `del` a noop if passed an empty list of keys. See #998.
|
|
81
|
+
* Add support for `ZINTER`. See #995.
|
|
82
|
+
|
|
3
83
|
# 4.3.1
|
|
4
84
|
|
|
5
85
|
* Fix password authentication against redis server 5 and older.
|
data/README.md
CHANGED
|
@@ -184,9 +184,9 @@ commands to Redis and gathers their replies. These replies are returned
|
|
|
184
184
|
by the `#pipelined` method.
|
|
185
185
|
|
|
186
186
|
```ruby
|
|
187
|
-
redis.pipelined do
|
|
188
|
-
|
|
189
|
-
|
|
187
|
+
redis.pipelined do |pipeline|
|
|
188
|
+
pipeline.set "foo", "bar"
|
|
189
|
+
pipeline.incr "baz"
|
|
190
190
|
end
|
|
191
191
|
# => ["OK", 1]
|
|
192
192
|
```
|
|
@@ -200,9 +200,9 @@ the regular pipeline, the replies to the commands are returned by the
|
|
|
200
200
|
`#multi` method.
|
|
201
201
|
|
|
202
202
|
```ruby
|
|
203
|
-
redis.multi do
|
|
204
|
-
|
|
205
|
-
|
|
203
|
+
redis.multi do |transaction|
|
|
204
|
+
transaction.set "foo", "bar"
|
|
205
|
+
transaction.incr "baz"
|
|
206
206
|
end
|
|
207
207
|
# => ["OK", 1]
|
|
208
208
|
```
|
|
@@ -210,15 +210,15 @@ end
|
|
|
210
210
|
### Futures
|
|
211
211
|
|
|
212
212
|
Replies to commands in a pipeline can be accessed via the *futures* they
|
|
213
|
-
emit (since redis-rb 3.0). All calls
|
|
213
|
+
emit (since redis-rb 3.0). All calls on the pipeline object return a
|
|
214
214
|
`Future` object, which responds to the `#value` method. When the
|
|
215
215
|
pipeline has successfully executed, all futures are assigned their
|
|
216
216
|
respective replies and can be used.
|
|
217
217
|
|
|
218
218
|
```ruby
|
|
219
|
-
redis.pipelined do
|
|
220
|
-
@set =
|
|
221
|
-
@incr =
|
|
219
|
+
redis.pipelined do |pipeline|
|
|
220
|
+
@set = pipeline.set "foo", "bar"
|
|
221
|
+
@incr = pipeline.incr "baz"
|
|
222
222
|
end
|
|
223
223
|
|
|
224
224
|
@set.value
|
data/lib/redis/client.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "errors"
|
|
4
3
|
require "socket"
|
|
5
4
|
require "cgi"
|
|
5
|
+
require "redis/errors"
|
|
6
6
|
|
|
7
7
|
class Redis
|
|
8
8
|
class Client
|
|
@@ -32,7 +32,7 @@ class Redis
|
|
|
32
32
|
role: nil
|
|
33
33
|
}.freeze
|
|
34
34
|
|
|
35
|
-
attr_reader :options
|
|
35
|
+
attr_reader :options, :connection, :command_map
|
|
36
36
|
|
|
37
37
|
def scheme
|
|
38
38
|
@options[:scheme]
|
|
@@ -87,8 +87,6 @@ class Redis
|
|
|
87
87
|
end
|
|
88
88
|
|
|
89
89
|
attr_accessor :logger
|
|
90
|
-
attr_reader :connection
|
|
91
|
-
attr_reader :command_map
|
|
92
90
|
|
|
93
91
|
def initialize(options = {})
|
|
94
92
|
@options = _parse_options(options)
|
|
@@ -119,13 +117,30 @@ class Redis
|
|
|
119
117
|
if username
|
|
120
118
|
begin
|
|
121
119
|
call [:auth, username, password]
|
|
122
|
-
rescue CommandError # Likely on Redis < 6
|
|
123
|
-
|
|
120
|
+
rescue CommandError => err # Likely on Redis < 6
|
|
121
|
+
case err.message
|
|
122
|
+
when /ERR wrong number of arguments for 'auth' command/
|
|
123
|
+
call [:auth, password]
|
|
124
|
+
when /WRONGPASS invalid username-password pair/
|
|
125
|
+
begin
|
|
126
|
+
call [:auth, password]
|
|
127
|
+
rescue CommandError
|
|
128
|
+
raise err
|
|
129
|
+
end
|
|
130
|
+
::Redis.deprecate!(
|
|
131
|
+
"[redis-rb] The Redis connection was configured with username #{username.inspect}, but" \
|
|
132
|
+
" the provided password was for the default user. This will start failing in redis-rb 5.0.0."
|
|
133
|
+
)
|
|
134
|
+
else
|
|
135
|
+
raise
|
|
136
|
+
end
|
|
124
137
|
end
|
|
125
138
|
else
|
|
126
139
|
call [:auth, password]
|
|
127
140
|
end
|
|
128
141
|
end
|
|
142
|
+
|
|
143
|
+
call [:readonly] if @options[:readonly]
|
|
129
144
|
call [:select, db] if db != 0
|
|
130
145
|
call [:client, :setname, @options[:id]] if @options[:id]
|
|
131
146
|
@connector.check(self)
|
|
@@ -236,7 +251,8 @@ class Redis
|
|
|
236
251
|
result
|
|
237
252
|
end
|
|
238
253
|
|
|
239
|
-
def call_with_timeout(command,
|
|
254
|
+
def call_with_timeout(command, extra_timeout, &blk)
|
|
255
|
+
timeout = extra_timeout == 0 ? 0 : self.timeout + extra_timeout
|
|
240
256
|
with_socket_timeout(timeout) do
|
|
241
257
|
call(command, &blk)
|
|
242
258
|
end
|
|
@@ -426,7 +442,7 @@ class Redis
|
|
|
426
442
|
defaults = DEFAULTS.dup
|
|
427
443
|
options = options.dup
|
|
428
444
|
|
|
429
|
-
defaults.
|
|
445
|
+
defaults.each_key do |key|
|
|
430
446
|
# Fill in defaults if needed
|
|
431
447
|
defaults[key] = defaults[key].call if defaults[key].respond_to?(:call)
|
|
432
448
|
|
|
@@ -443,9 +459,10 @@ class Redis
|
|
|
443
459
|
|
|
444
460
|
uri = URI(url)
|
|
445
461
|
|
|
446
|
-
|
|
462
|
+
case uri.scheme
|
|
463
|
+
when "unix"
|
|
447
464
|
defaults[:path] = uri.path
|
|
448
|
-
|
|
465
|
+
when "redis", "rediss"
|
|
449
466
|
defaults[:scheme] = uri.scheme
|
|
450
467
|
defaults[:host] = uri.host if uri.host
|
|
451
468
|
defaults[:port] = uri.port if uri.port
|
|
@@ -461,7 +478,7 @@ class Redis
|
|
|
461
478
|
end
|
|
462
479
|
|
|
463
480
|
# Use default when option is not specified or nil
|
|
464
|
-
defaults.
|
|
481
|
+
defaults.each_key do |key|
|
|
465
482
|
options[key] = defaults[key] if options[key].nil?
|
|
466
483
|
end
|
|
467
484
|
|
|
@@ -31,13 +31,13 @@ class Redis
|
|
|
31
31
|
private
|
|
32
32
|
|
|
33
33
|
def pick_details(details)
|
|
34
|
-
details.
|
|
35
|
-
|
|
34
|
+
details.transform_values do |detail|
|
|
35
|
+
{
|
|
36
36
|
first_key_position: detail[:first],
|
|
37
37
|
write: detail[:flags].include?('write'),
|
|
38
38
|
readonly: detail[:flags].include?('readonly')
|
|
39
|
-
}
|
|
40
|
-
end
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def dig_details(command, key)
|
|
@@ -53,8 +53,6 @@ class Redis
|
|
|
53
53
|
when 'object' then 2
|
|
54
54
|
when 'memory'
|
|
55
55
|
command[1].to_s.casecmp('usage').zero? ? 2 : 0
|
|
56
|
-
when 'scan', 'sscan', 'hscan', 'zscan'
|
|
57
|
-
determine_optional_key_position(command, 'match')
|
|
58
56
|
when 'xread', 'xreadgroup'
|
|
59
57
|
determine_optional_key_position(command, 'streams')
|
|
60
58
|
else
|
|
@@ -10,22 +10,21 @@ class Redis
|
|
|
10
10
|
module_function
|
|
11
11
|
|
|
12
12
|
def load(nodes)
|
|
13
|
-
details = {}
|
|
14
|
-
|
|
15
13
|
nodes.each do |node|
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
begin
|
|
15
|
+
return fetch_command_details(node)
|
|
16
|
+
rescue CannotConnectError, ConnectionError, CommandError
|
|
17
|
+
next # can retry on another node
|
|
18
|
+
end
|
|
18
19
|
end
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
raise CannotConnectError, 'Redis client could not connect to any cluster nodes'
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
def fetch_command_details(node)
|
|
24
25
|
node.call(%i[command]).map do |reply|
|
|
25
26
|
[reply[0], { arity: reply[1], flags: reply[2], first: reply[3], last: reply[4], step: reply[5] }]
|
|
26
27
|
end.to_h
|
|
27
|
-
rescue CannotConnectError, ConnectionError, CommandError
|
|
28
|
-
{} # can retry on another node
|
|
29
28
|
end
|
|
30
29
|
|
|
31
30
|
private_class_method :fetch_command_details
|
data/lib/redis/cluster/node.rb
CHANGED
|
@@ -58,6 +58,18 @@ class Redis
|
|
|
58
58
|
try_map { |_, client| client.process(commands, &block) }.values
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
+
def scale_reading_clients
|
|
62
|
+
reading_clients = []
|
|
63
|
+
|
|
64
|
+
@clients.each do |node_key, client|
|
|
65
|
+
next unless replica_disabled? ? master?(node_key) : slave?(node_key)
|
|
66
|
+
|
|
67
|
+
reading_clients << client
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
reading_clients
|
|
71
|
+
end
|
|
72
|
+
|
|
61
73
|
private
|
|
62
74
|
|
|
63
75
|
def replica_disabled?
|
|
@@ -76,8 +88,9 @@ class Redis
|
|
|
76
88
|
clients = options.map do |node_key, option|
|
|
77
89
|
next if replica_disabled? && slave?(node_key)
|
|
78
90
|
|
|
91
|
+
option = option.merge(readonly: true) if slave?(node_key)
|
|
92
|
+
|
|
79
93
|
client = Client.new(option)
|
|
80
|
-
client.call(%i[readonly]) if slave?(node_key)
|
|
81
94
|
[node_key, client]
|
|
82
95
|
end
|
|
83
96
|
|
data/lib/redis/cluster.rb
CHANGED
|
@@ -78,11 +78,13 @@ class Redis
|
|
|
78
78
|
end
|
|
79
79
|
|
|
80
80
|
def call_pipeline(pipeline)
|
|
81
|
-
node_keys
|
|
82
|
-
|
|
81
|
+
node_keys = pipeline.commands.map { |cmd| find_node_key(cmd, primary_only: true) }.compact.uniq
|
|
82
|
+
if node_keys.size > 1
|
|
83
|
+
raise(CrossSlotPipeliningError,
|
|
84
|
+
pipeline.commands.map { |cmd| @command.extract_first_key(cmd) }.reject(&:empty?).uniq)
|
|
85
|
+
end
|
|
83
86
|
|
|
84
|
-
|
|
85
|
-
try_send(node, :call_pipeline, pipeline)
|
|
87
|
+
try_send(find_node(node_keys.first), :call_pipeline, pipeline)
|
|
86
88
|
end
|
|
87
89
|
|
|
88
90
|
def call_with_timeout(command, timeout, &block)
|
|
@@ -135,6 +137,7 @@ class Redis
|
|
|
135
137
|
when 'wait' then @node.call_master(command, &block).reduce(:+)
|
|
136
138
|
when 'keys' then @node.call_slave(command, &block).flatten.sort
|
|
137
139
|
when 'dbsize' then @node.call_slave(command, &block).reduce(:+)
|
|
140
|
+
when 'scan' then _scan(command, &block)
|
|
138
141
|
when 'lastsave' then @node.call_all(command, &block).sort
|
|
139
142
|
when 'role' then @node.call_all(command, &block)
|
|
140
143
|
when 'config' then send_config_command(command, &block)
|
|
@@ -236,6 +239,29 @@ class Redis
|
|
|
236
239
|
raise
|
|
237
240
|
end
|
|
238
241
|
|
|
242
|
+
def _scan(command, &block)
|
|
243
|
+
input_cursor = Integer(command[1])
|
|
244
|
+
|
|
245
|
+
client_index = input_cursor % 256
|
|
246
|
+
raw_cursor = input_cursor >> 8
|
|
247
|
+
|
|
248
|
+
clients = @node.scale_reading_clients
|
|
249
|
+
|
|
250
|
+
client = clients[client_index]
|
|
251
|
+
return ['0', []] unless client
|
|
252
|
+
|
|
253
|
+
command[1] = raw_cursor.to_s
|
|
254
|
+
|
|
255
|
+
result_cursor, result_keys = client.call(command, &block)
|
|
256
|
+
result_cursor = Integer(result_cursor)
|
|
257
|
+
|
|
258
|
+
if result_cursor == 0
|
|
259
|
+
client_index += 1
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
[((result_cursor << 8) + client_index).to_s, result_keys]
|
|
263
|
+
end
|
|
264
|
+
|
|
239
265
|
def assign_redirection_node(err_msg)
|
|
240
266
|
_, slot, node_key = err_msg.split(' ')
|
|
241
267
|
slot = slot.to_i
|
|
@@ -253,14 +279,14 @@ class Redis
|
|
|
253
279
|
find_node(node_key)
|
|
254
280
|
end
|
|
255
281
|
|
|
256
|
-
def find_node_key(command)
|
|
282
|
+
def find_node_key(command, primary_only: false)
|
|
257
283
|
key = @command.extract_first_key(command)
|
|
258
284
|
return if key.empty?
|
|
259
285
|
|
|
260
286
|
slot = KeySlotConverter.convert(key)
|
|
261
287
|
return unless @slot.exists?(slot)
|
|
262
288
|
|
|
263
|
-
if @command.should_send_to_master?(command)
|
|
289
|
+
if @command.should_send_to_master?(command) || primary_only
|
|
264
290
|
@slot.find_node_key_of_master(slot)
|
|
265
291
|
else
|
|
266
292
|
@slot.find_node_key_of_slave(slot)
|
|
@@ -285,11 +311,5 @@ class Redis
|
|
|
285
311
|
@node.map(&:disconnect)
|
|
286
312
|
@node, @slot = fetch_cluster_info!(@option)
|
|
287
313
|
end
|
|
288
|
-
|
|
289
|
-
def extract_keys_in_pipeline(pipeline)
|
|
290
|
-
node_keys = pipeline.commands.map { |cmd| find_node_key(cmd) }.compact.uniq
|
|
291
|
-
command_keys = pipeline.commands.map { |cmd| @command.extract_first_key(cmd) }.reject(&:empty?)
|
|
292
|
-
[node_keys, command_keys]
|
|
293
|
-
end
|
|
294
314
|
end
|
|
295
315
|
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Redis
|
|
4
|
+
module Commands
|
|
5
|
+
module Bitmaps
|
|
6
|
+
# Sets or clears the bit at offset in the string value stored at key.
|
|
7
|
+
#
|
|
8
|
+
# @param [String] key
|
|
9
|
+
# @param [Integer] offset bit offset
|
|
10
|
+
# @param [Integer] value bit value `0` or `1`
|
|
11
|
+
# @return [Integer] the original bit value stored at `offset`
|
|
12
|
+
def setbit(key, offset, value)
|
|
13
|
+
send_command([:setbit, key, offset, value])
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Returns the bit value at offset in the string value stored at key.
|
|
17
|
+
#
|
|
18
|
+
# @param [String] key
|
|
19
|
+
# @param [Integer] offset bit offset
|
|
20
|
+
# @return [Integer] `0` or `1`
|
|
21
|
+
def getbit(key, offset)
|
|
22
|
+
send_command([:getbit, key, offset])
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Count the number of set bits in a range of the string value stored at key.
|
|
26
|
+
#
|
|
27
|
+
# @param [String] key
|
|
28
|
+
# @param [Integer] start start index
|
|
29
|
+
# @param [Integer] stop stop index
|
|
30
|
+
# @return [Integer] the number of bits set to 1
|
|
31
|
+
def bitcount(key, start = 0, stop = -1)
|
|
32
|
+
send_command([:bitcount, key, start, stop])
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Perform a bitwise operation between strings and store the resulting string in a key.
|
|
36
|
+
#
|
|
37
|
+
# @param [String] operation e.g. `and`, `or`, `xor`, `not`
|
|
38
|
+
# @param [String] destkey destination key
|
|
39
|
+
# @param [String, Array<String>] keys one or more source keys to perform `operation`
|
|
40
|
+
# @return [Integer] the length of the string stored in `destkey`
|
|
41
|
+
def bitop(operation, destkey, *keys)
|
|
42
|
+
send_command([:bitop, operation, destkey, *keys])
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Return the position of the first bit set to 1 or 0 in a string.
|
|
46
|
+
#
|
|
47
|
+
# @param [String] key
|
|
48
|
+
# @param [Integer] bit whether to look for the first 1 or 0 bit
|
|
49
|
+
# @param [Integer] start start index
|
|
50
|
+
# @param [Integer] stop stop index
|
|
51
|
+
# @return [Integer] the position of the first 1/0 bit.
|
|
52
|
+
# -1 if looking for 1 and it is not found or start and stop are given.
|
|
53
|
+
def bitpos(key, bit, start = nil, stop = nil)
|
|
54
|
+
raise(ArgumentError, 'stop parameter specified without start parameter') if stop && !start
|
|
55
|
+
|
|
56
|
+
command = [:bitpos, key, bit]
|
|
57
|
+
command << start if start
|
|
58
|
+
command << stop if stop
|
|
59
|
+
send_command(command)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Redis
|
|
4
|
+
module Commands
|
|
5
|
+
module Cluster
|
|
6
|
+
# Sends `CLUSTER *` command to random node and returns its reply.
|
|
7
|
+
#
|
|
8
|
+
# @see https://redis.io/commands#cluster Reference of cluster command
|
|
9
|
+
#
|
|
10
|
+
# @param subcommand [String, Symbol] the subcommand of cluster command
|
|
11
|
+
# e.g. `:slots`, `:nodes`, `:slaves`, `:info`
|
|
12
|
+
#
|
|
13
|
+
# @return [Object] depends on the subcommand
|
|
14
|
+
def cluster(subcommand, *args)
|
|
15
|
+
subcommand = subcommand.to_s.downcase
|
|
16
|
+
block = case subcommand
|
|
17
|
+
when 'slots'
|
|
18
|
+
HashifyClusterSlots
|
|
19
|
+
when 'nodes'
|
|
20
|
+
HashifyClusterNodes
|
|
21
|
+
when 'slaves'
|
|
22
|
+
HashifyClusterSlaves
|
|
23
|
+
when 'info'
|
|
24
|
+
HashifyInfo
|
|
25
|
+
else
|
|
26
|
+
Noop
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @see https://github.com/antirez/redis/blob/unstable/src/redis-trib.rb#L127 raw reply expected
|
|
30
|
+
block = Noop unless @cluster_mode
|
|
31
|
+
|
|
32
|
+
send_command([:cluster, subcommand] + args, &block)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Sends `ASKING` command to random node and returns its reply.
|
|
36
|
+
#
|
|
37
|
+
# @see https://redis.io/topics/cluster-spec#ask-redirection ASK redirection
|
|
38
|
+
#
|
|
39
|
+
# @return [String] `'OK'`
|
|
40
|
+
def asking
|
|
41
|
+
send_command(%i[asking])
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Redis
|
|
4
|
+
module Commands
|
|
5
|
+
module Connection
|
|
6
|
+
# Authenticate to the server.
|
|
7
|
+
#
|
|
8
|
+
# @param [Array<String>] args includes both username and password
|
|
9
|
+
# or only password
|
|
10
|
+
# @return [String] `OK`
|
|
11
|
+
# @see https://redis.io/commands/auth AUTH command
|
|
12
|
+
def auth(*args)
|
|
13
|
+
send_command([:auth, *args])
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Ping the server.
|
|
17
|
+
#
|
|
18
|
+
# @param [optional, String] message
|
|
19
|
+
# @return [String] `PONG`
|
|
20
|
+
def ping(message = nil)
|
|
21
|
+
send_command([:ping, message].compact)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Echo the given string.
|
|
25
|
+
#
|
|
26
|
+
# @param [String] value
|
|
27
|
+
# @return [String]
|
|
28
|
+
def echo(value)
|
|
29
|
+
send_command([:echo, value])
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Change the selected database for the current connection.
|
|
33
|
+
#
|
|
34
|
+
# @param [Integer] db zero-based index of the DB to use (0 to 15)
|
|
35
|
+
# @return [String] `OK`
|
|
36
|
+
def select(db)
|
|
37
|
+
synchronize do |client|
|
|
38
|
+
client.db = db
|
|
39
|
+
client.call([:select, db])
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Close the connection.
|
|
44
|
+
#
|
|
45
|
+
# @return [String] `OK`
|
|
46
|
+
def quit
|
|
47
|
+
synchronize do |client|
|
|
48
|
+
begin
|
|
49
|
+
client.call([:quit])
|
|
50
|
+
rescue ConnectionError
|
|
51
|
+
ensure
|
|
52
|
+
client.disconnect
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Redis
|
|
4
|
+
module Commands
|
|
5
|
+
module Geo
|
|
6
|
+
# Adds the specified geospatial items (latitude, longitude, name) to the specified key
|
|
7
|
+
#
|
|
8
|
+
# @param [String] key
|
|
9
|
+
# @param [Array] member arguemnts for member or members: longitude, latitude, name
|
|
10
|
+
# @return [Integer] number of elements added to the sorted set
|
|
11
|
+
def geoadd(key, *member)
|
|
12
|
+
send_command([:geoadd, key, *member])
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Returns geohash string representing position for specified members of the specified key.
|
|
16
|
+
#
|
|
17
|
+
# @param [String] key
|
|
18
|
+
# @param [String, Array<String>] member one member or array of members
|
|
19
|
+
# @return [Array<String, nil>] returns array containg geohash string if member is present, nil otherwise
|
|
20
|
+
def geohash(key, member)
|
|
21
|
+
send_command([:geohash, key, member])
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Query a sorted set representing a geospatial index to fetch members matching a
|
|
25
|
+
# given maximum distance from a point
|
|
26
|
+
#
|
|
27
|
+
# @param [Array] args key, longitude, latitude, radius, unit(m|km|ft|mi)
|
|
28
|
+
# @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest
|
|
29
|
+
# or the farthest to the nearest relative to the center
|
|
30
|
+
# @param [Integer] count limit the results to the first N matching items
|
|
31
|
+
# @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information
|
|
32
|
+
# @return [Array<String>] may be changed with `options`
|
|
33
|
+
def georadius(*args, **geoptions)
|
|
34
|
+
geoarguments = _geoarguments(*args, **geoptions)
|
|
35
|
+
|
|
36
|
+
send_command([:georadius, *geoarguments])
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Query a sorted set representing a geospatial index to fetch members matching a
|
|
40
|
+
# given maximum distance from an already existing member
|
|
41
|
+
#
|
|
42
|
+
# @param [Array] args key, member, radius, unit(m|km|ft|mi)
|
|
43
|
+
# @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest or the farthest
|
|
44
|
+
# to the nearest relative to the center
|
|
45
|
+
# @param [Integer] count limit the results to the first N matching items
|
|
46
|
+
# @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information
|
|
47
|
+
# @return [Array<String>] may be changed with `options`
|
|
48
|
+
def georadiusbymember(*args, **geoptions)
|
|
49
|
+
geoarguments = _geoarguments(*args, **geoptions)
|
|
50
|
+
|
|
51
|
+
send_command([:georadiusbymember, *geoarguments])
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Returns longitude and latitude of members of a geospatial index
|
|
55
|
+
#
|
|
56
|
+
# @param [String] key
|
|
57
|
+
# @param [String, Array<String>] member one member or array of members
|
|
58
|
+
# @return [Array<Array<String>, nil>] returns array of elements, where each
|
|
59
|
+
# element is either array of longitude and latitude or nil
|
|
60
|
+
def geopos(key, member)
|
|
61
|
+
send_command([:geopos, key, member])
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Returns the distance between two members of a geospatial index
|
|
65
|
+
#
|
|
66
|
+
# @param [String ]key
|
|
67
|
+
# @param [Array<String>] members
|
|
68
|
+
# @param ['m', 'km', 'mi', 'ft'] unit
|
|
69
|
+
# @return [String, nil] returns distance in spefied unit if both members present, nil otherwise.
|
|
70
|
+
def geodist(key, member1, member2, unit = 'm')
|
|
71
|
+
send_command([:geodist, key, member1, member2, unit])
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def _geoarguments(*args, options: nil, sort: nil, count: nil)
|
|
77
|
+
args.push sort if sort
|
|
78
|
+
args.push 'count', count if count
|
|
79
|
+
args.push options if options
|
|
80
|
+
args
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|