From 51dfd02640681ad803c534108784740cc55be22b Mon Sep 17 00:00:00 2001 From: sdairs Date: Mon, 9 Mar 2026 21:47:49 +0000 Subject: [PATCH 1/9] Add --name flag to local client for connecting by server name `clickhousectl local client --name dev` looks up the named server's TCP port and passes it to clickhouse-client automatically. Defaults to "default" if --name is not specified. Errors if the server is not running. Also fixes a stale `local run client` reference in CLAUDE.md. Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 2 +- README.md | 5 +++-- src/cli.rs | 7 ++++++- src/main.rs | 17 ++++++++++++++--- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 764e9f1..ea2bb1f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -80,5 +80,5 @@ cargo add rpassword # add latest version ```bash cargo run -- local install stable cargo run -- local server start # starts server in .clickhouse/servers/default/ -cargo run -- local run client -- --query "SELECT 1" +cargo run -- local client -- --query "SELECT 1" ``` diff --git a/README.md b/README.md index 3e9bd09..3721066 100644 --- a/README.md +++ b/README.md @@ -90,8 +90,9 @@ clickhouse/ ```bash # Connect to a running server with clickhouse-client -clickhousectl local client -clickhousectl local client -- --host localhost --query "SHOW DATABASES" +clickhousectl local client # Connects to "default" server +clickhousectl local client --name dev # Connects to "dev" server +clickhousectl local client -- --query "SHOW DATABASES" ``` ### Creating and managing ClickHouse servers diff --git a/src/cli.rs b/src/cli.rs index 5f4c646..687d2e0 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -124,10 +124,15 @@ CONTEXT FOR AGENTS: #[command(after_help = "\ CONTEXT FOR AGENTS: Connects to a running clickhouse-server. Server must already be running via `clickhousectl local server start`. + Use --name to connect to a specific named server (default: \"default\"). Pass clickhouse-client args after -- (e.g., `clickhousectl local client -- --query 'SELECT 1'`). Common args: --host, --port, --query, --multiquery, --format. - Related: `clickhousectl local server start` to start a server first.")] + Related: `clickhousectl local server start` to start a server first, `clickhousectl local server list` to see servers.")] Client { + /// Server name to connect to (default: "default") + #[arg(long, short)] + name: Option, + /// Arguments to pass to clickhouse-client #[arg(trailing_var_arg = true, allow_hyphen_values = true)] args: Vec, diff --git a/src/main.rs b/src/main.rs index 853c2c3..cddaa53 100644 --- a/src/main.rs +++ b/src/main.rs @@ -52,7 +52,7 @@ async fn run_local(cmd: LocalCommands) -> Result<()> { init::init()?; Ok(()) } - LocalCommands::Client { args } => run_client(args), + LocalCommands::Client { name, args } => run_client(name, args), LocalCommands::Server { command } => run_server_commands(command), } } @@ -159,7 +159,15 @@ fn which() -> Result<()> { Ok(()) } -fn run_client(args: Vec) -> Result<()> { +fn run_client(name: Option, args: Vec) -> Result<()> { + let name = name.as_deref().unwrap_or("default"); + + let servers = server::list_running_servers(); + let info = servers + .iter() + .find(|s| s.name == name) + .ok_or_else(|| Error::ServerNotFound(name.to_string()))?; + let version = version_manager::get_default_version()?; let binary = paths::binary_path(&version)?; @@ -168,7 +176,10 @@ fn run_client(args: Vec) -> Result<()> { } let mut cmd = Command::new(&binary); - cmd.arg("client").args(&args); + cmd.arg("client") + .arg("--port") + .arg(info.tcp_port.to_string()); + cmd.args(&args); let err = cmd.exec(); Err(Error::Exec(err.to_string())) } From f43917967253c1db95a19e7b918ff94310ee62b2 Mon Sep 17 00:00:00 2001 From: sdairs Date: Mon, 9 Mar 2026 22:09:00 +0000 Subject: [PATCH 2/9] Promote --host, --port, --query as first-class client flags Instead of requiring passthrough (-- --query ...), these common clickhouse-client options are now proper flags on `local client`. Port is auto-detected from the named server if not explicitly set. Co-Authored-By: Claude Opus 4.6 --- README.md | 3 ++- src/cli.rs | 18 +++++++++++++++--- src/main.rs | 45 +++++++++++++++++++++++++++++++++++---------- 3 files changed, 52 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 3721066..651013e 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,8 @@ clickhouse/ # Connect to a running server with clickhouse-client clickhousectl local client # Connects to "default" server clickhousectl local client --name dev # Connects to "dev" server -clickhousectl local client -- --query "SHOW DATABASES" +clickhousectl local client --query "SHOW DATABASES" # Run a query +clickhousectl local client --host remote-host --port 9000 # Connect to a specific host/port ``` ### Creating and managing ClickHouse servers diff --git a/src/cli.rs b/src/cli.rs index 687d2e0..a7ea7a6 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -125,15 +125,27 @@ CONTEXT FOR AGENTS: CONTEXT FOR AGENTS: Connects to a running clickhouse-server. Server must already be running via `clickhousectl local server start`. Use --name to connect to a specific named server (default: \"default\"). - Pass clickhouse-client args after -- (e.g., `clickhousectl local client -- --query 'SELECT 1'`). - Common args: --host, --port, --query, --multiquery, --format. + --host, --port, and --query are promoted as first-class flags. + Additional clickhouse-client args can be passed after --. Related: `clickhousectl local server start` to start a server first, `clickhousectl local server list` to see servers.")] Client { /// Server name to connect to (default: "default") #[arg(long, short)] name: Option, - /// Arguments to pass to clickhouse-client + /// Host to connect to + #[arg(long, default_value = "localhost")] + host: String, + + /// TCP port to connect to (auto-detected from --name if not set) + #[arg(long, short)] + port: Option, + + /// Execute a SQL query + #[arg(long, short)] + query: Option, + + /// Additional arguments to pass to clickhouse-client #[arg(trailing_var_arg = true, allow_hyphen_values = true)] args: Vec, }, diff --git a/src/main.rs b/src/main.rs index cddaa53..c57ca5c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -52,7 +52,13 @@ async fn run_local(cmd: LocalCommands) -> Result<()> { init::init()?; Ok(()) } - LocalCommands::Client { name, args } => run_client(name, args), + LocalCommands::Client { + name, + host, + port, + query, + args, + } => run_client(name, host, port, query, args), LocalCommands::Server { command } => run_server_commands(command), } } @@ -159,14 +165,26 @@ fn which() -> Result<()> { Ok(()) } -fn run_client(name: Option, args: Vec) -> Result<()> { - let name = name.as_deref().unwrap_or("default"); - - let servers = server::list_running_servers(); - let info = servers - .iter() - .find(|s| s.name == name) - .ok_or_else(|| Error::ServerNotFound(name.to_string()))?; +fn run_client( + name: Option, + host: String, + port: Option, + query: Option, + args: Vec, +) -> Result<()> { + // Resolve port: explicit --port wins, otherwise look up from named server + let tcp_port = match port { + Some(p) => p, + None => { + let server_name = name.as_deref().unwrap_or("default"); + let servers = server::list_running_servers(); + let info = servers + .iter() + .find(|s| s.name == server_name) + .ok_or_else(|| Error::ServerNotFound(server_name.to_string()))?; + info.tcp_port + } + }; let version = version_manager::get_default_version()?; let binary = paths::binary_path(&version)?; @@ -177,8 +195,15 @@ fn run_client(name: Option, args: Vec) -> Result<()> { let mut cmd = Command::new(&binary); cmd.arg("client") + .arg("--host") + .arg(&host) .arg("--port") - .arg(info.tcp_port.to_string()); + .arg(tcp_port.to_string()); + + if let Some(q) = &query { + cmd.arg("--query").arg(q); + } + cmd.args(&args); let err = cmd.exec(); Err(Error::Exec(err.to_string())) From 72fa16e5bc37917d94aa8b7097c8639b74cc0848 Mon Sep 17 00:00:00 2001 From: sdairs Date: Mon, 9 Mar 2026 22:10:40 +0000 Subject: [PATCH 3/9] Add --queries-file flag to local client Promotes the clickhouse-client --queries-file option as a first-class flag for executing SQL from a file. Co-Authored-By: Claude Opus 4.6 --- README.md | 1 + src/cli.rs | 6 +++++- src/main.rs | 8 +++++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 651013e..976254a 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ clickhouse/ clickhousectl local client # Connects to "default" server clickhousectl local client --name dev # Connects to "dev" server clickhousectl local client --query "SHOW DATABASES" # Run a query +clickhousectl local client --queries-file schema.sql # Run queries from a file clickhousectl local client --host remote-host --port 9000 # Connect to a specific host/port ``` diff --git a/src/cli.rs b/src/cli.rs index a7ea7a6..1cdd45f 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -125,7 +125,7 @@ CONTEXT FOR AGENTS: CONTEXT FOR AGENTS: Connects to a running clickhouse-server. Server must already be running via `clickhousectl local server start`. Use --name to connect to a specific named server (default: \"default\"). - --host, --port, and --query are promoted as first-class flags. + --host, --port, --query, and --queries-file are promoted as first-class flags. Additional clickhouse-client args can be passed after --. Related: `clickhousectl local server start` to start a server first, `clickhousectl local server list` to see servers.")] Client { @@ -145,6 +145,10 @@ CONTEXT FOR AGENTS: #[arg(long, short)] query: Option, + /// Execute queries from a SQL file + #[arg(long)] + queries_file: Option, + /// Additional arguments to pass to clickhouse-client #[arg(trailing_var_arg = true, allow_hyphen_values = true)] args: Vec, diff --git a/src/main.rs b/src/main.rs index c57ca5c..a651810 100644 --- a/src/main.rs +++ b/src/main.rs @@ -57,8 +57,9 @@ async fn run_local(cmd: LocalCommands) -> Result<()> { host, port, query, + queries_file, args, - } => run_client(name, host, port, query, args), + } => run_client(name, host, port, query, queries_file, args), LocalCommands::Server { command } => run_server_commands(command), } } @@ -170,6 +171,7 @@ fn run_client( host: String, port: Option, query: Option, + queries_file: Option, args: Vec, ) -> Result<()> { // Resolve port: explicit --port wins, otherwise look up from named server @@ -204,6 +206,10 @@ fn run_client( cmd.arg("--query").arg(q); } + if let Some(f) = &queries_file { + cmd.arg("--queries-file").arg(f); + } + cmd.args(&args); let err = cmd.exec(); Err(Error::Exec(err.to_string())) From 862cc81563c3931cf2b18cf1fa15e5d9314f3fd8 Mon Sep 17 00:00:00 2001 From: sdairs Date: Mon, 9 Mar 2026 22:11:34 +0000 Subject: [PATCH 4/9] Use server's ClickHouse version for client binary selection When connecting by name, use the version the server was started with (from server info) instead of the current default. This prevents client/server version mismatches when running multiple servers across different versions. Falls back to the default version only when --port is explicitly set (bypassing server lookup). Co-Authored-By: Claude Opus 4.6 --- src/main.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index a651810..c791bc2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -174,9 +174,10 @@ fn run_client( queries_file: Option, args: Vec, ) -> Result<()> { - // Resolve port: explicit --port wins, otherwise look up from named server - let tcp_port = match port { - Some(p) => p, + // Resolve port and version: explicit --port bypasses server lookup and uses + // the default version; otherwise look up the named server for both. + let (tcp_port, version) = match port { + Some(p) => (p, version_manager::get_default_version()?), None => { let server_name = name.as_deref().unwrap_or("default"); let servers = server::list_running_servers(); @@ -184,11 +185,10 @@ fn run_client( .iter() .find(|s| s.name == server_name) .ok_or_else(|| Error::ServerNotFound(server_name.to_string()))?; - info.tcp_port + (info.tcp_port, info.version.clone()) } }; - let version = version_manager::get_default_version()?; let binary = paths::binary_path(&version)?; if !binary.exists() { From 97750a429d5bc0320a90bd13a16278296b478370 Mon Sep 17 00:00:00 2001 From: sdairs Date: Mon, 9 Mar 2026 22:12:39 +0000 Subject: [PATCH 5/9] Distinguish ServerNotFound from ServerNotRunning in client Check all servers (not just running ones) so a stopped server gets a "server not running" error instead of the misleading "not found". Co-Authored-By: Claude Opus 4.6 --- src/main.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index c791bc2..1a6b0fd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -180,11 +180,15 @@ fn run_client( Some(p) => (p, version_manager::get_default_version()?), None => { let server_name = name.as_deref().unwrap_or("default"); - let servers = server::list_running_servers(); - let info = servers + let entries = server::list_all_servers(); + let entry = entries .iter() - .find(|s| s.name == server_name) + .find(|e| e.name == server_name) .ok_or_else(|| Error::ServerNotFound(server_name.to_string()))?; + let info = entry + .info + .as_ref() + .ok_or_else(|| Error::ServerNotRunning(server_name.to_string()))?; (info.tcp_port, info.version.clone()) } }; From 9d4ad8af9b3fd9c9f8811f6651d4ce85cda515bc Mon Sep 17 00:00:00 2001 From: sdairs Date: Mon, 9 Mar 2026 22:39:57 +0000 Subject: [PATCH 6/9] Update CLAUDE.md example to use first-class --query flag Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index ea2bb1f..0ecc6b8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -80,5 +80,5 @@ cargo add rpassword # add latest version ```bash cargo run -- local install stable cargo run -- local server start # starts server in .clickhouse/servers/default/ -cargo run -- local client -- --query "SELECT 1" +cargo run -- local client --query "SELECT 1" ``` From f772ba8f3ba998955ede8695c88a2d16ba521233 Mon Sep 17 00:00:00 2001 From: sdairs Date: Mon, 9 Mar 2026 22:41:46 +0000 Subject: [PATCH 7/9] Clarify client help text for named server vs explicit host/port modes Co-Authored-By: Claude Opus 4.6 --- src/cli.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 1cdd45f..c3af224 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -123,11 +123,14 @@ CONTEXT FOR AGENTS: /// Connect to a running ClickHouse server with clickhouse-client #[command(after_help = "\ CONTEXT FOR AGENTS: - Connects to a running clickhouse-server. Server must already be running via `clickhousectl local server start`. - Use --name to connect to a specific named server (default: \"default\"). - --host, --port, --query, and --queries-file are promoted as first-class flags. + Two connection modes: + 1. Named server: `clickhousectl local client --name dev` — looks up port and version from a + locally managed server started via `clickhousectl local server start`. Defaults to \"default\". + 2. Explicit host/port: `clickhousectl local client --host myhost --port 9000` — connects to any + ClickHouse server directly, bypassing local server lookup. + --query and --queries-file execute SQL inline or from a file. Additional clickhouse-client args can be passed after --. - Related: `clickhousectl local server start` to start a server first, `clickhousectl local server list` to see servers.")] + Related: `clickhousectl local server start` to start a local server, `clickhousectl local server list` to see servers.")] Client { /// Server name to connect to (default: "default") #[arg(long, short)] From 5aa6ec993117f508070cd0f809127b9d8ac22f9b Mon Sep 17 00:00:00 2001 From: sdairs Date: Mon, 9 Mar 2026 22:45:07 +0000 Subject: [PATCH 8/9] Make --host bypass local server lookup like --port Either --host or --port signals a direct connection, skipping named server lookup. --host is now Option so the code can detect when it was explicitly set. Co-Authored-By: Claude Opus 4.6 --- src/cli.rs | 8 ++++---- src/main.rs | 40 +++++++++++++++++++++------------------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index c3af224..7b5b019 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -136,11 +136,11 @@ CONTEXT FOR AGENTS: #[arg(long, short)] name: Option, - /// Host to connect to - #[arg(long, default_value = "localhost")] - host: String, + /// Host to connect to (bypasses local server lookup) + #[arg(long)] + host: Option, - /// TCP port to connect to (auto-detected from --name if not set) + /// TCP port to connect to (bypasses local server lookup if set) #[arg(long, short)] port: Option, diff --git a/src/main.rs b/src/main.rs index 1a6b0fd..ea54679 100644 --- a/src/main.rs +++ b/src/main.rs @@ -168,29 +168,31 @@ fn which() -> Result<()> { fn run_client( name: Option, - host: String, + host: Option, port: Option, query: Option, queries_file: Option, args: Vec, ) -> Result<()> { - // Resolve port and version: explicit --port bypasses server lookup and uses - // the default version; otherwise look up the named server for both. - let (tcp_port, version) = match port { - Some(p) => (p, version_manager::get_default_version()?), - None => { - let server_name = name.as_deref().unwrap_or("default"); - let entries = server::list_all_servers(); - let entry = entries - .iter() - .find(|e| e.name == server_name) - .ok_or_else(|| Error::ServerNotFound(server_name.to_string()))?; - let info = entry - .info - .as_ref() - .ok_or_else(|| Error::ServerNotRunning(server_name.to_string()))?; - (info.tcp_port, info.version.clone()) - } + // If --host or --port is set, connect directly (bypass local server lookup). + // Otherwise, look up the named server for port and version. + let (resolved_host, tcp_port, version) = if host.is_some() || port.is_some() { + let h = host.unwrap_or_else(|| "localhost".to_string()); + let p = port.unwrap_or(9000); + let v = version_manager::get_default_version()?; + (h, p, v) + } else { + let server_name = name.as_deref().unwrap_or("default"); + let entries = server::list_all_servers(); + let entry = entries + .iter() + .find(|e| e.name == server_name) + .ok_or_else(|| Error::ServerNotFound(server_name.to_string()))?; + let info = entry + .info + .as_ref() + .ok_or_else(|| Error::ServerNotRunning(server_name.to_string()))?; + ("localhost".to_string(), info.tcp_port, info.version.clone()) }; let binary = paths::binary_path(&version)?; @@ -202,7 +204,7 @@ fn run_client( let mut cmd = Command::new(&binary); cmd.arg("client") .arg("--host") - .arg(&host) + .arg(&resolved_host) .arg("--port") .arg(tcp_port.to_string()); From 8cdc1089fa392d1d70ecf5f3473c56d22a3421de Mon Sep 17 00:00:00 2001 From: sdairs Date: Mon, 9 Mar 2026 22:50:22 +0000 Subject: [PATCH 9/9] Fix stale references in CLAUDE.md and README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CLAUDE.md: update local subcommand list (run → client), fix exec() reference to point at local client instead of old run server - README: remove clickhouse-local mention from intro (no longer exposed) Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 4 ++-- README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 0ecc6b8..2678823 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,7 +20,7 @@ Cross-compilation for aarch64-linux uses `cross` (see `.github/workflows/release `clickhousectl` is the official ClickHouse CLI — a version manager + cloud CLI. Two top-level subcommands: `local` and `cloud`. -1. **Local** (`local install|list|use|remove|which|init|run|server`) — version management in `src/version_manager/`, server management in `src/server.rs`, run/init in `main.rs`. Binaries live in `~/.clickhouse/versions/{version}/clickhouse`, default tracked in `~/.clickhouse/default`. Project data lives in `.clickhouse/`. +1. **Local** (`local install|list|use|remove|which|init|client|server`) — version management in `src/version_manager/`, server management in `src/server.rs`, client/init in `main.rs`. Binaries live in `~/.clickhouse/versions/{version}/clickhouse`, default tracked in `~/.clickhouse/default`. Project data lives in `.clickhouse/`. 2. **Cloud** (`cloud org|service|backup|auth`) — handled by `src/cloud/`. `CloudClient` wraps reqwest with Basic auth. Commands go through `cloud/commands.rs`, types in `cloud/types.rs`. All cloud commands support `--json` output. @@ -62,7 +62,7 @@ cargo add rpassword # add latest version - CLI is defined with clap derive macros in `src/cli.rs`, dispatched in `src/main.rs` - `src/paths.rs` handles `~/.clickhouse/` paths (global install dir); `src/init.rs` handles `.clickhouse/` paths (project-local data dir) -- `run server` uses `exec()` (process replacement), so code after `cmd.exec()` only runs on failure +- `local client` uses `exec()` (process replacement), so code after `cmd.exec()` only runs on failure - Error types use `thiserror` in `src/error.rs`; cloud module has its own error type wrapped as `Error::Cloud(String)` - Version resolution (`version_manager/resolve.rs`) handles specs like `stable`, `lts`, `25.12`, or exact `25.12.5.44` — all resolve to an exact version + channel via GitHub API - Releases are triggered by pushing a version tag (`v0.1.3`), which runs the GitHub Actions workflow diff --git a/README.md b/README.md index 976254a..bcd90cf 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ With `clickhousectl` you can: - Install and manage local ClickHouse versions - Launch and manage local ClickHouse servers -- Execute queries against ClickHouse servers, or using clickhouse-local +- Execute queries against ClickHouse servers - Setup ClickHouse Cloud and create cloud-managed ClickHouse clusters - Manage ClickHouse Cloud resources - Push your local ClickHouse development to cloud