diff --git a/skills/hotdata/SKILL.md b/skills/hotdata/SKILL.md index c5ea69e..eb61b11 100644 --- a/skills/hotdata/SKILL.md +++ b/skills/hotdata/SKILL.md @@ -118,7 +118,7 @@ hotdata databases detach [--database ] # or t # Preferred: load by catalog alias (server declares the table/schema if missing). # Loads csv, json, or parquet — format read from the extension. -hotdata databases load --catalog --table [--schema public] (--file | --url | --upload-id | --result-id ) [--mode replace|append|delete|update|upsert] [--append] [--format csv|json|parquet] [--key ]... [--retry-timeout ] [--workspace-id ] +hotdata databases load (--catalog | --database ) --table
[--schema public] (--file | --url | --upload-id | --result-id ) [--mode replace|append|delete|update|upsert] [--append] [--format csv|json|parquet] [--key ]... [--retry-timeout ] [--workspace-id ] # Also available via tables subcommand hotdata databases tables list [--database ] [--schema ] [--workspace-id ] [--output table|json|yaml] @@ -136,8 +136,9 @@ hotdata databases tables remove
[--database ] [--schema public] [--w - `use` — saves the database as the current database (accepts an id, catalog alias, or name; prefer the id). Subsequent `query`, `databases tables`, and `databases context` commands use it automatically. This is the **only** command that changes the current database. - `unset` — clears the current database from config. - `show ` / `` — inspect one database (returns id, catalog, name, expires_at, attached databases; a fork also shows its `forked_from` record). -- `remove` — removes the instant database; clears the active-database config if it matched. +- `remove` — removes the instant database immediately, with no confirmation prompt (safe to script; there is no `--yes` because none is needed); clears the active-database config if it matched. - `load` (top-level shorthand) — loads a file into `--catalog.--schema.--table`. Accepts `--file`, `--url`, `--upload-id`, or `--result-id` (load a saved query result by id — from `hotdata databases results` or a query's `[result-id: …]` footer — instead of a file; the result must belong to the target database). **Formats:** csv, json (`.json`/`.jsonl`/`.ndjson`), and parquet; the format comes from the file's extension, and `--format` overrides it (needed when the extension is absent or misleading). An unrecognised extension is not rejected — the server reads the bytes, and a file that plainly opens a json array is taken as json even without an extension. A json source in any shape (array, pretty-printed, one object per line) is reshaped locally to newline-delimited json before upload; an already-newline-delimited file is uploaded untouched. A table or schema that was never declared is declared by the server as part of the load, so no up-front `--table` is required. +- **After a fork, `--catalog` is ambiguous.** A fork answers to the same catalog alias as its source, so `databases load --catalog ` (and `tables show .schema.table`) fail with `multiple databases have catalog …` once a fork exists. Pass the database id instead: `databases load --database …` or `databases tables load
--database …`. Prefer the id form in scripts. - **Concurrent loads** — loads into one table run one at a time. While another load holds the table the server answers 409 `RESOURCE_LOCKED` with a `Retry-After`; the CLI waits and retries for up to `--retry-timeout` seconds (default 60). Pass `--retry-timeout 0` to fail immediately instead. Loads into different tables never block each other. - **Load modes** (`--mode`, default `replace`) — `replace` supersedes the table's contents; `append` adds rows; `delete`, `update`, and `upsert` match existing rows **by key**. `--append` is the old shorthand for `--mode append` and still works, but the two cannot be combined. The keyed modes need a key: declare one with `databases tables add --key`, or name it per-load with `--key` (repeat for a composite key). `delete` uploads only the key columns; `update` replaces matching rows and ignores unmatched ones; `upsert` inserts the unmatched instead. Keyed modes are not available with `--result-id`. - `tables list` — lists tables with `TABLE` (`..
`), `SYNCED`, `LAST_SYNC`. Uses current database when `--database` is omitted. @@ -255,7 +256,7 @@ hotdata databases context push [--database ] [--dry-run] ``` hotdata query "" [--workspace-id ] [--database ] [--dialect hotsql|duckdb|postgres|snowflake] [--output table|json|csv|id] -hotdata query status +hotdata query [-d ] status # -d before `status`, or set a current database first # Same commands under the databases group (identical flags and exit codes) hotdata databases query "" [-d ] [--output table|json|csv|id] @@ -280,7 +281,7 @@ hotdata jobs list [--workspace-id ] [--job-type ] [--status hotdata jobs [--workspace-id ] [--output table|json|yaml] ``` - `list` shows only active jobs (`pending`, `running`) by default. Use `--all` to see all jobs. -- `--job-type`: `create_index`, `managed_load`, `database_fork`. (`data_refresh_table` / `data_refresh_connection` were retired server-side — the engine is push-only; nothing refreshes.) +- `--job-type`: `create_index`, `managed_load`, `database_fork`. (`database_fork` is accepted for forward compatibility; today a fork completes synchronously and records no job.) (`data_refresh_table` / `data_refresh_connection` were retired server-side — the engine is push-only; nothing refreshes.) - `--status`: `pending`, `running`, `succeeded`, `partially_succeeded`, `failed`. - Use `hotdata jobs ` to inspect a specific job's status, error, and result. diff --git a/src/cli.rs b/src/cli.rs index e7c6389..e7724cf 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -53,7 +53,7 @@ pub enum Commands { workspace_id: Option, /// Run against a specific instant database, for this command only (defaults to the current database; switch with `databases use`) - #[arg(long, short = 'd')] + #[arg(long, short = 'd', global = true)] database: Option, /// SQL dialect the query is written in — a non-`hotsql` dialect is @@ -64,7 +64,7 @@ pub enum Commands { /// Output format. `id` prints only the stored result's id, without /// downloading rows (for commands that read a result by id) - #[arg(long = "output", short = 'o', default_value = "table", value_parser = ["table", "json", "csv", "id"])] + #[arg(long = "output", short = 'o', default_value = "table", value_parser = ["table", "json", "csv", "id"], global = true)] output: String, #[command(subcommand)] diff --git a/src/commands/connections.rs b/src/commands/connections.rs index 8259ffc..892b070 100644 --- a/src/commands/connections.rs +++ b/src/commands/connections.rs @@ -48,11 +48,13 @@ pub fn try_resolve_connection_id(api: &Api, name_or_id: &str) -> Result Ok(db.default_connection_id), + Err(e) if e.contains("multiple databases") => Err(e), + Err(_) => Err(format!("no catalog with id '{name_or_id}'")), } - - Err(format!("no catalog with id '{name_or_id}'")) } /// Resolve a connection name or ID to a connection ID, exiting on failure. diff --git a/src/commands/databases.rs b/src/commands/databases.rs index d963aae..67e57a5 100644 --- a/src/commands/databases.rs +++ b/src/commands/databases.rs @@ -45,7 +45,8 @@ pub enum DatabasesCommands { name: Option, /// SQL catalog alias used in queries: SELECT … FROM .schema.table. - /// Must be [a-z_][a-z0-9_]*, globally unique. + /// Must be [a-z_][a-z0-9_]*. Not required to be unique — a fork answers + /// to its source's alias — so select databases by id when it matters. #[arg(long)] catalog: Option, @@ -201,9 +202,16 @@ pub enum DatabasesCommands { /// Load a csv, json, or parquet file — or a saved query result — into an /// instant database table Load { - /// SQL catalog alias of the target database (e.g. `--catalog airbnb`) - #[arg(long)] - catalog: String, + /// SQL catalog alias of the target database (e.g. `--catalog airbnb`). + /// A fork answers to the same alias as its source, so once a database + /// has been forked the alias is ambiguous: pass `--database ` instead. + #[arg(long, required_unless_present = "database")] + catalog: Option, + + /// Database id (or name) to load into. Takes precedence over `--catalog` + /// and is the unambiguous form when forks share a catalog alias. + #[arg(long, conflicts_with = "catalog")] + database: Option, /// Schema to load into (default: public) #[arg(long, default_value = "public")] @@ -291,7 +299,7 @@ pub enum DatabasesCommands { sql: Option, /// Instant database to run against (this command only; defaults to the current database) - #[arg(long, short = 'd')] + #[arg(long, short = 'd', global = true)] database: Option, /// SQL dialect the query is written in — a non-`hotsql` dialect is @@ -302,7 +310,7 @@ pub enum DatabasesCommands { /// Output format. `id` prints only the stored result's id, without /// downloading rows (for commands that read a result by id) - #[arg(long = "output", short = 'o', default_value = "table", value_parser = ["table", "json", "csv", "id"])] + #[arg(long = "output", short = 'o', default_value = "table", value_parser = ["table", "json", "csv", "id"], global = true)] output: String, #[command(subcommand)] diff --git a/src/main.rs b/src/main.rs index 53c7221..f3b5308 100644 --- a/src/main.rs +++ b/src/main.rs @@ -312,6 +312,7 @@ fn main() { } Some(DatabasesCommands::Load { catalog, + database, schema, table, file, @@ -325,7 +326,7 @@ fn main() { retry_timeout, }) => databases::tables_load( &workspace_id, - Some(catalog.as_str()), + database.as_deref().or(catalog.as_deref()), &table, Some(schema.as_str()), file.as_deref(),