From caf2c52eccf7a821eb0bfb75e97d5c16c5be1a14 Mon Sep 17 00:00:00 2001 From: Eddie A Tejeda <669988+eddietejeda@users.noreply.github.com> Date: Fri, 18 Sep 2026 15:20:56 -0700 Subject: [PATCH 1/3] docs: describe attach as joining across instant databases --- README.md | 8 ++-- skills/hotdata/SKILL.md | 47 +++++++++++-------- .../hotdata/references/DATA_MODEL.template.md | 4 +- skills/hotdata/references/MODEL_BUILD.md | 6 +-- skills/hotdata/references/WORKFLOWS.md | 20 ++++---- skills/hotdata/subskills/analytics/SKILL.md | 2 +- skills/hotdata/subskills/search/SKILL.md | 2 +- .../subskills/search/references/INDEXES.md | 2 +- 8 files changed, 51 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index c9bf1ac..3efe3c6 100644 --- a/README.md +++ b/README.md @@ -98,10 +98,9 @@ but the printed result is a truncated preview). Re-fetch past results with `hotdata databases results get `; browse history with `hotdata databases queries list`. -## Join across sources +## Join across databases -Attach another catalog to an instant database and join its live tables directly, -no copying: +Attach another instant database and join its live tables directly, no copying: ```sh hotdata databases attach prod-replica --alias prod @@ -109,6 +108,9 @@ hotdata query "SELECT t.id, o.total FROM demo.public.tickets t JOIN prod.public.orders o ON o.ticket_id = t.id" ``` +The attached database is read-only here: loads still go to your own database, +and `detach` withdraws visibility without deleting anything. + ## Search Create an index once, then search server-side. Vector search auto-embeds the diff --git a/skills/hotdata/SKILL.md b/skills/hotdata/SKILL.md index 6b72408..e9af142 100644 --- a/skills/hotdata/SKILL.md +++ b/skills/hotdata/SKILL.md @@ -59,7 +59,7 @@ A workspace's query worker scales to zero after inactivity. The **first** comman **Agents — list before show.** Run `hotdata databases context list` (optionally `--prefix DATAMODEL`) first; run `hotdata databases context show DATAMODEL` *only if* the stem is listed. A missing stem makes `show` exit 1 — normal for a fresh database, not a failure: don't retry in a loop or run speculative `show` in parallel with other tools. Proceed without context:DATAMODEL until one exists. -**context:DATAMODEL is the durable, shared store** — entities, keys, cross-catalog joins, and the naming/query conventions the whole team relies on. Keep task-scoped exploration (scratch SQL, hypotheses, one-off join checks) in the conversation or local notes; **promote** to context:DATAMODEL only when findings should outlive the session and guide everyone — reconcile against `databases context show DATAMODEL` (if listed), write `./DATAMODEL.md`, then `hotdata databases context push DATAMODEL`. No need to update it after every ad-hoc query. What to write inside the document: [references/DATA_MODEL.template.md](references/DATA_MODEL.template.md) and [references/MODEL_BUILD.md](references/MODEL_BUILD.md). +**context:DATAMODEL is the durable, shared store** — entities, keys, cross-database joins, and the naming/query conventions the whole team relies on. Keep task-scoped exploration (scratch SQL, hypotheses, one-off join checks) in the conversation or local notes; **promote** to context:DATAMODEL only when findings should outlive the session and guide everyone — reconcile against `databases context show DATAMODEL` (if listed), write `./DATAMODEL.md`, then `hotdata databases context push DATAMODEL`. No need to update it after every ad-hoc query. What to write inside the document: [references/DATA_MODEL.template.md](references/DATA_MODEL.template.md) and [references/MODEL_BUILD.md](references/MODEL_BUILD.md). ## Multi-step workflows @@ -110,9 +110,9 @@ hotdata databases unset hotdata databases [--workspace-id ] [--output table|json|yaml] hotdata databases remove [--workspace-id ] -# Attach a catalog so its tables are queryable (enables cross-catalog queries — see below) -hotdata databases attach [--database ] [--alias ] -hotdata databases detach [--database ] +# Attach another database so its tables are queryable (enables cross-database queries — see below) +hotdata databases attach [--database ] [--alias ] +hotdata databases detach [--database ] # Preferred: load by catalog alias (server declares the table/schema if missing). # Loads csv, json, or parquet — format read from the extension. @@ -140,9 +140,9 @@ hotdata databases tables remove [--database ] [--schema public] [--w - `tables add` — declares a table **with its key and storage layout**, which a load cannot infer. `--key` (repeatable) is what enables the `delete`/`update`/`upsert` load modes on that table. `--sorted-by ` or `=desc` sets sort order; `--partition-by ` partitions on the value, `=month` (or `year`/`day`/`hour`) on a calendar part — one partition per calendar month needs **both** `=year` and `=month`, or every March shares a partition. Sort and partition are fixed once the table exists. `--key-determines` (repeatable) asserts a column's value is fixed by the key: it prunes keyed loads harder, and is **correctness-affecting** — declare it only where the invariant really holds, or a keyed load can leave a duplicate key behind. Re-adding an existing table is a conflict (409), and `tables remove` does not clear the declaration — the table leaves the listing but the name stays declared and still conflicts. So **a key cannot be retrofitted onto a table declared without one**: declare it with `--key` up front, or use a new table name. - `tables load` — publishes to an instant-database table from a local file (`--file`), a remote URL (`--url`), a pre-staged upload (`--upload-id`), or a saved query result (`--result-id`, must belong to the target database). Same `--mode`, `--format`, and `--key` flags as the top-level `load` above. - `tables remove` — drops a table from the instant database. -- `attach` — attaches a **catalog** to an instant database, so the catalog's **live** tables become visible inside that database's query scope. Defaults to the active database; target another with `--database`. `--alias` sets the SQL name the catalog answers to (defaults to the catalog's name). This is how you query an attached catalog's tables and **join across catalogs** — see [Querying across catalogs](#querying-across-catalogs-attach). -- `detach` — removes an attached catalog. Accepts the catalog name/id **or** the alias you attached it under. Defaults to the active database. -- `create --attach [=]` — attach one or more catalogs at creation time (repeatable), e.g. `--attach github --attach salesdb=sales`. +- `attach` — attaches **another instant database** to this one, so its **live** tables become visible inside this database's query scope. Name the other database by name, catalog alias, or id. Defaults to the active database; target another with `--database`. `--alias` sets the SQL name it answers to (defaults to the attached database's own catalog alias). This is how you **join across databases** — see [Querying across databases](#querying-across-databases-attach). Read-only: loads still target your own database, and attaching is not transitive — you see what you attached, not what it attached. +- `detach` — removes an attachment, withdrawing visibility without deleting any data. Accepts the attached database's name/id **or** the alias you attached it under. Defaults to the active database. +- `create --attach [=]` — attach one or more databases at creation time (repeatable), e.g. `--attach reference --attach salesdb=sales`. Example: @@ -175,30 +175,37 @@ hotdata databases load --catalog airbnb --table bookings --file removed.csv --mo `removed.csv` carries only the key columns. On a table already declared without a key, name one per load instead: `--mode upsert --key booking_id`. -#### Querying across catalogs (attach) +#### Querying across databases (attach) -**A `hotdata query` runs inside exactly one instant database** — the active database (`hotdata databases use `) or the one named by `--database`. With none set, the query fails with *"a database is required."* That database's query scope sees **only its own catalog plus any catalogs explicitly attached to it** — a workspace catalog is **not** visible just because it exists. Referencing an unattached catalog fails with *"table '\.\.\' not found."* +**A `hotdata query` runs inside exactly one instant database** — the active database (`hotdata databases use `) or the one named by `--database`. With none set, the query fails with *"a database is required."* That database's query scope sees **only its own catalog plus whatever is explicitly attached to it** — another database is **not** visible just because it exists. Referencing something unattached fails with *"table '\.\.\' not found."* -To query an attached catalog's tables, or **join a managed table against an attached catalog's table in one query**, attach the catalog to the database first. The catalog's data stays **live** (synced) — this is not a copy: +To read another database's tables, or **join your own tables against them in one query**, attach it first. The data stays **live** — this is not a copy: ``` -# Attach the 'github' catalog (live) to the active database under alias 'gh' -hotdata databases attach github --alias gh +# Attach the 'reference' database to the active one under alias 'ref' +hotdata databases attach reference --alias ref -# Now both the database's own tables and the attached catalog are in scope: -hotdata query "SELECT * FROM gh.github.issues WHERE state = 'OPEN' LIMIT 10" +# Now both this database's own tables and the attached one are in scope: +hotdata query "SELECT * FROM ref.public.regions LIMIT 10" -# Cross-catalog join: a managed table JOINed against the live attached-catalog table +# Cross-database join: your table JOINed against the attached one, live hotdata query " - SELECT t.id, i.title + SELECT t.id, r.name FROM mycatalog.public.tickets t - JOIN gh.github.issues i ON i.number = t.gh_issue + JOIN ref.public.regions r ON r.id = t.region_id " -hotdata databases detach gh # when finished (optional) +hotdata databases detach ref # when finished (optional) ``` -Without `--alias`, the catalog answers to its own name (`github.github.issues`). Do **not** export a catalog to parquet just to query it — attach is the live, sync-preserving path. +Without `--alias`, the attached database answers to its own catalog alias +(`reference.public.regions`). Notes worth knowing: + +- **Read-only.** Loads always target your own database's catalog, never an attached one. +- **Not transitive.** You see the database you attached, not the ones *it* has attached. +- **The source cannot be deleted while you hold it** — that delete is refused until you detach. An *expiring* source is still removed on its `expires_at`, so check that date before relying on one. + +Do **not** export a database to parquet just to query it — attach is the live path. ### Tables @@ -247,7 +254,7 @@ hotdata query status ``` - Default output is `table` (row count and execution time). -- **A query runs inside one instant database** (active database or `--database`); with none set it fails *"a database is required."* The scope sees the database's own catalog **plus any attached catalogs only**. To query an attached catalog's tables or join across catalogs, attach the catalog first — see [Querying across catalogs (attach)](#querying-across-catalogs-attach). +- **A query runs inside one instant database** (active database or `--database`); with none set it fails *"a database is required."* The scope sees the database's own catalog **plus whatever is attached to it only**. To read another database's tables or join across databases, attach it first — see [Querying across databases (attach)](#querying-across-databases-attach). - Use `hotdata databases tables list` and `hotdata databases tables show` for discovery — not `information_schema` via `query`. (Discovery lists every workspace table; queryability still requires the table's catalog to be in the active database's scope.) - **PostgreSQL dialect.** Quote non-lowercase columns with double quotes. To write DuckDB/Postgres/Snowflake SQL instead, pass `--dialect` (server-side transpile, read-only queries) — details in **`hotdata-analytics`**. - Async runs return `query_run_id` → poll with `query status ` (do not re-run the same heavy SQL). `query status` exit codes: `0` succeeded, `1` failed, `2` still running (poll again), `3` succeeded but the result is a truncated/incomplete preview. diff --git a/skills/hotdata/references/DATA_MODEL.template.md b/skills/hotdata/references/DATA_MODEL.template.md index 3e7d514..4bd9141 100644 --- a/skills/hotdata/references/DATA_MODEL.template.md +++ b/skills/hotdata/references/DATA_MODEL.template.md @@ -48,11 +48,11 @@ For each business entity: - **Primary tables:** `catalog.schema.table` - **Key columns:** -## Cross-catalog joins +## Cross-database joins Document safe join paths and caveats (fan-out, timing, different refresh cadence, type mismatches). -> A cross-catalog join runs inside one instant database; each catalog it touches must be **attached** to that database (`hotdata databases attach `) so its live tables are in query scope. Note here which catalogs a join requires attached, and the alias each is attached under. See **`hotdata`** skill → Querying across catalogs. +> A cross-database join runs inside one instant database; every other database it touches must be **attached** to that one (`hotdata databases attach `) so its live tables are in query scope. Note here which databases a join requires attached, and the alias each is attached under. See **`hotdata`** skill → Querying across databases. ## Search & index summary (optional) diff --git a/skills/hotdata/references/MODEL_BUILD.md b/skills/hotdata/references/MODEL_BUILD.md index cf8163f..d9f1a9c 100644 --- a/skills/hotdata/references/MODEL_BUILD.md +++ b/skills/hotdata/references/MODEL_BUILD.md @@ -10,7 +10,7 @@ Optional **deep pass** for a single authoritative markdown document stored as ** ## 1. Discover catalogs and tables -List the catalogs you can query — instant databases you own and any attached catalogs — and the tables they expose: +List the catalogs you can query — your instant database and anything attached to it — and the tables they expose: ```bash hotdata databases list # instant databases (catalogs you own) @@ -74,7 +74,7 @@ For each table, capture where reasonable: 2. **Primary keys** — `id`, `_id`, or composite patterns from names + types. 3. **Foreign keys** — `_id` / `_fk` / name matches to other tables; confirm with connector docs when possible. 4. **Parent–child** — Flattened API/JSON tables (often nested names) and dlt parent keys. -5. **Cross-catalog** — Same logical entity in two catalogs (keys, type mismatches, caveats). +5. **Cross-database** — Same logical entity in two databases (keys, type mismatches, caveats). For **small** schemas (e.g. ≤5 tables in a domain), a short **ASCII diagram** helps. For larger ones, group by domain in prose (e.g. billing, identity, product). @@ -114,7 +114,7 @@ This Markdown body is what you store as **context:DATAMODEL** (`hotdata database - **Overview** — Domains and what the workspace is for. - **Per catalog** — Optional subsection per source; for **deep** models, **repeat** one block per `catalog.schema.table` (grain, column table with name/type/nullable/PK-FK/notes, relationships, queryability, caveats)—the template’s single `####` heading is a pattern to copy for each table. - **Instant databases** — Same treatment as catalog tables where relevant. -- **Cross-catalog joins** — Keys, semantics, type caveats. +- **Cross-database joins** — Keys, semantics, type caveats. - **Search / index summary** — Table, column, index status, intended use. If the workspace has **many** tables (e.g. 50+), add a **table of contents** after the overview (catalog → table counts). diff --git a/skills/hotdata/references/WORKFLOWS.md b/skills/hotdata/references/WORKFLOWS.md index 07c9f93..7fee3bc 100644 --- a/skills/hotdata/references/WORKFLOWS.md +++ b/skills/hotdata/references/WORKFLOWS.md @@ -63,7 +63,7 @@ End-to-end checklists. Use the linked sections for command detail and guardrails 1. [ ] `hotdata databases tables list` (filter with `--schema`/`--table`) — pick text column (BM25) or embedding/text column (vector) 2. [ ] `hotdata search list` — avoid duplicate text/vector indexes on the same column 3. [ ] Create index (address by name): - - [ ] **Instant DB only:** `hotdata search create _--type text --from .public. --column ` (vector: `--type vector [--provider

]`). An external catalog must be attached to an instant database first (`hotdata databases attach`). + - [ ] **Instant DB only:** `hotdata search create _

--type text --from .public. --column ` (vector: `--type vector [--provider

]`). A table in another database must be attached first (`hotdata databases attach `). - [ ] Large build: add `--async`, then `hotdata jobs ` 4. [ ] Search (address the index by name): - [ ] `hotdata search "…" --index _

` @@ -71,20 +71,22 @@ End-to-end checklists. Use the linked sections for command detail and guardrails **Detail:** [hotdata-search INDEXES.md](../subskills/search/references/INDEXES.md) -### Cross-source query (attach a catalog) +### Cross-database query (attach another database) **Skill:** **`hotdata`** -A `hotdata query` runs inside **one** instant database; its scope sees that database's own catalog plus **attached** catalog catalogs only. To query a catalog's tables — or join a managed table against a live catalog table in one query — attach the catalog. (No instant database set → *"a database is required."*; an unattached catalog → *"table not found."*) +A `hotdata query` runs inside **one** instant database; its scope sees that database's own catalog plus whatever is **attached** to it only. To read another database's tables — or join your own tables against them in one query — attach that database. (No instant database set → *"a database is required."*; something unattached → *"table not found."*) 1. [ ] Pick/create the instant database that will be the query context (`hotdata databases use ` or `databases create --catalog `) -2. [ ] Attach the catalog(s) you need (live, sync intact): `hotdata databases attach [--alias ]` - - Or attach at creation: `hotdata databases create --catalog --attach [=]` -3. [ ] Confirm scope: `hotdata databases ` lists attached catalogs -4. [ ] Query across sources: `hotdata query "SELECT … FROM .public. JOIN ..
ON …"` -5. [ ] (Optional) `hotdata databases detach ` when finished; record required attachments in **context:DATAMODEL → Cross-catalog joins** +2. [ ] Attach the database(s) you need (live, no copy): `hotdata databases attach [--alias ]` + - Or attach at creation: `hotdata databases create --catalog --attach [=]` +3. [ ] Confirm scope: `hotdata databases ` lists what is attached +4. [ ] Query across databases: `hotdata query "SELECT … FROM .public. JOIN ..
ON …"` +5. [ ] (Optional) `hotdata databases detach ` when finished; record required attachments in **context:DATAMODEL → Cross-database joins** -**Do not** export a catalog to parquet just to query it — attach is the live, sync-preserving path. +Attaching is read-only (loads still target your own database) and not transitive (you see what you attached, not what it attached). The source cannot be deleted while you hold it, but an *expiring* source still goes on its `expires_at` — check that date before depending on one. + +**Do not** export a database to parquet just to query it — attach is the live path. --- diff --git a/skills/hotdata/subskills/analytics/SKILL.md b/skills/hotdata/subskills/analytics/SKILL.md index 9627790..578bebb 100644 --- a/skills/hotdata/subskills/analytics/SKILL.md +++ b/skills/hotdata/subskills/analytics/SKILL.md @@ -25,7 +25,7 @@ hotdata query status - **`--dialect`** (default `hotsql`): write SQL in `duckdb`/`postgres`/`snowflake` and the server transpiles it to HotSQL before running (e.g. Snowflake `IFF(...)`, DuckDB `len(...)`). Read-only queries only for a non-`hotsql` dialect. - Use **`hotdata databases tables list`** for schema discovery — not `information_schema` via `query`. - Fully qualified names: `..
`, `..
`. -- **Query scope:** every query runs inside one instant database (active or `--database`); it sees that database's own catalog plus **attached** catalogs only. To query an attached catalog's table, or **join a managed table against an attached catalog's table**, attach the catalog first: `hotdata databases attach ` — see **`hotdata`** skill → [Querying across catalogs](../../SKILL.md#querying-across-catalogs-attach). No instant database set → *"a database is required."* +- **Query scope:** every query runs inside one instant database (active or `--database`); it sees that database's own catalog plus whatever is **attached** to it only. To read another database's table, or **join your own table against one**, attach that database first: `hotdata databases attach ` — see **`hotdata`** skill → [Querying across databases](../../SKILL.md#querying-across-databases-attach). No instant database set → *"a database is required."* - Long-running queries may return `query_run_id` → poll with **`query status`** (exit `2` = still running). Do not re-run identical heavy SQL while polling. - For **workspace-wide** joins and naming, load **context:DATAMODEL** when listed (`hotdata databases context list` → `show DATAMODEL`) — see **`hotdata`** skill. diff --git a/skills/hotdata/subskills/search/SKILL.md b/skills/hotdata/subskills/search/SKILL.md index 1fac849..0d6b124 100644 --- a/skills/hotdata/subskills/search/SKILL.md +++ b/skills/hotdata/subskills/search/SKILL.md @@ -48,7 +48,7 @@ hotdata search "" --in ## Indexes (text and vector) -Indexes are an **instant-database** concept. Create names the index (positional) and attaches to a table on an instant database via `--from` — `catalog.schema.table` (the instant database's catalog), or `schema.table` with an active database set. A plain connection catalog is rejected. `list` narrows to the **active database** when one is set; without one it scans the whole workspace. `show`/`remove` resolve the index by name in the active database (or `--database `). +Indexes are an **instant-database** concept, and belong to the database's own tables — an attached database is read-only, so index its tables there rather than here. Create names the index (positional) and attaches to a table via `--from` — `catalog.schema.table` (the database's own catalog), or `schema.table` with an active database set. `list` narrows to the **active database** when one is set; without one it scans the whole workspace. `show`/`remove` resolve the index by name in the active database (or `--database `). ```bash # List — active-database scope when a DB is set, else whole-workspace scan diff --git a/skills/hotdata/subskills/search/references/INDEXES.md b/skills/hotdata/subskills/search/references/INDEXES.md index 7369c45..cc78b06 100644 --- a/skills/hotdata/subskills/search/references/INDEXES.md +++ b/skills/hotdata/subskills/search/references/INDEXES.md @@ -39,7 +39,7 @@ hotdata search create
_embedding_vec --type vector \ --from ..
--column embedding --metric cosine ``` -Indexes are created on **instant databases** only. To index a table that lives in an external catalog, attach the catalog to an instant database first (`hotdata databases attach `), then create the index with the instant database's catalog in `--from` — a bare connection/catalog is rejected. +Indexes are created on **instant databases** only, and on the database's own tables: an index is a write, and an attached database is read-only. To index a table that lives in another database, build the index there, or load a copy into this one — attaching it does not make it indexable here. Large builds: `--async`, then `hotdata jobs list` / `hotdata jobs `. From edb7803dbab89f1cd97285126fd1408ec02f75d1 Mon Sep 17 00:00:00 2001 From: Eddie A Tejeda <669988+eddietejeda@users.noreply.github.com> Date: Fri, 18 Sep 2026 16:17:46 -0700 Subject: [PATCH 2/3] docs(review): resolve the indexing contradiction and the stale help text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WORKFLOWS.md told an agent to attach another database before indexing its table, which INDEXES.md in the same change says does not work — an attached database is read-only, so the index create is rejected and the checklist offers no next step. Both now say to build the index in the database that owns the table. Also brings the README command table and the clap help for attach and create --attach onto the new wording, with the assertion that pins the documented alias form. --- README.md | 4 ++-- skills/hotdata/references/WORKFLOWS.md | 2 +- src/commands/databases.rs | 20 ++++++++++++-------- tests/databases_cli.rs | 4 ++-- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 3efe3c6..6d51f5a 100644 --- a/README.md +++ b/README.md @@ -165,8 +165,8 @@ The full command surface. The top level has nine groups — `auth`, `workspaces` | `databases create` | Create a new instant database | | `databases fork` | Fork a database into a new, independent database | | `databases lineage` | Show a database's whole fork family tree | -| `databases attach` | Attach a catalog so its tables are queryable | -| `databases detach` | Detach a previously attached catalog | +| `databases attach` | Attach another database so its tables are queryable | +| `databases detach` | Detach a previously attached database | | `databases use` | Set the current (default) database | | `databases unset` | Clear the current database | | `databases remove` | Delete a database and all its tables | diff --git a/skills/hotdata/references/WORKFLOWS.md b/skills/hotdata/references/WORKFLOWS.md index 7fee3bc..3def865 100644 --- a/skills/hotdata/references/WORKFLOWS.md +++ b/skills/hotdata/references/WORKFLOWS.md @@ -63,7 +63,7 @@ End-to-end checklists. Use the linked sections for command detail and guardrails 1. [ ] `hotdata databases tables list` (filter with `--schema`/`--table`) — pick text column (BM25) or embedding/text column (vector) 2. [ ] `hotdata search list` — avoid duplicate text/vector indexes on the same column 3. [ ] Create index (address by name): - - [ ] **Instant DB only:** `hotdata search create _--type text --from .public. --column ` (vector: `--type vector [--provider

]`). A table in another database must be attached first (`hotdata databases attach `). + - [ ] **Instant DB only:** `hotdata search create _

--type text --from .public. --column ` (vector: `--type vector [--provider

]`). Indexes belong to the database that owns the table: to index a table in another database, build the index there, or load a copy into this one. Attaching does not make it indexable — an attached database is read-only. - [ ] Large build: add `--async`, then `hotdata jobs ` 4. [ ] Search (address the index by name): - [ ] `hotdata search "…" --index _

` diff --git a/src/commands/databases.rs b/src/commands/databases.rs index fb28145..c1c99ab 100644 --- a/src/commands/databases.rs +++ b/src/commands/databases.rs @@ -66,9 +66,10 @@ pub enum DatabasesCommands { #[arg(long)] expires_at: Option, - /// Attach a catalog to the new database so its tables are queryable (repeatable). - /// Accepts a catalog name or id, optionally `catalog=alias` to set the - /// SQL alias it answers to: `--attach github --attach salesdb=sales`. + /// Attach another database to the new one so its tables are queryable + /// (repeatable). Accepts a database name, catalog alias, or id, + /// optionally `database=alias` to set the SQL alias it answers to: + /// `--attach reference --attach salesdb=sales`. #[arg(long = "attach")] attach: Vec, @@ -125,13 +126,16 @@ pub enum DatabasesCommands { output: String, }, - /// Attach a catalog to an instant database so its tables are queryable. + /// Attach another instant database so its tables are queryable here. /// - /// A `query` runs inside one instant database; attaching a catalog makes - /// its live tables visible in that database's scope, so you can join across - /// catalogs in a single query without exporting data. Reachable in SQL as - /// `..
`, or `..
` when + /// A `query` runs inside one instant database; attaching another makes its + /// live tables visible in this database's scope, so you can join across the + /// two in a single query without exporting data. Reachable in SQL as + /// `..
`, or `..
` when /// `--alias` is omitted. + /// + /// Read-only: loads still target this database's own catalog. Not + /// transitive: you see what you attached, not what it attached. Attach { /// Catalog name or id to attach (e.g. `github`) catalog: String, diff --git a/tests/databases_cli.rs b/tests/databases_cli.rs index 830d271..f039297 100644 --- a/tests/databases_cli.rs +++ b/tests/databases_cli.rs @@ -46,8 +46,8 @@ fn databases_create_help_documents_attach_flag() { assert!(output.status.success()); let help = String::from_utf8_lossy(&output.stdout); assert!(help.contains("--attach"), "help: {help}"); - // The `catalog=alias` form is the documented way to set the SQL alias. - assert!(help.contains("catalog=alias"), "help: {help}"); + // The `database=alias` form is the documented way to set the SQL alias. + assert!(help.contains("database=alias"), "help: {help}"); } #[test] From da56cfce7c836a2de31ee0b3d55180503037685a Mon Sep 17 00:00:00 2001 From: Eddie A Tejeda <669988+eddietejeda@users.noreply.github.com> Date: Fri, 18 Sep 2026 17:35:16 -0700 Subject: [PATCH 3/3] docs(review): finish the rename where --help and the CLI still said catalog The positional arg, --alias, Detach, the show label, the attach success message and one line of SKILL.md still described attach as taking a catalog while everything around them had moved to 'another instant database'. The positional now renders as , matching the documented synopsis, and --alias says when it is required: the attached database's own catalog alias is the default, and 'default' cannot be attached under its own name, so a database created without --catalog needs one. --- README.md | 3 ++- skills/hotdata/SKILL.md | 8 ++++---- src/commands/databases.rs | 32 +++++++++++++++++++------------- tests/databases_cli.rs | 8 ++++---- 4 files changed, 29 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 6d51f5a..0bc45f2 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,8 @@ hotdata query "SELECT t.id, o.total FROM demo.public.tickets t ``` The attached database is read-only here: loads still go to your own database, -and `detach` withdraws visibility without deleting anything. +and `detach` withdraws visibility without deleting anything. `--alias` is +required when the other database kept the stock `default` catalog name. ## Search diff --git a/skills/hotdata/SKILL.md b/skills/hotdata/SKILL.md index e9af142..382ef13 100644 --- a/skills/hotdata/SKILL.md +++ b/skills/hotdata/SKILL.md @@ -111,8 +111,8 @@ hotdata databases [--workspace-id ] [--output table|json|yaml hotdata databases remove [--workspace-id ] # Attach another database so its tables are queryable (enables cross-database queries — see below) -hotdata databases attach [--database ] [--alias ] -hotdata databases detach [--database ] +hotdata databases attach [--database ] [--alias ] # : name, catalog alias, or id +hotdata databases detach [--database ] # or the alias it was attached under # Preferred: load by catalog alias (server declares the table/schema if missing). # Loads csv, json, or parquet — format read from the extension. @@ -140,7 +140,7 @@ hotdata databases tables remove
[--database ] [--schema public] [--w - `tables add` — declares a table **with its key and storage layout**, which a load cannot infer. `--key` (repeatable) is what enables the `delete`/`update`/`upsert` load modes on that table. `--sorted-by ` or `=desc` sets sort order; `--partition-by ` partitions on the value, `=month` (or `year`/`day`/`hour`) on a calendar part — one partition per calendar month needs **both** `=year` and `=month`, or every March shares a partition. Sort and partition are fixed once the table exists. `--key-determines` (repeatable) asserts a column's value is fixed by the key: it prunes keyed loads harder, and is **correctness-affecting** — declare it only where the invariant really holds, or a keyed load can leave a duplicate key behind. Re-adding an existing table is a conflict (409), and `tables remove` does not clear the declaration — the table leaves the listing but the name stays declared and still conflicts. So **a key cannot be retrofitted onto a table declared without one**: declare it with `--key` up front, or use a new table name. - `tables load` — publishes to an instant-database table from a local file (`--file`), a remote URL (`--url`), a pre-staged upload (`--upload-id`), or a saved query result (`--result-id`, must belong to the target database). Same `--mode`, `--format`, and `--key` flags as the top-level `load` above. - `tables remove` — drops a table from the instant database. -- `attach` — attaches **another instant database** to this one, so its **live** tables become visible inside this database's query scope. Name the other database by name, catalog alias, or id. Defaults to the active database; target another with `--database`. `--alias` sets the SQL name it answers to (defaults to the attached database's own catalog alias). This is how you **join across databases** — see [Querying across databases](#querying-across-databases-attach). Read-only: loads still target your own database, and attaching is not transitive — you see what you attached, not what it attached. +- `attach` — attaches **another instant database** to this one, so its **live** tables become visible inside this database's query scope. Name the other database by name, catalog alias, or id. Defaults to the active database; target another with `--database`. `--alias` sets the SQL name it answers to (defaults to the attached database's own catalog alias). **Required when that alias is `default`** — the stock name for a database created without `--catalog` — since `default` cannot be attached under its own name. This is how you **join across databases** — see [Querying across databases](#querying-across-databases-attach). Read-only: loads still target your own database, and attaching is not transitive — you see what you attached, not what it attached. - `detach` — removes an attachment, withdrawing visibility without deleting any data. Accepts the attached database's name/id **or** the alias you attached it under. Defaults to the active database. - `create --attach [=]` — attach one or more databases at creation time (repeatable), e.g. `--attach reference --attach salesdb=sales`. @@ -223,7 +223,7 @@ hotdata databases tables show [--output tabl **`databases tables show`** - Fetches column definitions (`COLUMN`, `DATA_TYPE`, `NULLABLE`) for a single table. -- **`catalog.schema.table`** — three-part form; the catalog resolves to an instant database or an attached source by name. +- **`catalog.schema.table`** — three-part form; the catalog resolves to an instant database or an attached database by name. - **`schema.table`** — two-part form; uses the active database (errors if none is set). - Copy the name directly from `databases tables list` output — both forms match what `list` prints. - **Always use `databases tables show` to inspect columns before writing queries.** diff --git a/src/commands/databases.rs b/src/commands/databases.rs index c1c99ab..0e0b617 100644 --- a/src/commands/databases.rs +++ b/src/commands/databases.rs @@ -137,21 +137,27 @@ pub enum DatabasesCommands { /// Read-only: loads still target this database's own catalog. Not /// transitive: you see what you attached, not what it attached. Attach { - /// Catalog name or id to attach (e.g. `github`) + /// The database to attach: its name, catalog alias, or id (e.g. `reference`) + #[arg(value_name = "DATABASE")] catalog: String, /// Database id, catalog, or name to attach into (defaults to the current database) #[arg(long, short = 'd')] database: Option, - /// Alias the catalog answers to in SQL. Defaults to the catalog's name. + /// Alias the attached database answers to in SQL. Defaults to its own + /// catalog alias; required when that alias is `default`, which cannot + /// be attached under its own name. #[arg(long)] alias: Option, }, - /// Detach a previously attached catalog from an instant database. + /// Detach a previously attached database, withdrawing visibility without + /// deleting anything. Detach { - /// Catalog name or id to detach + /// The attached database: its name, catalog alias, or id, or the alias + /// it was attached under + #[arg(value_name = "DATABASE")] catalog: String, /// Database id, catalog, or name to detach from (defaults to the current database) @@ -1790,7 +1796,7 @@ pub fn get(workspace_id: &str, id_or_name: &str, format: &str) { format!("{catalog}.{{schema}}.{{table}}").green() ); if !db.attachments.is_empty() { - println!("{}({})", label("attached catalogs:"), db.attachments.len()); + println!("{}({})", label("attached databases:"), db.attachments.len()); for a in &db.attachments { let alias = a .alias @@ -1809,9 +1815,9 @@ pub fn get(workspace_id: &str, id_or_name: &str, format: &str) { } } -/// Attach a connection as a queryable catalog on an instant database, so its -/// live tables are visible inside that database's query scope (cross-source -/// joins without exporting data). Defaults to the current database. +/// Attach another instant database's catalog to this one, so its live tables +/// are visible inside this database's query scope (cross-database joins +/// without exporting data). Defaults to the current database. pub fn attach(workspace_id: &str, catalog: &str, database: Option<&str>, alias: Option<&str>) { use crossterm::style::Stylize; @@ -1835,7 +1841,7 @@ pub fn attach(workspace_id: &str, catalog: &str, database: Option<&str>, alias: Some(a) => println!( "{}", format!( - "Attached '{catalog}' to database '{where_}' as catalog '{a}'.\n\ + "Attached '{catalog}' to database '{where_}' as '{a}'.\n\ Query: hotdata query \"SELECT * FROM {a}..
LIMIT 10\" -d {where_}" ) .green() @@ -1843,16 +1849,16 @@ pub fn attach(workspace_id: &str, catalog: &str, database: Option<&str>, alias: None => println!( "{}", format!( - "Attached '{catalog}' to database '{where_}'. It is reachable by the \ - catalog's name; run `hotdata databases {where_}` to see attached catalogs." + "Attached '{catalog}' to database '{where_}'. It answers to its own \ + catalog alias; run `hotdata databases {where_}` to see what is attached." ) .green() ), } } -/// Detach a previously attached catalog from an instant database. -/// Defaults to the current database. +/// Detach a previously attached database, withdrawing visibility without +/// deleting anything. Defaults to the current database. pub fn detach(workspace_id: &str, catalog: &str, database: Option<&str>) { use crossterm::style::Stylize; diff --git a/tests/databases_cli.rs b/tests/databases_cli.rs index f039297..b3792d5 100644 --- a/tests/databases_cli.rs +++ b/tests/databases_cli.rs @@ -51,7 +51,7 @@ fn databases_create_help_documents_attach_flag() { } #[test] -fn databases_attach_help_documents_connection_and_alias() { +fn databases_attach_help_documents_database_and_alias() { let output = hotdata() .args(["databases", "attach", "--help"]) .output() @@ -63,8 +63,8 @@ fn databases_attach_help_documents_connection_and_alias() { } #[test] -fn databases_attach_requires_a_connection_argument() { - // `catalog` is a required positional — parsing must fail without it. +fn databases_attach_requires_a_database_argument() { + // the database is a required positional — parsing must fail without it. let output = hotdata().args(["databases", "attach"]).output().unwrap(); assert!(!output.status.success()); let combined = format!( @@ -73,7 +73,7 @@ fn databases_attach_requires_a_connection_argument() { String::from_utf8_lossy(&output.stderr) ); assert!( - combined.contains("required") || combined.contains("CATALOG"), + combined.contains("required") || combined.contains("DATABASE"), "output: {combined}" ); }