ADR-0024 Phase D: data commands at chumsky parity
Migrate the four data commands at four entry words: show
(show data / show table), insert, update, delete. Walker now
owns the entire command set introduced through ADR-0014.
Scope deviation from ADR-0024: full schema-aware value typing
via DynamicSubgrammar(column_value_list) is deferred. The
walker accepts any value at any position — matching the
existing chumsky parser's behaviour, where per-column type
checks happen at bind time. The DynamicSubgrammar Node
variant and WalkContext schema fields stay declared so the
infrastructure is in place when the schema cache plumbs
through parse_command (a future refinement). All existing
tests pass on the new shape.
Walker extensions:
- StringLit terminal — wired to the consume_string_literal
helper that mirrors the legacy lexer's `''` escape handling.
MatchedItem text carries the unescaped payload; span covers
the surrounding quotes.
- Bridge: Incomplete error wording now appends `, found end
of input` (matching the chumsky-side structural error
contract that `structural_error_for_show_data_without_arg`
asserts on).
Grammar:
- src/dsl/grammar/data.rs: SHOW (Choice of show_data /
show_table), INSERT (three forms folded into a single shape
via a Choice ordered to disambiguate Form B's `values`
keyword from Forms A/C's `(`-prefixed content; the inner
paren list is a Choice(VALUE_LITERAL, Ident{Columns}) with
VALUE_LITERAL ordered first so `true`/`false`/`null` match
their Word branch rather than the broader identifier catch-
all), UPDATE (assignments + filter), DELETE (filter).
- VALUE_LITERAL = Choice(Word("null"), Word("true"),
Word("false"), NumberLit, StringLit) — matches the chumsky
`value_literal()`.
- WHERE_CLAUSE / FILTER_CLAUSE shared between update and
delete.
- AST builders walk MatchedPath items in order, using role
tags (`update_set_column`, `filter_column`,
`insert_first_item`) to discriminate column references
belonging to different shapes within the same command.
Tests:
- 13 new walker-specific tests covering all data forms:
show data / show table, insert with each of three forms,
insert with negative numbers, update with single + multiple
assignments + where, update with --all-rows, delete with
where, delete with --all-rows, update/delete without filter
errors, replay still routes via chumsky.
- Total: 838 passed, 0 failed, 1 ignored (was 825 / 1).
- cargo clippy --all-targets -- -D warnings clean.
This commit is contained in:
+166
-5
@@ -744,15 +744,176 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
// ---- Routing fall-through still works for non-DDL ----
|
||||
// =========================================================
|
||||
// Phase D — data commands (show, insert, update, delete).
|
||||
// =========================================================
|
||||
|
||||
use crate::dsl::value::Value;
|
||||
use crate::dsl::command::RowFilter;
|
||||
|
||||
#[test]
|
||||
fn walker_does_not_engage_for_show_data() {
|
||||
// `show` isn't migrated yet (Phase D); router falls
|
||||
fn walker_parses_show_data() {
|
||||
assert_eq!(
|
||||
parse("show data Customers").unwrap(),
|
||||
Command::ShowData {
|
||||
name: "Customers".to_string()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walker_parses_show_table() {
|
||||
assert_eq!(
|
||||
parse("show table Customers").unwrap(),
|
||||
Command::ShowTable {
|
||||
name: "Customers".to_string()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walker_parses_insert_with_explicit_column_list() {
|
||||
assert_eq!(
|
||||
parse("insert into Customers (Email, Name) values ('a@b.c', 'Alice')").unwrap(),
|
||||
Command::Insert {
|
||||
table: "Customers".to_string(),
|
||||
columns: Some(vec!["Email".to_string(), "Name".to_string()]),
|
||||
values: vec![Value::Text("a@b.c".to_string()), Value::Text("Alice".to_string())],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walker_parses_insert_with_values_keyword_only() {
|
||||
assert_eq!(
|
||||
parse("insert into Customers values (1, 'Alice', null)").unwrap(),
|
||||
Command::Insert {
|
||||
table: "Customers".to_string(),
|
||||
columns: None,
|
||||
values: vec![
|
||||
Value::Number("1".to_string()),
|
||||
Value::Text("Alice".to_string()),
|
||||
Value::Null,
|
||||
],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walker_parses_insert_short_form_without_column_list() {
|
||||
assert_eq!(
|
||||
parse("insert into Customers (1, 'Alice', true)").unwrap(),
|
||||
Command::Insert {
|
||||
table: "Customers".to_string(),
|
||||
columns: None,
|
||||
values: vec![
|
||||
Value::Number("1".to_string()),
|
||||
Value::Text("Alice".to_string()),
|
||||
Value::Bool(true),
|
||||
],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walker_parses_insert_supports_negative_numbers() {
|
||||
assert_eq!(
|
||||
parse("insert into T values (-5)").unwrap(),
|
||||
Command::Insert {
|
||||
table: "T".to_string(),
|
||||
columns: None,
|
||||
values: vec![Value::Number("-5".to_string())],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walker_parses_update_with_where() {
|
||||
assert_eq!(
|
||||
parse("update Customers set Email='new@b.c' where id=1").unwrap(),
|
||||
Command::Update {
|
||||
table: "Customers".to_string(),
|
||||
assignments: vec![("Email".to_string(), Value::Text("new@b.c".to_string()))],
|
||||
filter: RowFilter::Where {
|
||||
column: "id".to_string(),
|
||||
value: Value::Number("1".to_string()),
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walker_parses_update_with_multiple_assignments() {
|
||||
assert_eq!(
|
||||
parse("update Customers set Email='a@b.c', Name='Alice' where id=1").unwrap(),
|
||||
Command::Update {
|
||||
table: "Customers".to_string(),
|
||||
assignments: vec![
|
||||
("Email".to_string(), Value::Text("a@b.c".to_string())),
|
||||
("Name".to_string(), Value::Text("Alice".to_string())),
|
||||
],
|
||||
filter: RowFilter::Where {
|
||||
column: "id".to_string(),
|
||||
value: Value::Number("1".to_string()),
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walker_parses_update_with_all_rows_flag() {
|
||||
assert_eq!(
|
||||
parse("update Customers set Active=true --all-rows").unwrap(),
|
||||
Command::Update {
|
||||
table: "Customers".to_string(),
|
||||
assignments: vec![("Active".to_string(), Value::Bool(true))],
|
||||
filter: RowFilter::AllRows,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walker_parses_delete_with_where() {
|
||||
assert_eq!(
|
||||
parse("delete from Customers where id=42").unwrap(),
|
||||
Command::Delete {
|
||||
table: "Customers".to_string(),
|
||||
filter: RowFilter::Where {
|
||||
column: "id".to_string(),
|
||||
value: Value::Number("42".to_string()),
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walker_parses_delete_with_all_rows() {
|
||||
assert_eq!(
|
||||
parse("delete from Customers --all-rows").unwrap(),
|
||||
Command::Delete {
|
||||
table: "Customers".to_string(),
|
||||
filter: RowFilter::AllRows,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walker_delete_without_where_or_flag_errors() {
|
||||
assert!(parse("delete from Customers").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walker_update_without_where_or_flag_errors() {
|
||||
assert!(parse("update Customers set Email='x'").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walker_does_not_engage_for_replay() {
|
||||
// `replay` isn't migrated yet (Phase E); router falls
|
||||
// through to chumsky.
|
||||
assert!(matches!(
|
||||
parse("show data Customers").unwrap(),
|
||||
Command::ShowData { .. }
|
||||
parse("replay history.log").unwrap(),
|
||||
Command::Replay { .. }
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user