parser: make to and table independently optional in add column

Previously the grammar accepted only `to table` together or
neither. The user-stated convention is that bare table
identifiers are accepted in unambiguous positions (matching
how `add 1:n relationship from <T>.<col> to <T>.<col>` takes
bare table names). Both `to` and `table` are now or_not'd
independently, so all four combinations parse identically.

Updates the in-app `help` listing to advertise the new
shape: `add column [to] [table] <T>: <col> (<type>)`.

3 new parser tests cover the variants.
This commit is contained in:
claude@clouddev1
2026-05-08 09:16:50 +00:00
parent 1b27a0c9b1
commit 41cef5399b
2 changed files with 52 additions and 7 deletions
+1 -1
View File
@@ -1108,7 +1108,7 @@ impl App {
"DSL data commands (in simple mode):",
" create table <T> with pk [<col>:<type>...]",
" drop table <T>",
" add column [to table] <T>: <col> (<type>)",
" add column [to] [table] <T>: <col> (<type>)",
" add 1:n relationship [as <name>] from <P>.<col> to <C>.<col>",
" [on delete <action>] [on update <action>] [--create-fk]",
" drop relationship <name>",
+51 -6
View File
@@ -129,14 +129,19 @@ fn command_parser<'a>()
.ignore_then(identifier())
.map(|name| Command::DropTable { name });
// `to table` is optional — both `add column to table T: c (text)`
// and `add column T: c (text)` parse identically.
let to_table_optional = keyword_ci("to")
.ignore_then(keyword_ci("table"))
.or_not();
// Both `to` and `table` are independently optional —
// `add column to table T: c (text)`,
// `add column to T: c (text)`,
// `add column table T: c (text)`,
// and `add column T: c (text)` all parse identically.
// Matches the convention elsewhere in the DSL where bare
// identifiers are accepted in unambiguous positions.
let optional_to = keyword_ci("to").or_not();
let optional_table = keyword_ci("table").or_not();
let add_column = keyword_ci("add")
.ignore_then(keyword_ci("column"))
.ignore_then(to_table_optional)
.ignore_then(optional_to)
.ignore_then(optional_table)
.ignore_then(identifier())
.then_ignore(just(':').padded())
.then(identifier())
@@ -764,6 +769,46 @@ mod tests {
}
}
#[test]
fn add_column_accepts_bare_table_name() {
// `to table` are both optional; bare table identifier
// is accepted in this unambiguous position.
assert_eq!(
ok("add column Customers: Name (text)"),
Command::AddColumn {
table: "Customers".to_string(),
column: "Name".to_string(),
ty: Type::Text,
}
);
}
#[test]
fn add_column_accepts_to_alone() {
// `to` without `table`.
assert_eq!(
ok("add column to Customers: Name (text)"),
Command::AddColumn {
table: "Customers".to_string(),
column: "Name".to_string(),
ty: Type::Text,
}
);
}
#[test]
fn add_column_accepts_table_alone() {
// `table` without `to`.
assert_eq!(
ok("add column table Customers: Name (text)"),
Command::AddColumn {
table: "Customers".to_string(),
column: "Name".to_string(),
ty: Type::Text,
}
);
}
#[test]
fn add_column_tolerates_whitespace_around_punctuation() {
assert_eq!(