diff --git a/CLAUDE.md b/CLAUDE.md index 764e9f1..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 @@ -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..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 @@ -90,8 +90,11 @@ 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" # 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 ``` ### Creating and managing ClickHouse servers diff --git a/src/cli.rs b/src/cli.rs index 5f4c646..7b5b019 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -123,12 +123,36 @@ 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`. - 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.")] + 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 local server, `clickhousectl local server list` to see servers.")] Client { - /// Arguments to pass to clickhouse-client + /// Server name to connect to (default: "default") + #[arg(long, short)] + name: Option, + + /// Host to connect to (bypasses local server lookup) + #[arg(long)] + host: Option, + + /// TCP port to connect to (bypasses local server lookup if set) + #[arg(long, short)] + port: Option, + + /// Execute a SQL query + #[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 853c2c3..ea54679 100644 --- a/src/main.rs +++ b/src/main.rs @@ -52,7 +52,14 @@ async fn run_local(cmd: LocalCommands) -> Result<()> { init::init()?; Ok(()) } - LocalCommands::Client { args } => run_client(args), + LocalCommands::Client { + name, + host, + port, + query, + queries_file, + args, + } => run_client(name, host, port, query, queries_file, args), LocalCommands::Server { command } => run_server_commands(command), } } @@ -159,8 +166,35 @@ fn which() -> Result<()> { Ok(()) } -fn run_client(args: Vec) -> Result<()> { - let version = version_manager::get_default_version()?; +fn run_client( + name: Option, + host: Option, + port: Option, + query: Option, + queries_file: Option, + args: Vec, +) -> Result<()> { + // 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)?; if !binary.exists() { @@ -168,7 +202,21 @@ fn run_client(args: Vec) -> Result<()> { } let mut cmd = Command::new(&binary); - cmd.arg("client").args(&args); + cmd.arg("client") + .arg("--host") + .arg(&resolved_host) + .arg("--port") + .arg(tcp_port.to_string()); + + if let Some(q) = &query { + 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())) }