From 076e9cbb0ea6cdbd6ddc07e4a5f236b79dfbdafd Mon Sep 17 00:00:00 2001 From: Alex Le Date: Thu, 10 Sep 2026 14:51:35 -0700 Subject: [PATCH 1/2] docs(ruby): correct cluster routing return types in YARD docs Seven route-taking commands documented return shapes that disagree with glide-core's response policy (ResponsePolicy::for_command in glide-core/redis-rs/redis/src/cluster_routing.rs). - bgsave, bgrewriteaof: default to all primaries with no response policy, so the cluster reply is a per-node Hash even with no route; @return said String. - lastsave, lolwut, function_dump, function_list: a multi-node route yields a per-node Hash; @return listed only the scalar. - randomkey: claimed a per-node Hash, but RANDOMKEY's FirstSucceededNonEmptyOrAllEmpty policy always collapses to a single key, or nil when every shard is empty. - function_stats: claimed a Hash keyed by "host:port" unconditionally, but a single-node route returns that node's stats unkeyed. Docs only; no behavior change. All aggregation happens in glide-core, and every affected method is a bare send_command pass-through. Cross-checked against the Go, Java, Python and Node clients, which express the same split through ClusterValue / TClusterResponse / ClusterResponse. Signed-off-by: Alex Le --- lib/valkey/commands/function_commands.rb | 16 ++++++++++------ lib/valkey/commands/generic_commands.rb | 3 ++- lib/valkey/commands/server_commands.rb | 20 ++++++++++++-------- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/lib/valkey/commands/function_commands.rb b/lib/valkey/commands/function_commands.rb index 2bcf5f57..f34a625b 100644 --- a/lib/valkey/commands/function_commands.rb +++ b/lib/valkey/commands/function_commands.rb @@ -28,8 +28,9 @@ def function_delete(library_name, route: nil) # valkey.function_dump # # => # - # @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value. - # @return [String] the serialized payload + # @param route [Valkey::Route, nil] cluster routing. A multi-node route returns a `Hash` of + # `"host:port" => String`. + # @return [String, Hash{String => String}] the serialized payload # # @see https://valkey.io/commands/function-dump/ def function_dump(route: nil) @@ -94,8 +95,9 @@ def function_kill(route: nil) # # @param [String] library_name filter by library name pattern # @param [Boolean] with_code include the library code in the response - # @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value. - # @return [Array] array of library information + # @param route [Valkey::Route, nil] cluster routing. Default is a single random node. + # A multi-node route returns a `Hash` of `"host:port" => Array`. + # @return [Array, Hash{String => Array}] array of library information # # @see https://valkey.io/commands/function-list/ def function_list(library_name: nil, with_code: false, route: nil) @@ -171,8 +173,10 @@ def function_restore(serialized_value, policy: nil, route: nil) # valkey.function_stats # # => {"127.0.0.1:6379" => {"running_script" => nil, "engines" => {...}}} # - # @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value. - # @return [Hash{String => Hash}] a Hash keyed by `"host:port"`. + # @param route [Valkey::Route, nil] cluster routing. On cluster the default is all nodes, so the + # reply is keyed by `"host:port"` even with no route. A single-node route returns that node's + # stats directly, without the `"host:port"` key. + # @return [Hash{String => Hash}, Hash] a Hash keyed by `"host:port"`, or one node's stats. # # @see https://valkey.io/commands/function-stats/ def function_stats(route: nil) diff --git a/lib/valkey/commands/generic_commands.rb b/lib/valkey/commands/generic_commands.rb index ad061c68..b5d23723 100644 --- a/lib/valkey/commands/generic_commands.rb +++ b/lib/valkey/commands/generic_commands.rb @@ -398,7 +398,8 @@ def object(subcommand, *args) # Return a random key from the keyspace. # - # @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value. + # @param route [Valkey::Route, nil] cluster routing. Never returns a per-node Hash: glide-core + # collapses multi-node replies to the first non-empty key, or `nil` if every shard is empty. # @return [String, nil] def randomkey(route: nil) send_command(RequestType::RANDOM_KEY, [], route: route) diff --git a/lib/valkey/commands/server_commands.rb b/lib/valkey/commands/server_commands.rb index eb103d5f..cfe4c4bc 100644 --- a/lib/valkey/commands/server_commands.rb +++ b/lib/valkey/commands/server_commands.rb @@ -9,16 +9,18 @@ module Commands module ServerCommands # Asynchronously rewrite the append-only file. # - # @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value. - # @return [String] + # @param route [Valkey::Route, nil] cluster routing. On cluster the default is all primaries, + # so the reply is a per-node Hash even with no route. A single-node route returns a String. + # @return [String, Hash{String => String}] def bgrewriteaof(route: nil) send_command(RequestType::BG_REWRITE_AOF, [], route: route) end # Asynchronously save the dataset to disk. # - # @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value. - # @return [String] + # @param route [Valkey::Route, nil] cluster routing. On cluster the default is all primaries, + # so the reply is a per-node Hash even with no route. A single-node route returns a String. + # @return [String, Hash{String => String}] def bgsave(route: nil) send_command(RequestType::BG_SAVE, [], route: route) end @@ -173,8 +175,9 @@ def parse_info_reply(reply, cmd) # Get the UNIX time stamp of the last successful save to disk. # - # @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value. - # @return [Integer] + # @param route [Valkey::Route, nil] cluster routing. Default is a single random node. + # A multi-node route returns a `Hash` of `"host:port" => Integer`. + # @return [Integer, Hash{String => Integer}] def lastsave(route: nil) send_command(RequestType::LAST_SAVE, [], route: route) end @@ -532,8 +535,9 @@ def failover(to: nil, force: false, abort: false, timeout: nil) # Display some computer art and the Valkey version. # # @param version [Integer, nil] optional version number for different art - # @param route [Valkey::Route, nil] cluster routing. When routed, may return a Hash of node => value. - # @return [String] ASCII art and version information + # @param route [Valkey::Route, nil] cluster routing. Default is a single random node. + # A multi-node route returns a `Hash` of `"host:port" => String`. + # @return [String, Hash{String => String}] ASCII art and version information # # @example # valkey.lolwut From 9455814b33f3fd9c681a32a08765f6eba4347eb5 Mon Sep 17 00:00:00 2001 From: Alex Le Date: Thu, 10 Sep 2026 14:52:29 -0700 Subject: [PATCH 2/2] docs(ruby): add CHANGELOG entry for cluster routing doc fixes Signed-off-by: Alex Le --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4507d475..b86c597c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ * fix(ruby): raise `Valkey::InheritedError` instead of crashing the process when a client created before `fork()` is used in the child ([#255](https://github.com/valkey-io/valkey-glide-ruby/issues/255)) * fix(ruby): `pipelined` and `multi` no longer discard successful replies when a queued command fails at runtime. The block form of `multi` now accepts an `exception:` kwarg (default `true`, matching `pipelined`), so `multi(exception: false) { ... }` returns an Array with `Valkey::CommandError` embedded at the position of any queued command that fails at runtime (e.g. `INCR` on a non-integer), instead of raising and discarding every other command's reply. This matches the server's "no rollback on a runtime error" semantics — other queued commands still commit and their replies stay reachable even though the default is still to raise and discard them. This kwarg only affects the block form; the imperative `multi` (no block) / `#exec` pair has no equivalent control. A genuine queue-time abort (nothing ran) now raises the new `Valkey::ExecAbortError` (a `CommandError` subclass) instead of a plain `CommandError`, so callers can tell the two apart from a mid-EXEC runtime error. Also fixed `incrbyfloat` and `geodist` returning a `String` instead of a `Float` when called inside `multi`/`exec`'s imperative (non-block) form, and a latent bug where a runtime error embedded in that form's `EXEC` reply could be silently coerced to `true` by boolean-typed commands' reconversion ([#260](https://github.com/valkey-io/valkey-glide-ruby/issues/260)) * Ruby: Fix `blpop`, `brpop`, `blmove`, `rpoplpush` and `brpoplpush`, all of which were non-functional. `blpop`/`brpop` called a non-existent `send_blocking_command` helper, `blmove` leaked the command name into argv, and `rpoplpush`/`brpoplpush` dispatched `RequestType::RPOPLPUSH`/`BRPOPLPUSH`, for which glide-core has no command mapping. Since Valkey defines `RPOPLPUSH src dst` as exactly `LMOVE src dst RIGHT LEFT` (and `BRPOPLPUSH src dst timeout` as `BLMOVE src dst RIGHT LEFT timeout`), `rpoplpush`/`brpoplpush` are now fixed-argument facades over `lmove`/`blmove`. Both remain deprecated as of Redis 6.2; prefer `lmove`/`blmove` in new code. The unusable `RequestType::RPOPLPUSH`/`BRPOPLPUSH` constants were removed. +* fix(ruby): correct the documented cluster return types for `bgsave`, `bgrewriteaof`, `lastsave`, `lolwut`, `function_dump`, `function_list`, `randomkey` and `function_stats`. The first six can return a per-node `Hash` keyed by `"host:port"` but documented only a scalar — and `bgsave`/`bgrewriteaof` do so even with no `route:`, since they default to all primaries — while `randomkey` documented a per-node `Hash` it never returns (glide-core collapses it to a single key, or `nil` when every shard is empty) and `function_stats` documented a `"host:port"`-keyed `Hash` unconditionally, though a single-node route returns that node's stats unkeyed ([#312](https://github.com/valkey-io/valkey-glide-ruby/pull/312)) ### Changes