Skip to main content

uqa_sql/schema/table_creation/
foreign_keys.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Define each foreign key in written order after the table and all its unique keys exist.
8
9use super::declaration::CreateTableAnalysisContext;
10use crate::ast::{CreateTable, DeclaredForeignKey, ForeignKey};
11use crate::schema::constraint_changes::ForeignKeyLocation;
12use crate::schema::constraint_metadata::{
13    assign_constraint_name, materialize_foreign_key_identity, CatalogIdentityAllocator,
14    ConstraintMetadataError,
15};
16use crate::schema::foreign_keys::{
17    column_foreign_key, validate_bound_foreign_key_definition_with_local_state,
18};
19use crate::SQLError;
20use std::collections::BTreeSet;
21
22pub fn define_foreign_keys(
23    context: &CreateTableAnalysisContext<'_>,
24    table: &mut CreateTable,
25    inherited: usize,
26    allocate: &mut CatalogIdentityAllocator<'_>,
27) -> Result<(), SQLError> {
28    let relation =
29        uqa_core::RelationIdentity::from_legacy_name(&table.name).map_err(SQLError::Internal)?;
30    let local_columns = table.columns.clone();
31    let mut held = held_names(table, inherited);
32    let mut used = context
33        .index_names
34        .automatic_constraint_names(&table.name)?;
35    used.extend(held.iter().cloned());
36    // A partition's cloned constraints already have their names and catalog identities.
37    for foreign_key in &mut table.foreign_keys[..inherited] {
38        validate_bound_foreign_key_definition_with_local_state(
39            &context.foreign_keys,
40            &table.name,
41            Some(&local_columns),
42            Some(&table.key_constraints),
43            foreign_key,
44        )?;
45    }
46    let mut order = std::mem::take(&mut table.foreign_key_order);
47    // Older serialized declarations have no written order; still validate every retained constraint.
48    if order.is_empty() {
49        order.extend(
50            table
51                .columns
52                .iter()
53                .filter(|column| column.references.is_some())
54                .map(|column| DeclaredForeignKey::Column(column.name.clone())),
55        );
56        order.extend((0..table.foreign_keys.len() - inherited).map(DeclaredForeignKey::Table));
57    }
58    for declared in order {
59        let (mut foreign_key, location) = declaration(table, declared, inherited)?;
60        if let Some(name) = &foreign_key.name {
61            if !held.insert(name.clone()) {
62                return Err(crate::schema::check_inheritance::duplicate_check(
63                    &relation.name,
64                    name,
65                ));
66            }
67            used.insert(name.clone());
68        }
69        assign_constraint_name(
70            &mut foreign_key.name,
71            (&relation.name, &foreign_key.local_columns.join("_"), "fkey"),
72            &mut used,
73        )
74        .map_err(ConstraintMetadataError::into_sql_error)?;
75        held.extend(foreign_key.name.iter().cloned());
76        bind_reference(context, &table.name, &table.qualifier, &mut foreign_key)?;
77        validate_bound_foreign_key_definition_with_local_state(
78            &context.foreign_keys,
79            &table.name,
80            Some(&local_columns),
81            Some(&table.key_constraints),
82            &mut foreign_key,
83        )?;
84        materialize_foreign_key_identity(
85            &mut foreign_key.object_id,
86            &mut foreign_key.catalog_identity,
87            allocate,
88        )
89        .map_err(ConstraintMetadataError::into_sql_error)?;
90        store_definition(table, location, foreign_key)?;
91    }
92    Ok(())
93}
94
95fn bind_reference(
96    context: &CreateTableAnalysisContext<'_>,
97    table: &str,
98    qualifier: &str,
99    key: &mut ForeignKey,
100) -> Result<(), SQLError> {
101    let reference = &mut key.ref_table;
102    let self_reference = reference == table
103        || reference == qualifier
104        || table
105            .rsplit_once('.')
106            .is_some_and(|(_, local)| local == reference);
107    if self_reference {
108        table.clone_into(reference);
109    } else {
110        *reference = context
111            .foreign_keys
112            .catalog
113            .resolve_table_reference(reference)?;
114    }
115    Ok(())
116}
117
118fn lost_foreign_key(name: &str) -> SQLError {
119    SQLError::Internal(format!("declared foreign key `{name}` disappeared"))
120}
121
122fn held_names(table: &CreateTable, inherited: usize) -> BTreeSet<String> {
123    table
124        .columns
125        .iter()
126        .flat_map(|column| {
127            [
128                column.not_null_name.clone().filter(|_| column.not_null),
129                column.check_name.clone().filter(|_| column.check.is_some()),
130            ]
131        })
132        .chain(table.checks.iter().map(|check| check.name.clone()))
133        .chain(table.key_constraints.iter().map(|key| key.name.clone()))
134        .chain(
135            table.foreign_keys[..inherited]
136                .iter()
137                .map(|key| key.name.clone()),
138        )
139        .flatten()
140        .collect()
141}
142
143fn declaration(
144    table: &CreateTable,
145    declared: DeclaredForeignKey,
146    inherited: usize,
147) -> Result<(ForeignKey, ForeignKeyLocation), SQLError> {
148    Ok(match declared {
149        DeclaredForeignKey::Column(name) => {
150            let position = table
151                .columns
152                .iter()
153                .position(|column| column.name == name)
154                .ok_or_else(|| lost_foreign_key(&name))?;
155            let column = &table.columns[position];
156            let reference = column
157                .references
158                .as_ref()
159                .ok_or_else(|| lost_foreign_key(&name))?;
160            (
161                column_foreign_key(column, reference),
162                ForeignKeyLocation::Column(position),
163            )
164        }
165        DeclaredForeignKey::Table(position) => {
166            let position = position + inherited;
167            let key = table
168                .foreign_keys
169                .get(position)
170                .ok_or_else(|| lost_foreign_key(&position.to_string()))?;
171            (key.clone(), ForeignKeyLocation::Table(position))
172        }
173    })
174}
175
176fn store_definition(
177    table: &mut CreateTable,
178    location: ForeignKeyLocation,
179    foreign_key: ForeignKey,
180) -> Result<(), SQLError> {
181    match location {
182        ForeignKeyLocation::Column(position) => {
183            let reference = table.columns[position]
184                .references
185                .as_mut()
186                .ok_or_else(|| lost_foreign_key(&position.to_string()))?;
187            let [referenced_column] = foreign_key.ref_columns.as_slice() else {
188                return Err(SQLError::Internal(
189                    "column FOREIGN KEY did not resolve one referenced column".into(),
190                ));
191            };
192            reference.name = foreign_key.name;
193            reference.object_id = foreign_key.object_id;
194            reference.catalog_identity = foreign_key.catalog_identity;
195            reference.referenced_key = foreign_key.referenced_key;
196            reference.referenced_index = foreign_key.referenced_index;
197            reference.table = foreign_key.ref_table;
198            reference.column = Some(referenced_column.clone());
199        }
200        ForeignKeyLocation::Table(position) => table.foreign_keys[position] = foreign_key,
201    }
202    Ok(())
203}