e52e90c45b
Add advanced-mode SQL `DROP TABLE [IF EXISTS] <name>` -> SqlDropTable, executing through the existing do_drop_table (cascade / inbound- relationship refusal / metadata cleanup) — full parity with the simple `drop table`. The only new behaviour is `IF EXISTS` as a no-op-with-note: a new DropOutcome::Skipped mirroring CreateOutcome::Skipped (journalled, no snapshot), rendered via a new ddl.drop_skipped_absent note + DslDropSkipped event. - Grammar: SQL_DROP_TABLE node (entry `drop`, shape `table [if exists] <name> [;]`), registered Advanced. SQL-first dispatch: `drop table T` -> SqlDropTable in advanced; `drop column`/`relationship`/`index`/ `constraint` fall back to the simple `drop` node (and still execute). - Worker: Request::SqlDropTable + db.sql_drop_table; the if-exists-and- absent arm journals + replies Skipped without a snapshot, else snapshot_then(do_drop_table) -> Dropped. - Completion: advanced `drop ` now surfaces the SQL `table` (the shared-entry-word behaviour from `create`); test split into simple (full DSL list) + advanced (SQL surface). Known shared-entry-word completion unevenness (advanced `drop ` offers only `table`; partial `drop rel` returns an empty list) deferred to 4i (merge candidate sets for shared entry words) along with a flagged user request to visually distinguish simple- vs advanced-mode completions in the hint UI — tracked in ADR §13 4i (d)/(e), the 4c plan, and the completion test. The DSL drops still parse + execute via fallback. 10 new tests (parse/builder + Tier-3: drop existing + one-undo-step + restore, IF EXISTS skip + journal, plain-absent error, inbound refusal). Docs: ADR-0035 Status/§13, README, requirements.md Q1. Tests: 1805 passing, 0 failing, 1 ignored. Clippy clean.
8.2 KiB
8.2 KiB
Plan: ADR-0035 Phase 4, sub-phase 4c — DROP TABLE [IF EXISTS]
Add advanced-mode SQL DROP TABLE [IF EXISTS] <name> → SqlDropTable.
Reuses the existing do_drop_table (cascade/inbound-refusal +
metadata cleanup), so it has full parity with the simple drop table;
the only new behaviour is IF EXISTS as a no-op-that-succeeds-with-a-
note, mirroring 4a's CreateOutcome::Skipped with a DropOutcome
(ADR-0035 §4/§13). Small slice.
1. Baseline
- Tests: 1795 passing, 0 failing, 1 ignored; clippy clean. Branch
main, last commit76d6059(4b). 4c starts here.
2. Decisions (from ADR §4/§13 + the 4a precedent — not re-litigated)
IF EXISTS→ no-op-with-note (universal cross-vendor idiom, already in scope per the 4aIF [NOT] EXISTSdecision): dropping an absent table succeeds with a note instead of the plain "no such table" error. MirrorsCreateOutcome::Skippedvia a newDropOutcome { Dropped, Skipped }. The skip is journalled (no snapshot), exactly as the create-skip is.- Table only. SQL
DROP TABLEdrops a table;DROP INDEXis 4c's sibling 4d (SqlDropIndex); column/constraint drops areALTER TABLE(4e+).drop column/drop relationship/drop indexkeep falling back to the simpledropnode. - Dispatch (ADR-0033 Amendment 1).
dropis a shared entry word; the SQL node is tried first in advanced mode.drop table Tparses asSqlDropTablein advanced mode (equivalent execution to the simpleDropTable— both calldo_drop_table); the simple form is the only one in simple mode. - Cascade / inbound-relationship refusal parity — reuse
do_drop_tableunchanged (it already refuses dropping a table with inbound relationships and cleans__rdbms_*metadata, incl. the 4a.3table_checksrows). One undo step (snapshot_then).
3. Phase 1 — Requirements checklist (4c)
DROP TABLE <name>parses in advanced mode →SqlDropTable.DROP TABLE IF EXISTS <name>sets theif_existsflag.- Dropping an existing table removes it + its metadata (parity with
simple
drop table); one undo step;undorestores it. IF EXISTSon an absent table → success with a note, no error, no snapshot; the line is journalled.- Plain
DROP TABLEon an absent table → the existing "no such table" error (unchanged). - Dropping a table with inbound relationships is refused
(existing
do_drop_tablesemantics), in advanced mode too. drop column/drop relationship/drop indexstill parse as the simple forms in advanced mode (fallback).- Engine-neutral note wording.
Testing
- Tier 1 (builder):
drop table <name>→SqlDropTable{if_exists: false};drop table if exists <name>→if_exists: true; the simpledrop column/drop relationshipforms still parse (fallback). - Tier 3 (
tests/sql_drop_table.rs): drop existing → gone + one undo step + undo restores;IF EXISTSabsent → skipped + note + journalled; plain absent → error; inbound-relationship refusal. - Catalog lockstep + vocab audit for the new note key.
4. Architecture & design
4.1 Grammar (src/dsl/grammar/ddl.rs)
IF_EXISTS_OPT=Optional(Seq[Word("if"), Word("exists")]).SQL_DROP_TABLE_SHAPE=Seq[Word("table"), IF_EXISTS_OPT, TABLE_NAME_EXISTING, Optional(Punct(';'))](mirrors the simpleDROP_TABLEshape + the optionalIF EXISTS+ trailing;).pub static SQL_DROP_TABLE: CommandNode { entry: "drop", shape, ast_builder: build_sql_drop_table, help_id: "ddl.sql_drop_table", usage_ids: ["parse.usage.sql_drop_table"] }.build_sql_drop_table:name = require_ident("table_name"),if_exists = path.contains_word("if")(theifonly appears in theIF EXISTSprefix; mirror the create builder'sif_not_exists).
4.2 Command (src/dsl/command.rs)
SqlDropTable { name: String, if_exists: bool }. Verb label
"drop table"; target_table() → name (add to the existing match
arms alongside DropTable).
4.3 Worker (src/db.rs)
DropOutcome { Dropped, Skipped }(the drop peer ofCreateOutcome;Skippedneeds no payload — the runtime renders the note from the command's name).Request::SqlDropTable { name, if_exists, source, reply: oneshot<Result<DropOutcome>> }+db.sql_drop_tablemethod.- Dispatch arm: if
if_exists && !user_table_exists(name)→ journal the line (no snapshot) and replySkipped(mirror the create-skip arm); elsesnapshot_then(… do_drop_table(name) … → Dropped). do_drop_tableunchanged.
4.4 Runtime + event + app (src/runtime.rs, src/event.rs, src/app.rs)
Command::SqlDropTable→db.sql_drop_table→ mapDropped→CommandOutcome::Schema(None)(same as simple drop),Skipped→ a newCommandOutcome::SchemaDropSkipped.CommandOutcome::SchemaDropSkipped→AppEvent::DslDropSkipped { command }; the app handler notesddl.drop_skipped_absentwithcommand.target_table()(mirrorDslCreateSkipped). No structure to render (the table is gone / never existed).
4.5 Friendly catalog
- New key
ddl.drop_skipped_absent: "table '{name}' doesn't exist — skipped (no changes made)"+ akeys.rs("ddl.drop_skipped_absent", &["name"])entry. Engine-neutral. Help/usage skeleton body forddl.sql_drop_table+parse.usage.sql_drop_table(a fresh node — its keys must exist; unlike the deferred refresh of the create skeleton, these are new keys the node references, so they land now).
5. Out of 4c scope
DROP INDEX(4d →SqlDropIndex);ALTER TABLE(4e–4h).- Shared-entry-word completion (deferred to 4i, user-confirmed
2026-05-25). Adding the SQL
dropnode makes advanced-modedropcompletion surface onlytable; a partial DSL subcommand keyword (drop rel) returns an empty list (mid-word dead end). The DSL drops still parse + execute via fallback — only the completion hint is affected. This is the pre-existing shared-entry-word model (same ascreate/insert/update/delete), exposed here becausedrophas five distinct DSL subcommands with no SQL equivalent. 4i merges the candidate sets for shared entry words; the user also wants to discuss visually distinguishing simple- vs advanced-mode completions in the hint UI (likely by colour) before/with that work. Tracked in ADR-0035 §13 4i (d)/(e) and the advanced-mode completion test insrc/completion.rs.
6. Devil's Advocate review of this plan
- Reuse vs fork?
do_drop_tableis the single executor; the SQL path differs only at theIF EXISTSbranch (mirrors the create-skip). ✓ - Dispatch fallback? A builder test asserts
drop column/drop relationshipstill parse simple in advanced mode. ✓ - One undo step + restore?
snapshot_thenwrap + a dedicated undo test (drop → undo → table back, with its data/metadata). ✓ - No-op journalled, not snapshotted? Mirrors the create-skip arm exactly; a journalling test. ✓
- Engine-neutral? The note is a catalog key under the vocab audit;
the plain-absent error is the existing
do_drop_tablepath (unchanged, already engine-neutral). ✓
7. Implementation sequence (test-first)
- Command + grammar + builder — Tier-1 builder tests (parse +
if_exists+ simple fallback) → red → addSqlDropTable, the grammar node + shape + builder, REGISTRY entry, threadRequest/method/ dispatch/runtime (drop execution can land here too) → green. - Worker no-op-with-note — Tier-3 (
tests/sql_drop_table.rs): drop existing,IF EXISTSskip + journal, plain-absent error, inbound refusal, one undo step + restore → red → addDropOutcome+ the dispatch arm + theSchemaDropSkipped/DslDropSkipped/app-note path → green. - Catalog — add the note + help/usage keys + bodies; lockstep + vocab audit → green.
- Full sweep —
cargo test(no regression from 1795) + clippy. - Docs — ADR-0035 Status/§13 4c; README;
requirements.mdQ1. Propose commit; wait for approval.
8. Exit gate
- All §3 items satisfied; four tiers green, zero skips; no regression from the 1795 baseline; written DA pass; clippy clean.