uqa_sql/schema/constraint_metadata/
identity.rs1use super::{CatalogIdentityAllocator, ConstraintMetadataError, ConstraintMetadataResult};
10use crate::ast::{ColumnDef, ConstraintCatalogIdentity};
11
12pub mod claims;
13pub mod foreign_keys;
14pub mod keys;
15pub mod legacy;
16
17pub fn materialize_key_identity(
19 key: &mut crate::ast::TableKeyConstraint,
20 allocate: &mut CatalogIdentityAllocator<'_>,
21) -> ConstraintMetadataResult<bool> {
22 if let Some(identity) = key.catalog_identity {
23 if identity.is_valid() {
24 return Ok(false);
25 }
26 return Err(ConstraintMetadataError::Invalid(
27 "invalid key constraint catalog identity".into(),
28 ));
29 }
30 let index_object_id = allocate.allocate_object_id("index")?;
32 key.index_identity = Some(ConstraintCatalogIdentity {
33 object_id: index_object_id,
34 oid: allocate.allocate_catalog_oid(super::CatalogOidClass::Relation, &index_object_id)?,
35 });
36 let object_id = allocate.allocate_object_id("key constraint")?;
37 key.catalog_identity = Some(ConstraintCatalogIdentity {
38 object_id,
39 oid: allocate.allocate_catalog_oid(super::CatalogOidClass::Constraint, &object_id)?,
40 });
41 if !key
42 .catalog_identity
43 .is_some_and(ConstraintCatalogIdentity::is_valid)
44 {
45 return Err(ConstraintMetadataError::Invalid(
46 "invalid key constraint catalog identity".into(),
47 ));
48 }
49 Ok(true)
50}
51
52pub(super) fn materialize_check_oid(
53 object_id: Option<[u8; 16]>,
54 oid: &mut Option<i64>,
55 allocate: &mut CatalogIdentityAllocator<'_>,
56) -> ConstraintMetadataResult<bool> {
57 let object_id = object_id.ok_or_else(|| {
58 ConstraintMetadataError::Invalid("CHECK has no catalog incarnation".into())
59 })?;
60 let changed = oid.is_none();
61 let value = match *oid {
62 Some(value) => value,
63 None => allocate.allocate_catalog_oid(super::CatalogOidClass::Constraint, &object_id)?,
64 };
65 if !(ConstraintCatalogIdentity {
66 object_id,
67 oid: value,
68 })
69 .is_valid()
70 {
71 return Err(ConstraintMetadataError::Invalid(
72 "invalid CHECK catalog identity".into(),
73 ));
74 }
75 *oid = Some(value);
76 Ok(changed)
77}
78
79pub fn materialize_default_oid(
81 column: &mut ColumnDef,
82 allocate: &mut CatalogIdentityAllocator<'_>,
83) -> ConstraintMetadataResult<bool> {
84 let has_expression = column.default.is_some()
85 || column.generated.is_some()
86 || column
87 .auto_increment
88 .as_ref()
89 .is_some_and(crate::ast::AutoIncrement::is_legacy);
90 if !has_expression {
91 return Ok(column.default_catalog_oid.take().is_some());
92 }
93 if column.default_catalog_oid.is_some() {
94 return Ok(false);
95 }
96 let object_id = column.object_id.ok_or_else(|| {
97 ConstraintMetadataError::Invalid("column default has no column incarnation".into())
98 })?;
99 column.default_catalog_oid =
100 Some(allocate.allocate_catalog_oid(super::CatalogOidClass::AttributeDefault, &object_id)?);
101 Ok(true)
102}
103
104pub fn materialize_not_null_identity(
106 column: &mut ColumnDef,
107 allocate: &mut CatalogIdentityAllocator<'_>,
108) -> ConstraintMetadataResult<bool> {
109 if let Some(identity) = column.not_null_identity {
110 validate(identity)?;
111 return Ok(false);
112 }
113 let object_id = allocate.allocate_object_id("NOT NULL constraint")?;
114 let identity = ConstraintCatalogIdentity {
115 object_id,
116 oid: allocate.allocate_catalog_oid(super::CatalogOidClass::Constraint, &object_id)?,
117 };
118 validate(identity)?;
119 column.not_null_identity = Some(identity);
120 Ok(true)
121}
122
123fn validate(identity: ConstraintCatalogIdentity) -> ConstraintMetadataResult<()> {
124 if identity.is_valid() {
125 Ok(())
126 } else {
127 Err(ConstraintMetadataError::Invalid(
128 "invalid NOT NULL constraint catalog identity".into(),
129 ))
130 }
131}
132
133pub fn validate_not_null_identities(columns: &[ColumnDef]) -> ConstraintMetadataResult<()> {
134 let mut identities = std::collections::BTreeSet::new();
135 let mut oids = std::collections::BTreeSet::new();
136 for column in columns {
137 match (column.not_null, column.not_null_identity) {
138 (true, Some(identity)) => {
139 validate(identity)?;
140 if !identities.insert(identity.object_id) || !oids.insert(identity.oid) {
141 return Err(ConstraintMetadataError::Invalid(
142 "duplicate NOT NULL constraint catalog identity".into(),
143 ));
144 }
145 }
146 (true, None) => {
147 return Err(ConstraintMetadataError::Invalid(
148 "NOT NULL constraint requires an initial catalog identity migration".into(),
149 ))
150 }
151 (false, Some(_)) => {
152 return Err(ConstraintMetadataError::Invalid(
153 "nullable column retains a NOT NULL constraint identity".into(),
154 ))
155 }
156 (false, None) => {}
157 }
158 }
159 Ok(())
160}
161
162pub fn migrate_constraint_metadata(
164 relation: &uqa_core::RelationIdentity,
165 columns: &mut [ColumnDef],
166 constraints: &mut crate::ast::TableConstraintSet,
167 allocate: &mut CatalogIdentityAllocator<'_>,
168) -> ConstraintMetadataResult<bool> {
169 let missing: Vec<_> = columns
170 .iter()
171 .map(|column| column.not_null && column.not_null_identity.is_none())
172 .collect();
173 let changed = super::materialize_constraint_metadata(relation, columns, constraints, allocate)?;
174 for (column, missing) in columns.iter_mut().zip(missing) {
175 if missing {
176 let name = column.not_null_name.as_deref().ok_or_else(|| {
177 ConstraintMetadataError::Invalid("migrated NOT NULL constraint has no name".into())
178 })?;
179 let identity = column.not_null_identity.as_mut().ok_or_else(|| {
180 ConstraintMetadataError::Invalid(
181 "migrated NOT NULL constraint has no identity".into(),
182 )
183 })?;
184 identity.oid = crate::catalog::oids::stable_oid(
185 "constraint",
186 &format!("{}.{}.{name}", relation.schema, relation.name),
187 );
188 }
189 }
190 validate_not_null_identities(columns)?;
191 Ok(changed)
192}
193
194#[cfg(test)]
195mod tests;