1use crate::assignment::columns::{AssignmentColumnCatalog, ColumnCatalogError};
9use crate::ast::TableKeyConstraint;
10use crate::SQLError;
11pub trait ForeignKeyDefinitionCatalog {
13 fn resolve_table_reference(&self, name: &str) -> Result<String, SQLError>;
14 fn bound_table_name(&self, name: &str) -> Result<Option<String>, SQLError>;
15 fn referenceable_keys(
16 &self,
17 table: &str,
18 ) -> Result<Vec<TableKeyConstraint>, ColumnCatalogError>;
19 fn ensure_reference_privilege(&self, table: &str, column: &str) -> Result<(), SQLError>;
20}
21pub struct ForeignKeyDefinitionContext<'a> {
22 pub catalog: &'a dyn ForeignKeyDefinitionCatalog,
23 pub columns: &'a dyn AssignmentColumnCatalog,
24}
25fn ddl_storage_error(action: &str, error: ColumnCatalogError) -> SQLError {
26 crate::catalog::errors::storage_error(action, error.as_ref())
27}
28fn constraint_error(sqlstate: &str, message: impl Into<String>) -> SQLError {
29 SQLError::Routine {
30 sqlstate: sqlstate.into(),
31 message: message.into(),
32 }
33}
34pub fn validate_foreign_key_definition(
35 context: &ForeignKeyDefinitionContext<'_>,
36 table: &str,
37 foreign_key: &mut crate::ast::ForeignKey,
38) -> Result<(), SQLError> {
39 validate_foreign_key_definition_with_local_state(context, table, None, None, foreign_key)
40}
41
42pub fn validate_foreign_key_definition_with_local_state(
43 context: &ForeignKeyDefinitionContext<'_>,
44 table: &str,
45 local_columns: Option<&[crate::ast::ColumnDef]>,
46 local_keys: Option<&[crate::ast::TableKeyConstraint]>,
47 foreign_key: &mut crate::ast::ForeignKey,
48) -> Result<(), SQLError> {
49 foreign_key.ref_table = context
50 .catalog
51 .resolve_table_reference(&foreign_key.ref_table)?;
52 validate_bound_foreign_key_definition_with_local_state(
53 context,
54 table,
55 local_columns,
56 local_keys,
57 foreign_key,
58 )
59}
60
61#[expect(
62 clippy::too_many_lines,
63 reason = "preserves DDL dependency and action order"
64)]
65pub fn validate_bound_foreign_key_definition_with_local_state(
66 context: &ForeignKeyDefinitionContext<'_>,
67 table: &str,
68 local_columns: Option<&[crate::ast::ColumnDef]>,
69 local_keys: Option<&[crate::ast::TableKeyConstraint]>,
70 foreign_key: &mut crate::ast::ForeignKey,
71) -> Result<(), SQLError> {
72 let stored_columns;
73 let columns = if let Some(columns) = local_columns {
74 columns
75 } else {
76 stored_columns = context
77 .columns
78 .try_describe_table(table)
79 .map_err(|error| ddl_storage_error("FOREIGN KEY local table", error))?
80 .ok_or_else(|| SQLError::UnknownTable(table.to_string()))?;
81 &stored_columns
82 };
83 for column in &foreign_key.local_columns {
84 if !columns.iter().any(|definition| definition.name == *column) {
85 return Err(SQLError::UnknownColumn(format!("{table}.{column}")));
86 }
87 }
88 let referenced = context
89 .catalog
90 .bound_table_name(&foreign_key.ref_table)?
91 .ok_or_else(|| SQLError::UnknownTable(foreign_key.ref_table.clone()))?;
92 let local = context
93 .catalog
94 .bound_table_name(table)?
95 .ok_or_else(|| SQLError::UnknownTable(table.to_string()))?;
96 let referenced_columns = if referenced == local {
97 local_columns.map(<[crate::ast::ColumnDef]>::to_vec)
98 } else {
99 None
100 }
101 .map(Ok)
102 .unwrap_or_else(|| {
103 context
104 .columns
105 .try_describe_table(&referenced)
106 .map_err(|error| ddl_storage_error("FOREIGN KEY referenced columns", error))?
107 .ok_or_else(|| SQLError::UnknownTable(referenced.clone()))
108 })?;
109 let referenced_keys = if referenced == local {
110 match local_keys {
111 Some(keys) => keys.to_vec(),
112 None => context
113 .catalog
114 .referenceable_keys(&referenced)
115 .map_err(|error| ddl_storage_error("FOREIGN KEY referenced key", error))?,
116 }
117 } else {
118 context
119 .catalog
120 .referenceable_keys(&referenced)
121 .map_err(|error| ddl_storage_error("FOREIGN KEY referenced key", error))?
122 };
123 if foreign_key.ref_columns.is_empty() {
124 let primary_key = referenced_keys
125 .iter()
126 .find(|key| key.kind == crate::ast::TableKeyConstraintKind::PrimaryKey)
127 .ok_or_else(|| {
128 constraint_error(
129 "42704",
130 format!("there is no primary key for referenced table \"{referenced}\""),
131 )
132 })?;
133 foreign_key.ref_columns.clone_from(&primary_key.columns);
134 }
135 if foreign_key.local_columns.len() != foreign_key.ref_columns.len() {
136 return Err(constraint_error(
137 "42830",
138 "number of referencing and referenced columns for foreign key disagree",
139 ));
140 }
141 for (local_column, referenced_column) in foreign_key
142 .local_columns
143 .iter()
144 .zip(&foreign_key.ref_columns)
145 {
146 let local_definition = columns
147 .iter()
148 .find(|definition| definition.name == *local_column)
149 .ok_or_else(|| SQLError::UnknownColumn(format!("{table}.{local_column}")))?;
150 let referenced_definition = referenced_columns
151 .iter()
152 .find(|definition| definition.name == *referenced_column)
153 .ok_or_else(|| SQLError::UnknownColumn(format!("{referenced}.{referenced_column}")))?;
154 if crate::type_resolution::foreign_key_operand_type(
155 &local_definition.ty,
156 &referenced_definition.ty,
157 )
158 .is_err()
159 {
160 return Err(constraint_error(
161 "42804",
162 format!(
163 "foreign key constraint cannot be implemented: key columns \"{local_column}\" and \"{referenced_column}\" are of incompatible types: {} and {}",
164 local_definition.ty.sql_name(),
165 referenced_definition.ty.sql_name()
166 ),
167 ));
168 }
169 }
170 if foreign_key.period {
171 super::constraints::validate_foreign_key_definition(
172 table,
173 columns,
174 &referenced,
175 &referenced_columns,
176 &referenced_keys,
177 foreign_key,
178 )?;
179 } else {
180 let referenced_column_set = foreign_key
181 .ref_columns
182 .iter()
183 .collect::<std::collections::BTreeSet<_>>();
184 let has_unique_key = referenced_column_set.len() == foreign_key.ref_columns.len()
185 && referenced_keys.iter().any(|key| {
186 key.columns.len() == foreign_key.ref_columns.len()
187 && key
188 .columns
189 .iter()
190 .collect::<std::collections::BTreeSet<_>>()
191 == referenced_column_set
192 });
193 if !has_unique_key {
194 return Err(constraint_error(
195 "42830",
196 format!(
197 "there is no unique constraint matching given keys for referenced table \"{referenced}\""
198 ),
199 ));
200 }
201 }
202 for column in &foreign_key.ref_columns {
203 context
204 .catalog
205 .ensure_reference_privilege(&referenced, column)?;
206 }
207 foreign_key.referenced_key = referenced_keys
208 .iter()
209 .find(|key| {
210 key.columns.len() == foreign_key.ref_columns.len()
211 && foreign_key
212 .ref_columns
213 .iter()
214 .all(|column| key.columns.contains(column))
215 && (!foreign_key.period || key.without_overlaps)
216 })
217 .and_then(|key| key.name.clone());
218 foreign_key.ref_table = referenced;
219 Ok(())
220}
221
222pub fn column_foreign_key(
223 column: &crate::ast::ColumnDef,
224 reference: &crate::ast::ForeignKeyRef,
225) -> crate::ast::ForeignKey {
226 crate::ast::ForeignKey {
227 referenced_key: reference.referenced_key.clone(),
228 referenced_index: reference.referenced_index,
229 name: reference.name.clone(),
230 object_id: reference.object_id,
231 catalog_identity: reference.catalog_identity,
232 local_columns: vec![column.name.clone()],
233 ref_table: reference.table.clone(),
234 ref_columns: reference.column.iter().cloned().collect(),
235 on_update: reference.on_update,
236 on_delete: reference.on_delete,
237 on_delete_set_columns: Vec::new(),
238 match_type: reference.match_type,
239 enforced: reference.enforced,
240 validated: reference.validated,
241 deferrable: reference.deferrable,
242 initially_deferred: reference.initially_deferred,
243 period: reference.period,
244 }
245}
246
247pub fn resolve_foreign_key_parent(
248 context: &ForeignKeyDefinitionContext<'_>,
249 reference: &str,
250) -> Result<(String, Vec<crate::ast::ColumnDef>, Vec<TableKeyConstraint>), SQLError> {
251 let canonical = context
252 .catalog
253 .bound_table_name(reference)?
254 .ok_or_else(|| SQLError::UnknownTable(reference.to_string()))?;
255 let columns = context
256 .columns
257 .try_describe_table(&canonical)
258 .map_err(|error| SQLError::Internal(format!("describe FOREIGN KEY target: {error}")))?
259 .ok_or_else(|| SQLError::UnknownTable(canonical.clone()))?;
260 let keys = context
261 .catalog
262 .referenceable_keys(&canonical)
263 .map_err(|error| SQLError::Internal(format!("read FOREIGN KEY target keys: {error}")))?;
264 Ok((canonical, columns, keys))
265}