Skip to main content

uqa_sql/schema/removal/
tables.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Detect ordinary-table schema dependencies and detach inbound foreign-key metadata.
8use crate::{
9    ast::{ColumnDef, ForeignKey, TableCheck, TableKeyConstraint},
10    schema::dependencies::rewrites::{
11        schema_expr_references_relation, stored_relation_reference_matches,
12    },
13};
14use std::ops::Deref;
15use uqa_core::RelationIdentity;
16pub type TableColumnsRead<'a> = Box<dyn Deref<Target = Vec<ColumnDef>> + 'a>;
17pub type TableChecksRead<'a> = Box<dyn Deref<Target = Vec<TableCheck>> + 'a>;
18pub type TableForeignKeysRead<'a> = Box<dyn Deref<Target = Vec<ForeignKey>> + 'a>;
19pub type TableKeysRead<'a> = Box<dyn Deref<Target = Vec<TableKeyConstraint>> + 'a>;
20pub trait TableRemovalMetadata {
21    fn columns(&self) -> TableColumnsRead<'_>;
22    fn table_checks(&self) -> TableChecksRead<'_>;
23    fn foreign_keys(&self) -> TableForeignKeysRead<'_>;
24    fn key_constraints(&self) -> TableKeysRead<'_>;
25}
26pub fn table_schema_references_relation(
27    table: &dyn TableRemovalMetadata,
28    target: &RelationIdentity,
29) -> bool {
30    table.columns().iter().any(|column| {
31        column
32            .default
33            .as_ref()
34            .is_some_and(|expr| schema_expr_references_relation(expr, target))
35            || column
36                .check
37                .as_ref()
38                .is_some_and(|expr| schema_expr_references_relation(expr, target))
39            || column.generated.as_ref().is_some_and(|generated| {
40                schema_expr_references_relation(&generated.expression, target)
41            })
42    }) || table
43        .table_checks()
44        .iter()
45        .any(|check| schema_expr_references_relation(&check.expr, target))
46}
47pub fn foreign_key_targets(
48    foreign_key: &crate::ast::ForeignKey,
49    target: &RelationIdentity,
50) -> bool {
51    stored_relation_reference_matches(&foreign_key.ref_table, target)
52}
53pub fn resolved_table_ddl_target(
54    resolved: Option<(String, &str)>,
55    action: &str,
56) -> Result<Option<String>, String> {
57    match resolved {
58        Some((canonical, "table")) => Ok(Some(canonical)),
59        Some((canonical, kind)) => Err(format!(
60            "{action}: relation `{canonical}` is a {kind}, not a table"
61        )),
62        None => Ok(None),
63    }
64}
65pub fn detach_inbound_foreign_keys(
66    columns: &mut [ColumnDef],
67    foreign_keys: &mut Vec<ForeignKey>,
68    targets: &[RelationIdentity],
69) -> bool {
70    let previous_fk_len = foreign_keys.len();
71    foreign_keys.retain(|foreign_key| {
72        !targets
73            .iter()
74            .any(|target| foreign_key_targets(foreign_key, target))
75    });
76    let mut changed = previous_fk_len != foreign_keys.len();
77    for column in columns {
78        if column.references.as_ref().is_some_and(|reference| {
79            targets
80                .iter()
81                .any(|target| stored_relation_reference_matches(&reference.table, target))
82        }) {
83            column.references = None;
84            changed = true;
85        }
86    }
87    changed
88}
89
90#[cfg(test)]
91mod tests;