uqa_sql/schema/inheritance/
foreign_keys.rs1use std::collections::{BTreeMap, BTreeSet};
10
11use crate::ast::{ColumnDef, ForeignKey, TableConstraintSet};
12use crate::SQLError;
13
14#[cfg(test)]
15mod tests;
16
17fn local_name(table: &str) -> Result<String, SQLError> {
18 uqa_core::RelationIdentity::from_legacy_name(table)
19 .map(|relation| relation.name)
20 .map_err(SQLError::Internal)
21}
22
23pub fn attachable_foreign_key<'a>(
25 partition: &str,
26 parent_key: &ForeignKey,
27 candidates: &'a [ForeignKey],
28) -> Result<Option<&'a ForeignKey>, SQLError> {
29 let mut ordered = candidates.iter().collect::<Vec<_>>();
30 ordered.sort_by(|left, right| left.name.cmp(&right.name));
31 for candidate in ordered {
32 if candidate.ref_table != parent_key.ref_table
33 || candidate.local_columns != parent_key.local_columns
34 || candidate.ref_columns != parent_key.ref_columns
35 || candidate.period != parent_key.period
36 {
37 continue;
38 }
39 if candidate.enforced != parent_key.enforced {
40 return Err(SQLError::Diagnostic {
41 sqlstate: "42P16".into(),
42 message: format!(
43 "constraint \"{}\" enforceability conflicts with constraint \"{}\" on relation \"{}\"",
44 parent_key.name.as_deref().unwrap_or_default(),
45 candidate.name.as_deref().unwrap_or_default(),
46 local_name(partition)?
47 ),
48 detail: None,
49 hint: None,
50 });
51 }
52 if candidate.deferrable == parent_key.deferrable
53 && candidate.initially_deferred == parent_key.initially_deferred
54 && candidate.on_update == parent_key.on_update
55 && candidate.on_delete == parent_key.on_delete
56 && candidate.match_type == parent_key.match_type
57 {
58 return Ok(Some(candidate));
59 }
60 }
61 Ok(None)
62}
63
64pub fn partition_foreign_key_copy(
66 parent_key: &ForeignKey,
67 used: &BTreeSet<String>,
68 schema: &mut BTreeSet<String>,
69) -> Result<ForeignKey, SQLError> {
70 let mut copy = parent_key.clone();
71 copy.catalog_identity = None;
72 copy.referenced_partitions.clear();
74 if let Some(name) = copy.name.as_ref().filter(|name| used.contains(*name)) {
75 copy.name = Some(
76 crate::schema::constraint_metadata::choose_suffixed_constraint_name(name, schema)
77 .map_err(|error| SQLError::Internal(error.to_string()))?,
78 );
79 }
80 Ok(copy)
81}
82
83pub fn declared_foreign_key_families(
85 columns: &[ColumnDef],
86 constraints: &TableConstraintSet,
87) -> BTreeSet<[u8; 16]> {
88 columns
89 .iter()
90 .filter_map(|column| column.references.as_ref()?.object_id)
91 .chain(
92 constraints
93 .foreign_keys
94 .iter()
95 .filter_map(|foreign_key| foreign_key.object_id),
96 )
97 .collect()
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub enum DeclaredForeignKey {
103 Column(usize),
104 Table(usize),
105}
106
107#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
109pub struct ForeignKeyAlterEffects {
110 pub changed: bool,
111 pub became_enforced: bool,
112}
113
114impl DeclaredForeignKey {
115 pub fn by_catalog_identity(
117 columns: &[ColumnDef],
118 constraints: &TableConstraintSet,
119 identity: crate::ast::ConstraintCatalogIdentity,
120 ) -> Option<Self> {
121 columns
122 .iter()
123 .position(|column| {
124 column
125 .references
126 .as_ref()
127 .is_some_and(|reference| reference.catalog_identity == Some(identity))
128 })
129 .map(Self::Column)
130 .or_else(|| {
131 constraints
132 .foreign_keys
133 .iter()
134 .position(|foreign_key| foreign_key.catalog_identity == Some(identity))
135 .map(Self::Table)
136 })
137 }
138
139 pub fn by_family(
141 columns: &[ColumnDef],
142 constraints: &TableConstraintSet,
143 object_id: [u8; 16],
144 ) -> Option<Self> {
145 columns
146 .iter()
147 .position(|column| {
148 column
149 .references
150 .as_ref()
151 .is_some_and(|reference| reference.object_id == Some(object_id))
152 })
153 .map(Self::Column)
154 .or_else(|| {
155 constraints
156 .foreign_keys
157 .iter()
158 .position(|foreign_key| foreign_key.object_id == Some(object_id))
159 .map(Self::Table)
160 })
161 }
162
163 pub fn foreign_key(
165 self,
166 columns: &[ColumnDef],
167 constraints: &TableConstraintSet,
168 ) -> Option<ForeignKey> {
169 match self {
170 Self::Column(index) => {
171 let column = columns.get(index)?;
172 column.references.as_ref().map(|reference| {
173 crate::schema::foreign_keys::column_foreign_key(column, reference)
174 })
175 }
176 Self::Table(index) => constraints.foreign_keys.get(index).cloned(),
177 }
178 }
179
180 pub fn validated(self, columns: &[ColumnDef], constraints: &TableConstraintSet) -> bool {
181 match self {
182 Self::Column(index) => columns[index]
183 .references
184 .as_ref()
185 .is_some_and(|reference| reference.validated),
186 Self::Table(index) => constraints.foreign_keys[index].validated,
187 }
188 }
189
190 pub fn set_validated(
191 self,
192 columns: &mut [ColumnDef],
193 constraints: &mut TableConstraintSet,
194 validated: bool,
195 ) {
196 match self {
197 Self::Column(index) => {
198 if let Some(reference) = columns[index].references.as_mut() {
199 reference.validated = validated;
200 }
201 }
202 Self::Table(index) => constraints.foreign_keys[index].validated = validated,
203 }
204 }
205
206 pub fn alter(
208 self,
209 columns: &mut [ColumnDef],
210 constraints: &mut TableConstraintSet,
211 enforceability: Option<bool>,
212 deferrability: Option<(bool, bool)>,
213 ) -> bool {
214 self.alter_with_effects(columns, constraints, enforceability, deferrability)
215 .became_enforced
216 }
217
218 pub fn alter_with_effects(
220 self,
221 columns: &mut [ColumnDef],
222 constraints: &mut TableConstraintSet,
223 enforceability: Option<bool>,
224 deferrability: Option<(bool, bool)>,
225 ) -> ForeignKeyAlterEffects {
226 let (enforced, validated, deferrable, initially_deferred) = match self {
227 Self::Column(index) => {
228 let Some(reference) = columns[index].references.as_mut() else {
229 return ForeignKeyAlterEffects::default();
230 };
231 (
232 &mut reference.enforced,
233 &mut reference.validated,
234 &mut reference.deferrable,
235 &mut reference.initially_deferred,
236 )
237 }
238 Self::Table(index) => {
239 let foreign_key = &mut constraints.foreign_keys[index];
240 (
241 &mut foreign_key.enforced,
242 &mut foreign_key.validated,
243 &mut foreign_key.deferrable,
244 &mut foreign_key.initially_deferred,
245 )
246 }
247 };
248 let previous = (*enforced, *validated, *deferrable, *initially_deferred);
249 let mut became_enforced = false;
250 match enforceability {
251 Some(false) => {
252 *enforced = false;
253 *validated = false;
254 }
255 Some(true) if !*enforced => {
256 *enforced = true;
257 *validated = false;
258 became_enforced = true;
259 }
260 Some(true) | None => {}
261 }
262 if let Some((deferrable_value, initially_deferred_value)) = deferrability {
263 *deferrable = deferrable_value;
264 *initially_deferred = initially_deferred_value;
265 }
266 ForeignKeyAlterEffects {
267 changed: previous != (*enforced, *validated, *deferrable, *initially_deferred),
268 became_enforced,
269 }
270 }
271
272 pub fn set_family(
273 self,
274 columns: &mut [ColumnDef],
275 constraints: &mut TableConstraintSet,
276 object_id: [u8; 16],
277 ) {
278 match self {
279 Self::Column(index) => {
280 if let Some(reference) = columns[index].references.as_mut() {
281 reference.object_id = Some(object_id);
282 }
283 }
284 Self::Table(index) => constraints.foreign_keys[index].object_id = Some(object_id),
285 }
286 }
287}
288
289pub fn rejoin_foreign_key_families(
291 columns: &mut [ColumnDef],
292 constraints: &mut TableConstraintSet,
293 joined: &BTreeMap<[u8; 16], [u8; 16]>,
294) -> bool {
295 let mut changed = false;
296 for object_id in columns
297 .iter_mut()
298 .filter_map(|column| column.references.as_mut())
299 .map(|reference| &mut reference.object_id)
300 .chain(
301 constraints
302 .foreign_keys
303 .iter_mut()
304 .map(|foreign_key| &mut foreign_key.object_id),
305 )
306 {
307 if let Some(target) = object_id.and_then(|current| joined.get(¤t)) {
308 *object_id = Some(*target);
309 changed = true;
310 }
311 }
312 changed
313}