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:
@@ -151,7 +151,7 @@ fn build_import(path: &MatchedPath) -> Result<Command, ValidationError> {
|
||||
.map(|i| i.text.clone())
|
||||
.unwrap_or_default();
|
||||
let target = path
|
||||
.find(|i| matches!(&i.kind, MatchedKind::Ident { role } if *role == "target"))
|
||||
.find(|i| matches!(&i.kind, MatchedKind::Ident { role, .. } if *role == "target"))
|
||||
.map(|i| i.text.clone());
|
||||
Ok(Command::App(AppCommand::Import {
|
||||
path: bare_path,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -475,7 +475,7 @@ const CHANGE_COLUMN: Node = Node::Seq(CHANGE_COLUMN_NODES);
|
||||
/// First ident whose role matches.
|
||||
fn ident<'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,
|
||||
})
|
||||
}
|
||||
@@ -495,7 +495,7 @@ fn collect_idents(path: &MatchedPath, role: &str) -> Vec<String> {
|
||||
path.items
|
||||
.iter()
|
||||
.filter_map(|i| match &i.kind {
|
||||
MatchedKind::Ident { role: r } if *r == role => Some(i.text.clone()),
|
||||
MatchedKind::Ident { role: r, .. } if *r == role => Some(i.text.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
@@ -862,7 +862,7 @@ fn build_create_table(path: &MatchedPath) -> Result<Command, ValidationError> {
|
||||
.items
|
||||
.iter()
|
||||
.filter_map(|i| match &i.kind {
|
||||
MatchedKind::Ident { role: "col_name" } => Some(i.text.clone()),
|
||||
MatchedKind::Ident { role: "col_name", .. } => Some(i.text.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
@@ -870,7 +870,7 @@ fn build_create_table(path: &MatchedPath) -> Result<Command, ValidationError> {
|
||||
.items
|
||||
.iter()
|
||||
.filter_map(|i| match &i.kind {
|
||||
MatchedKind::Ident { role: "col_type" } => Some(i.text.as_str()),
|
||||
MatchedKind::Ident { role: "col_type", .. } => Some(i.text.as_str()),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -525,7 +525,7 @@ impl<'a> ExprParser<'a> {
|
||||
.advance()
|
||||
.ok_or_else(|| drift_error("expected an operand"))?;
|
||||
match &item.kind {
|
||||
MatchedKind::Ident { role: "expr_column" } => {
|
||||
MatchedKind::Ident { role: "expr_column", .. } => {
|
||||
Ok(Operand::Column(item.text.clone()))
|
||||
}
|
||||
MatchedKind::Word("null") => Ok(Operand::Literal(Value::Null)),
|
||||
|
||||
Reference in New Issue
Block a user