Skip to main content

uqa_sql/schema/inheritance/
restoration.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Retain local schema objects when legacy hierarchy edges lose their parent.
8
9use super::{alter, origins::InheritanceOriginChange};
10use crate::{
11    ast::{AutoIncrement, ColumnDef, TableConstraintSet},
12    SQLError,
13};
14use std::collections::BTreeSet;
15use uqa_core::RelationIdentity;
16
17pub struct ParentEdgeRepair {
18    pub changed: bool,
19    pub origins: Option<InheritanceOriginChange>,
20    pub detached_partition: bool,
21    pub inherited_identity: Vec<(String, AutoIncrement)>,
22}
23
24/// Normalize legacy names and remove absent parents without removing constraints belonging to the surviving relation. The caller applies origin changes against the remaining parents and separates detached subtree enforcement families.
25pub fn repair_parent_edges(
26    columns: &mut [ColumnDef],
27    constraints: &mut TableConstraintSet,
28    existing: &BTreeSet<String>,
29) -> Result<ParentEdgeRepair, SQLError> {
30    let previous = constraints.hierarchy.clone();
31    let mut parents = Vec::with_capacity(previous.parents.len());
32    let mut sequence_numbers = Vec::with_capacity(previous.parents.len());
33    for (index, parent) in previous.parents.iter().enumerate() {
34        let parent = RelationIdentity::from_legacy_name(parent)
35            .map_err(SQLError::Internal)?
36            .qualified_name();
37        if existing.contains(&parent) {
38            parents.push(parent);
39            sequence_numbers.push(previous.parent_sequence_number(index));
40        }
41    }
42    let changed = parents != previous.parents;
43    let detached_partition = changed && parents.is_empty() && previous.is_partition();
44    let mut inherited_identity = Vec::new();
45    if changed {
46        constraints.hierarchy.parents = parents;
47        constraints.hierarchy.parent_sequence_numbers = sequence_numbers;
48        if constraints.hierarchy.parents.is_empty() {
49            constraints.hierarchy.local_columns =
50                columns.iter().map(|column| column.name.clone()).collect();
51        }
52        if detached_partition {
53            inherited_identity = columns
54                .iter()
55                .filter_map(|column| {
56                    column
57                        .auto_increment
58                        .as_ref()
59                        .filter(|increment| increment.is_identity())
60                        .map(|increment| (column.name.clone(), increment.clone()))
61                })
62                .collect();
63            restore_partition_identity(columns, constraints, &inherited_identity);
64            constraints.hierarchy.partition_bound = None;
65            alter::clear_partition_constraint_provenance(constraints);
66        }
67    }
68    Ok(ParentEdgeRepair {
69        changed,
70        origins: InheritanceOriginChange::between(&previous, &constraints.hierarchy),
71        detached_partition,
72        inherited_identity,
73    })
74}
75
76pub fn restore_partition_identity(
77    columns: &mut [ColumnDef],
78    constraints: &mut TableConstraintSet,
79    inherited: &[(String, AutoIncrement)],
80) {
81    alter::restore_identity_overrides(
82        columns,
83        inherited,
84        &constraints.hierarchy.partition_identity_overrides,
85    );
86    constraints.hierarchy.partition_identity_overrides.clear();
87}
88
89/// Freeze predecessor constructors in partition keys, including keys retained by detached partition constraints. Partition bounds already contain evaluated datums.
90pub fn restore_composite_constructors(
91    catalog: &dyn crate::schema::SchemaExpressionCatalog,
92    columns: &[ColumnDef],
93    checks: &mut [crate::ast::TableCheck],
94    hierarchy: &mut crate::ast::TableHierarchy,
95) -> Result<bool, SQLError> {
96    let mut changed = false;
97    for spec in hierarchy
98        .partition_spec
99        .iter_mut()
100        .chain(checks.iter_mut().filter_map(|check| {
101            check
102                .partition_constraint
103                .as_mut()
104                .map(|constraint| &mut constraint.spec)
105        }))
106    {
107        for expression in &mut spec.keys {
108            if crate::type_resolution::composite_rows::expression_requires_binding(
109                expression, catalog,
110            )? {
111                *expression = catalog
112                    .plan_schema_expression(expression, columns)?
113                    .expression;
114                changed = true;
115            }
116        }
117    }
118    Ok(changed)
119}
120
121#[cfg(test)]
122mod tests;