uqa_sql/schema/constraint_changes/
foreign_key_target.rs1use super::{find_constraint, ConstraintLocation};
10use crate::{
11 ast::{ColumnDef, TableConstraintSet},
12 SQLError,
13};
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub struct ForeignKeyTarget<'a> {
17 pub location: ConstraintLocation,
18 pub name: &'a str,
19 pub object_id: [u8; 16],
20 pub referenced_table: &'a str,
21}
22
23impl<'a> ForeignKeyTarget<'a> {
24 pub fn by_name(
25 columns: &'a [ColumnDef],
26 constraints: &'a TableConstraintSet,
27 name: &str,
28 ) -> Result<Option<Self>, SQLError> {
29 find_constraint(columns, constraints, name).map_or(Ok(None), |location| {
30 Self::at(columns, constraints, location)
31 })
32 }
33
34 pub fn by_id(
35 columns: &'a [ColumnDef],
36 constraints: &'a TableConstraintSet,
37 object_id: [u8; 16],
38 ) -> Result<Option<Self>, SQLError> {
39 let location = columns
40 .iter()
41 .position(|column| {
42 column
43 .references
44 .as_ref()
45 .is_some_and(|reference| reference.object_id == Some(object_id))
46 })
47 .map(ConstraintLocation::ColumnForeignKey)
48 .or_else(|| {
49 constraints
50 .foreign_keys
51 .iter()
52 .position(|reference| reference.object_id == Some(object_id))
53 .map(ConstraintLocation::TableForeignKey)
54 });
55 location.map_or(Ok(None), |location| {
56 Self::at(columns, constraints, location)
57 })
58 }
59
60 fn at(
61 columns: &'a [ColumnDef],
62 constraints: &'a TableConstraintSet,
63 location: ConstraintLocation,
64 ) -> Result<Option<Self>, SQLError> {
65 let (name, object_id, referenced_table) = match location {
66 ConstraintLocation::ColumnForeignKey(index) => {
67 let reference = columns[index]
68 .references
69 .as_ref()
70 .ok_or_else(|| SQLError::Internal("column FOREIGN KEY disappeared".into()))?;
71 (
72 reference.name.as_deref(),
73 reference.object_id,
74 reference.table.as_str(),
75 )
76 }
77 ConstraintLocation::TableForeignKey(index) => {
78 let reference = &constraints.foreign_keys[index];
79 (
80 reference.name.as_deref(),
81 reference.object_id,
82 reference.ref_table.as_str(),
83 )
84 }
85 _ => return Ok(None),
86 };
87 Ok(Some(Self {
88 location,
89 name: name
90 .ok_or_else(|| SQLError::Internal("FOREIGN KEY has no durable name".into()))?,
91 object_id: object_id
92 .ok_or_else(|| SQLError::Internal("FOREIGN KEY has no durable identity".into()))?,
93 referenced_table,
94 }))
95 }
96}
97
98pub fn remove_foreign_key(
100 columns: &mut [ColumnDef],
101 constraints: &mut TableConstraintSet,
102 object_id: [u8; 16],
103) -> Result<bool, SQLError> {
104 let Some(target) = ForeignKeyTarget::by_id(columns, constraints, object_id)? else {
105 return Ok(false);
106 };
107 match target.location {
108 ConstraintLocation::ColumnForeignKey(index) => columns[index].references = None,
109 ConstraintLocation::TableForeignKey(index) => {
110 constraints.foreign_keys.remove(index);
111 }
112 _ => unreachable!("a foreign-key target has a foreign-key location"),
113 }
114 constraints
115 .hierarchy
116 .partition_inherited_foreign_keys
117 .retain(|key| key.object_id != Some(object_id));
118 Ok(true)
119}
120
121#[cfg(test)]
122mod tests;