uqa_sql/schema/constraints/
checks.rs1use crate::{
10 ast::{ColumnDef, Expr},
11 plpgsql::{bind_expr, ResolvedVariable, VariableResolver},
12 schema::{ColumnIdentity, SchemaBindingContext},
13 ColumnType, RowSchema, SQLError,
14};
15use uqa_core::Value;
16
17pub fn check_row_schema(
19 qualifier: &str,
20 columns: &[ColumnDef],
21 row_type: &ColumnType,
22) -> Result<RowSchema, SQLError> {
23 let schema = RowSchema::with_column_definitions(Some(qualifier), columns)
24 .with_relation_row_type(qualifier, row_type.clone())?;
25 Ok(RowSchema::with_typed_virtual_identities(
26 &schema,
27 &[
28 (
29 ColumnIdentity::unqualified("tableoid"),
30 Some(ColumnType::Oid),
31 ),
32 (
33 ColumnIdentity::qualified(qualifier, "tableoid"),
34 Some(ColumnType::Oid),
35 ),
36 ],
37 ))
38}
39
40pub fn validate_check_expression(
41 context: &SchemaBindingContext<'_, '_>,
42 table: &str,
43 qualifier: &str,
44 columns: &[ColumnDef],
45 expression: &mut Expr,
46) -> Result<(), SQLError> {
47 let row_type = crate::FunctionTypeResolver::resolve_type_name(context.catalog, table)?
48 .ok_or_else(|| SQLError::UnknownTable(table.into()))?;
49 validate_check_expression_with_type(context, table, qualifier, columns, &row_type, expression)
50}
51
52pub fn bind_stored_check_expression(
54 context: &SchemaBindingContext<'_, '_>,
55 table: &str,
56 qualifier: &str,
57 columns: &[ColumnDef],
58 expression: &mut Expr,
59) -> Result<bool, SQLError> {
60 let row_type = crate::FunctionTypeResolver::resolve_type_name(context.catalog, table)?
61 .ok_or_else(|| SQLError::UnknownTable(table.into()))?;
62 let relation = crate::RelationIdentity::from_legacy_name(table).map_err(SQLError::Internal)?;
63 let schema = check_row_schema(&relation.name, columns, &row_type)?;
64 let typed = bind_expr(
65 expression,
66 &mut RelationReferences {
67 table,
68 qualifier,
69 local: &relation.name,
70 columns,
71 },
72 )?;
73 crate::schema::defaults::bind_stored_schema_expression_with_schema(
74 context, expression, typed, &schema,
75 )
76}
77
78pub fn validate_check_expression_with_type(
80 context: &SchemaBindingContext<'_, '_>,
81 table: &str,
82 qualifier: &str,
83 columns: &[ColumnDef],
84 row_type: &ColumnType,
85 expression: &mut Expr,
86) -> Result<(), SQLError> {
87 let relation = crate::RelationIdentity::from_legacy_name(table).map_err(SQLError::Internal)?;
88 let schema = check_row_schema(&relation.name, columns, row_type)?;
89 *expression = bind_expr(
90 expression,
91 &mut RelationReferences {
92 table,
93 qualifier,
94 local: &relation.name,
95 columns,
96 },
97 )?;
98 bind_whole_row_fields(expression, &schema)?;
99 let original = crate::plan::ExpressionPlan::lower(expression.clone());
100 let mut plan = original.clone();
101 let ty =
102 crate::binding::analyze_table_check(context.catalog, &mut plan, &schema, context.binding)?;
103 let sites = crate::binding::syntax_sites::expression_syntax_sites(&original, &plan)?;
104 crate::catalog::stored_ast::bind_stored_expression_sites(expression, &sites)?;
105 require_boolean(context, expression, ty.as_ref())?;
106 crate::schema::defaults::bind_stored_schema_expression_with_schema(
107 context,
108 expression,
109 expression.clone(),
110 &schema,
111 )?;
112 crate::schema::generated::bind_schema_column_references(expression, &relation.name);
113 crate::schema::check_inheritance::remove_identity_casts(expression, columns)
114}
115
116fn bind_whole_row_fields(expression: &mut Expr, schema: &RowSchema) -> Result<(), SQLError> {
118 crate::catalog::stored_ast::visit_stored_expression(expression, &mut |node| {
119 let Expr::Func {
120 binding: Some(binding),
121 args,
122 ..
123 } = node
124 else {
125 return Ok(());
126 };
127 if binding.dispatch != Some(crate::ast::FunctionDispatch::FieldSelect) {
128 return Ok(());
129 }
130 if let [Expr::QualifiedStar(qualifier), Expr::Literal(Value::Str(column))] = args.as_slice()
131 {
132 if schema.has_qualifier(qualifier) {
133 *node = Expr::QualifiedColumn {
134 qualifier: qualifier.clone(),
135 column: column.clone(),
136 };
137 }
138 }
139 Ok(())
140 })
141}
142
143fn require_boolean(
144 context: &SchemaBindingContext<'_, '_>,
145 expression: &mut Expr,
146 ty: Option<&ColumnType>,
147) -> Result<(), SQLError> {
148 if let Some(mut ty) = ty {
149 while let ColumnType::Domain { base, .. } = ty {
150 ty = base;
151 }
152 if *ty != ColumnType::Boolean {
153 return Err(SQLError::Routine {
154 sqlstate: "42804".into(),
155 message: format!(
156 "argument of CHECK must be type boolean, not type {}",
157 ty.regtype_name()
158 ),
159 });
160 }
161 } else if matches!(expression, Expr::Literal(Value::Null | Value::Str(_))) {
162 crate::catalog::stored_ast::read_unknown_stored_literal(
163 crate::FunctionTypeResolver::enum_labels(context.catalog),
164 crate::FunctionTypeResolver::catalog_input_functions(context.catalog),
165 expression,
166 &ColumnType::Boolean,
167 false,
168 )?;
169 } else {
170 *expression = Expr::Cast {
171 implicit: true,
172 expr: Box::new(expression.clone()),
173 ty: "boolean".into(),
174 };
175 }
176 Ok(())
177}
178
179struct RelationReferences<'a> {
181 table: &'a str,
182 qualifier: &'a str,
183 local: &'a str,
184 columns: &'a [ColumnDef],
185}
186
187impl RelationReferences<'_> {
188 fn matches(&self, qualifier: &str) -> bool {
189 [self.table, self.qualifier, self.local].contains(&qualifier)
190 }
191}
192
193impl VariableResolver for RelationReferences<'_> {
194 fn resolve_name(&mut self, _: &str) -> Result<Option<ResolvedVariable>, SQLError> {
195 Ok(None)
196 }
197
198 fn resolve_qualified(
199 &mut self,
200 _: &str,
201 _: &str,
202 ) -> Result<Option<ResolvedVariable>, SQLError> {
203 Ok(None)
204 }
205
206 fn resolve_param(&mut self, _: usize) -> Result<Option<ResolvedVariable>, SQLError> {
207 Ok(None)
208 }
209
210 fn rewrite_name(&mut self, name: &str) -> Result<Option<Expr>, SQLError> {
211 Ok(
212 (self.matches(name) && !self.columns.iter().any(|column| column.name == name))
213 .then(|| Expr::QualifiedStar(self.local.into())),
214 )
215 }
216
217 fn rewrite_qualified(
218 &mut self,
219 qualifier: &str,
220 column: &str,
221 ) -> Result<Option<Expr>, SQLError> {
222 Ok(self.matches(qualifier).then(|| Expr::QualifiedColumn {
223 qualifier: self.local.into(),
224 column: column.into(),
225 }))
226 }
227
228 fn rewrite_qualified_whole_row(&mut self, qualifier: &str) -> Result<Option<Expr>, SQLError> {
229 Ok(self
230 .matches(qualifier)
231 .then(|| Expr::QualifiedStar(self.local.into())))
232 }
233}
234
235#[cfg(test)]
236mod tests;