protocol-redis 0.10.0 → 0.11.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
- checksums.yaml.gz.sig +0 -0
- data/lib/protocol/redis/cluster/methods/generic.rb +94 -0
- data/lib/protocol/redis/cluster/methods/pubsub.rb +27 -0
- data/lib/protocol/redis/cluster/methods/scripting.rb +93 -0
- data/lib/protocol/redis/cluster/methods/streams.rb +204 -0
- data/lib/protocol/redis/cluster/methods/strings.rb +304 -0
- data/lib/protocol/redis/cluster/methods.rb +27 -0
- data/lib/protocol/redis/connection.rb +1 -1
- data/lib/protocol/redis/error.rb +4 -0
- data/lib/protocol/redis/methods/scripting.rb +19 -21
- data/lib/protocol/redis/methods/strings.rb +4 -2
- data/lib/protocol/redis/version.rb +1 -1
- data/readme.md +4 -4
- data/releases.md +4 -0
- data.tar.gz.sig +0 -0
- metadata +7 -1
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b39632c3f7210dd4eea7d4b561a74c94442434a3e9ae9220d44303ea51aba2af
|
4
|
+
data.tar.gz: 6a930186fd89b24a6469a79025de532bd638a8e942228dffe25c4a4752561731
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cdeeba41310895026b7e5f7c0f79c382ab91d63a4a27a3e3131adb593f71b2bf780a4974b6b08a9c7fa163f1223acadece21a8d08bc652d45962269226958c94
|
7
|
+
data.tar.gz: 53859905dfad6b1b1e4944aff3357c13ec1fdcd0e1635962ce5d83811d7c625b08efa5da46c5eef76c22f0cf66cf5ac477a01796fff47499b6849a1fb3118fe2
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Protocol
|
4
|
+
module Redis
|
5
|
+
module Cluster
|
6
|
+
module Methods
|
7
|
+
# Provides generic Redis commands for cluster environments.
|
8
|
+
# These methods distribute operations across cluster nodes based on key slots.
|
9
|
+
module Generic
|
10
|
+
# Delete one or more keys from the cluster.
|
11
|
+
# Uses the appropriate client(s) for each key's slot.
|
12
|
+
# @parameter keys [Array(String)] The keys to delete.
|
13
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
14
|
+
# @parameter options [Hash] Additional options passed to the client.
|
15
|
+
# @returns [Integer] The number of keys deleted.
|
16
|
+
def del(*keys, role: :master, **options)
|
17
|
+
return 0 if keys.empty?
|
18
|
+
|
19
|
+
count = 0
|
20
|
+
|
21
|
+
clients_for(*keys, role: role) do |client, grouped_keys|
|
22
|
+
count += client.call("DEL", *grouped_keys)
|
23
|
+
end
|
24
|
+
|
25
|
+
return count
|
26
|
+
end
|
27
|
+
|
28
|
+
# Check existence of one or more keys in the cluster.
|
29
|
+
# @parameter keys [Array(String)] The keys to check.
|
30
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
31
|
+
# @parameter options [Hash] Additional options passed to the client.
|
32
|
+
# @returns [Integer] The number of keys existing.
|
33
|
+
def exists(*keys, role: :master, **options)
|
34
|
+
return 0 if keys.empty?
|
35
|
+
|
36
|
+
count = 0
|
37
|
+
|
38
|
+
clients_for(*keys, role: role) do |client, grouped_keys|
|
39
|
+
count += client.call("EXISTS", *grouped_keys)
|
40
|
+
end
|
41
|
+
|
42
|
+
return count
|
43
|
+
end
|
44
|
+
|
45
|
+
# Get the values of multiple keys from the cluster.
|
46
|
+
# @parameter keys [Array(String)] The keys to fetch.
|
47
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
48
|
+
# @parameter options [Hash] Additional options passed to the client.
|
49
|
+
# @returns [Array] The values for the given keys, in order.
|
50
|
+
def mget(*keys, role: :master, **options)
|
51
|
+
return [] if keys.empty?
|
52
|
+
|
53
|
+
results = Array.new(keys.size)
|
54
|
+
key_to_index = keys.each_with_index.to_h
|
55
|
+
|
56
|
+
clients_for(*keys, role: role) do |client, grouped_keys|
|
57
|
+
values = client.call("MGET", *grouped_keys)
|
58
|
+
grouped_keys.each_with_index do |key, i|
|
59
|
+
results[key_to_index[key]] = values[i]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
return results
|
64
|
+
end
|
65
|
+
|
66
|
+
# Get the value of a single key from the cluster.
|
67
|
+
# @parameter key [String] The key to fetch.
|
68
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
69
|
+
# @parameter options [Hash] Additional options passed to the client.
|
70
|
+
# @returns [Object] The value for the given key.
|
71
|
+
def get(key, role: :master, **options)
|
72
|
+
slot = slot_for(key)
|
73
|
+
client = client_for(slot, role)
|
74
|
+
|
75
|
+
return client.call("GET", key)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Set the value of a single key in the cluster.
|
79
|
+
# @parameter key [String] The key to set.
|
80
|
+
# @parameter value [Object] The value to set.
|
81
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
82
|
+
# @parameter options [Hash] Additional options passed to the client.
|
83
|
+
# @returns [String | Boolean] Status reply or `true`/`false` depending on client implementation.
|
84
|
+
def set(key, value, role: :master, **options)
|
85
|
+
slot = slot_for(key)
|
86
|
+
client = client_for(slot, role)
|
87
|
+
|
88
|
+
return client.call("SET", key, value)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Protocol
|
4
|
+
module Redis
|
5
|
+
module Cluster
|
6
|
+
module Methods
|
7
|
+
# Provides Redis Pub/Sub commands for cluster environments.
|
8
|
+
# Uses sharded pub/sub by default for optimal cluster performance.
|
9
|
+
module Pubsub
|
10
|
+
# Post a message to a channel using cluster-optimized sharded publish.
|
11
|
+
# Routes the message directly to the appropriate shard based on the channel name.
|
12
|
+
# @parameter channel [String] The channel name.
|
13
|
+
# @parameter message [String] The message to publish.
|
14
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
15
|
+
# @returns [Integer] The number of clients that received the message.
|
16
|
+
def publish(channel, message, role: :master)
|
17
|
+
# Route to the correct shard based on channel name:
|
18
|
+
slot = slot_for(channel)
|
19
|
+
client = client_for(slot, role)
|
20
|
+
|
21
|
+
return client.call("SPUBLISH", channel, message)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2023, by Samuel Williams.
|
5
|
+
|
6
|
+
module Protocol
|
7
|
+
module Redis
|
8
|
+
module Cluster
|
9
|
+
module Methods
|
10
|
+
# Methods for managing Redis scripting in cluster environments.
|
11
|
+
#
|
12
|
+
# Scripting operations in Redis clusters require careful consideration of key distribution.
|
13
|
+
# EVAL and EVALSHA operations are routed based on the keys they access, while SCRIPT
|
14
|
+
# management commands may need to be executed on specific nodes or all nodes.
|
15
|
+
module Scripting
|
16
|
+
# Execute a Lua script server side in a cluster environment.
|
17
|
+
#
|
18
|
+
# The script will be executed on the node determined by the first key's slot.
|
19
|
+
# Redis will return a CROSSSLOT error if keys span multiple slots.
|
20
|
+
#
|
21
|
+
# @parameter script [String] The Lua script to execute.
|
22
|
+
# @parameter key_count [Integer] Number of keys that follow.
|
23
|
+
# @parameter keys [Array[String]] The keys the script will access.
|
24
|
+
# @parameter args [Array[String]] Additional arguments to the script.
|
25
|
+
# @parameter role [Symbol] The role of the cluster node (:master or :slave).
|
26
|
+
# @returns [Object] The result of the script execution.
|
27
|
+
def eval(script, key_count = 0, *keys_and_args, role: :master)
|
28
|
+
if key_count == 0
|
29
|
+
# No keys, can execute on any client
|
30
|
+
any_client(role).call("EVAL", script, key_count, *keys_and_args)
|
31
|
+
else
|
32
|
+
# Extract keys for routing
|
33
|
+
keys = keys_and_args[0, key_count]
|
34
|
+
args = keys_and_args[key_count..-1] || []
|
35
|
+
|
36
|
+
# Route to appropriate cluster node based on first key
|
37
|
+
# Redis will handle CROSSSLOT validation
|
38
|
+
slot = slot_for(keys.first)
|
39
|
+
client_for(slot, role).call("EVAL", script, key_count, *keys, *args)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Execute a cached Lua script by SHA1 digest in a cluster environment.
|
44
|
+
#
|
45
|
+
# The script will be executed on the node determined by the first key's slot.
|
46
|
+
# Redis will return a CROSSSLOT error if keys span multiple slots.
|
47
|
+
# The script must already be loaded on the target node via SCRIPT LOAD.
|
48
|
+
#
|
49
|
+
# @parameter sha1 [String] The SHA1 digest of the script to execute.
|
50
|
+
# @parameter key_count [Integer] Number of keys that follow.
|
51
|
+
# @parameter keys [Array[String]] The keys the script will access.
|
52
|
+
# @parameter args [Array[String]] Additional arguments to the script.
|
53
|
+
# @parameter role [Symbol] The role of the cluster node (:master or :slave).
|
54
|
+
# @returns [Object] The result of the script execution.
|
55
|
+
def evalsha(sha1, key_count = 0, *keys_and_args, role: :master)
|
56
|
+
if key_count == 0
|
57
|
+
# No keys, can execute on any client
|
58
|
+
any_client(role).call("EVALSHA", sha1, key_count, *keys_and_args)
|
59
|
+
else
|
60
|
+
# Extract keys for routing
|
61
|
+
keys = keys_and_args[0, key_count]
|
62
|
+
args = keys_and_args[key_count..-1] || []
|
63
|
+
|
64
|
+
# Route to appropriate cluster node based on first key
|
65
|
+
# Redis will handle CROSSSLOT validation
|
66
|
+
slot = slot_for(keys.first)
|
67
|
+
client_for(slot, role).call("EVALSHA", sha1, key_count, *keys, *args)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Execute script management commands in a cluster environment.
|
72
|
+
#
|
73
|
+
# Supported script subcommands:
|
74
|
+
# - DEBUG: Set the debug mode for executed scripts on the target node.
|
75
|
+
# - EXISTS: Check if scripts exist in the script cache on the target node.
|
76
|
+
# - FLUSH: Remove all scripts from the script cache (propagates cluster-wide when executed on master).
|
77
|
+
# - KILL: Kill the currently executing script on the target node.
|
78
|
+
# - LOAD: Load a script into the script cache (propagates cluster-wide when executed on master).
|
79
|
+
#
|
80
|
+
# It is unlikely that DEBUG, EXISTS and KILL are useful when run on a cluster node at random.
|
81
|
+
#
|
82
|
+
# @parameter subcommand [String|Symbol] The script subcommand (debug, exists, flush, load, kill).
|
83
|
+
# @parameter arguments [Array] Additional arguments for the subcommand.
|
84
|
+
# @parameter role [Symbol] The role of the cluster node (:master or :slave).
|
85
|
+
# @returns [Object] The result of the script command.
|
86
|
+
def script(subcommand, *arguments, role: :master)
|
87
|
+
any_client(role).call("SCRIPT", subcommand.to_s, *arguments)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Protocol
|
4
|
+
module Redis
|
5
|
+
module Cluster
|
6
|
+
module Methods
|
7
|
+
# Provides Redis Streams commands for cluster environments.
|
8
|
+
# Stream operations are routed to the appropriate shard based on the stream key.
|
9
|
+
module Streams
|
10
|
+
# Get information on streams and consumer groups.
|
11
|
+
# @parameter arguments [Array] XINFO command arguments (subcommand and stream key).
|
12
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
13
|
+
# @returns [Array] Stream information.
|
14
|
+
def xinfo(*arguments, role: :master)
|
15
|
+
# Extract stream key (usually the second argument after subcommand):
|
16
|
+
stream_key = arguments[1] if arguments.length > 1
|
17
|
+
|
18
|
+
if stream_key
|
19
|
+
slot = slot_for(stream_key)
|
20
|
+
client = client_for(slot, role)
|
21
|
+
else
|
22
|
+
# Fallback for commands without a specific key:
|
23
|
+
client = any_client(role)
|
24
|
+
end
|
25
|
+
|
26
|
+
return client.call("XINFO", *arguments)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Append a new entry to a stream.
|
30
|
+
# @parameter key [String] The stream key.
|
31
|
+
# @parameter arguments [Array] Additional XADD arguments (ID and field-value pairs).
|
32
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
33
|
+
# @returns [String] The ID of the added entry.
|
34
|
+
def xadd(key, *arguments, role: :master)
|
35
|
+
slot = slot_for(key)
|
36
|
+
client = client_for(slot, role)
|
37
|
+
|
38
|
+
return client.call("XADD", key, *arguments)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Trim the stream to a certain size.
|
42
|
+
# @parameter key [String] The stream key.
|
43
|
+
# @parameter arguments [Array] Trim strategy and parameters.
|
44
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
45
|
+
# @returns [Integer] Number of entries removed.
|
46
|
+
def xtrim(key, *arguments, role: :master)
|
47
|
+
slot = slot_for(key)
|
48
|
+
client = client_for(slot, role)
|
49
|
+
|
50
|
+
return client.call("XTRIM", key, *arguments)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Remove specified entries from the stream.
|
54
|
+
# @parameter key [String] The stream key.
|
55
|
+
# @parameter arguments [Array] Entry IDs to remove.
|
56
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
57
|
+
# @returns [Integer] Number of entries actually deleted.
|
58
|
+
def xdel(key, *arguments, role: :master)
|
59
|
+
slot = slot_for(key)
|
60
|
+
client = client_for(slot, role)
|
61
|
+
|
62
|
+
return client.call("XDEL", key, *arguments)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Return a range of elements in a stream.
|
66
|
+
# @parameter key [String] The stream key.
|
67
|
+
# @parameter arguments [Array] Range parameters (start, end, optional COUNT).
|
68
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
69
|
+
# @returns [Array] Stream entries in the specified range.
|
70
|
+
def xrange(key, *arguments, role: :master)
|
71
|
+
slot = slot_for(key)
|
72
|
+
client = client_for(slot, role)
|
73
|
+
|
74
|
+
return client.call("XRANGE", key, *arguments)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Return a range of elements in a stream in reverse order.
|
78
|
+
# @parameter key [String] The stream key.
|
79
|
+
# @parameter arguments [Array] Range parameters (end, start, optional COUNT).
|
80
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
81
|
+
# @returns [Array] Stream entries in reverse order.
|
82
|
+
def xrevrange(key, *arguments, role: :master)
|
83
|
+
slot = slot_for(key)
|
84
|
+
client = client_for(slot, role)
|
85
|
+
|
86
|
+
return client.call("XREVRANGE", key, *arguments)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Return the number of entries in a stream.
|
90
|
+
# @parameter key [String] The stream key.
|
91
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
92
|
+
# @returns [Integer] Number of entries in the stream.
|
93
|
+
def xlen(key, role: :master)
|
94
|
+
slot = slot_for(key)
|
95
|
+
client = client_for(slot, role)
|
96
|
+
|
97
|
+
return client.call("XLEN", key)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Read new entries from multiple streams.
|
101
|
+
# Note: In cluster mode, all streams in a single XREAD must be on the same shard.
|
102
|
+
# @parameter arguments [Array] XREAD arguments including STREAMS keyword and stream keys/IDs.
|
103
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
104
|
+
# @returns [Array] New entries from the specified streams.
|
105
|
+
def xread(*arguments, role: :master)
|
106
|
+
# Extract first stream key to determine shard:
|
107
|
+
streams_index = arguments.index("STREAMS")
|
108
|
+
|
109
|
+
if streams_index && streams_index + 1 < arguments.length
|
110
|
+
first_stream_key = arguments[streams_index + 1]
|
111
|
+
slot = slot_for(first_stream_key)
|
112
|
+
client = client_for(slot, role)
|
113
|
+
else
|
114
|
+
# Fallback if STREAMS keyword not found:
|
115
|
+
client = any_client(role)
|
116
|
+
end
|
117
|
+
|
118
|
+
return client.call("XREAD", *arguments)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Create, destroy, and manage consumer groups.
|
122
|
+
# @parameter arguments [Array] XGROUP command arguments.
|
123
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
124
|
+
# @returns [String | Integer] Command result.
|
125
|
+
def xgroup(*arguments, role: :master)
|
126
|
+
# Extract stream key (usually third argument for CREATE, second for others):
|
127
|
+
stream_key = case arguments[0]&.upcase
|
128
|
+
when "CREATE", "SETID"
|
129
|
+
arguments[1] # CREATE stream group id, SETID stream group id
|
130
|
+
when "DESTROY", "DELCONSUMER"
|
131
|
+
arguments[1] # DESTROY stream group, DELCONSUMER stream group consumer
|
132
|
+
else
|
133
|
+
arguments[1] if arguments.length > 1
|
134
|
+
end
|
135
|
+
|
136
|
+
if stream_key
|
137
|
+
slot = slot_for(stream_key)
|
138
|
+
client = client_for(slot, role)
|
139
|
+
else
|
140
|
+
client = any_client(role)
|
141
|
+
end
|
142
|
+
|
143
|
+
return client.call("XGROUP", *arguments)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Read new entries from streams using a consumer group.
|
147
|
+
# @parameter arguments [Array] XREADGROUP arguments.
|
148
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
149
|
+
# @returns [Array] Entries for the consumer group.
|
150
|
+
def xreadgroup(*arguments, role: :master)
|
151
|
+
# Extract first stream key to determine shard:
|
152
|
+
streams_index = arguments.index("STREAMS")
|
153
|
+
|
154
|
+
if streams_index && streams_index + 1 < arguments.length
|
155
|
+
first_stream_key = arguments[streams_index + 1]
|
156
|
+
slot = slot_for(first_stream_key)
|
157
|
+
client = client_for(slot, role)
|
158
|
+
else
|
159
|
+
client = any_client(role)
|
160
|
+
end
|
161
|
+
|
162
|
+
return client.call("XREADGROUP", *arguments)
|
163
|
+
end
|
164
|
+
|
165
|
+
# Acknowledge processed messages in a consumer group.
|
166
|
+
# @parameter key [String] The stream key.
|
167
|
+
# @parameter arguments [Array] Group name and message IDs.
|
168
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
169
|
+
# @returns [Integer] Number of messages acknowledged.
|
170
|
+
def xack(key, *arguments, role: :master)
|
171
|
+
slot = slot_for(key)
|
172
|
+
client = client_for(slot, role)
|
173
|
+
|
174
|
+
return client.call("XACK", key, *arguments)
|
175
|
+
end
|
176
|
+
|
177
|
+
# Change ownership of messages in a consumer group.
|
178
|
+
# @parameter key [String] The stream key.
|
179
|
+
# @parameter arguments [Array] Group, consumer, min-idle-time, and message IDs.
|
180
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
181
|
+
# @returns [Array] Claimed messages.
|
182
|
+
def xclaim(key, *arguments, role: :master)
|
183
|
+
slot = slot_for(key)
|
184
|
+
client = client_for(slot, role)
|
185
|
+
|
186
|
+
return client.call("XCLAIM", key, *arguments)
|
187
|
+
end
|
188
|
+
|
189
|
+
# Get information about pending messages in a consumer group.
|
190
|
+
# @parameter key [String] The stream key.
|
191
|
+
# @parameter arguments [Array] Group name and optional consumer/range parameters.
|
192
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
193
|
+
# @returns [Array] Pending message information.
|
194
|
+
def xpending(key, *arguments, role: :master)
|
195
|
+
slot = slot_for(key)
|
196
|
+
client = client_for(slot, role)
|
197
|
+
|
198
|
+
return client.call("XPENDING", key, *arguments)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
@@ -0,0 +1,304 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Protocol
|
4
|
+
module Redis
|
5
|
+
module Cluster
|
6
|
+
module Methods
|
7
|
+
# Provides Redis String commands for cluster environments.
|
8
|
+
# String operations are routed to the appropriate shard based on the key.
|
9
|
+
module Strings
|
10
|
+
# Append a value to a key.
|
11
|
+
# @parameter key [String] The key to append to.
|
12
|
+
# @parameter value [String] The value to append.
|
13
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
14
|
+
# @returns [Integer] The length of the string after the append operation.
|
15
|
+
def append(key, value, role: :master)
|
16
|
+
slot = slot_for(key)
|
17
|
+
client = client_for(slot, role)
|
18
|
+
|
19
|
+
return client.call("APPEND", key, value)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Count set bits in a string.
|
23
|
+
# @parameter key [String] The key to count bits in.
|
24
|
+
# @parameter range [Array] Optional start and end positions.
|
25
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
26
|
+
# @returns [Integer] The number of set bits.
|
27
|
+
def bitcount(key, *range, role: :master)
|
28
|
+
slot = slot_for(key)
|
29
|
+
client = client_for(slot, role)
|
30
|
+
|
31
|
+
return client.call("BITCOUNT", key, *range)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Decrement the integer value of a key by one.
|
35
|
+
# @parameter key [String] The key to decrement.
|
36
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
37
|
+
# @returns [Integer] The value after decrementing.
|
38
|
+
def decr(key, role: :master)
|
39
|
+
slot = slot_for(key)
|
40
|
+
client = client_for(slot, role)
|
41
|
+
|
42
|
+
return client.call("DECR", key)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Decrement the integer value of a key by the given number.
|
46
|
+
# @parameter key [String] The key to decrement.
|
47
|
+
# @parameter decrement [Integer] The amount to decrement by.
|
48
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
49
|
+
# @returns [Integer] The value after decrementing.
|
50
|
+
def decrby(key, decrement, role: :master)
|
51
|
+
slot = slot_for(key)
|
52
|
+
client = client_for(slot, role)
|
53
|
+
|
54
|
+
return client.call("DECRBY", key, decrement)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Get the value of a key.
|
58
|
+
# @parameter key [String] The key to get.
|
59
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
60
|
+
# @returns [String | nil] The value, or `nil` if key doesn't exist.
|
61
|
+
def get(key, role: :master)
|
62
|
+
slot = slot_for(key)
|
63
|
+
client = client_for(slot, role)
|
64
|
+
|
65
|
+
return client.call("GET", key)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Get the bit value at offset in the string value stored at key.
|
69
|
+
# @parameter key [String] The key to get bit from.
|
70
|
+
# @parameter offset [Integer] The bit offset.
|
71
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
72
|
+
# @returns [Integer] The bit value (0 or 1).
|
73
|
+
def getbit(key, offset, role: :master)
|
74
|
+
slot = slot_for(key)
|
75
|
+
client = client_for(slot, role)
|
76
|
+
|
77
|
+
return client.call("GETBIT", key, offset)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Get a substring of the string stored at a key.
|
81
|
+
# @parameter key [String] The key to get range from.
|
82
|
+
# @parameter start_index [Integer] The start position.
|
83
|
+
# @parameter end_index [Integer] The end position.
|
84
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
85
|
+
# @returns [String] The substring.
|
86
|
+
def getrange(key, start_index, end_index, role: :master)
|
87
|
+
slot = slot_for(key)
|
88
|
+
client = client_for(slot, role)
|
89
|
+
|
90
|
+
return client.call("GETRANGE", key, start_index, end_index)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Set the string value of a key and return its old value.
|
94
|
+
# @parameter key [String] The key to set.
|
95
|
+
# @parameter value [String] The new value.
|
96
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
97
|
+
# @returns [String | nil] The old value, or `nil` if key didn't exist.
|
98
|
+
def getset(key, value, role: :master)
|
99
|
+
slot = slot_for(key)
|
100
|
+
client = client_for(slot, role)
|
101
|
+
|
102
|
+
return client.call("GETSET", key, value)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Increment the integer value of a key by one.
|
106
|
+
# @parameter key [String] The key to increment.
|
107
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
108
|
+
# @returns [Integer] The value after incrementing.
|
109
|
+
def incr(key, role: :master)
|
110
|
+
slot = slot_for(key)
|
111
|
+
client = client_for(slot, role)
|
112
|
+
|
113
|
+
return client.call("INCR", key)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Increment the integer value of a key by the given amount.
|
117
|
+
# @parameter key [String] The key to increment.
|
118
|
+
# @parameter increment [Integer] The amount to increment by.
|
119
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
120
|
+
# @returns [Integer] The value after incrementing.
|
121
|
+
def incrby(key, increment, role: :master)
|
122
|
+
slot = slot_for(key)
|
123
|
+
client = client_for(slot, role)
|
124
|
+
|
125
|
+
return client.call("INCRBY", key, increment)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Increment the float value of a key by the given amount.
|
129
|
+
# @parameter key [String] The key to increment.
|
130
|
+
# @parameter increment [Float] The amount to increment by.
|
131
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
132
|
+
# @returns [String] The value after incrementing (as string).
|
133
|
+
def incrbyfloat(key, increment, role: :master)
|
134
|
+
slot = slot_for(key)
|
135
|
+
client = client_for(slot, role)
|
136
|
+
|
137
|
+
return client.call("INCRBYFLOAT", key, increment)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Get the values of all the given keys.
|
141
|
+
# Uses the cluster-aware multi-key handling from generic methods.
|
142
|
+
# @parameter keys [Array(String)] The keys to get.
|
143
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
144
|
+
# @returns [Array] The values for the given keys, in order.
|
145
|
+
def mget(*keys, role: :master)
|
146
|
+
return [] if keys.empty?
|
147
|
+
|
148
|
+
results = Array.new(keys.size)
|
149
|
+
key_to_index = keys.each_with_index.to_h
|
150
|
+
|
151
|
+
clients_for(*keys, role: role) do |client, grouped_keys|
|
152
|
+
values = client.call("MGET", *grouped_keys)
|
153
|
+
grouped_keys.each_with_index do |key, i|
|
154
|
+
results[key_to_index[key]] = values[i]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
return results
|
159
|
+
end
|
160
|
+
|
161
|
+
# Set multiple keys to multiple values.
|
162
|
+
# Redis will return a CROSSSLOT error if keys span multiple slots.
|
163
|
+
# @parameter pairs [Hash] The key-value pairs to set.
|
164
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
165
|
+
# @returns [String] Status reply.
|
166
|
+
def mset(pairs, role: :master)
|
167
|
+
return if pairs.empty?
|
168
|
+
|
169
|
+
if pairs.is_a?(Hash)
|
170
|
+
pairs = pairs.to_a.flatten
|
171
|
+
end
|
172
|
+
|
173
|
+
slot = slot_for(pairs.first)
|
174
|
+
client = client_for(slot, role)
|
175
|
+
|
176
|
+
return client.call("MSET", *pairs)
|
177
|
+
end
|
178
|
+
|
179
|
+
# Set multiple keys to multiple values, only if none exist.
|
180
|
+
# Redis will return a CROSSSLOT error if keys span multiple slots.
|
181
|
+
# @parameter pairs [Hash] The key-value pairs to set.
|
182
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
183
|
+
# @returns [Integer] 1 if all keys were set, 0 otherwise.
|
184
|
+
def msetnx(pairs, role: :master)
|
185
|
+
keys = pairs.keys
|
186
|
+
return 0 if keys.empty?
|
187
|
+
|
188
|
+
flattened_pairs = pairs.keys.zip(pairs.values).flatten
|
189
|
+
slot = slot_for(keys.first)
|
190
|
+
client = client_for(slot, role)
|
191
|
+
|
192
|
+
return client.call("MSETNX", *flattened_pairs)
|
193
|
+
end
|
194
|
+
|
195
|
+
# Set the value and expiration in milliseconds of a key.
|
196
|
+
# @parameter key [String] The key to set.
|
197
|
+
# @parameter milliseconds [Integer] The expiration time in milliseconds.
|
198
|
+
# @parameter value [String] The value to set.
|
199
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
200
|
+
# @returns [String] Status reply.
|
201
|
+
def psetex(key, milliseconds, value, role: :master)
|
202
|
+
slot = slot_for(key)
|
203
|
+
client = client_for(slot, role)
|
204
|
+
|
205
|
+
return client.call("PSETEX", key, milliseconds, value)
|
206
|
+
end
|
207
|
+
|
208
|
+
# Set the string value of a key.
|
209
|
+
# @parameter key [String] The key to set.
|
210
|
+
# @parameter value [String] The value to set.
|
211
|
+
# @parameter update [Boolean | nil] If `true`, only update existing keys. If `false`, only set new keys.
|
212
|
+
# @parameter seconds [Integer | nil] Expiration time in seconds.
|
213
|
+
# @parameter milliseconds [Integer | nil] Expiration time in milliseconds.
|
214
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
215
|
+
# @returns [String] Status reply.
|
216
|
+
def set(key, value, update: nil, seconds: nil, milliseconds: nil, role: :master)
|
217
|
+
arguments = []
|
218
|
+
|
219
|
+
if seconds
|
220
|
+
arguments << "EX" << seconds
|
221
|
+
end
|
222
|
+
|
223
|
+
if milliseconds
|
224
|
+
arguments << "PX" << milliseconds
|
225
|
+
end
|
226
|
+
|
227
|
+
if update == true
|
228
|
+
arguments << "XX"
|
229
|
+
elsif update == false
|
230
|
+
arguments << "NX"
|
231
|
+
end
|
232
|
+
|
233
|
+
slot = slot_for(key)
|
234
|
+
client = client_for(slot, role)
|
235
|
+
|
236
|
+
return client.call("SET", key, value, *arguments)
|
237
|
+
end
|
238
|
+
|
239
|
+
# Set or clear the bit at offset in the string value stored at key.
|
240
|
+
# @parameter key [String] The key to modify.
|
241
|
+
# @parameter offset [Integer] The bit offset.
|
242
|
+
# @parameter value [Integer] The bit value (0 or 1).
|
243
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
244
|
+
# @returns [Integer] The original bit value.
|
245
|
+
def setbit(key, offset, value, role: :master)
|
246
|
+
slot = slot_for(key)
|
247
|
+
client = client_for(slot, role)
|
248
|
+
|
249
|
+
return client.call("SETBIT", key, offset, value)
|
250
|
+
end
|
251
|
+
|
252
|
+
# Set the value and expiration of a key.
|
253
|
+
# @parameter key [String] The key to set.
|
254
|
+
# @parameter seconds [Integer] The expiration time in seconds.
|
255
|
+
# @parameter value [String] The value to set.
|
256
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
257
|
+
# @returns [String] Status reply.
|
258
|
+
def setex(key, seconds, value, role: :master)
|
259
|
+
slot = slot_for(key)
|
260
|
+
client = client_for(slot, role)
|
261
|
+
|
262
|
+
return client.call("SETEX", key, seconds, value)
|
263
|
+
end
|
264
|
+
|
265
|
+
# Set the value of a key, only if the key does not exist.
|
266
|
+
# @parameter key [String] The key to set.
|
267
|
+
# @parameter value [String] The value to set.
|
268
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
269
|
+
# @returns [Boolean] `true` if the key was set, `false` otherwise.
|
270
|
+
def setnx(key, value, role: :master)
|
271
|
+
slot = slot_for(key)
|
272
|
+
client = client_for(slot, role)
|
273
|
+
|
274
|
+
return client.call("SETNX", key, value) == 1
|
275
|
+
end
|
276
|
+
|
277
|
+
# Overwrite part of a string at key starting at the specified offset.
|
278
|
+
# @parameter key [String] The key to modify.
|
279
|
+
# @parameter offset [Integer] The offset to start overwriting at.
|
280
|
+
# @parameter value [String] The value to write.
|
281
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
282
|
+
# @returns [Integer] The length of the string after modification.
|
283
|
+
def setrange(key, offset, value, role: :master)
|
284
|
+
slot = slot_for(key)
|
285
|
+
client = client_for(slot, role)
|
286
|
+
|
287
|
+
return client.call("SETRANGE", key, offset, value)
|
288
|
+
end
|
289
|
+
|
290
|
+
# Get the length of the value stored in a key.
|
291
|
+
# @parameter key [String] The key to get length of.
|
292
|
+
# @parameter role [Symbol] The role of node to use (`:master` or `:slave`).
|
293
|
+
# @returns [Integer] The length of the string value, or 0 if key doesn't exist.
|
294
|
+
def strlen(key, role: :master)
|
295
|
+
slot = slot_for(key)
|
296
|
+
client = client_for(slot, role)
|
297
|
+
|
298
|
+
return client.call("STRLEN", key)
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
5
|
+
|
6
|
+
require_relative "methods/generic"
|
7
|
+
require_relative "methods/pubsub"
|
8
|
+
require_relative "methods/streams"
|
9
|
+
require_relative "methods/strings"
|
10
|
+
|
11
|
+
module Protocol
|
12
|
+
module Redis
|
13
|
+
# @namespace
|
14
|
+
module Cluster
|
15
|
+
# A collection of methods for interacting with Redis in cluster mode.
|
16
|
+
module Methods
|
17
|
+
# Includes all Redis methods into the given class.
|
18
|
+
def self.included(klass)
|
19
|
+
klass.include Methods::Generic
|
20
|
+
klass.include Methods::Pubsub
|
21
|
+
klass.include Methods::Streams
|
22
|
+
klass.include Methods::Strings
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -137,7 +137,7 @@ module Protocol
|
|
137
137
|
else
|
138
138
|
@stream.flush
|
139
139
|
|
140
|
-
raise
|
140
|
+
raise UnknownTokenError, token.inspect
|
141
141
|
end
|
142
142
|
|
143
143
|
# TODO: If an exception (e.g. Async::TimeoutError) propagates out of this function, perhaps @stream should be closed? Otherwise it might be in a weird state.
|
data/lib/protocol/redis/error.rb
CHANGED
@@ -7,32 +7,30 @@
|
|
7
7
|
module Protocol
|
8
8
|
module Redis
|
9
9
|
module Methods
|
10
|
-
#
|
10
|
+
# Methods for managing Redis scripting.
|
11
11
|
module Scripting
|
12
|
-
# Execute a Lua script server side.
|
13
|
-
#
|
14
|
-
# @parameter
|
15
|
-
# @parameter
|
16
|
-
# @
|
17
|
-
|
18
|
-
|
19
|
-
call("EVAL", *arguments)
|
12
|
+
# Execute a Lua script server side.
|
13
|
+
# @parameter script [String] The Lua script to execute.
|
14
|
+
# @parameter key_count [Integer] Number of keys that follow.
|
15
|
+
# @parameter keys_and_args [Array] Keys followed by arguments to the script.
|
16
|
+
# @returns [Object] The result of the script execution.
|
17
|
+
def eval(script, key_count = 0, *keys_and_args)
|
18
|
+
call("EVAL", script, key_count, *keys_and_args)
|
20
19
|
end
|
21
20
|
|
22
|
-
# Execute a Lua script
|
23
|
-
#
|
24
|
-
# @parameter
|
25
|
-
# @parameter
|
26
|
-
# @
|
27
|
-
|
28
|
-
|
29
|
-
call("EVALSHA", *arguments)
|
21
|
+
# Execute a cached Lua script by SHA1 digest.
|
22
|
+
# @parameter sha1 [String] The SHA1 digest of the script to execute.
|
23
|
+
# @parameter key_count [Integer] Number of keys that follow.
|
24
|
+
# @parameter keys_and_args [Array] Keys followed by arguments to the script.
|
25
|
+
# @returns [Object] The result of the script execution.
|
26
|
+
def evalsha(sha1, key_count = 0, *keys_and_args)
|
27
|
+
call("EVALSHA", sha1, key_count, *keys_and_args)
|
30
28
|
end
|
31
29
|
|
32
|
-
# Execute script management commands
|
33
|
-
#
|
34
|
-
# @parameter
|
35
|
-
# @
|
30
|
+
# Execute script management commands.
|
31
|
+
# @parameter subcommand [String|Symbol] The script subcommand (debug, exists, flush, load, kill).
|
32
|
+
# @parameter arguments [Array] Additional arguments for the subcommand.
|
33
|
+
# @returns [Object] The result of the script command.
|
36
34
|
def script(subcommand, *arguments)
|
37
35
|
call("SCRIPT", subcommand.to_s, *arguments)
|
38
36
|
end
|
@@ -103,9 +103,11 @@ module Protocol
|
|
103
103
|
# Set multiple keys to multiple values. O(N) where N is the number of keys to set.
|
104
104
|
# See <https://redis.io/commands/mset> for more details.
|
105
105
|
def mset(pairs)
|
106
|
-
|
106
|
+
if pairs.is_a?(Hash)
|
107
|
+
pairs = pairs.to_a.flatten
|
108
|
+
end
|
107
109
|
|
108
|
-
call("MSET", *
|
110
|
+
call("MSET", *pairs)
|
109
111
|
end
|
110
112
|
|
111
113
|
# Set multiple keys to multiple values, only if none of the keys exist. O(N) where N is the number of keys to set.
|
data/readme.md
CHANGED
@@ -14,6 +14,10 @@ Please see the [project documentation](https://socketry.github.io/protocol-redis
|
|
14
14
|
|
15
15
|
Please see the [project releases](https://socketry.github.io/protocol-redis/releases/index) for all releases.
|
16
16
|
|
17
|
+
### v0.11.0
|
18
|
+
|
19
|
+
- Introduce cluster methods, including `Strings`, `Streams`, `Scripting` and `Pubsub`.
|
20
|
+
|
17
21
|
### v0.10.0
|
18
22
|
|
19
23
|
- Add agent context.
|
@@ -63,10 +67,6 @@ Please see the [project releases](https://socketry.github.io/protocol-redis/rele
|
|
63
67
|
- Improve argument management.
|
64
68
|
- Modernize testing infrastructure.
|
65
69
|
|
66
|
-
### v0.4.2
|
67
|
-
|
68
|
-
- Prefer implicit returns and improve return value for `setnx`.
|
69
|
-
|
70
70
|
## Contributing
|
71
71
|
|
72
72
|
We welcome contributions to this project.
|
data/releases.md
CHANGED
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: protocol-redis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
@@ -53,6 +53,12 @@ files:
|
|
53
53
|
- context/getting-started.md
|
54
54
|
- context/index.yaml
|
55
55
|
- lib/protocol/redis.rb
|
56
|
+
- lib/protocol/redis/cluster/methods.rb
|
57
|
+
- lib/protocol/redis/cluster/methods/generic.rb
|
58
|
+
- lib/protocol/redis/cluster/methods/pubsub.rb
|
59
|
+
- lib/protocol/redis/cluster/methods/scripting.rb
|
60
|
+
- lib/protocol/redis/cluster/methods/streams.rb
|
61
|
+
- lib/protocol/redis/cluster/methods/strings.rb
|
56
62
|
- lib/protocol/redis/connection.rb
|
57
63
|
- lib/protocol/redis/error.rb
|
58
64
|
- lib/protocol/redis/methods.rb
|
metadata.gz.sig
CHANGED
Binary file
|