protocol-redis 0.9.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 +1 -2
- data/context/getting-started.md +55 -0
- data/context/index.yaml +13 -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 +41 -12
- data/lib/protocol/redis/error.rb +6 -0
- data/lib/protocol/redis/methods/cluster.rb +9 -7
- data/lib/protocol/redis/methods/connection.rb +9 -8
- data/lib/protocol/redis/methods/counting.rb +9 -8
- data/lib/protocol/redis/methods/generic.rb +100 -99
- data/lib/protocol/redis/methods/geospatial.rb +42 -49
- data/lib/protocol/redis/methods/hashes.rb +84 -83
- data/lib/protocol/redis/methods/lists.rb +75 -74
- data/lib/protocol/redis/methods/pubsub.rb +5 -4
- data/lib/protocol/redis/methods/scripting.rb +19 -20
- data/lib/protocol/redis/methods/server.rb +13 -9
- data/lib/protocol/redis/methods/sets.rb +42 -41
- data/lib/protocol/redis/methods/sorted_sets.rb +110 -109
- data/lib/protocol/redis/methods/streams.rb +48 -47
- data/lib/protocol/redis/methods/strings.rb +112 -109
- data/lib/protocol/redis/methods.rb +16 -14
- data/lib/protocol/redis/version.rb +1 -1
- data/lib/protocol/redis.rb +9 -2
- data/readme.md +49 -21
- data/releases.md +98 -0
- data.tar.gz.sig +0 -0
- metadata +13 -9
- 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
@@ -1,2 +1 @@
|
|
1
|
-
x
|
2
|
-
�S7fT���_
|
1
|
+
x��n1�Xԋ�!��<VT}�)�3�a�i<CaX�d<��@�t�?d
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# Getting Started
|
2
|
+
|
3
|
+
This guide explains how to use the `Protocol::Redis` gem to implement the RESP2 and RESP3 Redis protocols for low level client and server implementations.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add the gem to your project:
|
8
|
+
|
9
|
+
```bash
|
10
|
+
$ bundle add protocol-redis
|
11
|
+
```
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
Here is a basic example communicating over a bi-directional socket pair:
|
16
|
+
|
17
|
+
``` ruby
|
18
|
+
sockets = Socket.pair(Socket::PF_UNIX, Socket::SOCK_STREAM)
|
19
|
+
|
20
|
+
client = Protocol::Redis::Connection.new(sockets.first)
|
21
|
+
server = Protocol::Redis::Connection.new(sockets.last)
|
22
|
+
|
23
|
+
client.write_object("Hello World!")
|
24
|
+
puts server.read_object
|
25
|
+
# => "Hello World!"
|
26
|
+
```
|
27
|
+
|
28
|
+
## Methods
|
29
|
+
|
30
|
+
{ruby Protocol::Redis::Methods} provides access to documented Redis commands. You can use these methods by inluding the module in your class:
|
31
|
+
|
32
|
+
``` ruby
|
33
|
+
class MyRedisClient
|
34
|
+
include Protocol::Redis::Methods
|
35
|
+
|
36
|
+
def call(*arguments)
|
37
|
+
connection = self.acquire # Connection management is up to you
|
38
|
+
|
39
|
+
connection.write_request(arguments)
|
40
|
+
connection.flush
|
41
|
+
|
42
|
+
return connection.read_response
|
43
|
+
end
|
44
|
+
end
|
45
|
+
```
|
46
|
+
You can then call Redis commands like this:
|
47
|
+
|
48
|
+
``` ruby
|
49
|
+
client = MyRedisClient.new
|
50
|
+
client.set("key", "value")
|
51
|
+
```
|
52
|
+
|
53
|
+
### Valkey Support
|
54
|
+
|
55
|
+
You can always use `#call` to send any command. This library provides a set of methods for what we believe are the most commonly used commands (in other words, the intersection of Redis and Valkey commands). If you need more commands, youn could define these yourself similarly to how {ruby Protocol::Redis::Methods} does.
|
data/context/index.yaml
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Automatically generated context index for Utopia::Project guides.
|
2
|
+
# Do not edit then files in this directory directly, instead edit the guides and then run `bake utopia:project:agent:context:update`.
|
3
|
+
---
|
4
|
+
description: A transport agnostic RESP protocol client/server.
|
5
|
+
metadata:
|
6
|
+
documentation_uri: https://socketry.github.io/protocol-redis/
|
7
|
+
funding_uri: https://github.com/sponsors/ioquatix
|
8
|
+
source_code_uri: https://github.com/socketry/protocol-redis.git
|
9
|
+
files:
|
10
|
+
- path: getting-started.md
|
11
|
+
title: Getting Started
|
12
|
+
description: This guide explains how to use the `Protocol::Redis` gem to implement
|
13
|
+
the RESP2 and RESP3 Redis protocols for low level client and server implementations.
|
@@ -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
|