diff --git a/src/dsl/grammar/sql_create_table.rs b/src/dsl/grammar/sql_create_table.rs
index 1d0be1c..633f901 100644
--- a/src/dsl/grammar/sql_create_table.rs
+++ b/src/dsl/grammar/sql_create_table.rs
@@ -150,14 +150,12 @@ pub(crate) static CHECK_NODES: &[Node] = &[
// endpoints; the `(
)` is optional (the bare `REFERENCES
// ` form resolves to the parent's PK at execution).
-// NOTE (4i): `IdentSource::Tables` existence-checks the parent — good
-// for the common case (a typo'd parent shows a pre-submit hint), but a
-// self-referencing FK (`references ` while creating ``)
-// false-flags the not-yet-created table as unknown. Parse + execution
-// are correct (the self-ref is validated against the in-statement
-// columns); only the live typing indicator is briefly wrong. ADR-0035
-// §13 4i: teach the schema-existence diagnostic about the CREATE TABLE
-// target so the self-ref indicator stops lying.
+// `IdentSource::Tables` existence-checks the parent, so a typo'd parent
+// shows a pre-submit hint. A self-referencing FK (`references `
+// while creating ``) is NOT flagged: the schema-existence
+// diagnostic exempts a `Tables` reference matching the `CREATE TABLE`
+// target — the table being created in the same statement (ADR-0035 §4i c,
+// `schema_existence_diagnostics`'s `created_tables`).
const FK_PARENT_TABLE: Node = Node::Ident {
source: IdentSource::Tables,
role: "fk_parent_table",
diff --git a/src/dsl/walker/mod.rs b/src/dsl/walker/mod.rs
index 1873fe1..ad789f2 100644
--- a/src/dsl/walker/mod.rs
+++ b/src/dsl/walker/mod.rs
@@ -652,6 +652,12 @@ fn schema_existence_diagnostics(
// (won't false-flag valid refs).
let mut bindings: Vec = Vec::new();
let mut cte_names: Vec = Vec::new();
+ // Tables being *created* in this statement (a `CREATE TABLE` target —
+ // `IdentSource::NewName`, role `table_name`). A FK that references the
+ // table being created (a self-reference) names it via
+ // `IdentSource::Tables` before it exists in the schema, so it would
+ // otherwise be flagged "no such table" pre-submit (ADR-0035 §4i c).
+ let mut created_tables: Vec = Vec::new();
{
let mut pending_alias_index: Option = None;
for item in &path.items {
@@ -685,6 +691,12 @@ fn schema_existence_diagnostics(
}
pending_alias_index = None;
}
+ IdentSource::NewName if role == "table_name" => {
+ // The `CREATE TABLE` target — record it so a
+ // self-referencing FK parent isn't flagged unknown.
+ created_tables.push(item.text.clone());
+ pending_alias_index = None;
+ }
_ => {
pending_alias_index = None;
}
@@ -871,10 +883,16 @@ fn schema_existence_diagnostics(
}
} else if !schema_has_table(schema, &item.text)
&& !cte_names_contains(&cte_names, &item.text)
+ && !created_tables
+ .iter()
+ .any(|t| t.eq_ignore_ascii_case(&item.text))
{
// Unknown table — the pre-pass skipped
// pushing this as a binding, so it's not in
- // the resolution scope. Flag it here.
+ // the resolution scope. A self-referencing FK
+ // parent (the table being created in this same
+ // statement) is exempt (ADR-0035 §4i c). Flag
+ // it here.
diagnostics.push(Diagnostic {
severity: Severity::Error,
span: item.span,
@@ -5396,6 +5414,38 @@ mod tests {
);
}
+ #[test]
+ fn self_referencing_fk_in_create_table_is_not_flagged_unknown() {
+ // ADR-0035 §4i (c): a CREATE TABLE whose FK references the table
+ // being created (a self-reference) must not pre-flag the
+ // not-yet-created table as unknown — the FK parent equals the
+ // CREATE target. Empty schema: `T` does not exist yet.
+ let schema = SchemaCache::default();
+ let diags = diag_keys(
+ "create table T (id int primary key, parent_id int references T(id))",
+ &schema,
+ );
+ assert!(
+ !diags.iter().any(|d| d.contains("no such table")),
+ "self-ref FK parent must not be flagged unknown; got {diags:?}",
+ );
+ }
+
+ #[test]
+ fn create_table_fk_to_genuinely_unknown_table_still_flags() {
+ // The exemption is only for the self-reference: a FK to some
+ // *other* non-existent table is still flagged pre-submit.
+ let schema = SchemaCache::default();
+ let diags = diag_keys(
+ "create table T (id int primary key, ghost_id int references Ghost(id))",
+ &schema,
+ );
+ assert!(
+ diags.iter().any(|d| d.contains("no such table")),
+ "FK to a genuinely-unknown table must still flag; got {diags:?}",
+ );
+ }
+
#[test]
fn alias_resolves_qualifier() {
let schema = two_table_schema();