Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,9 +246,14 @@ Start and manage ClickHouse server instances. Each server gets its own isolated
A bare `clickhousectl local server start` bootstraps from zero: if no version is installed and no default is set, it installs `latest` and starts with it (it does not set a default, so you keep tracking `latest` on subsequent starts). Pin a version with `--version`, or set a default with `local use`, to opt out. Because `latest` tracks the rolling master build, repeat `latest` installs/starts do a cheap `HEAD` against `builds.clickhouse.com` and skip the ~150 MB re-download when master hasn't changed (the build's `etag` is cached in `~/.clickhouse/versions/.master-builds.json`).

```bash
# Start a server (runs in background by default)
# Canonical named lifecycle
clickhousectl local server start dev
clickhousectl local server list
clickhousectl local server stop dev
clickhousectl local server remove dev

# Other start options (servers run in background by default)
clickhousectl local server start # Named "default" (installs latest if nothing is set up yet)
clickhousectl local server start dev # Named "dev"
clickhousectl local server start --version latest # Use a specific version (installs if needed, doesn't change default)
clickhousectl local server start --foreground # Run in foreground (-F / --fg)
clickhousectl local server start --no-wait # Return after spawning without waiting for readiness
Expand All @@ -264,15 +269,13 @@ clickhousectl local server list --global # List running ClickHo

# Stop servers
clickhousectl local server stop # Stop "default", or the sole ClickHouse server
clickhousectl local server stop dev # Stop by name
clickhousectl local server stop default --global # Stop from any project
clickhousectl local server stop default --global --project /path/to/project # Disambiguate
clickhousectl local server stop-all # Stop all ClickHouse and Postgres servers in this project
clickhousectl local server stop-all --global # Stop all ClickHouse servers system-wide

# Remove a stopped server and its data
clickhousectl local server remove # Remove "default" only; never guesses a custom name
clickhousectl local server remove test # Remove by name

# Write connection env vars to .env file
clickhousectl local server dotenv # From "default" server → .env
Expand All @@ -287,7 +290,14 @@ Project-local server commands select `.clickhouse` under the exact current worki

Without a name, `server stop` selects an existing `default`, then a sole known ClickHouse server. It succeeds without changing anything when none exist, and requires a name or `server stop-all` when multiple non-default servers exist. Bare `server remove` is deliberately stricter: it removes an existing `default` only and otherwise requires an explicit name, even when there is just one custom server.

**Server naming:** Without a name, the first server is called "default". If "default" is already running, a random name is generated (e.g. "bold-crane"). Pass a name positionally for stable identities you can start/stop repeatedly.
Use `server stop-all` when the intent is to stop every ClickHouse and Postgres server in the current project. Version removal and server-data removal are separate operations:

| Command | Removes |
| --- | --- |
| `clickhousectl local remove <exact-version>` | An installed ClickHouse binary from the global version store. |
| `clickhousectl local server remove <server-name>` | A stopped named server and its data from the exact current project. |

**Server naming:** Without a name, the first server is called "default". If "default" is already running, a random name is generated (e.g. "bold-crane"). Retain the returned generated name for later stop and remove commands, or pass a name positionally for a stable identity.

**Ports:** Defaults are HTTP 8123 and TCP 9000. If these are already in use, free ports are automatically assigned and shown in the output. Use `--http-port` and `--tcp-port` to set explicit ports.

Expand Down
137 changes: 120 additions & 17 deletions crates/clickhousectl/src/local/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,11 +302,21 @@ CONTEXT FOR AGENTS:
Manage named local server instances. Project-scoped `server list` and `server stop-all`
include both ClickHouse processes and Docker-backed Postgres containers; other commands
here manage ClickHouse.
Project-local commands use .clickhouse under the exact current working directory and do
not search parent directories. Change to the project root before running them.
Project-local commands use `.clickhouse` under the exact current working directory; parent
`.clickhouse` directories are not searched. Change to the project root before running them.
Each server has its own data directory.
Data is stored in .clickhouse/servers/<name>/data/ and persists between restarts.
Typical: `clickhousectl local server start` (starts \"default\"), `clickhousectl local server start test`.

Canonical named lifecycle:
clickhousectl local server start dev
clickhousectl local server list
clickhousectl local server stop dev
clickhousectl local server remove dev

If `server start` generates a name, retain the returned name for later `stop` and `remove`
commands. For project cleanup, use `clickhousectl local server stop-all`.
`clickhousectl local remove <exact-version>` removes an installed ClickHouse binary;
`clickhousectl local server remove <server-name>` removes a stopped server and its data.
Related: `clickhousectl local client` to connect to a running server.")]
Server {
#[command(subcommand)]
Expand Down Expand Up @@ -337,6 +347,7 @@ CONTEXT FOR AGENTS:
Data is stored in .clickhouse/servers/<name>/data/ and persists between restarts.
Without a name, the first server is called \"default\"; if \"default\" is already running,
a random name is generated (e.g., \"bold-crane\").
Retain a generated name for later `server stop <name>` and `server remove <name>` commands.
Pass the name positionally to give a server a stable identity (e.g., `server start dev`).
The older `--name dev` form remains accepted, but cannot be combined with a positional name.
Use --version (-v) to run a specific ClickHouse version without changing the default.
Expand All @@ -355,6 +366,8 @@ CONTEXT FOR AGENTS:
to change (e.g. <query_log>). The data directory and ports stay managed regardless of the
file's contents (they are forced as command-line overrides).
Additional clickhouse-server arguments must follow `--`.
Project-local commands use `.clickhouse` under the exact current working directory; parent
`.clickhouse` directories are not searched. Change to the project root before running them.
Related: `clickhousectl local server list` to see servers, `clickhousectl local server stop [name]` to stop one.")]
Start {
/// Server name (default: "default", or random if default is already running)
Expand Down Expand Up @@ -410,8 +423,9 @@ CONTEXT FOR AGENTS:
#[command(after_help = "\
CONTEXT FOR AGENTS:
Shows all named ClickHouse server instances and their status.
The default list uses only .clickhouse under the exact current working directory; parent
directories are not searched. Use --global to list running ClickHouse servers across projects.
Project-local commands use `.clickhouse` under the exact current working directory; parent
`.clickhouse` directories are not searched. Change to the project root before running them.
Use --global to list running ClickHouse servers across projects.
Processes that exited unexpectedly are retained and shown as stopped.
Running ClickHouse entries also show their PID, version, and ports.
Related: `clickhousectl local server start` to start a server, `clickhousectl local server stop [name]` to stop one.")]
Expand All @@ -424,8 +438,8 @@ CONTEXT FOR AGENTS:
/// Stop a running server by name
#[command(after_help = "\
CONTEXT FOR AGENTS:
Without a name, stops \"default\" when it exists; otherwise stops the sole known ClickHouse
server. With no ClickHouse servers this is a successful no-op. Multiple non-default servers
Without a name, zero ClickHouse servers is a successful no-op; one server selects that sole
server; multiple servers select \"default\" when it exists; and multiple non-default servers
require an explicit name or `clickhousectl local server stop-all`.
Pass the name positionally (e.g. `server stop dev`). The compatibility form `--name dev`
remains accepted, but cannot be combined with a positional name.
Expand All @@ -434,8 +448,9 @@ CONTEXT FOR AGENTS:
Restart with `clickhousectl local server start <name>`.
Idempotent: a server that exists but is already stopped exits 0 (no error).
An unknown server name still errors so typos are caught.
Project-local lookup uses the exact current working directory and never searches parent
.clickhouse directories. Change to the project root, or inspect --global before global action.
Project-local commands use `.clickhouse` under the exact current working directory; parent
`.clickhouse` directories are not searched. Change to the project root before running them.
Use `clickhousectl local server stop-all` when the intent is project cleanup.
Related: `clickhousectl local server list` to see servers.")]
Stop {
/// Name of the server to stop (auto-selects default or a sole ClickHouse server when omitted)
Expand All @@ -459,6 +474,8 @@ CONTEXT FOR AGENTS:
#[command(after_help = "\
CONTEXT FOR AGENTS:
Stops all running ClickHouse and Postgres server instances in this project.
Project-local commands use `.clickhouse` under the exact current working directory; parent
`.clickhouse` directories are not searched. Change to the project root before running them.
ClickHouse processes receive SIGTERM first, then SIGKILL if they don't exit.
Postgres containers are stopped but retained for a subsequent start.
With --global, stops ClickHouse servers only; global Postgres discovery is not supported.
Expand All @@ -474,13 +491,15 @@ CONTEXT FOR AGENTS:
#[command(after_help = "\
CONTEXT FOR AGENTS:
Permanently deletes a server's data directory. The server must be stopped first.
This is irreversible all data for this server instance will be lost.
This is irreversible: all data for this server instance will be lost.
Pass the name positionally (e.g. `server remove dev`). The compatibility form `--name dev`
remains accepted, but cannot be combined with a positional name.
Without a name, removes \"default\" only when it exists. A custom server is never selected
implicitly; use `server list`, then pass its name explicitly.
Project-local lookup uses the exact current working directory and never searches parent
.clickhouse directories. Change to the project root before removing server data.
Project-local commands use `.clickhouse` under the exact current working directory; parent
`.clickhouse` directories are not searched. Change to the project root before running them.
Unlike `local server remove <server-name>`, `local remove <exact-version>` removes an installed
ClickHouse binary rather than server data.
Related: `clickhousectl local server stop [name]` to stop first, `clickhousectl local server list` to see servers.")]
Remove {
/// Name of the server to remove (only an existing "default" is selected when omitted)
Expand Down Expand Up @@ -729,6 +748,16 @@ mod tests {
.expect("invalid local arguments should fail during clap parsing")
}

fn rendered_help(args: &[&str]) -> String {
let mut argv = vec!["clickhousectl", "local"];
argv.extend_from_slice(args);
let error = Cli::try_parse_from(argv)
.err()
.expect("--help should stop parsing");
assert_eq!(error.kind(), clap::error::ErrorKind::DisplayHelp);
error.to_string()
}

#[test]
fn parses_supported_clickhouse_version_forms_for_each_command() {
for (input, expected) in [
Expand Down Expand Up @@ -1304,18 +1333,92 @@ mod tests {

#[test]
fn server_start_help_renders_default_name_without_escaped_quotes() {
let error = Cli::try_parse_from(["clickhousectl", "local", "server", "start", "--help"])
.err()
.expect("--help should stop parsing");
assert_eq!(error.kind(), clap::error::ErrorKind::DisplayHelp);
let help = error.to_string();
let help = rendered_help(&["server", "start", "--help"]);

assert!(help.lines().any(|line| {
line == r#" [NAME] Server name (default: "default", or random if default is already running)"#
}), "{help}");
assert!(!help.contains(r#"\"default\""#), "{help}");
}

#[test]
fn server_help_pins_scope_workflow_and_removal_distinction() {
let help = rendered_help(&["server", "--help"]);
let context = help
.split_once("CONTEXT FOR AGENTS:\n")
.expect("agent context")
.1;

assert_eq!(
context,
concat!(
" Manage named local server instances. Project-scoped `server list` and `server stop-all`\n",
" include both ClickHouse processes and Docker-backed Postgres containers; other commands\n",
" here manage ClickHouse.\n",
" Project-local commands use `.clickhouse` under the exact current working directory; parent\n",
" `.clickhouse` directories are not searched. Change to the project root before running them.\n",
" Each server has its own data directory.\n",
" Data is stored in .clickhouse/servers/<name>/data/ and persists between restarts.\n",
"\n",
" Canonical named lifecycle:\n",
" clickhousectl local server start dev\n",
" clickhousectl local server list\n",
" clickhousectl local server stop dev\n",
" clickhousectl local server remove dev\n",
"\n",
" If `server start` generates a name, retain the returned name for later `stop` and `remove`\n",
" commands. For project cleanup, use `clickhousectl local server stop-all`.\n",
" `clickhousectl local remove <exact-version>` removes an installed ClickHouse binary;\n",
" `clickhousectl local server remove <server-name>` removes a stopped server and its data.\n",
" Related: `clickhousectl local client` to connect to a running server.\n",
)
);
}

#[test]
fn server_stop_help_pins_omitted_name_selection() {
let help = rendered_help(&["server", "stop", "--help"]);

assert!(
help.contains(concat!(
" Without a name, zero ClickHouse servers is a successful no-op; one server selects that sole\n",
" server; multiple servers select \"default\" when it exists; and multiple non-default servers\n",
" require an explicit name or `clickhousectl local server stop-all`."
)),
"{help}"
);
}

#[test]
fn server_remove_help_pins_conservative_default_selection() {
let help = rendered_help(&["server", "remove", "--help"]);

assert!(
help.contains(concat!(
" Without a name, removes \"default\" only when it exists. A custom server is never selected\n",
" implicitly; use `server list`, then pass its name explicitly."
)),
"{help}"
);
assert!(
help.contains(concat!(
" Unlike `local server remove <server-name>`, `local remove <exact-version>` removes an installed\n",
" ClickHouse binary rather than server data."
)),
"{help}"
);
}

#[test]
fn readme_contrasts_version_and_server_removal() {
let readme = include_str!("../../../../README.md");

assert!(readme.contains(concat!(
"| `clickhousectl local remove <exact-version>` | An installed ClickHouse binary from the global version store. |\n",
"| `clickhousectl local server remove <server-name>` | A stopped named server and its data from the exact current project. |"
)));
}

#[test]
fn parses_server_start_positional_name_before_clickhousectl_options() {
let LocalCommands::Server {
Expand Down
Loading