valkey-glide-rb 0.9.2 → 1.0.0.pre.rc2
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/AGENTS.md +20 -4
- data/CONTRIBUTING.md +1 -1
- data/DEVELOPER.md +9 -12
- data/lib/valkey/commands/connection_commands.rb +1 -1
- data/lib/valkey/commands/list_commands.rb +2 -2
- data/lib/valkey/commands/pubsub_commands.rb +2 -2
- data/lib/valkey/commands/scripting_commands.rb +4 -22
- data/lib/valkey/commands/stream_commands.rb +1 -1
- data/lib/valkey/commands/string_commands.rb +2 -9
- data/lib/valkey/commands/transaction_commands.rb +14 -65
- data/lib/valkey/commands/vector_search_commands.rb +1 -1
- 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 +2 -0
- data/lib/valkey/version.rb +1 -1
- data/lib/valkey.rb +55 -77
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3637e780cfd586be7915d5a1bcd9993ce5772ca69afdee93e5ec1166f6f1fda6
|
|
4
|
+
data.tar.gz: 45c92cf3741eb914fadf412ae9cb26223133aebdce833045a935219fb4c79f7b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 730a56e57f83549ea454af827d121461961858ca629a03dbdb723808845e718afe27db1ffc901564d393e070d496b2ecd16736c22a3d37461388e26430350d2c
|
|
7
|
+
data.tar.gz: aef8450541af162b9c38149589a6eccb45ece28005c2f1af26a5bd93e1639e3a2126c208276d72f72c7e9bd6b1fb1b81288ddb11d40ae38a17d612b9a9c24e3a
|
data/AGENTS.md
CHANGED
|
@@ -83,10 +83,26 @@ RUBYOPT="-I$(pwd)/lib" ruby -r valkey -e 'p Valkey.new.ping'
|
|
|
83
83
|
| Suite | Server requirement |
|
|
84
84
|
|-------|-------------------|
|
|
85
85
|
| `test:standalone` | Standalone Valkey/Redis on `localhost:6379` (DB 15) |
|
|
86
|
-
| `test:cluster` | 6-node cluster on `127.0.0.1:7000`–`7005` |
|
|
87
|
-
| SSL tests | TLS Valkey on port `6380` +
|
|
86
|
+
| `test:cluster` | 6-node cluster on `127.0.0.1:7000`–`7005` (auto-started by the suite) |
|
|
87
|
+
| SSL tests | TLS Valkey on port `6380` + `export TLS_CERT_DIR=...` (or `SKIP_TLS_TESTS=true`) |
|
|
88
88
|
| Module tests | JSON, Bloom, Search modules loaded (see CI workflow) |
|
|
89
89
|
|
|
90
|
+
Start test servers with `cluster_manager.py` (matching CI). The cluster is
|
|
91
|
+
auto-started by the suite; standalone and TLS are started manually:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Standalone on :6379
|
|
95
|
+
python3 valkey-glide/utils/cluster_manager.py start -r 0 -p 6379 --prefix standalone
|
|
96
|
+
|
|
97
|
+
# TLS on :6380 (generates certs in valkey-glide/utils/tls_crts/)
|
|
98
|
+
python3 valkey-glide/utils/cluster_manager.py --tls start -r 0 -p 6380 --prefix tls-standalone
|
|
99
|
+
export TLS_CERT_DIR=$(pwd)/valkey-glide/utils/tls_crts # required for TLS tests
|
|
100
|
+
|
|
101
|
+
# Stop when done
|
|
102
|
+
python3 valkey-glide/utils/cluster_manager.py stop --prefix standalone
|
|
103
|
+
python3 valkey-glide/utils/cluster_manager.py --tls stop --prefix tls-standalone
|
|
104
|
+
```
|
|
105
|
+
|
|
90
106
|
### Rebuild Native FFI (when changing glide-core)
|
|
91
107
|
|
|
92
108
|
```bash
|
|
@@ -141,7 +157,7 @@ cargo fmt --manifest-path ./Cargo.toml --all
|
|
|
141
157
|
- `*.gem` — built gem packages
|
|
142
158
|
- `coverage/` — coverage reports
|
|
143
159
|
- `tmp/`, `test/tmp/` — temporary test artifacts
|
|
144
|
-
-
|
|
160
|
+
- SSL certs — never commit them; TLS tests read certs from `TLS_CERT_DIR` (generated by `cluster_manager.py --tls`)
|
|
145
161
|
- Wrong-platform `libglide_ffi` binaries (build per OS/arch)
|
|
146
162
|
|
|
147
163
|
### Ruby-Specific Rules
|
|
@@ -185,7 +201,7 @@ valkey-glide-ruby/
|
|
|
185
201
|
├── test/lint/ # shared lint
|
|
186
202
|
├── valkey.gemspec
|
|
187
203
|
├── Rakefile
|
|
188
|
-
└── .github/workflows/
|
|
204
|
+
└── .github/workflows/ci.yml
|
|
189
205
|
```
|
|
190
206
|
|
|
191
207
|
## Quality Gates (Agent Checklist)
|
data/CONTRIBUTING.md
CHANGED
|
@@ -39,7 +39,7 @@ For issues that affect the shared Rust core or other language clients, consider
|
|
|
39
39
|
```
|
|
40
40
|
Configure automatic signoff: `git config --global format.signOff true`
|
|
41
41
|
|
|
42
|
-
5. Open a PR and respond to CI feedback (RuboCop + test matrix in `.github/workflows/
|
|
42
|
+
5. Open a PR and respond to CI feedback (RuboCop + test matrix in `.github/workflows/ci.yml`).
|
|
43
43
|
|
|
44
44
|
GitHub guides: [fork a repo](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo), [create a pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request).
|
|
45
45
|
|
data/DEVELOPER.md
CHANGED
|
@@ -43,7 +43,7 @@ valkey-glide-ruby/
|
|
|
43
43
|
│ ├── setup # bundle install
|
|
44
44
|
│ └── console # IRB with gem loaded
|
|
45
45
|
├── .github/workflows/
|
|
46
|
-
│ ├──
|
|
46
|
+
│ ├── ci.yml # RuboCop + test matrix
|
|
47
47
|
│ └── cd.yml # Build and publish gem
|
|
48
48
|
├── valkey.gemspec
|
|
49
49
|
├── Gemfile
|
|
@@ -226,13 +226,14 @@ CI=1 bundle exec rake test:standalone
|
|
|
226
226
|
|
|
227
227
|
### SSL Tests
|
|
228
228
|
|
|
229
|
-
|
|
229
|
+
TLS tests require a TLS server plus `TLS_CERT_DIR` pointing at its certs. Use
|
|
230
|
+
`cluster_manager.py --tls` which will prepare a self-sign certs for use:
|
|
230
231
|
|
|
231
232
|
```bash
|
|
232
233
|
# Start a TLS-only server on port 6380 (generates certs in valkey-glide/utils/tls_crts/)
|
|
233
234
|
python3 valkey-glide/utils/cluster_manager.py --tls start -r 0 -p 6380 --prefix tls-standalone
|
|
234
235
|
|
|
235
|
-
# Point tests at the generated certs
|
|
236
|
+
# Point tests at the generated certs (required — the TLS tests raise if unset)
|
|
236
237
|
export TLS_CERT_DIR=$(pwd)/valkey-glide/utils/tls_crts
|
|
237
238
|
|
|
238
239
|
# Run tests
|
|
@@ -242,12 +243,8 @@ bundle exec rake test:standalone
|
|
|
242
243
|
python3 valkey-glide/utils/cluster_manager.py --tls stop --prefix tls-standalone
|
|
243
244
|
```
|
|
244
245
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
```bash
|
|
248
|
-
ruby test/fixtures/ssl/generate_certs.rb
|
|
249
|
-
# Then start a TLS Valkey server on port 6380 using those certs
|
|
250
|
-
```
|
|
246
|
+
If you don't want to run the TLS tests, set `SKIP_TLS_TESTS=true` and they are
|
|
247
|
+
skipped gracefully (this is what macOS CI does).
|
|
251
248
|
|
|
252
249
|
### Module Tests (JSON, Bloom, Search)
|
|
253
250
|
|
|
@@ -270,7 +267,7 @@ VALKEY_PORT=6379 TIMEOUT=10 bundle exec rake test:standalone
|
|
|
270
267
|
bundle exec rubocop
|
|
271
268
|
```
|
|
272
269
|
|
|
273
|
-
CI runs RuboCop on every push and pull request (see `.github/workflows/
|
|
270
|
+
CI runs RuboCop on every push and pull request (see `.github/workflows/ci.yml`).
|
|
274
271
|
|
|
275
272
|
Auto-correct safe offenses:
|
|
276
273
|
|
|
@@ -350,7 +347,7 @@ bundle exec ruby test/valkey/test_opentelemetry.rb
|
|
|
350
347
|
|
|
351
348
|
## CI Overview
|
|
352
349
|
|
|
353
|
-
GitHub Actions (`.github/workflows/
|
|
350
|
+
GitHub Actions (`.github/workflows/ci.yml`):
|
|
354
351
|
|
|
355
352
|
| Job | Matrix |
|
|
356
353
|
|-----|--------|
|
|
@@ -443,7 +440,7 @@ ruby -e "require 'valkey'; c = Valkey.new; puts c.ping; c.close"
|
|
|
443
440
|
| `LoadError` / FFI library not found | Confirm `lib/valkey/libglide_ffi.{so,dylib}` exists and matches your OS/arch. |
|
|
444
441
|
| Wrong architecture after FFI rebuild | Rebuild `glide-ffi` on the target platform; do not copy Linux `.so` to macOS. |
|
|
445
442
|
| Cluster tests flaky | Wait for `cluster_state:ok`; increase `TIMEOUT` env var. |
|
|
446
|
-
| SSL test failures |
|
|
443
|
+
| SSL test failures / `TLS_CERT_DIR is not set` | Start a TLS server via `cluster_manager.py --tls` and `export TLS_CERT_DIR=...` (see SSL Tests above), or set `SKIP_TLS_TESTS=true`. |
|
|
447
444
|
| Pipeline / MULTI crashes | Transaction commands in `pipelined` use sequential fallback by design. |
|
|
448
445
|
|
|
449
446
|
## Recommended Editor Extensions
|
|
@@ -70,7 +70,7 @@ class Valkey
|
|
|
70
70
|
#
|
|
71
71
|
# @param [Integer] protover Protocol version (2 or 3)
|
|
72
72
|
# @param [Hash] options Optional parameters like AUTH, SETNAME
|
|
73
|
-
# @return [
|
|
73
|
+
# @return [Hash] Server information (server, version, proto, id, mode, role, modules)
|
|
74
74
|
def hello(protover = 3, **options)
|
|
75
75
|
args = [protover]
|
|
76
76
|
|
|
@@ -209,7 +209,7 @@ class Valkey
|
|
|
209
209
|
# - when `"RIGHT"` - the elements popped are those from the right of the list
|
|
210
210
|
# @params count [Integer] a number of elements to pop
|
|
211
211
|
#
|
|
212
|
-
# @return [
|
|
212
|
+
# @return [Hash, nil] hash mapping key to popped elements, or nil on timeout
|
|
213
213
|
def blmpop(timeout, *keys, modifier: "LEFT", count: nil)
|
|
214
214
|
raise ArgumentError, "Pick either LEFT or RIGHT" unless %w[LEFT RIGHT].include?(modifier)
|
|
215
215
|
|
|
@@ -235,7 +235,7 @@ class Valkey
|
|
|
235
235
|
# - when `"RIGHT"` - the elements popped are those from the right of the list
|
|
236
236
|
# @params count [Integer] a number of elements to pop
|
|
237
237
|
#
|
|
238
|
-
# @return [
|
|
238
|
+
# @return [Hash, nil] hash mapping key to popped elements, or nil if no elements
|
|
239
239
|
def lmpop(*keys, modifier: "LEFT", count: nil)
|
|
240
240
|
raise ArgumentError, "Pick either LEFT or RIGHT" unless %w[LEFT RIGHT].include?(modifier)
|
|
241
241
|
|
|
@@ -168,7 +168,7 @@ class Valkey
|
|
|
168
168
|
# # => ["channel1", 5, "channel2", 3]
|
|
169
169
|
#
|
|
170
170
|
# @param [Array<String>] channels the channels to check
|
|
171
|
-
# @return [
|
|
171
|
+
# @return [Hash] channel names mapped to subscriber counts
|
|
172
172
|
#
|
|
173
173
|
# @see https://valkey.io/commands/pubsub-numsub/
|
|
174
174
|
def pubsub_numsub(*channels)
|
|
@@ -200,7 +200,7 @@ class Valkey
|
|
|
200
200
|
# # => ["shard1", 2, "shard2", 1]
|
|
201
201
|
#
|
|
202
202
|
# @param [Array<String>] channels the shard channels to check
|
|
203
|
-
# @return [
|
|
203
|
+
# @return [Hash] shard channel names mapped to subscriber counts
|
|
204
204
|
#
|
|
205
205
|
# @see https://valkey.io/commands/pubsub-shardnumsub/
|
|
206
206
|
def pubsub_shardnumsub(*channels)
|
|
@@ -137,22 +137,13 @@ class Valkey
|
|
|
137
137
|
# @example Execute script that returns different data types
|
|
138
138
|
# valkey.eval("return {1, 'hello', true, nil}")
|
|
139
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"
|
|
143
140
|
# Since the eval is not available in the rust backend
|
|
144
141
|
# using the load and invoke script
|
|
145
|
-
def eval(script,
|
|
142
|
+
def eval(script, keys: [], args: [])
|
|
146
143
|
# Validate script parameter
|
|
147
144
|
raise ArgumentError, "script must be a string" unless script.is_a?(String)
|
|
148
145
|
raise ArgumentError, "script cannot be empty" if script.empty?
|
|
149
146
|
|
|
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
|
-
|
|
156
147
|
# Validate and convert keys and args to strings
|
|
157
148
|
begin
|
|
158
149
|
keys = Array(keys).map(&:to_s)
|
|
@@ -190,22 +181,13 @@ class Valkey
|
|
|
190
181
|
# rescue Valkey::CommandError => e
|
|
191
182
|
# puts "Script not found: #{e.message}"
|
|
192
183
|
# end
|
|
193
|
-
# @example Positional form, matching redis-rb's evalsha(sha, keys, argv)
|
|
194
|
-
# valkey.evalsha(sha, ["user"], ["123"])
|
|
195
|
-
# # => "user:123"
|
|
196
184
|
# Since evalsha is not available in rust backend
|
|
197
185
|
# using invoke script
|
|
198
|
-
def evalsha(sha,
|
|
186
|
+
def evalsha(sha, keys: [], args: [])
|
|
199
187
|
# Validate SHA1 hash parameter
|
|
200
188
|
raise ArgumentError, "sha1 hash must be a string" unless sha.is_a?(String)
|
|
201
189
|
raise ArgumentError, "sha1 hash must be a 40-character hexadecimal string" unless valid_sha1?(sha)
|
|
202
190
|
|
|
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
|
-
|
|
209
191
|
# Validate and convert keys and args to strings
|
|
210
192
|
begin
|
|
211
193
|
keys = Array(keys).map(&:to_s)
|
|
@@ -271,8 +253,8 @@ class Valkey
|
|
|
271
253
|
end
|
|
272
254
|
|
|
273
255
|
def invoke_script(script, args: [], keys: [])
|
|
274
|
-
arg_ptrs, arg_lens = build_command_args(args)
|
|
275
|
-
keys_ptrs, keys_lens = build_command_args(keys)
|
|
256
|
+
arg_ptrs, arg_lens, _arg_bufs = build_command_args(args)
|
|
257
|
+
keys_ptrs, keys_lens, _keys_bufs = build_command_args(keys)
|
|
276
258
|
|
|
277
259
|
route = ""
|
|
278
260
|
route_buf = FFI::MemoryPointer.from_string(route)
|
|
@@ -565,7 +565,7 @@ class Valkey
|
|
|
565
565
|
# @param [Hash] options optional parameters
|
|
566
566
|
# - `:full => true`: return full information including entries
|
|
567
567
|
# - `:count => Integer`: limit number of entries (requires :full)
|
|
568
|
-
# @return [
|
|
568
|
+
# @return [Hash] stream information as a hash of key-value pairs
|
|
569
569
|
#
|
|
570
570
|
# @example Get basic stream info
|
|
571
571
|
# valkey.xinfo_stream("mystream")
|
|
@@ -129,14 +129,7 @@ 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
|
-
|
|
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)
|
|
132
|
+
send_command(RequestType::SET_NX, [key, value])
|
|
140
133
|
end
|
|
141
134
|
|
|
142
135
|
# Set one or more values.
|
|
@@ -327,7 +320,7 @@ class Valkey
|
|
|
327
320
|
# - `:idx => true`: Return the positions of the LCS
|
|
328
321
|
# - `:min_match_len => Integer`: Minimum match length
|
|
329
322
|
# - `:with_match_len => true`: Include match length in results
|
|
330
|
-
# @return [String, Integer,
|
|
323
|
+
# @return [String, Integer, Hash] the LCS result based on options (Hash when idx: true)
|
|
331
324
|
def lcs(key1, key2, len: nil, idx: nil, min_match_len: nil, with_match_len: nil)
|
|
332
325
|
args = [key1, key2]
|
|
333
326
|
args << "LEN" if len
|
|
@@ -16,12 +16,8 @@ class Valkey
|
|
|
16
16
|
# end # => ["OK", 6]
|
|
17
17
|
#
|
|
18
18
|
# @yield [multi] the commands that are called inside this block are cached
|
|
19
|
-
#
|
|
20
|
-
#
|
|
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
|
|
19
|
+
# and written to the server upon returning from it
|
|
20
|
+
# @yieldparam [Valkey] multi `self`
|
|
25
21
|
#
|
|
26
22
|
# @return [Array<...>]
|
|
27
23
|
# - an array with replies
|
|
@@ -30,12 +26,17 @@ class Valkey
|
|
|
30
26
|
# @see #unwatch
|
|
31
27
|
def multi
|
|
32
28
|
if block_given?
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
begin
|
|
30
|
+
@in_multi_block = true
|
|
31
|
+
start_multi
|
|
32
|
+
yield(self)
|
|
33
|
+
exec
|
|
34
|
+
rescue StandardError
|
|
35
|
+
discard
|
|
36
|
+
raise
|
|
37
|
+
ensure
|
|
38
|
+
@in_multi_block = false
|
|
39
|
+
end
|
|
39
40
|
else
|
|
40
41
|
start_multi
|
|
41
42
|
self
|
|
@@ -113,12 +114,11 @@ class Valkey
|
|
|
113
114
|
# @see #discard
|
|
114
115
|
def exec
|
|
115
116
|
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
|
|
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,59 +157,8 @@ 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
|
-
|
|
191
160
|
private
|
|
192
161
|
|
|
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
|
-
|
|
213
162
|
# Start a MULTI block if one isn't already active.
|
|
214
163
|
#
|
|
215
164
|
# This mirrors the behaviour of popular Valkey/Redis clients where
|
|
@@ -181,7 +181,7 @@ class Valkey
|
|
|
181
181
|
# # => ["index_name", "myIndex", "fields", [...], ...]
|
|
182
182
|
#
|
|
183
183
|
# @param [String] index the index name
|
|
184
|
-
# @return [
|
|
184
|
+
# @return [Hash] index information as a hash of key-value pairs
|
|
185
185
|
#
|
|
186
186
|
# @see https://redis.io/commands/ft.info/
|
|
187
187
|
def ft_info(index)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/lib/valkey/route.rb
CHANGED
|
@@ -67,6 +67,8 @@ class Valkey
|
|
|
67
67
|
# Returns both the struct and any pinned memory buffers that must remain
|
|
68
68
|
# alive until the FFI call completes.
|
|
69
69
|
#
|
|
70
|
+
# TODO: Refactor to return a struct (https://github.com/valkey-io/valkey-glide-ruby/issues/179)
|
|
71
|
+
#
|
|
70
72
|
# @return [Array(Bindings::RouteInfo, Array)] the struct and pinned buffers
|
|
71
73
|
def to_ffi
|
|
72
74
|
info = Bindings::RouteInfo.new
|
data/lib/valkey/version.rb
CHANGED
data/lib/valkey.rb
CHANGED
|
@@ -156,50 +156,8 @@ class Valkey
|
|
|
156
156
|
# TLS/SSL certificates
|
|
157
157
|
root_certs = []
|
|
158
158
|
if options[:ssl_params].is_a?(Hash)
|
|
159
|
-
# ca_file -
|
|
160
|
-
if options[:ssl_params][:ca_file]
|
|
161
|
-
ca_file = options[:ssl_params][:ca_file]
|
|
162
|
-
raise ArgumentError, "CA file does not exist: #{ca_file}" unless File.exist?(ca_file)
|
|
163
|
-
raise ArgumentError, "CA file is not readable: #{ca_file}" unless File.readable?(ca_file)
|
|
164
|
-
|
|
165
|
-
root_certs << File.binread(ca_file)
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
# cert - client certificate (file path or OpenSSL::X509::Certificate)
|
|
169
|
-
if options[:ssl_params][:cert]
|
|
170
|
-
cert_data = if options[:ssl_params][:cert].is_a?(String)
|
|
171
|
-
cert_file = options[:ssl_params][:cert]
|
|
172
|
-
raise ArgumentError, "Cert file does not exist: #{cert_file}" unless File.exist?(cert_file)
|
|
173
|
-
raise ArgumentError, "Cert file is not readable: #{cert_file}" unless File.readable?(cert_file)
|
|
174
|
-
|
|
175
|
-
File.binread(cert_file)
|
|
176
|
-
elsif options[:ssl_params][:cert].respond_to?(:to_pem)
|
|
177
|
-
options[:ssl_params][:cert].to_pem
|
|
178
|
-
elsif options[:ssl_params][:cert].respond_to?(:to_der)
|
|
179
|
-
options[:ssl_params][:cert].to_der
|
|
180
|
-
else
|
|
181
|
-
options[:ssl_params][:cert].to_s
|
|
182
|
-
end
|
|
183
|
-
root_certs << cert_data
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
# key - client key (file path or OpenSSL::PKey)
|
|
187
|
-
if options[:ssl_params][:key]
|
|
188
|
-
key_data = if options[:ssl_params][:key].is_a?(String)
|
|
189
|
-
key_file = options[:ssl_params][:key]
|
|
190
|
-
raise ArgumentError, "Key file does not exist: #{key_file}" unless File.exist?(key_file)
|
|
191
|
-
raise ArgumentError, "Key file is not readable: #{key_file}" unless File.readable?(key_file)
|
|
192
|
-
|
|
193
|
-
File.binread(key_file)
|
|
194
|
-
elsif options[:ssl_params][:key].respond_to?(:to_pem)
|
|
195
|
-
options[:ssl_params][:key].to_pem
|
|
196
|
-
elsif options[:ssl_params][:key].respond_to?(:to_der)
|
|
197
|
-
options[:ssl_params][:key].to_der
|
|
198
|
-
else
|
|
199
|
-
options[:ssl_params][:key].to_s
|
|
200
|
-
end
|
|
201
|
-
root_certs << key_data
|
|
202
|
-
end
|
|
159
|
+
# ca_file - CA certificate file path
|
|
160
|
+
root_certs << read_ssl_value(options[:ssl_params][:ca_file], "CA") if options[:ssl_params][:ca_file]
|
|
203
161
|
|
|
204
162
|
# Additional root certificates from ca_path
|
|
205
163
|
if options[:ssl_params][:ca_path]
|
|
@@ -214,6 +172,12 @@ class Valkey
|
|
|
214
172
|
end
|
|
215
173
|
end
|
|
216
174
|
|
|
175
|
+
# cert - file path or OpenSSL::X509::Certificate
|
|
176
|
+
json_options["client_cert"] = read_ssl_value(options[:ssl_params][:cert], "Cert") if options[:ssl_params][:cert]
|
|
177
|
+
|
|
178
|
+
# key - file path or OpenSSL::PKey
|
|
179
|
+
json_options["client_key"] = read_ssl_value(options[:ssl_params][:key], "Key") if options[:ssl_params][:key]
|
|
180
|
+
|
|
217
181
|
# Direct root_certs array support
|
|
218
182
|
root_certs.concat(options[:ssl_params][:root_certs]) if options[:ssl_params][:root_certs].is_a?(Array)
|
|
219
183
|
end
|
|
@@ -291,6 +255,11 @@ class Valkey
|
|
|
291
255
|
# Store cluster mode flag for response handling (MAP returns Hash in cluster, Array in standalone)
|
|
292
256
|
@cluster_mode = options[:cluster_mode] ? true : false
|
|
293
257
|
|
|
258
|
+
# Returns GLIDE Core map as a flattened-map array
|
|
259
|
+
# Ex: { "key1" => "value1", "key2" => "value2" } becomes ["key1", "value1", "key2", "value2"]
|
|
260
|
+
# Compatibility for redis-rb 4.x
|
|
261
|
+
@flatten_map = options[:flatten_map] ? true : false
|
|
262
|
+
|
|
294
263
|
# Track transactional state for `MULTI` / `EXEC` / `DISCARD` helpers.
|
|
295
264
|
# This avoids Ruby warnings about uninitialised instance variables and
|
|
296
265
|
# gives us a single source of truth for whether we're inside a TX.
|
|
@@ -435,7 +404,7 @@ class Valkey
|
|
|
435
404
|
)
|
|
436
405
|
end
|
|
437
406
|
|
|
438
|
-
result = convert_response(res,
|
|
407
|
+
result = convert_response(res, &block)
|
|
439
408
|
ensure
|
|
440
409
|
# Free the native CommandResult (arena + response + error) to prevent memory leak
|
|
441
410
|
Bindings.free_command_result(res) if res && !res.null?
|
|
@@ -457,9 +426,7 @@ class Valkey
|
|
|
457
426
|
RequestType::MULTI, RequestType::EXEC, RequestType::DISCARD,
|
|
458
427
|
RequestType::WATCH, RequestType::UNWATCH
|
|
459
428
|
]
|
|
460
|
-
if !tx_commands.include?(command_type) && result == "QUEUED"
|
|
461
|
-
@queued_commands << [command_type, command_args.dup, block]
|
|
462
|
-
end
|
|
429
|
+
@queued_commands << [command_type, command_args.dup] if !tx_commands.include?(command_type) && result == "QUEUED"
|
|
463
430
|
end
|
|
464
431
|
|
|
465
432
|
result
|
|
@@ -467,29 +434,41 @@ class Valkey
|
|
|
467
434
|
|
|
468
435
|
private
|
|
469
436
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
#
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
res = block.call(res) if block
|
|
488
|
-
results << res
|
|
489
|
-
end
|
|
437
|
+
# Read an SSL value
|
|
438
|
+
# Accepts a file path (String), an OpenSSL object (#to_pem / #to_der), or a fallback #to_s.
|
|
439
|
+
def read_ssl_value(value, label)
|
|
440
|
+
if value.is_a?(String)
|
|
441
|
+
raise ArgumentError, "#{label} file does not exist: #{value}" unless File.exist?(value)
|
|
442
|
+
raise ArgumentError, "#{label} file is not readable: #{value}" unless File.readable?(value)
|
|
443
|
+
|
|
444
|
+
File.binread(value)
|
|
445
|
+
# Duck-typing check
|
|
446
|
+
elsif value.respond_to?(:to_pem)
|
|
447
|
+
value.to_pem
|
|
448
|
+
elsif value.respond_to?(:to_der)
|
|
449
|
+
value.to_der
|
|
450
|
+
else
|
|
451
|
+
value.to_s
|
|
452
|
+
end
|
|
453
|
+
end
|
|
490
454
|
|
|
491
|
-
|
|
455
|
+
def send_batch_commands(commands, exception: true)
|
|
456
|
+
# WORKAROUND: The underlying Glide FFI backend has stability issues when
|
|
457
|
+
# batching transactional commands like MULTI / EXEC / DISCARD. To avoid
|
|
458
|
+
# native crashes we fall back to issuing those commands sequentially
|
|
459
|
+
# instead of via `Bindings.batch`.
|
|
460
|
+
tx_types = [RequestType::MULTI, RequestType::EXEC, RequestType::DISCARD]
|
|
461
|
+
|
|
462
|
+
if commands.any? { |(command_type, _args, _block)| tx_types.include?(command_type) }
|
|
463
|
+
results = []
|
|
464
|
+
|
|
465
|
+
commands.each do |command_type, command_args, block|
|
|
466
|
+
res = send_command(command_type, command_args)
|
|
467
|
+
res = block.call(res) if block
|
|
468
|
+
results << res
|
|
492
469
|
end
|
|
470
|
+
|
|
471
|
+
return results
|
|
493
472
|
end
|
|
494
473
|
|
|
495
474
|
cmds = []
|
|
@@ -519,7 +498,7 @@ class Valkey
|
|
|
519
498
|
batch_info = Bindings::BatchInfo.new
|
|
520
499
|
batch_info[:cmd_count] = cmds.size
|
|
521
500
|
batch_info[:cmds] = cmd_ptrs
|
|
522
|
-
batch_info[:is_atomic] =
|
|
501
|
+
batch_info[:is_atomic] = false
|
|
523
502
|
|
|
524
503
|
batch_options = Bindings::BatchOptionsInfo.new
|
|
525
504
|
batch_options[:retry_server_error] = true
|
|
@@ -601,6 +580,9 @@ class Valkey
|
|
|
601
580
|
{ "manual_interval" => { "duration_in_sec" => duration_in_sec } }
|
|
602
581
|
end
|
|
603
582
|
|
|
583
|
+
# Returns [ptrs, lens, buffers]. Callers must retain buffers until the FFI call returns.
|
|
584
|
+
# Otherwise, GC may free the buffers making the ptrs invalid.
|
|
585
|
+
# TODO: Refactor to return a struct (https://github.com/valkey-io/valkey-glide-ruby/issues/179)
|
|
604
586
|
def build_command_args(command_args)
|
|
605
587
|
# For empty arrays, pass NULL pointers as per Rust FFI contract
|
|
606
588
|
# This matches Go's approach which successfully uses nil pointers
|
|
@@ -622,7 +604,7 @@ class Valkey
|
|
|
622
604
|
[arg_ptrs, arg_lens, buffers]
|
|
623
605
|
end
|
|
624
606
|
|
|
625
|
-
def convert_response(res,
|
|
607
|
+
def convert_response(res, &block)
|
|
626
608
|
result = Bindings::CommandResult.new(res)
|
|
627
609
|
|
|
628
610
|
if result[:response].null?
|
|
@@ -678,8 +660,7 @@ class Valkey
|
|
|
678
660
|
map[map_key] = map_value
|
|
679
661
|
end
|
|
680
662
|
|
|
681
|
-
|
|
682
|
-
return_map_as_hash ? map : map.to_a.flatten(1)
|
|
663
|
+
@flatten_map ? map.to_a.flatten(1) : map
|
|
683
664
|
when ResponseType::SETS
|
|
684
665
|
ptr = response_item[:sets_value]
|
|
685
666
|
count = response_item[:sets_value_len].to_i
|
|
@@ -708,10 +689,7 @@ class Valkey
|
|
|
708
689
|
|
|
709
690
|
response = convert_response.call(result)
|
|
710
691
|
|
|
711
|
-
|
|
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"
|
|
692
|
+
if block_given?
|
|
715
693
|
block.call(response)
|
|
716
694
|
else
|
|
717
695
|
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.
|
|
4
|
+
version: 1.0.0.pre.rc2
|
|
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-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ffi
|