feat: ADR-0035 4c — DROP TABLE [IF EXISTS]

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.
This commit is contained in:
claude@clouddev1
2026-05-25 16:31:41 +00:00
parent 76d60591bf
commit e52e90c45b
16 changed files with 597 additions and 19 deletions
+16 -1
View File
@@ -30,7 +30,8 @@ use crate::app::App;
use crate::cli::Args;
use crate::db::{
AddColumnResult, ChangeColumnTypeResult, CreateOutcome, DataResult, Database, DbError,
DeleteResult, DropColumnResult, InsertResult, QueryPlan, TableDescription, UpdateResult,
DeleteResult, DropColumnResult, DropOutcome, InsertResult, QueryPlan, TableDescription,
UpdateResult,
};
use crate::dsl::{Command, ColumnSpec};
use crate::dsl::walker::Severity;
@@ -1257,6 +1258,9 @@ fn spawn_dsl_dispatch(
command: command.clone(),
description,
},
Ok(CommandOutcome::SchemaDropSkipped) => AppEvent::DslDropSkipped {
command: command.clone(),
},
Ok(CommandOutcome::Query(data)) => AppEvent::DslDataSucceeded {
command: command.clone(),
data,
@@ -1654,6 +1658,10 @@ enum CommandOutcome {
/// so the App can render it alongside the "already exists —
/// skipped" note.
SchemaSkipped(TableDescription),
/// A SQL `DROP TABLE IF EXISTS` that matched no table — a no-op
/// (ADR-0035 §4, 4c). Carries no structure (there is none); the App
/// renders the "doesn't exist — skipped" note from the command.
SchemaDropSkipped,
Query(DataResult),
QueryPlan(QueryPlan),
Insert(InsertResult),
@@ -1948,6 +1956,13 @@ async fn execute_command_typed(
.drop_table(name, src)
.await
.map(|()| CommandOutcome::Schema(None)),
Command::SqlDropTable { name, if_exists } => database
.sql_drop_table(name, if_exists, src)
.await
.map(|outcome| match outcome {
DropOutcome::Dropped => CommandOutcome::Schema(None),
DropOutcome::Skipped => CommandOutcome::SchemaDropSkipped,
}),
Command::AddColumn {
table,
column,