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:
@@ -187,6 +187,22 @@ const DROP_TABLE_NODES: &[Node] = &[
|
||||
];
|
||||
const DROP_TABLE: Node = Node::Seq(DROP_TABLE_NODES);
|
||||
|
||||
// Advanced-mode SQL `DROP TABLE [IF EXISTS] <name> [;]` (ADR-0035 §4,
|
||||
// sub-phase 4c). Same table-only target as the simple `drop table`,
|
||||
// plus the optional `IF EXISTS` no-op-with-note. The leading concrete
|
||||
// `table` keyword (not the Optional) keeps the element/dispatch
|
||||
// matching honest.
|
||||
static SQL_DROP_IF_EXISTS_NODES: &[Node] =
|
||||
&[Node::Word(Word::keyword("if")), Node::Word(Word::keyword("exists"))];
|
||||
const SQL_DROP_IF_EXISTS_OPT: Node = Node::Optional(&Node::Seq(SQL_DROP_IF_EXISTS_NODES));
|
||||
static SQL_DROP_TABLE_SHAPE_NODES: &[Node] = &[
|
||||
Node::Word(Word::keyword("table")),
|
||||
SQL_DROP_IF_EXISTS_OPT,
|
||||
TABLE_NAME_EXISTING,
|
||||
Node::Optional(&Node::Punct(';')),
|
||||
];
|
||||
const SQL_DROP_TABLE_SHAPE: Node = Node::Seq(SQL_DROP_TABLE_SHAPE_NODES);
|
||||
|
||||
// =================================================================
|
||||
// drop_column — `drop column [from] [table] <T> : <col>`
|
||||
// =================================================================
|
||||
@@ -1691,6 +1707,25 @@ pub static SQL_CREATE_TABLE: CommandNode = CommandNode {
|
||||
usage_ids: &["parse.usage.sql_create_table"],
|
||||
};
|
||||
|
||||
/// Build a `Command::SqlDropTable` from the advanced-mode SQL
|
||||
/// `DROP TABLE [IF EXISTS] <name>` shape (ADR-0035 §4, sub-phase 4c).
|
||||
/// `if` appears only in the `IF EXISTS` prefix, so its presence is the
|
||||
/// flag (mirroring `build_sql_create_table`'s `if_not_exists`).
|
||||
fn build_sql_drop_table(path: &MatchedPath, _source: &str) -> Result<Command, ValidationError> {
|
||||
Ok(Command::SqlDropTable {
|
||||
name: require_ident(path, "table_name")?,
|
||||
if_exists: path.contains_word("if"),
|
||||
})
|
||||
}
|
||||
|
||||
pub static SQL_DROP_TABLE: CommandNode = CommandNode {
|
||||
entry: Word::keyword("drop"),
|
||||
shape: SQL_DROP_TABLE_SHAPE,
|
||||
ast_builder: build_sql_drop_table,
|
||||
help_id: Some("ddl.sql_drop_table"),
|
||||
usage_ids: &["parse.usage.sql_drop_table"],
|
||||
};
|
||||
|
||||
// =================================================================
|
||||
// Tests — `create table` column constraints (ADR-0029 §2.1, §9)
|
||||
// =================================================================
|
||||
@@ -1900,3 +1935,62 @@ mod constraint_tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// Tests — advanced-mode SQL `DROP TABLE [IF EXISTS]` (ADR-0035 §4, 4c)
|
||||
// =================================================================
|
||||
|
||||
#[cfg(test)]
|
||||
mod sql_drop_table_tests {
|
||||
use crate::dsl::command::Command;
|
||||
use crate::dsl::parser::parse_command_in_mode;
|
||||
use crate::mode::Mode;
|
||||
|
||||
fn drop_fields(input: &str) -> (String, bool) {
|
||||
match parse_command_in_mode(input, Mode::Advanced).expect("should parse") {
|
||||
Command::SqlDropTable { name, if_exists } => (name, if_exists),
|
||||
other => panic!("expected SqlDropTable, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drop_table_parses_as_sql_drop_table_in_advanced_mode() {
|
||||
let (name, if_exists) = drop_fields("drop table Orders");
|
||||
assert_eq!(name, "Orders");
|
||||
assert!(!if_exists);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_exists_sets_the_flag() {
|
||||
let (name, if_exists) = drop_fields("drop table if exists Orders");
|
||||
assert_eq!(name, "Orders");
|
||||
assert!(if_exists);
|
||||
// trailing semicolon tolerated
|
||||
assert!(drop_fields("drop table if exists Orders;").1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_drop_table_in_simple_mode_is_the_dsl_command() {
|
||||
// In simple mode the SQL node is gated; `drop table T` is the
|
||||
// simple `DropTable` (which has no `if_exists`).
|
||||
match parse_command_in_mode("drop table Orders", Mode::Simple).expect("parses") {
|
||||
Command::DropTable { name } => assert_eq!(name, "Orders"),
|
||||
other => panic!("expected DropTable, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn other_drops_fall_back_to_the_simple_node_in_advanced_mode() {
|
||||
// `drop column` / `drop relationship` are not SQL DROP TABLE —
|
||||
// they fall through to the simple `drop` node even in advanced.
|
||||
assert!(matches!(
|
||||
parse_command_in_mode("drop column from Orders: note", Mode::Advanced).expect("parses"),
|
||||
Command::DropColumn { .. }
|
||||
));
|
||||
assert!(matches!(
|
||||
parse_command_in_mode("drop relationship Customers_id_to_Orders_CustId", Mode::Advanced)
|
||||
.expect("parses"),
|
||||
Command::DropRelationship { .. }
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -590,6 +590,11 @@ pub static REGISTRY: &[(&CommandNode, CommandCategory)] = &[
|
||||
// the `create table … with pk …` DSL node when the SQL shape
|
||||
// does not match — the `insert` precedent.
|
||||
(&ddl::SQL_CREATE_TABLE, CommandCategory::Advanced),
|
||||
// Shared `drop` entry word: `ddl::DROP` (simple) and this advanced
|
||||
// SQL node. SQL-first in advanced mode; `drop table [if exists] T`
|
||||
// matches here while `drop column`/`drop relationship`/`drop index`
|
||||
// fall back to the simple `drop` node.
|
||||
(&ddl::SQL_DROP_TABLE, CommandCategory::Advanced),
|
||||
];
|
||||
|
||||
/// Whether `entry` names an advanced-mode-only command (ADR-0030
|
||||
|
||||
Reference in New Issue
Block a user