Grammar: with-pk column specs use name(type), matching add column
`create table … with pk` parsed column types as `name:type`,
while `add column` uses `name(type)`. Unify on the parens
form so column-type syntax is consistent across the DSL:
create table T with pk id(serial), name(text)
Only `COL_SPEC` changes (`:` → `( … )`); `build_create_table`
reads columns by role, so it is unaffected. The `:` that
separates table from column in `add column` / `drop column`
is unchanged. Sweeps the test suite, the typing-surface
matrix (two `after_colon` cells renamed to `after_paren`,
4 snapshots regenerated), the friendly catalog's usage
templates, ADR-0009's example, and requirements.md.
1039 passing / 0 failing / 1 ignored; clippy clean.
This commit is contained in:
@@ -26,7 +26,7 @@ The DSL surface follows three rules.
|
|||||||
Required parts of a command are written in plain words and
|
Required parts of a command are written in plain words and
|
||||||
read like English. Examples:
|
read like English. Examples:
|
||||||
|
|
||||||
- `create table <Name> with pk <name>:<type>`
|
- `create table <Name> with pk <name>(<type>)`
|
||||||
- `add column to table <Name>: <Name> (<Type>)`
|
- `add column to table <Name>: <Name> (<Type>)`
|
||||||
- `drop table <Name>`
|
- `drop table <Name>`
|
||||||
|
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ group enabled. (Earlier reference points: 1006 after ADR-0024
|
|||||||
alphabet and length range.)*
|
alphabet and length range.)*
|
||||||
- [ ] **T3** Compound primary keys handled end-to-end (DSL,
|
- [ ] **T3** Compound primary keys handled end-to-end (DSL,
|
||||||
storage, display, FK reference).
|
storage, display, FK reference).
|
||||||
*(Progress: DSL grammar (`with pk a:int,b:int`), storage, and
|
*(Progress: DSL grammar (`with pk a(int),b(int)`), storage, and
|
||||||
table-info description are all present; the FK iteration
|
table-info description are all present; the FK iteration
|
||||||
references single-column PKs only — compound-key FK references
|
references single-column PKs only — compound-key FK references
|
||||||
remain pending.)*
|
remain pending.)*
|
||||||
|
|||||||
+1
-1
@@ -519,7 +519,7 @@ mod tests {
|
|||||||
fs::write(p.join(PROJECT_YAML), "version: 1\nproject:\n created_at: 2026-01-01T00:00:00Z\ntables: []\nrelationships: []\n").unwrap();
|
fs::write(p.join(PROJECT_YAML), "version: 1\nproject:\n created_at: 2026-01-01T00:00:00Z\ntables: []\nrelationships: []\n").unwrap();
|
||||||
fs::create_dir_all(p.join("data")).unwrap();
|
fs::create_dir_all(p.join("data")).unwrap();
|
||||||
fs::write(p.join("data/Customers.csv"), "Name\nAlice\nBob\n").unwrap();
|
fs::write(p.join("data/Customers.csv"), "Name\nAlice\nBob\n").unwrap();
|
||||||
fs::write(p.join(HISTORY_LOG), "T|ok|create table Customers with pk id:serial\n").unwrap();
|
fs::write(p.join(HISTORY_LOG), "T|ok|create table Customers with pk id(serial)\n").unwrap();
|
||||||
fs::write(p.join(PLAYGROUND_DB), [0u8; 32]).unwrap();
|
fs::write(p.join(PLAYGROUND_DB), [0u8; 32]).unwrap();
|
||||||
fs::write(p.join(GITIGNORE), "/playground.db\n").unwrap();
|
fs::write(p.join(GITIGNORE), "/playground.db\n").unwrap();
|
||||||
// Stray atomic-write staging file — must be excluded.
|
// Stray atomic-write staging file — must be excluded.
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
//! (e.g. "table does not exist").
|
//! (e.g. "table does not exist").
|
||||||
//!
|
//!
|
||||||
//! The shape supports compound primary keys natively even though
|
//! The shape supports compound primary keys natively even though
|
||||||
//! only the dedicated `with pk a:int,b:int` grammar exposes them
|
//! only the dedicated `with pk a(int),b(int)` grammar exposes them
|
||||||
//! today. Future grammar extensions (inline column specs, `set
|
//! today. Future grammar extensions (inline column specs, `set
|
||||||
//! primary key`, junction-table convenience commands) emit into
|
//! primary key`, junction-table convenience commands) emit into
|
||||||
//! the same shape.
|
//! the same shape.
|
||||||
|
|||||||
@@ -796,7 +796,7 @@ pub static CHANGE: CommandNode = CommandNode {
|
|||||||
usage_ids: &["parse.usage.change_column"],};
|
usage_ids: &["parse.usage.change_column"],};
|
||||||
|
|
||||||
// =================================================================
|
// =================================================================
|
||||||
// create_table — `create table <Name> [with pk [<col>:<type>[, ...]]]`
|
// create_table — `create table <Name> [with pk [<col>(<type>)[, ...]]]`
|
||||||
// (Phase C)
|
// (Phase C)
|
||||||
// =================================================================
|
// =================================================================
|
||||||
|
|
||||||
@@ -816,7 +816,7 @@ const COL_NAME: Node = Node::Hinted {
|
|||||||
|
|
||||||
const COL_SPEC_NODES: &[Node] = &[
|
const COL_SPEC_NODES: &[Node] = &[
|
||||||
COL_NAME,
|
COL_NAME,
|
||||||
Node::Punct(':'),
|
Node::Punct('('),
|
||||||
Node::Ident {
|
Node::Ident {
|
||||||
source: IdentSource::Types,
|
source: IdentSource::Types,
|
||||||
role: "col_type",
|
role: "col_type",
|
||||||
@@ -826,6 +826,7 @@ const COL_SPEC_NODES: &[Node] = &[
|
|||||||
writes_column: false,
|
writes_column: false,
|
||||||
writes_user_listed_column: false,
|
writes_user_listed_column: false,
|
||||||
},
|
},
|
||||||
|
Node::Punct(')'),
|
||||||
];
|
];
|
||||||
const COL_SPEC: Node = Node::Seq(COL_SPEC_NODES);
|
const COL_SPEC: Node = Node::Seq(COL_SPEC_NODES);
|
||||||
|
|
||||||
@@ -884,7 +885,7 @@ fn build_create_table(path: &MatchedPath) -> Result<Command, ValidationError> {
|
|||||||
|
|
||||||
let pk_specs: Vec<(String, Type)> = if names.is_empty() {
|
let pk_specs: Vec<(String, Type)> = if names.is_empty() {
|
||||||
if saw_with {
|
if saw_with {
|
||||||
// `with pk` alone — default to id:serial.
|
// `with pk` alone — default to id(serial).
|
||||||
vec![("id".to_string(), Type::Serial)]
|
vec![("id".to_string(), Type::Serial)]
|
||||||
} else {
|
} else {
|
||||||
return Err(ValidationError {
|
return Err(ValidationError {
|
||||||
|
|||||||
+7
-7
@@ -393,7 +393,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn create_table_with_named_typed_pk() {
|
fn create_table_with_named_typed_pk() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ok("create table Customers with pk email:text"),
|
ok("create table Customers with pk email(text)"),
|
||||||
Command::CreateTable {
|
Command::CreateTable {
|
||||||
name: "Customers".to_string(),
|
name: "Customers".to_string(),
|
||||||
columns: vec![col("email", Type::Text)],
|
columns: vec![col("email", Type::Text)],
|
||||||
@@ -405,7 +405,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn create_table_with_compound_pk() {
|
fn create_table_with_compound_pk() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ok("create table OrderLines with pk order_id:int,product_id:int"),
|
ok("create table OrderLines with pk order_id(int),product_id(int)"),
|
||||||
Command::CreateTable {
|
Command::CreateTable {
|
||||||
name: "OrderLines".to_string(),
|
name: "OrderLines".to_string(),
|
||||||
columns: vec![col("order_id", Type::Int), col("product_id", Type::Int),],
|
columns: vec![col("order_id", Type::Int), col("product_id", Type::Int),],
|
||||||
@@ -417,7 +417,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn create_table_pk_accepts_any_user_type() {
|
fn create_table_pk_accepts_any_user_type() {
|
||||||
for ty in Type::all() {
|
for ty in Type::all() {
|
||||||
let input = format!("create table T with pk col:{}", ty.keyword());
|
let input = format!("create table T with pk col({})", ty.keyword());
|
||||||
let cmd = ok(&input);
|
let cmd = ok(&input);
|
||||||
if let Command::CreateTable {
|
if let Command::CreateTable {
|
||||||
columns,
|
columns,
|
||||||
@@ -436,7 +436,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn create_table_pk_tolerates_whitespace() {
|
fn create_table_pk_tolerates_whitespace() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ok("create table T with pk id : serial"),
|
ok("create table T with pk id ( serial )"),
|
||||||
Command::CreateTable {
|
Command::CreateTable {
|
||||||
name: "T".to_string(),
|
name: "T".to_string(),
|
||||||
columns: vec![col("id", Type::Serial)],
|
columns: vec![col("id", Type::Serial)],
|
||||||
@@ -444,7 +444,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ok("create table T with pk a : int , b : int"),
|
ok("create table T with pk a ( int ) , b ( int )"),
|
||||||
Command::CreateTable {
|
Command::CreateTable {
|
||||||
name: "T".to_string(),
|
name: "T".to_string(),
|
||||||
columns: vec![col("a", Type::Int), col("b", Type::Int)],
|
columns: vec![col("a", Type::Int), col("b", Type::Int)],
|
||||||
@@ -456,7 +456,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn create_table_keywords_are_case_insensitive() {
|
fn create_table_keywords_are_case_insensitive() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ok("CREATE TABLE Customers WITH PK email:TEXT"),
|
ok("CREATE TABLE Customers WITH PK email(TEXT)"),
|
||||||
Command::CreateTable {
|
Command::CreateTable {
|
||||||
name: "Customers".to_string(),
|
name: "Customers".to_string(),
|
||||||
columns: vec![col("email", Type::Text)],
|
columns: vec![col("email", Type::Text)],
|
||||||
@@ -760,7 +760,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unknown_pk_type_errors_with_alternatives_listed() {
|
fn unknown_pk_type_errors_with_alternatives_listed() {
|
||||||
let e = err("create table T with pk id:varchar");
|
let e = err("create table T with pk id(varchar)");
|
||||||
match e {
|
match e {
|
||||||
ParseError::Invalid { message, .. } => {
|
ParseError::Invalid { message, .. } => {
|
||||||
assert!(message.contains("varchar"), "{message}");
|
assert!(message.contains("varchar"), "{message}");
|
||||||
|
|||||||
@@ -1076,7 +1076,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn walker_parses_create_table_named_typed_pk() {
|
fn walker_parses_create_table_named_typed_pk() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse("create table Customers with pk email:text").unwrap(),
|
parse("create table Customers with pk email(text)").unwrap(),
|
||||||
Command::CreateTable {
|
Command::CreateTable {
|
||||||
name: "Customers".to_string(),
|
name: "Customers".to_string(),
|
||||||
columns: vec![col("email", Type::Text)],
|
columns: vec![col("email", Type::Text)],
|
||||||
@@ -1088,7 +1088,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn walker_parses_create_table_compound_pk() {
|
fn walker_parses_create_table_compound_pk() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse("create table OrderLines with pk order_id:int,product_id:int").unwrap(),
|
parse("create table OrderLines with pk order_id(int),product_id(int)").unwrap(),
|
||||||
Command::CreateTable {
|
Command::CreateTable {
|
||||||
name: "OrderLines".to_string(),
|
name: "OrderLines".to_string(),
|
||||||
columns: vec![col("order_id", Type::Int), col("product_id", Type::Int)],
|
columns: vec![col("order_id", Type::Int), col("product_id", Type::Int)],
|
||||||
@@ -1100,7 +1100,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn walker_create_table_pk_tolerates_whitespace_around_punct() {
|
fn walker_create_table_pk_tolerates_whitespace_around_punct() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse("create table T with pk id : serial").unwrap(),
|
parse("create table T with pk id ( serial )").unwrap(),
|
||||||
Command::CreateTable {
|
Command::CreateTable {
|
||||||
name: "T".to_string(),
|
name: "T".to_string(),
|
||||||
columns: vec![col("id", Type::Serial)],
|
columns: vec![col("id", Type::Serial)],
|
||||||
@@ -1108,7 +1108,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse("create table T with pk a : int , b : int").unwrap(),
|
parse("create table T with pk a ( int ) , b ( int )").unwrap(),
|
||||||
Command::CreateTable {
|
Command::CreateTable {
|
||||||
name: "T".to_string(),
|
name: "T".to_string(),
|
||||||
columns: vec![col("a", Type::Int), col("b", Type::Int)],
|
columns: vec![col("a", Type::Int), col("b", Type::Int)],
|
||||||
@@ -1134,7 +1134,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn walker_create_table_keywords_are_case_insensitive() {
|
fn walker_create_table_keywords_are_case_insensitive() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse("CREATE TABLE Customers WITH PK email:TEXT").unwrap(),
|
parse("CREATE TABLE Customers WITH PK email(TEXT)").unwrap(),
|
||||||
Command::CreateTable {
|
Command::CreateTable {
|
||||||
name: "Customers".to_string(),
|
name: "Customers".to_string(),
|
||||||
columns: vec![col("email", Type::Text)],
|
columns: vec![col("email", Type::Text)],
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ help:
|
|||||||
messages [short|verbose] — show or switch error-message verbosity (verbose is the default)
|
messages [short|verbose] — show or switch error-message verbosity (verbose is the default)
|
||||||
ddl:
|
ddl:
|
||||||
create: |-
|
create: |-
|
||||||
create table <T> with pk [<col>:<type>, ...] — create a table
|
create table <T> with pk [<col>(<type>), ...] — create a table
|
||||||
drop: |-
|
drop: |-
|
||||||
drop table <T> — remove a table
|
drop table <T> — remove a table
|
||||||
drop column [from] [table] <T>: <col> [--cascade] — remove a column
|
drop column [from] [table] <T>: <col> [--cascade] — remove a column
|
||||||
@@ -371,7 +371,7 @@ parse:
|
|||||||
custom:
|
custom:
|
||||||
replay_path_expected: "expected a path after `replay`"
|
replay_path_expected: "expected a path after `replay`"
|
||||||
create_table_needs_pk: |-
|
create_table_needs_pk: |-
|
||||||
tables need at least one column. Add `with pk` for a default `id INTEGER PRIMARY KEY`, or `with pk <name>:<type>` to choose. Use a comma-separated list for compound primary keys.
|
tables need at least one column. Add `with pk` for a default `id INTEGER PRIMARY KEY`, or `with pk <name>(<type>)` to choose. Use a comma-separated list for compound primary keys.
|
||||||
on_action_specified_twice: "`on {target}` specified twice"
|
on_action_specified_twice: "`on {target}` specified twice"
|
||||||
change_column_flags_exclusive: "`--force-conversion` and `--dont-convert` are mutually exclusive — pick one."
|
change_column_flags_exclusive: "`--force-conversion` and `--dont-convert` are mutually exclusive — pick one."
|
||||||
unknown_type: "unknown type '{found}' (expected one of: {expected})"
|
unknown_type: "unknown type '{found}' (expected one of: {expected})"
|
||||||
@@ -410,7 +410,7 @@ parse:
|
|||||||
# marks optional parts; angle-bracket `<...>` marks
|
# marks optional parts; angle-bracket `<...>` marks
|
||||||
# placeholders. ADR-0009's surface conventions apply.
|
# placeholders. ADR-0009's surface conventions apply.
|
||||||
usage:
|
usage:
|
||||||
create_table: "create table <Name> with pk [<col>:<type>[, ...]]"
|
create_table: "create table <Name> with pk [<col>(<type>)[, ...]]"
|
||||||
drop_table: "drop table <Name>"
|
drop_table: "drop table <Name>"
|
||||||
drop_column: "drop column [from] [table] <Table>: <Name>"
|
drop_column: "drop column [from] [table] <Table>: <Name>"
|
||||||
drop_relationship: |-
|
drop_relationship: |-
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
//! Format: one record per line, three pipe-separated fields:
|
//! Format: one record per line, three pipe-separated fields:
|
||||||
//!
|
//!
|
||||||
//! ```text
|
//! ```text
|
||||||
//! 2026-05-07T14:30:12Z|ok|create table Customers with pk id:serial
|
//! 2026-05-07T14:30:12Z|ok|create table Customers with pk id(serial)
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! Status is always `ok` in v1; failed commands are not
|
//! Status is always `ok` in v1; failed commands are not
|
||||||
@@ -196,12 +196,12 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn record_format() {
|
fn record_format() {
|
||||||
let line = format_record(
|
let line = format_record(
|
||||||
"create table Customers with pk id:serial",
|
"create table Customers with pk id(serial)",
|
||||||
"2026-05-07T14:30:12Z".to_string(),
|
"2026-05-07T14:30:12Z".to_string(),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
line,
|
line,
|
||||||
"2026-05-07T14:30:12Z|ok|create table Customers with pk id:serial\n",
|
"2026-05-07T14:30:12Z|ok|create table Customers with pk id(serial)\n",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -395,12 +395,12 @@ mod tests {
|
|||||||
fn append_history_creates_and_appends() {
|
fn append_history_creates_and_appends() {
|
||||||
let dir = tempdir();
|
let dir = tempdir();
|
||||||
let p = Persistence::new(dir.path().to_path_buf());
|
let p = Persistence::new(dir.path().to_path_buf());
|
||||||
p.append_history("create table Foo with pk id:serial").unwrap();
|
p.append_history("create table Foo with pk id(serial)").unwrap();
|
||||||
p.append_history("insert into Foo (1)").unwrap();
|
p.append_history("insert into Foo (1)").unwrap();
|
||||||
let body = fs::read_to_string(dir.path().join(HISTORY_LOG)).unwrap();
|
let body = fs::read_to_string(dir.path().join(HISTORY_LOG)).unwrap();
|
||||||
let lines: Vec<&str> = body.trim_end().lines().collect();
|
let lines: Vec<&str> = body.trim_end().lines().collect();
|
||||||
assert_eq!(lines.len(), 2);
|
assert_eq!(lines.len(), 2);
|
||||||
assert!(lines[0].ends_with("|ok|create table Foo with pk id:serial"));
|
assert!(lines[0].ends_with("|ok|create table Foo with pk id(serial)"));
|
||||||
assert!(lines[1].ends_with("|ok|insert into Foo (1)"));
|
assert!(lines[1].ends_with("|ok|insert into Foo (1)"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ fn parse_errors_use_no_engine_vocabulary() {
|
|||||||
// tiny-win recipe from handoff-5).
|
// tiny-win recipe from handoff-5).
|
||||||
"change column Tag in Customers: Tag (text)",
|
"change column Tag in Customers: Tag (text)",
|
||||||
// unknown type token.
|
// unknown type token.
|
||||||
"create table T with pk id:varchar",
|
"create table T with pk id(varchar)",
|
||||||
// mutually exclusive flags on change column.
|
// mutually exclusive flags on change column.
|
||||||
"change column T: c (int) --force-conversion --dont-convert",
|
"change column T: c (int) --force-conversion --dont-convert",
|
||||||
// missing required clause.
|
// missing required clause.
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ fn create_table_writes_yaml_and_history() {
|
|||||||
ColumnSpec { name: "Name".to_string(), ty: Type::Text },
|
ColumnSpec { name: "Name".to_string(), ty: Type::Text },
|
||||||
],
|
],
|
||||||
vec!["id".to_string()],
|
vec!["id".to_string()],
|
||||||
Some("create table Customers with pk id:serial".to_string()),
|
Some("create table Customers with pk id(serial)".to_string()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -84,7 +84,7 @@ fn create_table_writes_yaml_and_history() {
|
|||||||
|
|
||||||
let history = read_history(&path);
|
let history = read_history(&path);
|
||||||
assert_eq!(history.len(), 1, "expected one history line; got {history:?}");
|
assert_eq!(history.len(), 1, "expected one history line; got {history:?}");
|
||||||
assert!(history[0].ends_with("|ok|create table Customers with pk id:serial"));
|
assert!(history[0].ends_with("|ok|create table Customers with pk id(serial)"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -100,7 +100,7 @@ fn insert_writes_csv_and_history() {
|
|||||||
ColumnSpec { name: "Name".to_string(), ty: Type::Text },
|
ColumnSpec { name: "Name".to_string(), ty: Type::Text },
|
||||||
],
|
],
|
||||||
vec!["id".to_string()],
|
vec!["id".to_string()],
|
||||||
Some("create table Customers with pk id:serial".to_string()),
|
Some("create table Customers with pk id(serial)".to_string()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -136,7 +136,7 @@ fn drop_table_removes_its_csv() {
|
|||||||
"Customers".to_string(),
|
"Customers".to_string(),
|
||||||
vec![ColumnSpec { name: "id".to_string(), ty: Type::Serial }],
|
vec![ColumnSpec { name: "id".to_string(), ty: Type::Serial }],
|
||||||
vec!["id".to_string()],
|
vec!["id".to_string()],
|
||||||
Some("create table Customers with pk id:serial".to_string()),
|
Some("create table Customers with pk id(serial)".to_string()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -174,7 +174,7 @@ fn delete_with_cascade_rewrites_both_csvs() {
|
|||||||
"Customers".to_string(),
|
"Customers".to_string(),
|
||||||
vec![ColumnSpec { name: "id".to_string(), ty: Type::Serial }],
|
vec![ColumnSpec { name: "id".to_string(), ty: Type::Serial }],
|
||||||
vec!["id".to_string()],
|
vec!["id".to_string()],
|
||||||
Some("create table Customers with pk id:serial".to_string()),
|
Some("create table Customers with pk id(serial)".to_string()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -185,7 +185,7 @@ fn delete_with_cascade_rewrites_both_csvs() {
|
|||||||
ColumnSpec { name: "CustId".to_string(), ty: Type::Int },
|
ColumnSpec { name: "CustId".to_string(), ty: Type::Int },
|
||||||
],
|
],
|
||||||
vec!["id".to_string()],
|
vec!["id".to_string()],
|
||||||
Some("create table Orders with pk id:serial, CustId:int".to_string()),
|
Some("create table Orders with pk id(serial), CustId(int)".to_string()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -266,7 +266,7 @@ fn create_table_does_not_write_csv_for_empty_table() {
|
|||||||
ColumnSpec { name: "Name".to_string(), ty: Type::Text },
|
ColumnSpec { name: "Name".to_string(), ty: Type::Text },
|
||||||
],
|
],
|
||||||
vec!["id".to_string()],
|
vec!["id".to_string()],
|
||||||
Some("create table Customers with pk id:serial".to_string()),
|
Some("create table Customers with pk id(serial)".to_string()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -295,7 +295,7 @@ fn delete_all_rows_removes_csv() {
|
|||||||
ColumnSpec { name: "Name".to_string(), ty: Type::Text },
|
ColumnSpec { name: "Name".to_string(), ty: Type::Text },
|
||||||
],
|
],
|
||||||
vec!["id".to_string()],
|
vec!["id".to_string()],
|
||||||
Some("create table Customers with pk id:serial".to_string()),
|
Some("create table Customers with pk id(serial)".to_string()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -335,7 +335,7 @@ fn show_table_appends_history_only() {
|
|||||||
"Customers".to_string(),
|
"Customers".to_string(),
|
||||||
vec![ColumnSpec { name: "id".to_string(), ty: Type::Serial }],
|
vec![ColumnSpec { name: "id".to_string(), ty: Type::Serial }],
|
||||||
vec!["id".to_string()],
|
vec!["id".to_string()],
|
||||||
Some("create table Customers with pk id:serial".to_string()),
|
Some("create table Customers with pk id(serial)".to_string()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -368,7 +368,7 @@ fn failed_command_does_not_append_history_or_change_yaml() {
|
|||||||
"Customers".to_string(),
|
"Customers".to_string(),
|
||||||
vec![ColumnSpec { name: "id".to_string(), ty: Type::Serial }],
|
vec![ColumnSpec { name: "id".to_string(), ty: Type::Serial }],
|
||||||
vec!["id".to_string()],
|
vec!["id".to_string()],
|
||||||
Some("create table Customers with pk id:serial".to_string()),
|
Some("create table Customers with pk id(serial)".to_string()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -380,7 +380,7 @@ fn failed_command_does_not_append_history_or_change_yaml() {
|
|||||||
"Customers".to_string(),
|
"Customers".to_string(),
|
||||||
vec![ColumnSpec { name: "id".to_string(), ty: Type::Serial }],
|
vec![ColumnSpec { name: "id".to_string(), ty: Type::Serial }],
|
||||||
vec!["id".to_string()],
|
vec!["id".to_string()],
|
||||||
Some("create table Customers with pk id:serial".to_string()),
|
Some("create table Customers with pk id(serial)".to_string()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect_err("must fail");
|
.expect_err("must fail");
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ fn rebuild_restores_schema_only_project() {
|
|||||||
ColumnSpec { name: "Name".to_string(), ty: Type::Text },
|
ColumnSpec { name: "Name".to_string(), ty: Type::Text },
|
||||||
],
|
],
|
||||||
vec!["id".to_string()],
|
vec!["id".to_string()],
|
||||||
Some("create table Customers with pk id:serial".to_string()),
|
Some("create table Customers with pk id(serial)".to_string()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -414,7 +414,7 @@ fn rebuild_restores_indexes() {
|
|||||||
ColumnSpec { name: "Email".to_string(), ty: Type::Text },
|
ColumnSpec { name: "Email".to_string(), ty: Type::Text },
|
||||||
],
|
],
|
||||||
vec!["id".to_string()],
|
vec!["id".to_string()],
|
||||||
Some("create table Customers with pk id:serial".to_string()),
|
Some("create table Customers with pk id(serial)".to_string()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@@ -303,7 +303,7 @@ fn end_to_end_export_then_import_real_project() {
|
|||||||
ColumnSpec { name: "Name".to_string(), ty: Type::Text },
|
ColumnSpec { name: "Name".to_string(), ty: Type::Text },
|
||||||
],
|
],
|
||||||
vec!["id".to_string()],
|
vec!["id".to_string()],
|
||||||
Some("create table Customers with pk id:serial".to_string()),
|
Some("create table Customers with pk id(serial)".to_string()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ fn replay_three_lines_dispatches_three_commands() {
|
|||||||
write_script(
|
write_script(
|
||||||
project.path(),
|
project.path(),
|
||||||
"seed.commands",
|
"seed.commands",
|
||||||
"create table T with pk id:int\n\
|
"create table T with pk id(int)\n\
|
||||||
add column T: name (text)\n\
|
add column T: name (text)\n\
|
||||||
insert into T (1, 'Alice')\n",
|
insert into T (1, 'Alice')\n",
|
||||||
);
|
);
|
||||||
@@ -122,7 +122,7 @@ fn replay_skips_blank_lines_and_comments() {
|
|||||||
"seed.commands",
|
"seed.commands",
|
||||||
"# this is a comment\n\
|
"# this is a comment\n\
|
||||||
\n\
|
\n\
|
||||||
create table T with pk id:int\n\
|
create table T with pk id(int)\n\
|
||||||
\n\
|
\n\
|
||||||
# another comment\n\
|
# another comment\n\
|
||||||
# comment with leading whitespace\n\
|
# comment with leading whitespace\n\
|
||||||
@@ -192,7 +192,7 @@ fn replay_aborts_on_first_parse_failure_and_reports_line() {
|
|||||||
"bad.commands",
|
"bad.commands",
|
||||||
// Line 1: ok. Line 2: ok. Line 3: parse error
|
// Line 1: ok. Line 2: ok. Line 3: parse error
|
||||||
// (`broken keyword X` — not a recognised command).
|
// (`broken keyword X` — not a recognised command).
|
||||||
"create table T with pk id:int\n\
|
"create table T with pk id(int)\n\
|
||||||
add column T: name (text)\n\
|
add column T: name (text)\n\
|
||||||
this is not a command\n\
|
this is not a command\n\
|
||||||
insert into T (1, 'should not happen')\n",
|
insert into T (1, 'should not happen')\n",
|
||||||
@@ -248,7 +248,7 @@ fn replay_rejects_typed_slot_violation_at_parse_time() {
|
|||||||
write_script(
|
write_script(
|
||||||
project.path(),
|
project.path(),
|
||||||
"typed.commands",
|
"typed.commands",
|
||||||
"create table T with pk id:int\n\
|
"create table T with pk id(int)\n\
|
||||||
add column T: count (int)\n\
|
add column T: count (int)\n\
|
||||||
insert into T values (1, 'not a number')\n",
|
insert into T values (1, 'not a number')\n",
|
||||||
);
|
);
|
||||||
@@ -285,7 +285,7 @@ fn replay_aborts_on_first_runtime_failure_and_reports_line() {
|
|||||||
"bad.commands",
|
"bad.commands",
|
||||||
// Line 2 references a table that doesn't exist; the
|
// Line 2 references a table that doesn't exist; the
|
||||||
// engine refuses, replay stops and reports line 2.
|
// engine refuses, replay stops and reports line 2.
|
||||||
"create table T with pk id:int\n\
|
"create table T with pk id(int)\n\
|
||||||
add column NotATable: x (text)\n\
|
add column NotATable: x (text)\n\
|
||||||
insert into T (1)\n",
|
insert into T (1)\n",
|
||||||
);
|
);
|
||||||
@@ -300,11 +300,11 @@ fn replay_aborts_on_first_runtime_failure_and_reports_line() {
|
|||||||
fn replay_refuses_nested_replay() {
|
fn replay_refuses_nested_replay() {
|
||||||
let data = tempdir();
|
let data = tempdir();
|
||||||
let (project, db) = open_project_db(data.path());
|
let (project, db) = open_project_db(data.path());
|
||||||
write_script(project.path(), "inner.commands", "create table T with pk id:int\n");
|
write_script(project.path(), "inner.commands", "create table T with pk id(int)\n");
|
||||||
write_script(
|
write_script(
|
||||||
project.path(),
|
project.path(),
|
||||||
"outer.commands",
|
"outer.commands",
|
||||||
"create table U with pk id:int\nreplay inner.commands\n",
|
"create table U with pk id(int)\nreplay inner.commands\n",
|
||||||
);
|
);
|
||||||
|
|
||||||
let events = rt().block_on(async {
|
let events = rt().block_on(async {
|
||||||
@@ -332,7 +332,7 @@ fn replay_history_log_records_subcommands_only() {
|
|||||||
write_script(
|
write_script(
|
||||||
project.path(),
|
project.path(),
|
||||||
"seed.commands",
|
"seed.commands",
|
||||||
"create table T with pk id:int\nadd column T: name (text)\n",
|
"create table T with pk id(int)\nadd column T: name (text)\n",
|
||||||
);
|
);
|
||||||
|
|
||||||
let events = rt().block_on(async {
|
let events = rt().block_on(async {
|
||||||
@@ -344,7 +344,7 @@ fn replay_history_log_records_subcommands_only() {
|
|||||||
.expect("history.log exists");
|
.expect("history.log exists");
|
||||||
// Per-command entries landed.
|
// Per-command entries landed.
|
||||||
assert!(
|
assert!(
|
||||||
history.lines().any(|l| l.contains("create table T with pk id:int")),
|
history.lines().any(|l| l.contains("create table T with pk id(int)")),
|
||||||
"history.log missing create line:\n{history}"
|
"history.log missing create line:\n{history}"
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//! Matrix coverage for `create table T with pk [<col>:<type>[, ...]]`
|
//! Matrix coverage for `create table T with pk [<col>(<type>)[, ...]]`
|
||||||
//! (ADR-0005, ADR-0009).
|
//! (ADR-0005, ADR-0009).
|
||||||
|
|
||||||
use crate::typing_surface::*;
|
use crate::typing_surface::*;
|
||||||
@@ -73,7 +73,7 @@ fn after_pk_word_does_not_re_offer_pk() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn after_pk_space_with_col_name_typed_expects_colon() {
|
fn after_pk_space_with_col_name_typed_expects_paren() {
|
||||||
let schema = schema_empty();
|
let schema = schema_empty();
|
||||||
let a = assess_at_end("create table Customers with pk Code", &schema);
|
let a = assess_at_end("create table Customers with pk Code", &schema);
|
||||||
assert!(matches!(a.state, InputState::IncompleteAtEof));
|
assert!(matches!(a.state, InputState::IncompleteAtEof));
|
||||||
@@ -81,10 +81,10 @@ fn after_pk_space_with_col_name_typed_expects_colon() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn after_colon_expects_type_candidates() {
|
fn after_paren_expects_type_candidates() {
|
||||||
let schema = schema_empty();
|
let schema = schema_empty();
|
||||||
let a = assess_at_end(
|
let a = assess_at_end(
|
||||||
"create table Customers with pk Code:",
|
"create table Customers with pk Code(",
|
||||||
&schema,
|
&schema,
|
||||||
);
|
);
|
||||||
assert!(matches!(a.state, InputState::IncompleteAtEof));
|
assert!(matches!(a.state, InputState::IncompleteAtEof));
|
||||||
@@ -92,14 +92,14 @@ fn after_colon_expects_type_candidates() {
|
|||||||
&a,
|
&a,
|
||||||
&["text", "int", "serial", "shortid", "bool"],
|
&["text", "int", "serial", "shortid", "bool"],
|
||||||
);
|
);
|
||||||
crate::snap!("after_colon", a);
|
crate::snap!("after_paren", a);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_table_with_explicit_pk_parses() {
|
fn create_table_with_explicit_pk_parses() {
|
||||||
let schema = schema_empty();
|
let schema = schema_empty();
|
||||||
let a = assess_at_end(
|
let a = assess_at_end(
|
||||||
"create table Customers with pk Code:text",
|
"create table Customers with pk Code(text)",
|
||||||
&schema,
|
&schema,
|
||||||
);
|
);
|
||||||
assert!(matches!(a.state, InputState::Valid));
|
assert!(matches!(a.state, InputState::Valid));
|
||||||
@@ -110,7 +110,7 @@ fn create_table_with_explicit_pk_parses() {
|
|||||||
fn create_table_with_compound_pk_parses() {
|
fn create_table_with_compound_pk_parses() {
|
||||||
let schema = schema_empty();
|
let schema = schema_empty();
|
||||||
let a = assess_at_end(
|
let a = assess_at_end(
|
||||||
"create table Memberships with pk UserId:int, GroupId:int",
|
"create table Memberships with pk UserId(int), GroupId(int)",
|
||||||
&schema,
|
&schema,
|
||||||
);
|
);
|
||||||
assert!(matches!(a.state, InputState::Valid));
|
assert!(matches!(a.state, InputState::Valid));
|
||||||
|
|||||||
+2
-2
@@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
source: tests/typing_surface/create_table.rs
|
source: tests/typing_surface/create_table.rs
|
||||||
description: "input=\"create table Customers with pk Code:\" cursor=36"
|
description: "input=\"create table Customers with pk Code(\" cursor=36"
|
||||||
expression: "& a"
|
expression: "& a"
|
||||||
---
|
---
|
||||||
Assessment {
|
Assessment {
|
||||||
input: "create table Customers with pk Code:",
|
input: "create table Customers with pk Code(",
|
||||||
cursor: 36,
|
cursor: 36,
|
||||||
state: IncompleteAtEof,
|
state: IncompleteAtEof,
|
||||||
hint: Some(
|
hint: Some(
|
||||||
+1
-1
@@ -9,7 +9,7 @@ Assessment {
|
|||||||
state: IncompleteAtEof,
|
state: IncompleteAtEof,
|
||||||
hint: Some(
|
hint: Some(
|
||||||
Prose(
|
Prose(
|
||||||
"Next: `:`",
|
"Next: `(`",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
completion: None,
|
completion: None,
|
||||||
+3
-3
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
source: tests/typing_surface/create_table.rs
|
source: tests/typing_surface/create_table.rs
|
||||||
description: "input=\"create table Memberships with pk UserId:int, GroupId:int\" cursor=56"
|
description: "input=\"create table Memberships with pk UserId(int), GroupId(int)\" cursor=58"
|
||||||
expression: "& a"
|
expression: "& a"
|
||||||
---
|
---
|
||||||
Assessment {
|
Assessment {
|
||||||
input: "create table Memberships with pk UserId:int, GroupId:int",
|
input: "create table Memberships with pk UserId(int), GroupId(int)",
|
||||||
cursor: 56,
|
cursor: 58,
|
||||||
state: Valid,
|
state: Valid,
|
||||||
hint: Some(
|
hint: Some(
|
||||||
Prose(
|
Prose(
|
||||||
|
|||||||
+3
-3
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
source: tests/typing_surface/create_table.rs
|
source: tests/typing_surface/create_table.rs
|
||||||
description: "input=\"create table Customers with pk Code:text\" cursor=40"
|
description: "input=\"create table Customers with pk Code(text)\" cursor=41"
|
||||||
expression: "& a"
|
expression: "& a"
|
||||||
---
|
---
|
||||||
Assessment {
|
Assessment {
|
||||||
input: "create table Customers with pk Code:text",
|
input: "create table Customers with pk Code(text)",
|
||||||
cursor: 40,
|
cursor: 41,
|
||||||
state: Valid,
|
state: Valid,
|
||||||
hint: Some(
|
hint: Some(
|
||||||
Prose(
|
Prose(
|
||||||
|
|||||||
Reference in New Issue
Block a user