walker: schema-existence ERROR diagnostics (ADR-0027 step B)

`MatchedKind::Ident` now carries its `IdentSource`. A
post-walk pass over a structurally-valid parse flags a
matched `Tables` ident that is absent from the schema, or a
`Columns` ident absent from the table in scope, as an ERROR
diagnostic — the command parses but would fail at execution
(ADR-0027 §2). New behaviour: an unknown table / column used
to parse cleanly and fail only when run.

Column scope is resolved by one left-to-right pass over the
matched path (every command places its table ident before
the columns that belong to it); an unknown table clears the
scope, so its columns are not cascaded into a second
diagnostic. New catalog keys `diagnostic.unknown_table` /
`diagnostic.unknown_column`.
This commit is contained in:
claude@clouddev1
2026-05-19 07:15:58 +00:00
parent e22f933e02
commit 827b47f88f
9 changed files with 169 additions and 15 deletions
+6 -3
View File
@@ -316,7 +316,7 @@ const DELETE_SHAPE: Node = Node::Seq(DELETE_NODES);
fn ident_text<'a>(path: &'a MatchedPath, role: &str) -> Option<&'a str> {
path.items.iter().find_map(|i| match &i.kind {
MatchedKind::Ident { role: r } if *r == role => Some(i.text.as_str()),
MatchedKind::Ident { role: r, .. } if *r == role => Some(i.text.as_str()),
_ => None,
})
}
@@ -450,7 +450,7 @@ fn build_insert(path: &MatchedPath) -> Result<Command, ValidationError> {
let table_idx = path
.items
.iter()
.position(|i| matches!(&i.kind, MatchedKind::Ident { role: "table_name" }))
.position(|i| matches!(&i.kind, MatchedKind::Ident { role: "table_name", .. }))
.ok_or_else(|| ValidationError {
message_key: "parse.error_wrapper",
args: vec![("detail", "missing table".to_string())],
@@ -498,6 +498,7 @@ fn build_insert(path: &MatchedPath) -> Result<Command, ValidationError> {
.filter_map(|i| match &i.kind {
MatchedKind::Ident {
role: "insert_first_item",
..
} => Some(i.text.clone()),
_ => None,
})
@@ -551,6 +552,7 @@ fn build_insert(path: &MatchedPath) -> Result<Command, ValidationError> {
.filter_map(|i| match &i.kind {
MatchedKind::Ident {
role: "insert_first_item",
..
} => Some(i.text.clone()),
_ => None,
})
@@ -619,7 +621,8 @@ fn collect_assignments(
if matches!(
item.kind,
MatchedKind::Ident {
role: "update_set_column"
role: "update_set_column",
..
}
) {
let column = item.text.clone();