uqa_sql/schema/constraint_metadata/identity/
foreign_keys.rs1use crate::ast::{ColumnDef, ConstraintCatalogIdentity, TableConstraintSet};
10use crate::schema::constraint_metadata::{
11 CatalogIdentityAllocator, ConstraintMetadataError, ConstraintMetadataResult,
12};
13use std::collections::BTreeSet;
14use uqa_core::RelationIdentity;
15
16pub(crate) fn materialize(
17 target: &mut Option<ConstraintCatalogIdentity>,
18 allocate: &mut CatalogIdentityAllocator<'_>,
19) -> ConstraintMetadataResult<bool> {
20 if let Some(identity) = target {
21 validate_identity(*identity)?;
22 return Ok(false);
23 }
24 let object_id = allocate.allocate_object_id("foreign-key catalog row")?;
25 let identity = ConstraintCatalogIdentity {
26 object_id,
27 oid: allocate.allocate_catalog_oid(
28 crate::schema::constraint_metadata::CatalogOidClass::Constraint,
29 &object_id,
30 )?,
31 };
32 validate_identity(identity)?;
33 *target = Some(identity);
34 Ok(true)
35}
36
37fn validate_identity(identity: ConstraintCatalogIdentity) -> ConstraintMetadataResult<()> {
38 if identity.is_valid() {
39 Ok(())
40 } else {
41 Err(ConstraintMetadataError::Invalid(
42 "invalid foreign-key catalog identity".into(),
43 ))
44 }
45}
46
47pub fn identities<'a>(
48 columns: &'a [ColumnDef],
49 constraints: &'a TableConstraintSet,
50) -> impl Iterator<Item = Option<ConstraintCatalogIdentity>> + 'a {
51 columns
52 .iter()
53 .filter_map(|column| column.references.as_ref())
54 .map(|reference| reference.catalog_identity)
55 .chain(
56 constraints
57 .foreign_keys
58 .iter()
59 .map(|key| key.catalog_identity),
60 )
61}
62
63pub fn validate(
64 columns: &[ColumnDef],
65 constraints: &TableConstraintSet,
66) -> ConstraintMetadataResult<()> {
67 let mut objects = BTreeSet::new();
68 let mut oids = BTreeSet::new();
69 for identity in identities(columns, constraints) {
70 let identity = identity.ok_or_else(|| {
71 ConstraintMetadataError::Invalid(
72 "foreign keys require an initial catalog identity migration".into(),
73 )
74 })?;
75 validate_identity(identity)?;
76 if !objects.insert(identity.object_id) || !oids.insert(identity.oid) {
77 return Err(ConstraintMetadataError::Invalid(
78 "duplicate foreign-key catalog identity".into(),
79 ));
80 }
81 }
82 for inherited in &constraints.hierarchy.partition_inherited_foreign_keys {
83 if !constraints.foreign_keys.iter().any(|key| {
84 key.catalog_identity == inherited.catalog_identity
85 && key.object_id == inherited.object_id
86 }) {
87 return Err(ConstraintMetadataError::Invalid(
88 "partition foreign-key provenance does not identify its catalog row".into(),
89 ));
90 }
91 }
92 Ok(())
93}
94
95pub struct LegacyIdentities {
97 columns: Vec<usize>,
98 constraints: Vec<usize>,
99}
100
101impl LegacyIdentities {
102 pub fn capture(columns: &[ColumnDef], constraints: &TableConstraintSet) -> Self {
103 Self {
104 columns: columns
105 .iter()
106 .enumerate()
107 .filter_map(|(index, column)| {
108 column
109 .references
110 .as_ref()
111 .filter(|key| key.catalog_identity.is_none())
112 .map(|_| index)
113 })
114 .collect(),
115 constraints: constraints
116 .foreign_keys
117 .iter()
118 .enumerate()
119 .filter_map(|(index, key)| key.catalog_identity.is_none().then_some(index))
120 .collect(),
121 }
122 }
123
124 pub fn preserve_oids(
125 self,
126 relation: &RelationIdentity,
127 columns: &mut [ColumnDef],
128 constraints: &mut TableConstraintSet,
129 ) -> ConstraintMetadataResult<bool> {
130 let changed = !self.columns.is_empty() || !self.constraints.is_empty();
131 for index in self.columns {
132 let key = columns[index].references.as_mut().ok_or_else(|| {
133 ConstraintMetadataError::Invalid("legacy foreign key disappeared".into())
134 })?;
135 preserve_oid(relation, key.name.as_deref(), key.catalog_identity.as_mut())?;
136 }
137 for index in self.constraints {
138 let key = &mut constraints.foreign_keys[index];
139 let previous = key.catalog_identity;
140 preserve_oid(relation, key.name.as_deref(), key.catalog_identity.as_mut())?;
141 let identity = key.catalog_identity;
142 for inherited in &mut constraints.hierarchy.partition_inherited_foreign_keys {
143 if inherited.catalog_identity == previous {
144 inherited.catalog_identity = identity;
145 }
146 }
147 }
148 super::super::synchronize_partition_inherited_foreign_key_ids(constraints);
149 validate(columns, constraints)?;
150 Ok(changed)
151 }
152}
153
154fn preserve_oid(
155 relation: &RelationIdentity,
156 name: Option<&str>,
157 identity: Option<&mut ConstraintCatalogIdentity>,
158) -> ConstraintMetadataResult<()> {
159 let name = name
160 .ok_or_else(|| ConstraintMetadataError::Invalid("legacy foreign key has no name".into()))?;
161 let identity = identity.ok_or_else(|| {
162 ConstraintMetadataError::Invalid("legacy foreign key has no catalog identity".into())
163 })?;
164 identity.oid = crate::catalog::oids::stable_oid(
165 "constraint",
166 &format!("{}.{}.{name}", relation.schema, relation.name),
167 );
168 Ok(())
169}
170
171#[cfg(test)]
172mod tests;