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#[cfg(test)]
90mod tests;