uqa_sql/schema/
constraints.rs1use crate::ast::{ColumnDef, ForeignKey, TableKeyConstraint};
10use crate::{ColumnType, SQLError};
11
12mod checks;
13mod references;
14pub use checks::{
15 bind_stored_check_expression, check_row_schema, validate_check_expression,
16 validate_check_expression_with_type,
17};
18pub use references::{check_references, CheckReference};
19
20pub fn validate_foreign_key_definition(
21 local_table: &str,
22 local_columns: &[ColumnDef],
23 parent_table: &str,
24 parent_columns: &[ColumnDef],
25 parent_keys: &[TableKeyConstraint],
26 foreign_key: &ForeignKey,
27) -> Result<(), SQLError> {
28 if foreign_key.local_columns.is_empty()
29 || foreign_key.local_columns.len() != foreign_key.ref_columns.len()
30 {
31 return Err(invalid_foreign_key(format!(
32 "foreign key on relation \"{local_table}\" has mismatched local and referenced columns"
33 )));
34 }
35
36 let local_types = foreign_key
37 .local_columns
38 .iter()
39 .map(|name| {
40 local_columns
41 .iter()
42 .find(|column| column.name == *name)
43 .map(|column| &column.ty)
44 .ok_or_else(|| SQLError::UnknownColumn(format!("{local_table}.{name}")))
45 })
46 .collect::<Result<Vec<_>, _>>()?;
47 let parent_types = foreign_key
48 .ref_columns
49 .iter()
50 .map(|name| {
51 parent_columns
52 .iter()
53 .find(|column| column.name == *name)
54 .map(|column| &column.ty)
55 .ok_or_else(|| SQLError::UnknownColumn(format!("{parent_table}.{name}")))
56 })
57 .collect::<Result<Vec<_>, _>>()?;
58
59 let target_key = parent_keys.iter().find(|constraint| {
60 constraint.columns == foreign_key.ref_columns && constraint.without_overlaps
61 });
62 if foreign_key.period && target_key.is_none() {
63 return Err(invalid_foreign_key(format!(
64 "there is no primary key or unique constraint declared WITH WITHOUT OVERLAPS matching the referenced columns for table \"{parent_table}\""
65 )));
66 }
67
68 if foreign_key.period {
69 if foreign_key.local_columns.len() < 2 {
70 return Err(invalid_foreign_key(
71 "PERIOD foreign key must contain at least one ordinary column and one period column",
72 ));
73 }
74 let local_period = local_types.last().expect("non-empty foreign key");
75 let parent_period = parent_types.last().expect("non-empty foreign key");
76 if !matches!(
77 local_period,
78 ColumnType::Range(_) | ColumnType::Multirange(_)
79 ) || local_period != parent_period
80 {
81 return Err(SQLError::Routine {
82 sqlstate: "42804".into(),
83 message: format!(
84 "PERIOD columns \"{}\" and \"{}\" have incompatible types {} and {}",
85 foreign_key
86 .local_columns
87 .last()
88 .expect("non-empty foreign key"),
89 foreign_key
90 .ref_columns
91 .last()
92 .expect("non-empty foreign key"),
93 local_period.sql_name(),
94 parent_period.sql_name()
95 ),
96 });
97 }
98 }
99
100 Ok(())
101}
102
103fn invalid_foreign_key(message: impl Into<String>) -> SQLError {
104 SQLError::Routine {
105 sqlstate: "42830".into(),
106 message: message.into(),
107 }
108}