valkey-glide-rb 0.9.1 → 0.9.2
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/.rubocop.yml +1 -1
- data/lib/valkey/bindings.rb +57 -0
- data/lib/valkey/commands/cluster_commands.rb +28 -14
- data/lib/valkey/commands/connection_commands.rb +21 -12
- data/lib/valkey/commands/function_commands.rb +30 -20
- data/lib/valkey/commands/generic_commands.rb +10 -7
- data/lib/valkey/commands/scripting_commands.rb +46 -20
- data/lib/valkey/commands/server_commands.rb +72 -50
- data/lib/valkey/commands/string_commands.rb +8 -1
- data/lib/valkey/commands/transaction_commands.rb +65 -14
- data/lib/valkey/native/aarch64-apple-darwin/libglide_ffi.dylib +0 -0
- data/lib/valkey/native/aarch64-unknown-linux-gnu/libglide_ffi.so +0 -0
- data/lib/valkey/native/aarch64-unknown-linux-musl/libglide_ffi.so +0 -0
- data/lib/valkey/native/x86_64-unknown-linux-gnu/libglide_ffi.so +0 -0
- data/lib/valkey/native/x86_64-unknown-linux-musl/libglide_ffi.so +0 -0
- data/lib/valkey/route.rb +106 -0
- data/lib/valkey/utils.rb +5 -0
- data/lib/valkey/version.rb +1 -1
- data/lib/valkey.rb +75 -37
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 17396d39342cbcc8a293f5e8b252e1689841b436664c630e330d06b0a1cff9c9
|
|
4
|
+
data.tar.gz: 7fa260b6afbb060a1c79a8dcb264aae5b211a24e69b042c720e242d39b7b3ab9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e96a09b0077bd54a30722201d03ccd8bb8baf7a47c0005d5c3d87441bfeb015479181548dce68bd1e882ee9e266be4658e68b2562e32dca6c48c101ca4e0e2cc
|
|
7
|
+
data.tar.gz: dbee972964d0a2e54dbf7f4916c2bab6427525ace1d97a679845409b9d55390419631c8407df7502ca9b3561bd51ea0bba1ebecd8bbef680cdaa45b5ba28f405
|
data/.rubocop.yml
CHANGED
data/lib/valkey/bindings.rb
CHANGED
|
@@ -123,6 +123,33 @@ class Valkey
|
|
|
123
123
|
)
|
|
124
124
|
end
|
|
125
125
|
|
|
126
|
+
# Mirrors Rust's `RouteType` enum (valkey-glide/ffi/src/lib.rs)
|
|
127
|
+
RouteType = enum(
|
|
128
|
+
:all_nodes, 0,
|
|
129
|
+
:all_primaries,
|
|
130
|
+
:random,
|
|
131
|
+
:slot_id,
|
|
132
|
+
:slot_key,
|
|
133
|
+
:by_address
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Mirrors Rust's `SlotType` enum (a mirror of `SlotAddr`)
|
|
137
|
+
SlotType = enum(
|
|
138
|
+
:primary, 0,
|
|
139
|
+
:replica
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
class RouteInfo < FFI::Struct
|
|
143
|
+
layout(
|
|
144
|
+
:route_type, RouteType,
|
|
145
|
+
:slot_id, :int32, # slot number (for SlotId route)
|
|
146
|
+
:slot_key, :pointer, # *const c_char (for SlotKey route; NULL otherwise)
|
|
147
|
+
:slot_type, SlotType,
|
|
148
|
+
:hostname, :pointer, # *const c_char (for ByAddress route; NULL otherwise)
|
|
149
|
+
:port, :int32 # port number (for ByAddress route)
|
|
150
|
+
)
|
|
151
|
+
end
|
|
152
|
+
|
|
126
153
|
class BatchOptionsInfo < FFI::Struct
|
|
127
154
|
layout(
|
|
128
155
|
:retry_server_error, :bool,
|
|
@@ -226,6 +253,23 @@ class Valkey
|
|
|
226
253
|
:pointer # *mut ConnectionResponse
|
|
227
254
|
], :void
|
|
228
255
|
|
|
256
|
+
attach_function :free_command_result, [
|
|
257
|
+
:pointer # *mut CommandResult
|
|
258
|
+
], :void
|
|
259
|
+
|
|
260
|
+
attach_function :free_script_hash_buffer, [
|
|
261
|
+
:pointer # *mut ScriptHashBuffer
|
|
262
|
+
], :void
|
|
263
|
+
|
|
264
|
+
attach_function :drop_script, [
|
|
265
|
+
:pointer, # *mut u8 (hash bytes)
|
|
266
|
+
:ulong # usize (hash length)
|
|
267
|
+
], :pointer # returns *mut c_char (null on success, error string on failure)
|
|
268
|
+
|
|
269
|
+
attach_function :free_drop_script_error, [
|
|
270
|
+
:pointer # *mut c_char (error from drop_script)
|
|
271
|
+
], :void
|
|
272
|
+
|
|
229
273
|
attach_function :close_client, [
|
|
230
274
|
:pointer # client_adapter_ptr
|
|
231
275
|
], :void
|
|
@@ -242,6 +286,19 @@ class Valkey
|
|
|
242
286
|
:ulong # span_ptr (u64)
|
|
243
287
|
], :pointer, blocking: true # returns *mut CommandResult, releases GVL during I/O
|
|
244
288
|
|
|
289
|
+
attach_function :command_with_route_info, [
|
|
290
|
+
:pointer, # client_adapter_ptr
|
|
291
|
+
:ulong, # request_id
|
|
292
|
+
:int, # command_type (RequestType)
|
|
293
|
+
:ulong, # arg_count
|
|
294
|
+
:pointer, # args (pointer to usize[])
|
|
295
|
+
:pointer, # args_len (pointer to c_ulong[])
|
|
296
|
+
:pointer, # route_info (*const RouteInfo, or NULL for no route)
|
|
297
|
+
:pointer, # response_buf (NULL = normal response path)
|
|
298
|
+
:ulong, # response_buf_len (0 if response_buf is NULL)
|
|
299
|
+
:ulong # span_ptr (u64)
|
|
300
|
+
], :pointer, blocking: true # returns *mut CommandResult, releases GVL during I/O
|
|
301
|
+
|
|
245
302
|
attach_function :batch, [
|
|
246
303
|
:pointer, # client_ptr
|
|
247
304
|
:ulong, # callback_index
|
|
@@ -124,10 +124,15 @@ class Valkey
|
|
|
124
124
|
|
|
125
125
|
# Get information about the cluster.
|
|
126
126
|
#
|
|
127
|
+
# @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
|
|
127
128
|
# @return [Hash<String, String>] cluster information
|
|
128
|
-
def cluster_info
|
|
129
|
-
send_command(RequestType::CLUSTER_INFO) do |reply|
|
|
130
|
-
|
|
129
|
+
def cluster_info(route: nil)
|
|
130
|
+
send_command(RequestType::CLUSTER_INFO, [], route: route) do |reply|
|
|
131
|
+
if reply.is_a?(Hash)
|
|
132
|
+
reply.transform_values { |v| Utils::HashifyInfo.call(v) }
|
|
133
|
+
else
|
|
134
|
+
Utils::HashifyInfo.call(reply)
|
|
135
|
+
end
|
|
131
136
|
end
|
|
132
137
|
end
|
|
133
138
|
|
|
@@ -141,9 +146,10 @@ class Valkey
|
|
|
141
146
|
|
|
142
147
|
# Get information about cluster links.
|
|
143
148
|
#
|
|
149
|
+
# @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
|
|
144
150
|
# @return [Array<Hash>] array of link information
|
|
145
|
-
def cluster_links
|
|
146
|
-
send_command(RequestType::CLUSTER_LINKS)
|
|
151
|
+
def cluster_links(route: nil)
|
|
152
|
+
send_command(RequestType::CLUSTER_LINKS, [], route: route)
|
|
147
153
|
end
|
|
148
154
|
|
|
149
155
|
# Meet another node in the cluster.
|
|
@@ -157,24 +163,31 @@ class Valkey
|
|
|
157
163
|
|
|
158
164
|
# Get the ID of the current node.
|
|
159
165
|
#
|
|
166
|
+
# @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
|
|
160
167
|
# @return [String] node ID
|
|
161
|
-
def cluster_myid
|
|
162
|
-
send_command(RequestType::CLUSTER_MY_ID)
|
|
168
|
+
def cluster_myid(route: nil)
|
|
169
|
+
send_command(RequestType::CLUSTER_MY_ID, [], route: route)
|
|
163
170
|
end
|
|
164
171
|
|
|
165
172
|
# Get the shard ID of the current node.
|
|
166
173
|
#
|
|
174
|
+
# @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
|
|
167
175
|
# @return [String] shard ID
|
|
168
|
-
def cluster_myshardid
|
|
169
|
-
send_command(RequestType::CLUSTER_MY_SHARD_ID)
|
|
176
|
+
def cluster_myshardid(route: nil)
|
|
177
|
+
send_command(RequestType::CLUSTER_MY_SHARD_ID, [], route: route)
|
|
170
178
|
end
|
|
171
179
|
|
|
172
180
|
# Get information about all nodes in the cluster.
|
|
173
181
|
#
|
|
182
|
+
# @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
|
|
174
183
|
# @return [Array<Hash>] array of node information
|
|
175
|
-
def cluster_nodes
|
|
176
|
-
send_command(RequestType::CLUSTER_NODES) do |reply|
|
|
177
|
-
|
|
184
|
+
def cluster_nodes(route: nil)
|
|
185
|
+
send_command(RequestType::CLUSTER_NODES, [], route: route) do |reply|
|
|
186
|
+
if reply.is_a?(Hash)
|
|
187
|
+
reply.transform_values { |v| Utils::HashifyClusterNodes.call(v) }
|
|
188
|
+
else
|
|
189
|
+
Utils::HashifyClusterNodes.call(reply)
|
|
190
|
+
end
|
|
178
191
|
end
|
|
179
192
|
end
|
|
180
193
|
|
|
@@ -235,9 +248,10 @@ class Valkey
|
|
|
235
248
|
|
|
236
249
|
# Get information about cluster shards.
|
|
237
250
|
#
|
|
251
|
+
# @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
|
|
238
252
|
# @return [Array<Hash>] array of shard information
|
|
239
|
-
def cluster_shards
|
|
240
|
-
send_command(RequestType::CLUSTER_SHARDS)
|
|
253
|
+
def cluster_shards(route: nil)
|
|
254
|
+
send_command(RequestType::CLUSTER_SHARDS, [], route: route)
|
|
241
255
|
end
|
|
242
256
|
|
|
243
257
|
# Get information about slave nodes (deprecated, use cluster_replicas).
|
|
@@ -18,18 +18,25 @@ class Valkey
|
|
|
18
18
|
|
|
19
19
|
# Ping the server.
|
|
20
20
|
#
|
|
21
|
-
# @param [
|
|
22
|
-
# @
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
# @param message [String, nil] optional message to echo back
|
|
22
|
+
# @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
|
|
23
|
+
# @return [String]
|
|
24
|
+
#
|
|
25
|
+
# @example
|
|
26
|
+
# ping #=> "PONG"
|
|
27
|
+
# ping("hello") #=> "hello"
|
|
28
|
+
# ping(route: Valkey::Route.all_nodes) #=> Hash (multi-node)
|
|
29
|
+
def ping(message = nil, route: nil)
|
|
30
|
+
send_command(RequestType::PING, [message].compact, route: route)
|
|
25
31
|
end
|
|
26
32
|
|
|
27
33
|
# Echo the given string.
|
|
28
34
|
#
|
|
29
|
-
# @param [String]
|
|
35
|
+
# @param value [String]
|
|
36
|
+
# @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
|
|
30
37
|
# @return [String]
|
|
31
|
-
def echo(value)
|
|
32
|
-
send_command(RequestType::ECHO, [value])
|
|
38
|
+
def echo(value, route: nil)
|
|
39
|
+
send_command(RequestType::ECHO, [value], route: route)
|
|
33
40
|
end
|
|
34
41
|
|
|
35
42
|
# Change the selected database for the current connection.
|
|
@@ -99,9 +106,10 @@ class Valkey
|
|
|
99
106
|
|
|
100
107
|
# Get the current client's ID.
|
|
101
108
|
#
|
|
102
|
-
# @
|
|
103
|
-
|
|
104
|
-
|
|
109
|
+
# @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
|
|
110
|
+
# @return [Integer]
|
|
111
|
+
def client_id(route: nil)
|
|
112
|
+
send_command(RequestType::CLIENT_ID, [], route: route)
|
|
105
113
|
end
|
|
106
114
|
|
|
107
115
|
# Get the current client's name.
|
|
@@ -205,9 +213,10 @@ class Valkey
|
|
|
205
213
|
|
|
206
214
|
# Unpause client processing.
|
|
207
215
|
#
|
|
216
|
+
# @param route [Valkey::Route, nil] cluster routing.
|
|
208
217
|
# @return [String] `OK`
|
|
209
|
-
def client_unpause
|
|
210
|
-
send_command(RequestType::CLIENT_UNPAUSE)
|
|
218
|
+
def client_unpause(route: nil)
|
|
219
|
+
send_command(RequestType::CLIENT_UNPAUSE, [], route: route)
|
|
211
220
|
end
|
|
212
221
|
|
|
213
222
|
# Configure client reply mode.
|
|
@@ -14,11 +14,12 @@ class Valkey
|
|
|
14
14
|
# # => "OK"
|
|
15
15
|
#
|
|
16
16
|
# @param [String] library_name the library name to delete
|
|
17
|
+
# @param route [Valkey::Route, nil] cluster routing.
|
|
17
18
|
# @return [String] "OK"
|
|
18
19
|
#
|
|
19
20
|
# @see https://valkey.io/commands/function-delete/
|
|
20
|
-
def function_delete(library_name)
|
|
21
|
-
send_command(RequestType::FUNCTION_DELETE, [library_name])
|
|
21
|
+
def function_delete(library_name, route: nil)
|
|
22
|
+
send_command(RequestType::FUNCTION_DELETE, [library_name], route: route)
|
|
22
23
|
end
|
|
23
24
|
|
|
24
25
|
# Return the serialized payload of loaded libraries.
|
|
@@ -27,11 +28,12 @@ class Valkey
|
|
|
27
28
|
# valkey.function_dump
|
|
28
29
|
# # => <binary string>
|
|
29
30
|
#
|
|
31
|
+
# @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
|
|
30
32
|
# @return [String] the serialized payload
|
|
31
33
|
#
|
|
32
34
|
# @see https://valkey.io/commands/function-dump/
|
|
33
|
-
def function_dump
|
|
34
|
-
send_command(RequestType::FUNCTION_DUMP)
|
|
35
|
+
def function_dump(route: nil)
|
|
36
|
+
send_command(RequestType::FUNCTION_DUMP, [], route: route)
|
|
35
37
|
end
|
|
36
38
|
|
|
37
39
|
# Delete all libraries.
|
|
@@ -48,10 +50,11 @@ class Valkey
|
|
|
48
50
|
#
|
|
49
51
|
# @param [Boolean] async flush asynchronously
|
|
50
52
|
# @param [Boolean] sync flush synchronously
|
|
53
|
+
# @param route [Valkey::Route, nil] cluster routing.
|
|
51
54
|
# @return [String] "OK"
|
|
52
55
|
#
|
|
53
56
|
# @see https://valkey.io/commands/function-flush/
|
|
54
|
-
def function_flush(async: false, sync: false)
|
|
57
|
+
def function_flush(async: false, sync: false, route: nil)
|
|
55
58
|
args = []
|
|
56
59
|
|
|
57
60
|
if async
|
|
@@ -60,7 +63,7 @@ class Valkey
|
|
|
60
63
|
args << "SYNC"
|
|
61
64
|
end
|
|
62
65
|
|
|
63
|
-
send_command(RequestType::FUNCTION_FLUSH, args)
|
|
66
|
+
send_command(RequestType::FUNCTION_FLUSH, args, route: route)
|
|
64
67
|
end
|
|
65
68
|
|
|
66
69
|
# Kill a function that is currently executing.
|
|
@@ -69,11 +72,12 @@ class Valkey
|
|
|
69
72
|
# valkey.function_kill
|
|
70
73
|
# # => "OK"
|
|
71
74
|
#
|
|
75
|
+
# @param route [Valkey::Route, nil] cluster routing.
|
|
72
76
|
# @return [String] "OK"
|
|
73
77
|
#
|
|
74
78
|
# @see https://valkey.io/commands/function-kill/
|
|
75
|
-
def function_kill
|
|
76
|
-
send_command(RequestType::FUNCTION_KILL)
|
|
79
|
+
def function_kill(route: nil)
|
|
80
|
+
send_command(RequestType::FUNCTION_KILL, [], route: route)
|
|
77
81
|
end
|
|
78
82
|
|
|
79
83
|
# Return information about the functions and libraries.
|
|
@@ -90,10 +94,11 @@ class Valkey
|
|
|
90
94
|
#
|
|
91
95
|
# @param [String] library_name filter by library name pattern
|
|
92
96
|
# @param [Boolean] with_code include the library code in the response
|
|
97
|
+
# @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
|
|
93
98
|
# @return [Array<Hash>] array of library information
|
|
94
99
|
#
|
|
95
100
|
# @see https://valkey.io/commands/function-list/
|
|
96
|
-
def function_list(library_name: nil, with_code: false)
|
|
101
|
+
def function_list(library_name: nil, with_code: false, route: nil)
|
|
97
102
|
args = []
|
|
98
103
|
|
|
99
104
|
if library_name
|
|
@@ -103,7 +108,7 @@ class Valkey
|
|
|
103
108
|
|
|
104
109
|
args << "WITHCODE" if with_code
|
|
105
110
|
|
|
106
|
-
send_command(RequestType::FUNCTION_LIST, args)
|
|
111
|
+
send_command(RequestType::FUNCTION_LIST, args, route: route)
|
|
107
112
|
end
|
|
108
113
|
|
|
109
114
|
# Load a library to Valkey.
|
|
@@ -118,15 +123,16 @@ class Valkey
|
|
|
118
123
|
#
|
|
119
124
|
# @param [String] function_code the source code
|
|
120
125
|
# @param [Boolean] replace replace the library if it exists
|
|
126
|
+
# @param route [Valkey::Route, nil] cluster routing.
|
|
121
127
|
# @return [String] the library name that was loaded
|
|
122
128
|
#
|
|
123
129
|
# @see https://valkey.io/commands/function-load/
|
|
124
|
-
def function_load(function_code, replace: false)
|
|
130
|
+
def function_load(function_code, replace: false, route: nil)
|
|
125
131
|
args = []
|
|
126
132
|
args << "REPLACE" if replace
|
|
127
133
|
args << function_code
|
|
128
134
|
|
|
129
|
-
send_command(RequestType::FUNCTION_LOAD, args)
|
|
135
|
+
send_command(RequestType::FUNCTION_LOAD, args, route: route)
|
|
130
136
|
end
|
|
131
137
|
|
|
132
138
|
# Restore libraries from a payload.
|
|
@@ -147,15 +153,16 @@ class Valkey
|
|
|
147
153
|
#
|
|
148
154
|
# @param [String] serialized_value the serialized payload from FUNCTION DUMP
|
|
149
155
|
# @param [String] policy the restore policy: "FLUSH", "APPEND", or "REPLACE"
|
|
156
|
+
# @param route [Valkey::Route, nil] cluster routing.
|
|
150
157
|
# @return [String] "OK"
|
|
151
158
|
#
|
|
152
159
|
# @see https://valkey.io/commands/function-restore/
|
|
153
|
-
def function_restore(serialized_value, policy: nil)
|
|
160
|
+
def function_restore(serialized_value, policy: nil, route: nil)
|
|
154
161
|
args = [serialized_value]
|
|
155
162
|
|
|
156
163
|
args << policy.to_s.upcase if policy
|
|
157
164
|
|
|
158
|
-
send_command(RequestType::FUNCTION_RESTORE, args)
|
|
165
|
+
send_command(RequestType::FUNCTION_RESTORE, args, route: route)
|
|
159
166
|
end
|
|
160
167
|
|
|
161
168
|
# Return information about the function that's currently running.
|
|
@@ -164,11 +171,12 @@ class Valkey
|
|
|
164
171
|
# valkey.function_stats
|
|
165
172
|
# # => {"running_script" => {...}, "engines" => {...}}
|
|
166
173
|
#
|
|
174
|
+
# @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
|
|
167
175
|
# @return [Hash] function execution statistics
|
|
168
176
|
#
|
|
169
177
|
# @see https://valkey.io/commands/function-stats/
|
|
170
|
-
def function_stats
|
|
171
|
-
send_command(RequestType::FUNCTION_STATS)
|
|
178
|
+
def function_stats(route: nil)
|
|
179
|
+
send_command(RequestType::FUNCTION_STATS, [], route: route)
|
|
172
180
|
end
|
|
173
181
|
|
|
174
182
|
# Invoke a function.
|
|
@@ -183,12 +191,13 @@ class Valkey
|
|
|
183
191
|
# @param [String] function the function name
|
|
184
192
|
# @param [Array<String>] keys the keys to pass to the function
|
|
185
193
|
# @param [Array<String>] args the arguments to pass to the function
|
|
194
|
+
# @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
|
|
186
195
|
# @return [Object] the function result
|
|
187
196
|
#
|
|
188
197
|
# @see https://valkey.io/commands/fcall/
|
|
189
|
-
def fcall(function, keys: [], args: [])
|
|
198
|
+
def fcall(function, keys: [], args: [], route: nil)
|
|
190
199
|
command_args = [function, keys.size] + keys + args
|
|
191
|
-
send_command(RequestType::FCALL, command_args)
|
|
200
|
+
send_command(RequestType::FCALL, command_args, route: route)
|
|
192
201
|
end
|
|
193
202
|
|
|
194
203
|
# Invoke a read-only function.
|
|
@@ -200,12 +209,13 @@ class Valkey
|
|
|
200
209
|
# @param [String] function the function name
|
|
201
210
|
# @param [Array<String>] keys the keys to pass to the function
|
|
202
211
|
# @param [Array<String>] args the arguments to pass to the function
|
|
212
|
+
# @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
|
|
203
213
|
# @return [Object] the function result
|
|
204
214
|
#
|
|
205
215
|
# @see https://valkey.io/commands/fcall_ro/
|
|
206
|
-
def fcall_ro(function, keys: [], args: [])
|
|
216
|
+
def fcall_ro(function, keys: [], args: [], route: nil)
|
|
207
217
|
command_args = [function, keys.size] + keys + args
|
|
208
|
-
send_command(RequestType::FCALL_READ_ONLY, command_args)
|
|
218
|
+
send_command(RequestType::FCALL_READ_ONLY, command_args, route: route)
|
|
209
219
|
end
|
|
210
220
|
|
|
211
221
|
# Control function registry (convenience method).
|
|
@@ -394,9 +394,10 @@ class Valkey
|
|
|
394
394
|
|
|
395
395
|
# Return a random key from the keyspace.
|
|
396
396
|
#
|
|
397
|
-
# @
|
|
398
|
-
|
|
399
|
-
|
|
397
|
+
# @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
|
|
398
|
+
# @return [String, nil]
|
|
399
|
+
def randomkey(route: nil)
|
|
400
|
+
send_command(RequestType::RANDOM_KEY, [], route: route)
|
|
400
401
|
end
|
|
401
402
|
|
|
402
403
|
# Rename a key. If the new key already exists it is overwritten.
|
|
@@ -552,11 +553,12 @@ class Valkey
|
|
|
552
553
|
# @param [Array<String, Integer, Float, Array, Hash>] argv command name and its arguments
|
|
553
554
|
# @param [Hash] kwargs trailing command flags; truthy values emit the upcased flag name,
|
|
554
555
|
# non-boolean values also emit the stringified value; falsy/nil values are dropped
|
|
556
|
+
# @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
|
|
555
557
|
# @return [Object] the raw reply, with no type-casting based on the command name
|
|
556
558
|
#
|
|
557
559
|
# @see https://valkey.io/commands/
|
|
558
|
-
def call(*argv, **kwargs)
|
|
559
|
-
send_command(RequestType::CUSTOM_COMMAND, flatten_call_args(argv).concat(call_flags(kwargs)))
|
|
560
|
+
def call(*argv, route: nil, **kwargs)
|
|
561
|
+
send_command(RequestType::CUSTOM_COMMAND, flatten_call_args(argv).concat(call_flags(kwargs)), route: route)
|
|
560
562
|
end
|
|
561
563
|
|
|
562
564
|
# Send any command as a single Array of arguments and get the raw reply back.
|
|
@@ -568,11 +570,12 @@ class Valkey
|
|
|
568
570
|
# valkey.call_v(["MGET"] + keys)
|
|
569
571
|
#
|
|
570
572
|
# @param [Array<String, Integer, Float, Array, Hash>] argv command name and its arguments
|
|
573
|
+
# @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
|
|
571
574
|
# @return [Object] the raw reply, with no type-casting based on the command name
|
|
572
575
|
#
|
|
573
576
|
# @see https://valkey.io/commands/
|
|
574
|
-
def call_v(argv)
|
|
575
|
-
send_command(RequestType::CUSTOM_COMMAND, flatten_call_args(argv))
|
|
577
|
+
def call_v(argv, route: nil)
|
|
578
|
+
send_command(RequestType::CUSTOM_COMMAND, flatten_call_args(argv), route: route)
|
|
576
579
|
end
|
|
577
580
|
|
|
578
581
|
private
|
|
@@ -108,8 +108,12 @@ class Valkey
|
|
|
108
108
|
|
|
109
109
|
result = Bindings.store_script(buf, script.bytesize)
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
begin
|
|
112
|
+
hash_buffer = Bindings::ScriptHashBuffer.new(result)
|
|
113
|
+
hash_buffer[:ptr].read_string(hash_buffer[:len])
|
|
114
|
+
ensure
|
|
115
|
+
Bindings.free_script_hash_buffer(result) if result && !result.null?
|
|
116
|
+
end
|
|
113
117
|
end
|
|
114
118
|
|
|
115
119
|
# Execute a Lua script on the server.
|
|
@@ -133,13 +137,22 @@ class Valkey
|
|
|
133
137
|
# @example Execute script that returns different data types
|
|
134
138
|
# valkey.eval("return {1, 'hello', true, nil}")
|
|
135
139
|
# # => [1, "hello", true, nil]
|
|
140
|
+
# @example Positional form, matching redis-rb's eval(script, keys, argv)
|
|
141
|
+
# valkey.eval("return KEYS[1] .. ARGV[1]", ["mykey"], ["myarg"])
|
|
142
|
+
# # => "mykeynyarg"
|
|
136
143
|
# Since the eval is not available in the rust backend
|
|
137
144
|
# using the load and invoke script
|
|
138
|
-
def eval(script, keys:
|
|
145
|
+
def eval(script, *rest, keys: nil, args: nil)
|
|
139
146
|
# Validate script parameter
|
|
140
147
|
raise ArgumentError, "script must be a string" unless script.is_a?(String)
|
|
141
148
|
raise ArgumentError, "script cannot be empty" if script.empty?
|
|
142
149
|
|
|
150
|
+
# Accept redis-rb's flexible positional form - eval(script, keys, argv)
|
|
151
|
+
# - in addition to the keyword form, mirroring the same treatment
|
|
152
|
+
# applied to evalsha (see that method for the full rationale).
|
|
153
|
+
keys ||= rest[0] || []
|
|
154
|
+
args ||= rest[1] || []
|
|
155
|
+
|
|
143
156
|
# Validate and convert keys and args to strings
|
|
144
157
|
begin
|
|
145
158
|
keys = Array(keys).map(&:to_s)
|
|
@@ -177,13 +190,22 @@ class Valkey
|
|
|
177
190
|
# rescue Valkey::CommandError => e
|
|
178
191
|
# puts "Script not found: #{e.message}"
|
|
179
192
|
# end
|
|
193
|
+
# @example Positional form, matching redis-rb's evalsha(sha, keys, argv)
|
|
194
|
+
# valkey.evalsha(sha, ["user"], ["123"])
|
|
195
|
+
# # => "user:123"
|
|
180
196
|
# Since evalsha is not available in rust backend
|
|
181
197
|
# using invoke script
|
|
182
|
-
def evalsha(sha, keys:
|
|
198
|
+
def evalsha(sha, *rest, keys: nil, args: nil)
|
|
183
199
|
# Validate SHA1 hash parameter
|
|
184
200
|
raise ArgumentError, "sha1 hash must be a string" unless sha.is_a?(String)
|
|
185
201
|
raise ArgumentError, "sha1 hash must be a 40-character hexadecimal string" unless valid_sha1?(sha)
|
|
186
202
|
|
|
203
|
+
# Accept redis-rb's flexible positional form - evalsha(sha, keys, argv)
|
|
204
|
+
# - in addition to the keyword form, so code written against redis-rb's
|
|
205
|
+
# API (e.g. redis_display_id) doesn't need to special-case this client.
|
|
206
|
+
keys ||= rest[0] || []
|
|
207
|
+
args ||= rest[1] || []
|
|
208
|
+
|
|
187
209
|
# Validate and convert keys and args to strings
|
|
188
210
|
begin
|
|
189
211
|
keys = Array(keys).map(&:to_s)
|
|
@@ -258,22 +280,26 @@ class Valkey
|
|
|
258
280
|
# Use from_string to ensure proper null termination
|
|
259
281
|
sha = FFI::MemoryPointer.from_string(script)
|
|
260
282
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
283
|
+
begin
|
|
284
|
+
res = Bindings.invoke_script(
|
|
285
|
+
@connection,
|
|
286
|
+
0,
|
|
287
|
+
sha,
|
|
288
|
+
keys.size,
|
|
289
|
+
keys_ptrs,
|
|
290
|
+
keys_lens,
|
|
291
|
+
args.size,
|
|
292
|
+
arg_ptrs,
|
|
293
|
+
arg_lens,
|
|
294
|
+
route_buf,
|
|
295
|
+
route.bytesize,
|
|
296
|
+
0 # span_ptr for OpenTelemetry (0 = no span)
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
convert_response(res)
|
|
300
|
+
ensure
|
|
301
|
+
Bindings.free_command_result(res) if res && !res.null?
|
|
302
|
+
end
|
|
277
303
|
end
|
|
278
304
|
|
|
279
305
|
private
|
|
@@ -9,16 +9,18 @@ class Valkey
|
|
|
9
9
|
module ServerCommands
|
|
10
10
|
# Asynchronously rewrite the append-only file.
|
|
11
11
|
#
|
|
12
|
-
# @
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
# @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
|
|
13
|
+
# @return [String]
|
|
14
|
+
def bgrewriteaof(route: nil)
|
|
15
|
+
send_command(RequestType::BG_REWRITE_AOF, [], route: route)
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
# Asynchronously save the dataset to disk.
|
|
18
19
|
#
|
|
19
|
-
# @
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
# @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
|
|
21
|
+
# @return [String]
|
|
22
|
+
def bgsave(route: nil)
|
|
23
|
+
send_command(RequestType::BG_SAVE, [], route: route)
|
|
22
24
|
end
|
|
23
25
|
|
|
24
26
|
# Get or set server configuration parameters.
|
|
@@ -35,18 +37,16 @@ class Valkey
|
|
|
35
37
|
# Sends the CONFIG GET command with the given arguments.
|
|
36
38
|
#
|
|
37
39
|
# @param [Array<String>] args Configuration parameters to get
|
|
38
|
-
# @
|
|
39
|
-
#
|
|
40
|
+
# @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
|
|
41
|
+
# @return [Hash, String]
|
|
40
42
|
#
|
|
41
43
|
# @example Get all configuration parameters
|
|
42
44
|
# config_get('*')
|
|
43
45
|
#
|
|
44
46
|
# @example Get a specific parameter
|
|
45
47
|
# config_get('maxmemory')
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def config_get(*args)
|
|
49
|
-
send_command(RequestType::CONFIG_GET, args) do |reply|
|
|
48
|
+
def config_get(*args, route: nil)
|
|
49
|
+
send_command(RequestType::CONFIG_GET, args, route: route) do |reply|
|
|
50
50
|
if reply.is_a?(Array)
|
|
51
51
|
Hash[*reply]
|
|
52
52
|
else
|
|
@@ -60,55 +60,60 @@ class Valkey
|
|
|
60
60
|
# Sends the CONFIG SET command with the given key-value pairs.
|
|
61
61
|
#
|
|
62
62
|
# @param [Array<String>] args Key-value pairs to set configuration
|
|
63
|
+
# @param route [Valkey::Route, nil] cluster routing.
|
|
63
64
|
# @return [String] Returns "OK" if successful
|
|
64
65
|
#
|
|
65
66
|
# @example Set maxmemory to 100mb
|
|
66
67
|
# config_set('maxmemory', '100mb')
|
|
67
|
-
def config_set(*args)
|
|
68
|
-
send_command(RequestType::CONFIG_SET, args)
|
|
68
|
+
def config_set(*args, route: nil)
|
|
69
|
+
send_command(RequestType::CONFIG_SET, args, route: route)
|
|
69
70
|
end
|
|
70
71
|
|
|
71
72
|
# Reset the server's statistics.
|
|
72
73
|
#
|
|
73
74
|
# Sends the CONFIG RESETSTAT command.
|
|
74
75
|
#
|
|
76
|
+
# @param route [Valkey::Route, nil] cluster routing.
|
|
75
77
|
# @return [String] Returns "OK" if successful
|
|
76
78
|
#
|
|
77
79
|
# @example
|
|
78
80
|
# config_resetstat
|
|
79
|
-
def config_resetstat
|
|
80
|
-
send_command(RequestType::CONFIG_RESET_STAT)
|
|
81
|
+
def config_resetstat(route: nil)
|
|
82
|
+
send_command(RequestType::CONFIG_RESET_STAT, [], route: route)
|
|
81
83
|
end
|
|
82
84
|
|
|
83
85
|
# Rewrite the server configuration file.
|
|
84
86
|
#
|
|
85
87
|
# Sends the CONFIG REWRITE command.
|
|
86
88
|
#
|
|
89
|
+
# @param route [Valkey::Route, nil] cluster routing.
|
|
87
90
|
# @return [String] Returns "OK" if successful
|
|
88
91
|
#
|
|
89
92
|
# @example
|
|
90
93
|
# config_rewrite
|
|
91
|
-
def config_rewrite
|
|
92
|
-
send_command(RequestType::CONFIG_REWRITE)
|
|
94
|
+
def config_rewrite(route: nil)
|
|
95
|
+
send_command(RequestType::CONFIG_REWRITE, [], route: route)
|
|
93
96
|
end
|
|
94
97
|
|
|
95
98
|
# Return the number of keys in the selected database.
|
|
96
99
|
#
|
|
100
|
+
# @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
|
|
97
101
|
# @return [Integer]
|
|
98
|
-
def dbsize
|
|
99
|
-
send_command(RequestType::DB_SIZE)
|
|
102
|
+
def dbsize(route: nil)
|
|
103
|
+
send_command(RequestType::DB_SIZE, [], route: route)
|
|
100
104
|
end
|
|
101
105
|
|
|
102
106
|
# Remove all keys from all databases.
|
|
103
107
|
#
|
|
104
108
|
# @param [Hash] options
|
|
105
109
|
# - `:async => Boolean`: async flush (default: false)
|
|
110
|
+
# @param route [Valkey::Route, nil] cluster routing.
|
|
106
111
|
# @return [String] `OK`
|
|
107
|
-
def flushall(options = nil)
|
|
112
|
+
def flushall(options = nil, route: nil)
|
|
108
113
|
if options && options[:async]
|
|
109
|
-
send_command(RequestType::FLUSH_ALL, ["async"])
|
|
114
|
+
send_command(RequestType::FLUSH_ALL, ["async"], route: route)
|
|
110
115
|
else
|
|
111
|
-
send_command(RequestType::FLUSH_ALL)
|
|
116
|
+
send_command(RequestType::FLUSH_ALL, [], route: route)
|
|
112
117
|
end
|
|
113
118
|
end
|
|
114
119
|
|
|
@@ -116,42 +121,56 @@ class Valkey
|
|
|
116
121
|
#
|
|
117
122
|
# @param [Hash] options
|
|
118
123
|
# - `:async => Boolean`: async flush (default: false)
|
|
124
|
+
# @param route [Valkey::Route, nil] cluster routing.
|
|
119
125
|
# @return [String] `OK`
|
|
120
|
-
def flushdb(options = nil)
|
|
126
|
+
def flushdb(options = nil, route: nil)
|
|
121
127
|
if options && options[:async]
|
|
122
|
-
send_command(RequestType::FLUSH_DB, ["async"])
|
|
128
|
+
send_command(RequestType::FLUSH_DB, ["async"], route: route)
|
|
123
129
|
else
|
|
124
|
-
send_command(RequestType::FLUSH_DB)
|
|
130
|
+
send_command(RequestType::FLUSH_DB, [], route: route)
|
|
125
131
|
end
|
|
126
132
|
end
|
|
127
133
|
|
|
128
134
|
# Get information and statistics about the server.
|
|
129
135
|
#
|
|
130
|
-
# @param [String, Symbol]
|
|
131
|
-
# @
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
[k[/^cmdstat_(.*)$/, 1], v.to_h]
|
|
142
|
-
end
|
|
143
|
-
end
|
|
136
|
+
# @param cmd [String, Symbol, nil] section name (e.g. "commandstats")
|
|
137
|
+
# @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
|
|
138
|
+
# @return [Hash]
|
|
139
|
+
def info(cmd = nil, route: nil)
|
|
140
|
+
send_command(RequestType::INFO, [cmd].compact, route: route) do |reply|
|
|
141
|
+
if reply.is_a?(Hash)
|
|
142
|
+
reply.transform_values { |v| parse_info_reply(v, cmd) }
|
|
143
|
+
elsif reply.is_a?(String)
|
|
144
|
+
parse_info_reply(reply, cmd)
|
|
145
|
+
else
|
|
146
|
+
reply
|
|
144
147
|
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
private
|
|
145
152
|
|
|
146
|
-
|
|
153
|
+
def parse_info_reply(reply, cmd)
|
|
154
|
+
reply = Utils::HashifyInfo.call(reply)
|
|
155
|
+
|
|
156
|
+
if cmd && cmd.to_s == "commandstats"
|
|
157
|
+
reply = reply.to_h do |k, v|
|
|
158
|
+
v = v.split(",").map { |e| e.split("=") }
|
|
159
|
+
[k[/^cmdstat_(.*)$/, 1], v.to_h]
|
|
160
|
+
end
|
|
147
161
|
end
|
|
162
|
+
|
|
163
|
+
reply
|
|
148
164
|
end
|
|
149
165
|
|
|
166
|
+
public
|
|
167
|
+
|
|
150
168
|
# Get the UNIX time stamp of the last successful save to disk.
|
|
151
169
|
#
|
|
170
|
+
# @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
|
|
152
171
|
# @return [Integer]
|
|
153
|
-
def lastsave
|
|
154
|
-
send_command(RequestType::LAST_SAVE)
|
|
172
|
+
def lastsave(route: nil)
|
|
173
|
+
send_command(RequestType::LAST_SAVE, [], route: route)
|
|
155
174
|
end
|
|
156
175
|
|
|
157
176
|
# Listen for all requests received by the server in real time.
|
|
@@ -172,9 +191,10 @@ class Valkey
|
|
|
172
191
|
|
|
173
192
|
# Synchronously save the dataset to disk.
|
|
174
193
|
#
|
|
194
|
+
# @param route [Valkey::Route, nil] cluster routing.
|
|
175
195
|
# @return [String]
|
|
176
|
-
def save
|
|
177
|
-
send_command(RequestType::SAVE)
|
|
196
|
+
def save(route: nil)
|
|
197
|
+
send_command(RequestType::SAVE, [], route: route)
|
|
178
198
|
end
|
|
179
199
|
|
|
180
200
|
# Synchronously save the dataset to disk and then shut down the server.
|
|
@@ -215,10 +235,11 @@ class Valkey
|
|
|
215
235
|
# @example
|
|
216
236
|
# r.time # => [ 1333093196, 606806 ]
|
|
217
237
|
#
|
|
238
|
+
# @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
|
|
218
239
|
# @return [Array<Integer>] tuple of seconds since UNIX epoch and
|
|
219
240
|
# microseconds in the current second
|
|
220
|
-
def time
|
|
221
|
-
send_command(RequestType::TIME)
|
|
241
|
+
def time(route: nil)
|
|
242
|
+
send_command(RequestType::TIME, [], route: route)
|
|
222
243
|
end
|
|
223
244
|
|
|
224
245
|
# RequestType::DEBUG not exist
|
|
@@ -517,7 +538,8 @@ class Valkey
|
|
|
517
538
|
|
|
518
539
|
# Display some computer art and the Valkey version.
|
|
519
540
|
#
|
|
520
|
-
# @param [Integer]
|
|
541
|
+
# @param version [Integer, nil] optional version number for different art
|
|
542
|
+
# @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value.
|
|
521
543
|
# @return [String] ASCII art and version information
|
|
522
544
|
#
|
|
523
545
|
# @example
|
|
@@ -528,9 +550,9 @@ class Valkey
|
|
|
528
550
|
# # => "Valkey ver. 7.0.0\n..."
|
|
529
551
|
#
|
|
530
552
|
# @see https://valkey.io/commands/lolwut/
|
|
531
|
-
def lolwut(version = nil)
|
|
553
|
+
def lolwut(version = nil, route: nil)
|
|
532
554
|
args = version ? ["VERSION", version.to_s] : []
|
|
533
|
-
send_command(RequestType::LOLWUT, args)
|
|
555
|
+
send_command(RequestType::LOLWUT, args, route: route)
|
|
534
556
|
end
|
|
535
557
|
|
|
536
558
|
# Internal command used for replication (partial resynchronization).
|
|
@@ -129,7 +129,14 @@ class Valkey
|
|
|
129
129
|
# @param [String] value
|
|
130
130
|
# @return [Boolean] whether the key was set or not
|
|
131
131
|
def setnx(key, value)
|
|
132
|
-
|
|
132
|
+
# &Utils::Boolify (not glide-core's generic Boolean-coercion table) is
|
|
133
|
+
# deliberately used here: glide-core's per-command coercion is keyed by
|
|
134
|
+
# command name alone, so it can't distinguish this dedicated SETNX
|
|
135
|
+
# RequestType from a raw `customCommand(["SETNX", ...])` call, which
|
|
136
|
+
# other GLIDE bindings' existing contracts expect to keep returning a
|
|
137
|
+
# plain 0/1 integer. Doing the conversion here keeps it scoped to this
|
|
138
|
+
# one Ruby-level method - see hexists/hsetnx for the same pattern.
|
|
139
|
+
send_command(RequestType::SET_NX, [key, value], &Utils::Boolify)
|
|
133
140
|
end
|
|
134
141
|
|
|
135
142
|
# Set one or more values.
|
|
@@ -16,8 +16,12 @@ class Valkey
|
|
|
16
16
|
# end # => ["OK", 6]
|
|
17
17
|
#
|
|
18
18
|
# @yield [multi] the commands that are called inside this block are cached
|
|
19
|
-
# and
|
|
20
|
-
#
|
|
19
|
+
# locally (no server round-trip per command) and sent to the server as a
|
|
20
|
+
# single atomic batch once the block returns - GLIDE wraps them in a real
|
|
21
|
+
# MULTI/EXEC transaction internally. If the block raises, nothing has been
|
|
22
|
+
# sent to the server yet, so the exception simply propagates - there is no
|
|
23
|
+
# transaction to discard.
|
|
24
|
+
# @yieldparam [Valkey::Pipeline] multi collects the block's commands
|
|
21
25
|
#
|
|
22
26
|
# @return [Array<...>]
|
|
23
27
|
# - an array with replies
|
|
@@ -26,17 +30,12 @@ class Valkey
|
|
|
26
30
|
# @see #unwatch
|
|
27
31
|
def multi
|
|
28
32
|
if block_given?
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
discard
|
|
36
|
-
raise
|
|
37
|
-
ensure
|
|
38
|
-
@in_multi_block = false
|
|
39
|
-
end
|
|
33
|
+
pipeline = Pipeline.new
|
|
34
|
+
yield pipeline
|
|
35
|
+
|
|
36
|
+
return [] if pipeline.commands.empty?
|
|
37
|
+
|
|
38
|
+
send_batch_commands(pipeline.commands, exception: true, is_atomic: true)
|
|
40
39
|
else
|
|
41
40
|
start_multi
|
|
42
41
|
self
|
|
@@ -114,11 +113,12 @@ class Valkey
|
|
|
114
113
|
# @see #discard
|
|
115
114
|
def exec
|
|
116
115
|
if @in_multi
|
|
116
|
+
queued_commands = @queued_commands
|
|
117
117
|
begin
|
|
118
118
|
begin
|
|
119
119
|
result = send_command(RequestType::EXEC)
|
|
120
120
|
# If EXEC returns an error object (from array), it's already handled
|
|
121
|
-
result
|
|
121
|
+
result.is_a?(Array) ? reconvert_queued_replies(result, queued_commands) : result
|
|
122
122
|
rescue CommandError => e
|
|
123
123
|
# If EXEC itself raises an error (like when transaction is aborted),
|
|
124
124
|
# return an array with the error to match expected behavior in tests
|
|
@@ -157,8 +157,59 @@ class Valkey
|
|
|
157
157
|
@queued_commands = []
|
|
158
158
|
end
|
|
159
159
|
|
|
160
|
+
# Commands whose reply is boolean when run standalone (via native return-type
|
|
161
|
+
# coercion server-side), but arrives as a raw 0/1 integer inside an EXEC array,
|
|
162
|
+
# since that coercion is keyed by the single command actually being run - which,
|
|
163
|
+
# for a queued command, is EXEC itself, not the original command.
|
|
164
|
+
#
|
|
165
|
+
# This list mirrors glide-core's own Boolean-coercion table in
|
|
166
|
+
# `value_conversion.rs::expected_type_for_cmd` (HEXISTS, HSETNX, EXPIRE, EXPIREAT,
|
|
167
|
+
# PEXPIRE, PEXPIREAT, SISMEMBER, PERSIST, SMOVE, PFADD, RENAMENX, MOVE, COPY,
|
|
168
|
+
# MSETNX, XGROUP DESTROY, XGROUP CREATECONSUMER) MINUS the commands whose Ruby
|
|
169
|
+
# method already passes its own explicit conversion block (`hexists`/`hsetnx`
|
|
170
|
+
# use `&Utils::Boolify`; `xgroup_destroy`/`xgroup_createconsumer` use a custom
|
|
171
|
+
# bool->int block). Those are already handled correctly by the `if block`
|
|
172
|
+
# branch below, before this list is even consulted - listing them here too
|
|
173
|
+
# would be redundant, not wrong. Every RequestType below calls `send_command`
|
|
174
|
+
# with NO block at all, so this static list is the only place their boolean-ness
|
|
175
|
+
# is recorded.
|
|
176
|
+
#
|
|
177
|
+
# NOTE: SETNX is deliberately NOT in glide-core's coercion table (and so isn't
|
|
178
|
+
# here as a "no block" entry either) - it's `redis-rb`-style boolean-ness only,
|
|
179
|
+
# not something the server/`glide-core` treats as boolean, since coercion there
|
|
180
|
+
# is keyed by command name alone and can't distinguish this dedicated RequestType
|
|
181
|
+
# from a raw `customCommand(["SETNX", ...])` call, which other bindings' existing
|
|
182
|
+
# contracts expect to keep returning a plain integer. `setnx`'s own Ruby method
|
|
183
|
+
# passes `&Utils::Boolify` directly instead - see string_commands.rb - so it's
|
|
184
|
+
# already covered by the `if block` branch, same as hexists/hsetnx.
|
|
185
|
+
BOOLEAN_REQUEST_TYPES = [
|
|
186
|
+
RequestType::EXPIRE, RequestType::EXPIRE_AT, RequestType::PEXPIRE, RequestType::PEXPIRE_AT,
|
|
187
|
+
RequestType::PERSIST, RequestType::SISMEMBER, RequestType::S_MOVE, RequestType::PFADD,
|
|
188
|
+
RequestType::RENAME_NX, RequestType::MOVE, RequestType::COPY, RequestType::MSET_NX
|
|
189
|
+
].freeze
|
|
190
|
+
|
|
160
191
|
private
|
|
161
192
|
|
|
193
|
+
# Re-applies each queued command's own reply conversion (e.g. `&Utils::Boolify`)
|
|
194
|
+
# to EXEC's raw array, since redis-rb's Future-based design does the equivalent
|
|
195
|
+
# (a Future remembers its conversion and re-applies it once EXEC resolves), but
|
|
196
|
+
# queued commands here go through the plain single-command path with no such
|
|
197
|
+
# memory - see BOOLEAN_REQUEST_TYPES above for the case with no explicit block.
|
|
198
|
+
def reconvert_queued_replies(result, queued_commands)
|
|
199
|
+
return result unless result.size == queued_commands.size
|
|
200
|
+
|
|
201
|
+
result.each_with_index.map do |value, i|
|
|
202
|
+
command_type, _args, block = queued_commands[i]
|
|
203
|
+
if block
|
|
204
|
+
block.call(value)
|
|
205
|
+
elsif BOOLEAN_REQUEST_TYPES.include?(command_type)
|
|
206
|
+
Utils::Boolify.call(value)
|
|
207
|
+
else
|
|
208
|
+
value
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
162
213
|
# Start a MULTI block if one isn't already active.
|
|
163
214
|
#
|
|
164
215
|
# This mirrors the behaviour of popular Valkey/Redis clients where
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/lib/valkey/route.rb
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Valkey
|
|
4
|
+
# Represents a cluster routing directive. Passed as `route:` to commands
|
|
5
|
+
# that support explicit cluster routing (no-key commands like DBSIZE, INFO,
|
|
6
|
+
# PING, FLUSHALL, FUNCTION_*, etc.).
|
|
7
|
+
#
|
|
8
|
+
# When `route:` is provided the command response type depends on the route:
|
|
9
|
+
# single-node routes return the value directly, multi-node routes return a
|
|
10
|
+
# Hash of `"host:port" => value`.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# client.dbsize(route: Valkey::Route.all_primaries)
|
|
14
|
+
# client.ping(route: Valkey::Route.random)
|
|
15
|
+
# client.info(route: Valkey::Route.all_nodes)
|
|
16
|
+
#
|
|
17
|
+
# @see https://valkey.io/topics/cluster-spec
|
|
18
|
+
class Route
|
|
19
|
+
class << self
|
|
20
|
+
# Route to all nodes (primaries + replicas).
|
|
21
|
+
# @note Don't use with write commands — they could be routed to replicas and fail.
|
|
22
|
+
# @return [Route]
|
|
23
|
+
def all_nodes
|
|
24
|
+
new(:all_nodes)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Route to all primary nodes.
|
|
28
|
+
# @return [Route]
|
|
29
|
+
def all_primaries
|
|
30
|
+
new(:all_primaries)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Route to a random node.
|
|
34
|
+
# @note Don't use with write commands — they could be randomly routed to a replica and fail.
|
|
35
|
+
# @return [Route]
|
|
36
|
+
def random
|
|
37
|
+
new(:random)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Route to a specific slot by ID.
|
|
41
|
+
# @param slot_id [Integer] slot number (0–16383)
|
|
42
|
+
# @param slot_type [Symbol] :primary or :replica
|
|
43
|
+
# @return [Route]
|
|
44
|
+
def slot_id(slot_id, slot_type = :primary)
|
|
45
|
+
new(:slot_id, slot_id: slot_id.to_i, slot_type: slot_type)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Route to the node owning a specific key's slot.
|
|
49
|
+
# @param key [String] the key whose slot determines routing
|
|
50
|
+
# @param slot_type [Symbol] :primary or :replica
|
|
51
|
+
# @return [Route]
|
|
52
|
+
def slot_key(key, slot_type = :primary)
|
|
53
|
+
new(:slot_key, slot_key: key.to_s, slot_type: slot_type)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Route to a specific node by address.
|
|
57
|
+
# @param host [String] hostname or IP
|
|
58
|
+
# @param port [Integer] port number
|
|
59
|
+
# @return [Route]
|
|
60
|
+
def by_address(host, port)
|
|
61
|
+
new(:by_address, hostname: host.to_s, port: port.to_i)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Build the FFI RouteInfo struct for passing to command_with_route_info.
|
|
66
|
+
#
|
|
67
|
+
# Returns both the struct and any pinned memory buffers that must remain
|
|
68
|
+
# alive until the FFI call completes.
|
|
69
|
+
#
|
|
70
|
+
# @return [Array(Bindings::RouteInfo, Array)] the struct and pinned buffers
|
|
71
|
+
def to_ffi
|
|
72
|
+
info = Bindings::RouteInfo.new
|
|
73
|
+
info[:route_type] = @route_type
|
|
74
|
+
info[:slot_id] = @slot_id || 0
|
|
75
|
+
info[:slot_type] = @slot_type || :primary
|
|
76
|
+
info[:port] = @port || 0
|
|
77
|
+
|
|
78
|
+
pinned = [] # prevent GC of string buffers during FFI call
|
|
79
|
+
info[:slot_key] = pin_string(@slot_key, pinned)
|
|
80
|
+
info[:hostname] = pin_string(@hostname, pinned)
|
|
81
|
+
|
|
82
|
+
[info, pinned]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
# Pin a string into an FFI pointer (or NULL if nil), appending the buffer
|
|
88
|
+
# to +pinned+ so it is not garbage-collected before the FFI call completes.
|
|
89
|
+
def pin_string(str, pinned)
|
|
90
|
+
return FFI::Pointer::NULL unless str
|
|
91
|
+
|
|
92
|
+
buf = FFI::MemoryPointer.from_string(str)
|
|
93
|
+
pinned << buf
|
|
94
|
+
buf
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def initialize(route_type, slot_id: nil, slot_key: nil, slot_type: :primary, hostname: nil, port: nil)
|
|
98
|
+
@route_type = route_type
|
|
99
|
+
@slot_id = slot_id
|
|
100
|
+
@slot_key = slot_key
|
|
101
|
+
@slot_type = slot_type
|
|
102
|
+
@hostname = hostname
|
|
103
|
+
@port = port
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
data/lib/valkey/utils.rb
CHANGED
|
@@ -102,6 +102,11 @@ class Valkey
|
|
|
102
102
|
HashifyStreamEntries = lambda { |reply|
|
|
103
103
|
return [] if reply.nil?
|
|
104
104
|
|
|
105
|
+
# In cluster mode, MAP responses come as Hash: {id => [fields], ...}
|
|
106
|
+
if reply.is_a?(Hash)
|
|
107
|
+
return reply.map { |entry_id, values| [entry_id, values.is_a?(Array) ? values.flatten : []] }
|
|
108
|
+
end
|
|
109
|
+
|
|
105
110
|
return [] if !reply.is_a?(Array) || reply.empty?
|
|
106
111
|
|
|
107
112
|
# Reply format: [[entry_id, [field1, value1, field2, value2, ...]], ...]
|
data/lib/valkey/version.rb
CHANGED
data/lib/valkey.rb
CHANGED
|
@@ -16,6 +16,7 @@ require "valkey/errors"
|
|
|
16
16
|
require "valkey/pubsub_callback"
|
|
17
17
|
require "valkey/pipeline"
|
|
18
18
|
require "valkey/opentelemetry"
|
|
19
|
+
require "valkey/route"
|
|
19
20
|
|
|
20
21
|
class Valkey
|
|
21
22
|
include Utils
|
|
@@ -287,6 +288,9 @@ class Valkey
|
|
|
287
288
|
@connection = res[:conn_ptr]
|
|
288
289
|
Bindings.free_connection_response(response_ptr)
|
|
289
290
|
|
|
291
|
+
# Store cluster mode flag for response handling (MAP returns Hash in cluster, Array in standalone)
|
|
292
|
+
@cluster_mode = options[:cluster_mode] ? true : false
|
|
293
|
+
|
|
290
294
|
# Track transactional state for `MULTI` / `EXEC` / `DISCARD` helpers.
|
|
291
295
|
# This avoids Ruby warnings about uninitialised instance variables and
|
|
292
296
|
# gives us a single source of truth for whether we're inside a TX.
|
|
@@ -357,7 +361,7 @@ class Valkey
|
|
|
357
361
|
# `test/valkey/connection_lifecycle_test.rb`) call it directly with an
|
|
358
362
|
# explicit receiver to issue commands with no dedicated wrapper method yet
|
|
359
363
|
# (e.g. `DEBUG SLEEP`, raw `HSET` in vector search fixtures).
|
|
360
|
-
def send_command(command_type, command_args = [], &block)
|
|
364
|
+
def send_command(command_type, command_args = [], route: nil, &block)
|
|
361
365
|
# Validate connection. A nil/null pointer means the client was closed (or
|
|
362
366
|
# never established a usable connection); surface it as a typed error with
|
|
363
367
|
# the same "the client is closed" wording the sibling GLIDE clients use
|
|
@@ -365,9 +369,6 @@ class Valkey
|
|
|
365
369
|
raise ConnectionError, "the client is closed" if @connection.nil? || @connection.null? || @connection.address.zero?
|
|
366
370
|
|
|
367
371
|
channel = 0
|
|
368
|
-
route = ""
|
|
369
|
-
|
|
370
|
-
route_buf = FFI::MemoryPointer.from_string(route)
|
|
371
372
|
|
|
372
373
|
# Handle empty command_args case
|
|
373
374
|
if command_args.empty?
|
|
@@ -402,20 +403,43 @@ class Valkey
|
|
|
402
403
|
end
|
|
403
404
|
|
|
404
405
|
begin
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
406
|
+
if route
|
|
407
|
+
# Use command_with_route_info when an explicit route is provided.
|
|
408
|
+
route_info, _pinned_bufs = route.to_ffi
|
|
409
|
+
res = Bindings.command_with_route_info(
|
|
410
|
+
@connection,
|
|
411
|
+
channel,
|
|
412
|
+
command_type,
|
|
413
|
+
command_args.size,
|
|
414
|
+
arg_ptrs,
|
|
415
|
+
arg_lens,
|
|
416
|
+
route_info.to_ptr,
|
|
417
|
+
FFI::Pointer::NULL, # response_buf (NULL = normal response path)
|
|
418
|
+
0, # response_buf_len
|
|
419
|
+
span_ptr
|
|
420
|
+
)
|
|
421
|
+
else
|
|
422
|
+
# Use legacy command() for unrouted calls to preserve existing behavior.
|
|
423
|
+
route_str = ""
|
|
424
|
+
route_buf = FFI::MemoryPointer.from_string(route_str)
|
|
425
|
+
res = Bindings.command(
|
|
426
|
+
@connection,
|
|
427
|
+
channel,
|
|
428
|
+
command_type,
|
|
429
|
+
command_args.size,
|
|
430
|
+
arg_ptrs,
|
|
431
|
+
arg_lens,
|
|
432
|
+
route_buf,
|
|
433
|
+
route_str.bytesize,
|
|
434
|
+
span_ptr
|
|
435
|
+
)
|
|
436
|
+
end
|
|
416
437
|
|
|
417
|
-
result = convert_response(res, &block)
|
|
438
|
+
result = convert_response(res, return_map_as_hash: @cluster_mode, &block)
|
|
418
439
|
ensure
|
|
440
|
+
# Free the native CommandResult (arena + response + error) to prevent memory leak
|
|
441
|
+
Bindings.free_command_result(res) if res && !res.null?
|
|
442
|
+
|
|
419
443
|
# Always drop the span if one was created, even if command fails
|
|
420
444
|
if span_ptr != 0
|
|
421
445
|
begin
|
|
@@ -433,7 +457,9 @@ class Valkey
|
|
|
433
457
|
RequestType::MULTI, RequestType::EXEC, RequestType::DISCARD,
|
|
434
458
|
RequestType::WATCH, RequestType::UNWATCH
|
|
435
459
|
]
|
|
436
|
-
|
|
460
|
+
if !tx_commands.include?(command_type) && result == "QUEUED"
|
|
461
|
+
@queued_commands << [command_type, command_args.dup, block]
|
|
462
|
+
end
|
|
437
463
|
end
|
|
438
464
|
|
|
439
465
|
result
|
|
@@ -441,23 +467,29 @@ class Valkey
|
|
|
441
467
|
|
|
442
468
|
private
|
|
443
469
|
|
|
444
|
-
def send_batch_commands(commands, exception: true)
|
|
470
|
+
def send_batch_commands(commands, exception: true, is_atomic: false)
|
|
445
471
|
# WORKAROUND: The underlying Glide FFI backend has stability issues when
|
|
446
|
-
# batching
|
|
447
|
-
#
|
|
448
|
-
# instead of via
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
results
|
|
458
|
-
|
|
472
|
+
# batching LITERAL MULTI / EXEC / DISCARD commands (e.g. a `pipelined` block
|
|
473
|
+
# that manually calls `pipeline.multi`/`pipeline.exec`). To avoid native
|
|
474
|
+
# crashes we fall back to issuing those commands sequentially instead of via
|
|
475
|
+
# `Bindings.batch`. This never applies to a real `is_atomic: true` batch
|
|
476
|
+
# (see `multi`'s block form) - that path never contains literal MULTI/EXEC
|
|
477
|
+
# commands, since the server-side transaction wrapping is handled by GLIDE
|
|
478
|
+
# itself based on the `is_atomic` flag, not by commands in the list.
|
|
479
|
+
unless is_atomic
|
|
480
|
+
tx_types = [RequestType::MULTI, RequestType::EXEC, RequestType::DISCARD]
|
|
481
|
+
|
|
482
|
+
if commands.any? { |(command_type, _args, _block)| tx_types.include?(command_type) }
|
|
483
|
+
results = []
|
|
484
|
+
|
|
485
|
+
commands.each do |command_type, command_args, block|
|
|
486
|
+
res = send_command(command_type, command_args)
|
|
487
|
+
res = block.call(res) if block
|
|
488
|
+
results << res
|
|
489
|
+
end
|
|
459
490
|
|
|
460
|
-
|
|
491
|
+
return results
|
|
492
|
+
end
|
|
461
493
|
end
|
|
462
494
|
|
|
463
495
|
cmds = []
|
|
@@ -487,7 +519,7 @@ class Valkey
|
|
|
487
519
|
batch_info = Bindings::BatchInfo.new
|
|
488
520
|
batch_info[:cmd_count] = cmds.size
|
|
489
521
|
batch_info[:cmds] = cmd_ptrs
|
|
490
|
-
batch_info[:is_atomic] =
|
|
522
|
+
batch_info[:is_atomic] = is_atomic
|
|
491
523
|
|
|
492
524
|
batch_options = Bindings::BatchOptionsInfo.new
|
|
493
525
|
batch_options[:retry_server_error] = true
|
|
@@ -527,6 +559,9 @@ class Valkey
|
|
|
527
559
|
|
|
528
560
|
results = convert_response(res)
|
|
529
561
|
ensure
|
|
562
|
+
# Free the native CommandResult (arena + response + error) to prevent memory leak
|
|
563
|
+
Bindings.free_command_result(res) if res && !res.null?
|
|
564
|
+
|
|
530
565
|
# Always drop the span if one was created
|
|
531
566
|
if span_ptr != 0
|
|
532
567
|
begin
|
|
@@ -587,7 +622,7 @@ class Valkey
|
|
|
587
622
|
[arg_ptrs, arg_lens, buffers]
|
|
588
623
|
end
|
|
589
624
|
|
|
590
|
-
def convert_response(res, &block)
|
|
625
|
+
def convert_response(res, return_map_as_hash: false, &block)
|
|
591
626
|
result = Bindings::CommandResult.new(res)
|
|
592
627
|
|
|
593
628
|
if result[:response].null?
|
|
@@ -643,8 +678,8 @@ class Valkey
|
|
|
643
678
|
map[map_key] = map_value
|
|
644
679
|
end
|
|
645
680
|
|
|
646
|
-
#
|
|
647
|
-
map.to_a.flatten(1)
|
|
681
|
+
# Return as Hash in cluster mode, flatten to pairs in standalone mode.
|
|
682
|
+
return_map_as_hash ? map : map.to_a.flatten(1)
|
|
648
683
|
when ResponseType::SETS
|
|
649
684
|
ptr = response_item[:sets_value]
|
|
650
685
|
count = response_item[:sets_value_len].to_i
|
|
@@ -673,7 +708,10 @@ class Valkey
|
|
|
673
708
|
|
|
674
709
|
response = convert_response.call(result)
|
|
675
710
|
|
|
676
|
-
|
|
711
|
+
# Don't run the caller's converter (e.g. Utils::Boolify) over the MULTI-queued
|
|
712
|
+
# "QUEUED" sentinel - send_command's own `result == "QUEUED"` check (used to
|
|
713
|
+
# track queued commands) needs to see the literal string, not e.g. `true`.
|
|
714
|
+
if block_given? && response != "QUEUED"
|
|
677
715
|
block.call(response)
|
|
678
716
|
else
|
|
679
717
|
response
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: valkey-glide-rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.9.
|
|
4
|
+
version: 0.9.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Valkey GLIDE Maintainers
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ffi
|
|
@@ -82,6 +82,7 @@ files:
|
|
|
82
82
|
- lib/valkey/request_error_type.rb
|
|
83
83
|
- lib/valkey/request_type.rb
|
|
84
84
|
- lib/valkey/response_type.rb
|
|
85
|
+
- lib/valkey/route.rb
|
|
85
86
|
- lib/valkey/utils.rb
|
|
86
87
|
- lib/valkey/version.rb
|
|
87
88
|
homepage: https://github.com/valkey-io/valkey-glide-ruby
|