Repository navigation
feat(databases): add fork subcommand - #227
Conversation
Add `hotdata databases fork [DATABASE]` wrapping the
`POST /v1/databases/{id}/fork` API: create an independent deep copy of a
managed database — same schemas, tables, and data, diverging freely
afterwards.
- Source positional defaults to the current database; resolves by id,
catalog, or name like `attach`/`detach`.
- `--name` defaults to `<source>-fork` so the fork and source stay
distinguishable in `databases list` (an unnamed fork otherwise inherits
both the source's display name and catalog alias).
- `--expires-at` accepts a relative duration or RFC 3339 timestamp; when
omitted a still-future source expiry is inherited, so the resolved
expiry is always printed ("never" when none).
- Sets the fork as the current database on success; source is unchanged.
- Routes through the raw `post_raw` seam (the endpoint is excluded from
the public SDK); surfaces the API error body, with a recovery hint on
the pre-DuckLake (parquet-backed) 400.
Validated end-to-end against production and fuzzed (malformed expiry,
hostile names, hostile source ids); no crashes or orphaned databases.
077a85c to
0ec9f26
Compare
There was a problem hiding this comment.
Reviewed the fork subcommand. Clean implementation that mirrors the existing create command's structure (raw post_raw seam, spinner, current-database save, output formats). fork_database_request name-defaulting and field omission are correct and well covered by the three new unit tests. Error surfacing and the DuckLake hint degrade gracefully. No blocking issues.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Reviewed the fork subcommand. Consistent with existing database command patterns (create/attach/detach), well-documented, and covered by meaningful unit tests (name-default logic, happy-path POST, 400 error body). Error handling, spinner lifecycle, and source resolution all check out. LGTM.
The fork endpoint is now public and generated into the SDK, so the CLI
calls it through the same ergonomic `databases()` handle as
create/get/list/delete instead of the raw `post_raw` seam:
api.client().databases().fork(&db.id, ForkDatabaseRequest { .. })
- fork_database_request now builds the typed ForkDatabaseRequest (double
-option fields) rather than raw JSON; behaviour (default name to
`<source>-fork`, omit fields to inherit) is unchanged.
- fork_error_exit preserves the verbatim API error plus the parquet
-backed-source recovery hint, now driven off the mapped ApiError.
- Bump hotdata 0.8.1 -> 0.9.1 (adds fork + ergonomic wrapper).
The bump also pulls in the `default_schema` field now required on the
database detail/summary/create SDK models, so the affected test fixtures
in databases.rs and indexes.rs gain it.
Summary
Adds
hotdata databases fork [DATABASE], wrapping thePOST /v1/databases/{id}/forkAPI. It creates an independent deep copy of a managed database — same schemas, tables, and data — that diverges freely from the source afterwards (writes to one never affect the other).Fork is a public API and is generated into the
hotdataRust SDK, so the CLI calls it through the same typed ergonomic handle as every other database op:This PR bumps the SDK dependency
hotdata0.8.1 → 0.9.1 (the release that adds thefork_databasemethod and its ergonomic wrapper).Design decisions
resolve_databasehelper, matchingattach/detach. Sohotdata databases forkjust forks whatever you're working in.--namedefaults to<source>-fork. An unnamed fork inherits both the source's display name and its catalog alias from the server, which would leave two rows indatabases listidentical in every user-facing column. The client-side default keeps them distinguishable.--expires-atomitted, a still-future source expiry is silently inherited, so the resolvedexpires_atis always printed (neverwhen none).create), since the flagship workflow is "fork to experiment, then throw away." The source is left unchanged and this is stated in the output.400(fork_error_exit).SDK-bump fallout
The 0.9.1 bump also pulls in the now-required
default_schemafield on the database detail/summary/create SDK models. Test fixtures indatabases.rsandindexes.rsthat mock those responses gain the field; no production code change beyond fork.Testing
databases().fork(), 400 error-body mapping). Full suite: 287 passed, 0 failed.cargo fmt --checkclean; release build green.--expires-at(banana,0h,1.5h, overflow, past timestamps), hostile--name(empty, unicode, SQL-injection string, oversized, control chars), and hostile source ids (traversal, quotes, junk). No crashes, no orphaned databases, no injection leakage.Notes
../workspacesprintserror parsing response: missing field ...instead of a clean not-found. It reproduces ondatabases show/delete— the SDK's typed GET handle normalizes../. Cosmetic, affects the wholename_or_idcommand family; worth a separate fix.cargo clippy -D warningsreports pre-existing errors onmainunrelated to this change.