uqa_sql/schema/
domains.rs1use super::SchemaBindingContext;
10use crate::assignment::domain::domain_error;
11use crate::plpgsql::{bind_expr, ResolvedVariable, VariableResolver};
12use crate::{
13 ast::{ColumnType, CreateDomain, Expr},
14 catalog::domain::DomainCatalog,
15 RowSchema, SQLError,
16};
17use std::collections::BTreeSet;
18use uqa_core::{RelationIdentity, Value};
19
20pub trait DomainCreationCatalog {
22 fn domain_type_exists(&self, name: &str) -> bool;
23 fn domain_table_exists(&self, name: &str) -> Result<bool, SQLError>;
24}
25
26pub fn bind_domain_creation_target(
27 catalog: &dyn DomainCreationCatalog,
28 definition: &mut CreateDomain,
29) -> Result<RelationIdentity, SQLError> {
30 let identity =
31 RelationIdentity::from_legacy_name(&definition.name).map_err(SQLError::Internal)?;
32 if catalog.domain_type_exists(&definition.name)
33 || catalog.domain_table_exists(&definition.name)?
34 {
35 return Err(domain_error(
36 "42710",
37 format!("type \"{}\" already exists", identity.name),
38 ));
39 }
40 Ok(identity)
41}
42
43pub fn prepare_domain_definition(
44 context: &SchemaBindingContext<'_, '_>,
45 domains: &dyn DomainCatalog,
46 definition: &mut CreateDomain,
47) -> Result<(), SQLError> {
48 let identity =
49 RelationIdentity::from_legacy_name(&definition.name).map_err(SQLError::Internal)?;
50 definition.base =
51 crate::type_resolution::resolve_declared_column_type(context.catalog, &definition.base)?;
52 if definition.default.is_none() {
53 definition.default =
54 crate::catalog::domain::domain_default_expression(domains, &definition.base);
55 }
56 if matches!(
57 definition.base,
58 ColumnType::Void | ColumnType::Record | ColumnType::AnyArray
59 ) {
60 return Err(domain_error(
61 "42809",
62 format!(
63 "\"{}\" is not a valid base type for a domain",
64 definition.base.sql_name()
65 ),
66 ));
67 }
68 if definition.collation.is_some() {
69 return Err(SQLError::Unsupported(
70 "domain collation binding is not implemented".into(),
71 ));
72 }
73 if let Some(default) = &mut definition.default {
74 super::defaults::validate_default_expression(context, default, &definition.base)?;
75 if let Expr::Literal(Value::Str(value)) = default {
76 let mut base = &definition.base;
77 while let ColumnType::Domain { base: parent, .. } = base {
78 base = parent;
79 }
80 crate::expr::cast_value_with_type_resolution(
81 &Value::Str(value.clone()),
82 None,
83 &base.without_type_modifiers().sql_name(),
84 Some(context.catalog),
85 )?;
86 }
87 }
88 let mut names = BTreeSet::new();
89 if let Some(not_null) = &mut definition.not_null {
90 let name = not_null
91 .name
92 .get_or_insert_with(|| format!("{}_not_null", identity.name));
93 names.insert(name.clone());
94 }
95 for check in &mut definition.checks {
96 if let Some(name) = &check.name {
97 if !names.insert(name.clone()) {
98 return Err(domain_error(
99 "42710",
100 format!(
101 "constraint \"{name}\" for domain \"{}\" already exists",
102 identity.name
103 ),
104 ));
105 }
106 } else {
107 let base = format!("{}_check", identity.name);
108 let mut name = base.clone();
109 let mut suffix = 1;
110 while !names.insert(name.clone()) {
111 name = format!("{base}{suffix}");
112 suffix += 1;
113 }
114 check.name = Some(name);
115 }
116 bind_domain_check(context, &definition.base, &mut check.expression)?;
117 }
118 definition
119 .checks
120 .sort_by(|left, right| left.name.cmp(&right.name));
121 Ok(())
122}
123
124struct DomainValueResolver<'a>(&'a ColumnType);
125
126impl VariableResolver for DomainValueResolver<'_> {
127 fn resolve_name(&mut self, name: &str) -> Result<Option<ResolvedVariable>, SQLError> {
128 if name != "value" {
129 return Err(SQLError::UnknownColumn(name.into()));
130 }
131 Ok(Some(ResolvedVariable {
132 value: Value::Null,
133 declared_type: Some(self.0.sql_name()),
134 }))
135 }
136
137 fn resolve_qualified(
138 &mut self,
139 qualifier: &str,
140 _column: &str,
141 ) -> Result<Option<ResolvedVariable>, SQLError> {
142 Err(SQLError::UnknownTable(qualifier.into()))
143 }
144
145 fn resolve_param(&mut self, index: usize) -> Result<Option<ResolvedVariable>, SQLError> {
146 Err(domain_error(
147 "42P02",
148 format!("there is no parameter ${index}"),
149 ))
150 }
151}
152
153fn bind_domain_check(
154 context: &SchemaBindingContext<'_, '_>,
155 base: &ColumnType,
156 expression: &mut Expr,
157) -> Result<(), SQLError> {
158 let typed = bind_expr(expression, &mut DomainValueResolver(base))?;
159 let plan = crate::plan::ExpressionPlan::lower(typed.clone());
160 if !plan.subqueries.is_empty() {
161 return Err(domain_error(
162 "0A000",
163 "cannot use subquery in check constraint",
164 ));
165 }
166 if crate::semantics::windows::expr_has_window(&plan.scalar) {
167 return Err(domain_error(
168 "42P20",
169 "window functions are not allowed in check constraints",
170 ));
171 }
172 if crate::semantics::aggregates::contains_aggregate(context.catalog, &plan.scalar) {
173 return Err(domain_error(
174 "42803",
175 "aggregate functions are not allowed in check constraints",
176 ));
177 }
178 let ty = crate::type_resolution::common_context_expression_type(
179 &plan.scalar,
180 &RowSchema::default(),
181 &[],
182 Some(context.catalog),
183 )?;
184 if let Some(ty) = ty {
185 if crate::expr::coercion_type_name(&ty) != "boolean" {
186 return Err(domain_error(
187 "42804",
188 format!(
189 "argument of CHECK must be type boolean, not type {}",
190 ty.sql_name()
191 ),
192 ));
193 }
194 } else {
195 *expression = Expr::Cast {
196 expr: Box::new(expression.clone()),
197 ty: "boolean".into(),
198 };
199 }
200 super::defaults::bind_stored_schema_expression_routines(context, expression, typed)?;
201 Ok(())
202}
203
204pub mod dependencies;
205pub mod removal;