Skip to main content

uqa_sql/schema/constraint_changes/
foreign_key_target.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Resolve materialized foreign keys by name initially and by durable identity after a wait.
8
9use super::{find_constraint, ConstraintLocation};
10use crate::{
11    ast::{ColumnDef, TableConstraintSet},
12    SQLError,
13};
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub struct ForeignKeyTarget<'a> {
17    pub location: ConstraintLocation,
18    pub name: &'a str,
19    pub object_id: [u8; 16],
20    pub referenced_table: &'a str,
21}
22
23impl<'a> ForeignKeyTarget<'a> {
24    pub fn by_name(
25        columns: &'a [ColumnDef],
26        constraints: &'a TableConstraintSet,
27        name: &str,
28    ) -> Result<Option<Self>, SQLError> {
29        find_constraint(columns, constraints, name).map_or(Ok(None), |location| {
30            Self::at(columns, constraints, location)
31        })
32    }
33
34    pub fn by_id(
35        columns: &'a [ColumnDef],
36        constraints: &'a TableConstraintSet,
37        object_id: [u8; 16],
38    ) -> Result<Option<Self>, SQLError> {
39        let location = columns
40            .iter()
41            .position(|column| {
42                column
43                    .references
44                    .as_ref()
45                    .is_some_and(|reference| reference.object_id == Some(object_id))
46            })
47            .map(ConstraintLocation::ColumnForeignKey)
48            .or_else(|| {
49                constraints
50                    .foreign_keys
51                    .iter()
52                    .position(|reference| reference.object_id == Some(object_id))
53                    .map(ConstraintLocation::TableForeignKey)
54            });
55        location.map_or(Ok(None), |location| {
56            Self::at(columns, constraints, location)
57        })
58    }
59
60    fn at(
61        columns: &'a [ColumnDef],
62        constraints: &'a TableConstraintSet,
63        location: ConstraintLocation,
64    ) -> Result<Option<Self>, SQLError> {
65        let (name, object_id, referenced_table) = match location {
66            ConstraintLocation::ColumnForeignKey(index) => {
67                let reference = columns[index]
68                    .references
69                    .as_ref()
70                    .ok_or_else(|| SQLError::Internal("column FOREIGN KEY disappeared".into()))?;
71                (
72                    reference.name.as_deref(),
73                    reference.object_id,
74                    reference.table.as_str(),
75                )
76            }
77            ConstraintLocation::TableForeignKey(index) => {
78                let reference = &constraints.foreign_keys[index];
79                (
80                    reference.name.as_deref(),
81                    reference.object_id,
82                    reference.ref_table.as_str(),
83                )
84            }
85            _ => return Ok(None),
86        };
87        Ok(Some(Self {
88            location,
89            name: name
90                .ok_or_else(|| SQLError::Internal("FOREIGN KEY has no durable name".into()))?,
91            object_id: object_id
92                .ok_or_else(|| SQLError::Internal("FOREIGN KEY has no durable identity".into()))?,
93            referenced_table,
94        }))
95    }
96}
97
98/// Retire the selected local catalog row and its attachment provenance together.
99pub fn remove_foreign_key(
100    columns: &mut [ColumnDef],
101    constraints: &mut TableConstraintSet,
102    object_id: [u8; 16],
103) -> Result<bool, SQLError> {
104    let Some(target) = ForeignKeyTarget::by_id(columns, constraints, object_id)? else {
105        return Ok(false);
106    };
107    match target.location {
108        ConstraintLocation::ColumnForeignKey(index) => columns[index].references = None,
109        ConstraintLocation::TableForeignKey(index) => {
110            constraints.foreign_keys.remove(index);
111        }
112        _ => unreachable!("a foreign-key target has a foreign-key location"),
113    }
114    constraints
115        .hierarchy
116        .partition_inherited_foreign_keys
117        .retain(|key| key.object_id != Some(object_id));
118    Ok(true)
119}
120
121#[cfg(test)]
122mod tests;