From 8486d27cb23bc00da6f002139ee6feff1cbe90b1 Mon Sep 17 00:00:00 2001 From: dalew <91363952+dal1wg@users.noreply.github.com> Date: Wed, 17 Jun 2026 11:20:07 +0800 Subject: [PATCH] fix(redis): unify INFO category command output in cluster mode * fix(redis):format INFO output as plain text in cluster mode * fix(redis): unify INFO category command output in cluster mode In Redis cluster mode, the INFO command (with category parameters) returns aggregated results from multiple primaries. This fix formats the output as human-readable plain text, consistent with standalone mode, to improve readability. Closes #1326 --- apps/desktop/src/lib/redisQueryResult.ts | 11 +++++++++ crates/dbx-core/src/db/redis_driver.rs | 31 ++++++++++++------------ 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/apps/desktop/src/lib/redisQueryResult.ts b/apps/desktop/src/lib/redisQueryResult.ts index 3ce555e6f6..7f188fc196 100644 --- a/apps/desktop/src/lib/redisQueryResult.ts +++ b/apps/desktop/src/lib/redisQueryResult.ts @@ -20,6 +20,17 @@ export function redisCommandResultToQueryResult(value: unknown, elapsedMs: numbe execution_time_ms: Math.max(0, Math.round(elapsedMs)), }; } + // INFO commands in cluster mode → [[node_addr, info_text], ...] pairs. + // Render as a two-column table with the addr as the index and info text as the value. + if (Array.isArray(value) && value.length > 0 && value.every((item) => Array.isArray(item) && item.length === 2 && typeof item[0] === "string" && typeof item[1] === "string")) { + const rows: (string | number | boolean | null)[][] = value.map((item: [string, string]) => [item[0], item[1]]); + return { + columns: ["(index)", "value"], + rows, + affected_rows: value.length, + execution_time_ms: Math.max(0, Math.round(elapsedMs)), + }; + } if (Array.isArray(value)) { const rows: (string | number | boolean | null)[][] = value.map((item, i) => [i + 1, formatRedisCommandResult(item)]); return { diff --git a/crates/dbx-core/src/db/redis_driver.rs b/crates/dbx-core/src/db/redis_driver.rs index cd887fe107..b2356d76ee 100644 --- a/crates/dbx-core/src/db/redis_driver.rs +++ b/crates/dbx-core/src/db/redis_driver.rs @@ -1014,27 +1014,28 @@ where // Special handling for INFO command in cluster mode. // redis-rs ClusterConnection routes INFO to all primaries and // aggregates the results as a Map(node_addr → full_info_text). - // We detect this pattern and format it as human-readable plain text - // instead of converting to a JSON array of {key, value} objects. + // We detect this pattern and return it as an array of + // [node_addr, info_text] pairs, which the frontend renders + // as a two-column (index → node_addr, value → info_text) table. if command == "INFO" { if let RedisRawValue::Map(entries) = &raw { - // Cluster-aggregated INFO has multi-line values starting with "# Server". + // Cluster-aggregated INFO has multi-line values starting with "# " + // (e.g. "# Server", "# Memory", "# Clients"). // This distinguishes it from a RESP3 standalone INFO map where values - // are single field values (e.g. "redis_version", "os"). + // are single field values (e.g. "redis_version", "os") or nested maps. let is_cluster_aggregation = - entries.iter().any(|(_, v)| redis_raw_value_as_str(v).is_some_and(|s| s.starts_with("# Server"))); + entries.iter().any(|(_, v)| redis_raw_value_as_str(v).is_some_and(|s| s.starts_with("# "))); if is_cluster_aggregation { - let mut parts: Vec = Vec::with_capacity(entries.len()); - for (key, value) in entries { - let addr = redis_raw_value_as_str(key); - let info = redis_raw_value_as_str(value); - if let (Some(addr), Some(info)) = (addr, info) { - parts.push(format!("{addr}\n{info}")); - } - } - let text = parts.join("\n\n"); - return Ok(RedisCommandResult { command, safety, value: serde_json::Value::String(text) }); + let pairs: Vec = entries + .iter() + .filter_map(|(key, value)| { + let addr = redis_raw_value_as_str(key)?; + let info = redis_raw_value_as_str(value)?; + Some(serde_json::json!([addr, info])) + }) + .collect(); + return Ok(RedisCommandResult { command, safety, value: serde_json::Value::Array(pairs) }); } } }