1use super::SchemaBindingContext;
10use crate::assignment::domain::domain_error;
11use crate::plpgsql::{bind_expr, ResolvedVariable, VariableResolver};
12use crate::{
13 ast::{ColumnType, CreateDomain, DomainCheck, DomainNotNull, Expr},
14 catalog::domain::DomainCatalog,
15 RowSchema, SQLError,
16};
17use std::collections::BTreeSet;
18use uqa_core::Value;
19
20pub fn prepare_domain_definition(
21 context: &SchemaBindingContext<'_, '_>,
22 domains: &dyn DomainCatalog,
23 definition: &mut CreateDomain,
24 schema_names: &BTreeSet<String>,
25) -> Result<(), SQLError> {
26 definition.base =
27 crate::type_resolution::resolve_declared_column_type(context.catalog, &definition.base)?;
28 if definition.default.is_none() {
29 definition.default =
30 crate::catalog::domain::domain_default_expression(domains, &definition.base);
31 }
32 if matches!(
33 definition.base,
34 ColumnType::Void | ColumnType::Record | ColumnType::AnyArray
35 ) {
36 return Err(domain_error(
37 "42804",
38 format!(
39 "\"{}\" is not a valid base type for a domain",
40 definition.base.sql_name()
41 ),
42 ));
43 }
44 context.catalog.require_type_usage(&definition.base)?;
46 if definition.collation.is_some() {
47 return Err(SQLError::Unsupported(
48 "domain collation binding is not implemented".into(),
49 ));
50 }
51 if let Some(default) = &mut definition.default {
52 let domain = definition
54 .name
55 .rsplit('.')
56 .next()
57 .unwrap_or(definition.name.as_str());
58 if !super::defaults::validate_default_expression(
59 context,
60 default,
61 &definition.base,
62 domain,
63 )? {
64 definition.default = None;
65 }
66 }
67 constraints::assign_names(definition, schema_names)?;
68 for check in &mut definition.checks {
69 bind_domain_check(context, &definition.base, &mut check.expression)?;
70 }
71 definition
72 .checks
73 .sort_by(|left, right| left.name.cmp(&right.name));
74 Ok(())
75}
76
77pub fn prepare_added_check(
79 context: &SchemaBindingContext<'_, '_>,
80 definition: &CreateDomain,
81 check: DomainCheck,
82 schema_names: &BTreeSet<String>,
83) -> Result<DomainCheck, SQLError> {
84 let mut named = definition.clone();
85 named.checks.push(check);
86 constraints::assign_names(&mut named, schema_names)?;
87 let mut check = named.checks.pop().expect("new domain CHECK");
88 bind_domain_check(context, &definition.base, &mut check.expression)?;
89 Ok(check)
90}
91
92pub fn prepare_added_not_null(
94 definition: &CreateDomain,
95 constraint: DomainNotNull,
96 schema_names: &BTreeSet<String>,
97) -> Result<DomainNotNull, SQLError> {
98 if let Some(existing) = &definition.not_null {
99 return Ok(existing.clone());
100 }
101 let mut named = definition.clone();
102 named.not_null = Some(constraint);
103 constraints::assign_names(&mut named, schema_names)?;
104 Ok(named.not_null.expect("new domain NOT NULL"))
105}
106
107pub fn restore_composite_constructors(
109 context: &SchemaBindingContext<'_, '_>,
110 definition: &mut CreateDomain,
111) -> Result<bool, SQLError> {
112 let mut changed = false;
113 if let Some(default) = &mut definition.default {
114 changed |= default.upgrade_legacy_serialized_dispatches();
115 if crate::type_resolution::composite_rows::expression_requires_binding(
116 default,
117 context.catalog,
118 )? {
119 changed |=
120 super::defaults::bind_stored_schema_expression(context, default, default.clone())?;
121 }
122 }
123 for check in &mut definition.checks {
124 changed |= check.expression.upgrade_legacy_serialized_dispatches();
125 if crate::type_resolution::composite_rows::expression_requires_binding(
126 &check.expression,
127 context.catalog,
128 )? {
129 bind_domain_check(context, &definition.base, &mut check.expression)?;
130 changed = true;
131 }
132 }
133 Ok(changed)
134}
135
136struct DomainValueResolver<'a>(&'a ColumnType);
137
138impl VariableResolver for DomainValueResolver<'_> {
139 fn resolve_name(&mut self, name: &str) -> Result<Option<ResolvedVariable>, SQLError> {
140 if name != "value" {
141 return Err(SQLError::UnknownColumn(name.into()));
142 }
143 Ok(Some(ResolvedVariable {
144 value: Value::Null,
145 declared_type: Some(self.0.catalog_name()),
146 }))
147 }
148
149 fn resolve_qualified(
150 &mut self,
151 qualifier: &str,
152 _column: &str,
153 ) -> Result<Option<ResolvedVariable>, SQLError> {
154 Err(SQLError::UnknownTable(qualifier.into()))
155 }
156
157 fn resolve_param(&mut self, index: usize) -> Result<Option<ResolvedVariable>, SQLError> {
158 Err(domain_error(
159 "42P02",
160 format!("there is no parameter ${index}"),
161 ))
162 }
163}
164
165fn bind_domain_check(
166 context: &SchemaBindingContext<'_, '_>,
167 base: &ColumnType,
168 expression: &mut Expr,
169) -> Result<(), SQLError> {
170 let original = crate::plan::ExpressionPlan::lower(expression.clone());
171 let mut plan = original.clone();
172 let input = RowSchema::with_types(vec!["value".into()], vec![Some(base.clone())]);
173 let ty =
174 crate::binding::analyze_domain_check(context.catalog, &mut plan, &input, context.binding)?;
175 let sites = crate::binding::syntax_sites::expression_syntax_sites(&original, &plan)?;
176 crate::catalog::stored_ast::bind_stored_expression_sites(expression, &sites)?;
177 if let Some(mut ty) = ty.as_ref() {
178 while let ColumnType::Domain { base, .. } = ty {
179 ty = base;
180 }
181 if *ty != ColumnType::Boolean {
182 return Err(domain_error(
183 "42804",
184 format!(
185 "argument of CHECK must be type boolean, not type {}",
186 ty.regtype_name()
187 ),
188 ));
189 }
190 } else if matches!(expression, Expr::Literal(Value::Null | Value::Str(_))) {
191 crate::catalog::stored_ast::read_unknown_stored_literal(
192 crate::FunctionTypeResolver::enum_labels(context.catalog),
193 crate::FunctionTypeResolver::catalog_input_functions(context.catalog),
194 expression,
195 &ColumnType::Boolean,
196 false,
197 )?;
198 } else {
199 *expression = Expr::Cast {
200 implicit: true,
201 expr: Box::new(expression.clone()),
202 ty: "boolean".into(),
203 };
204 }
205 let typed = bind_expr(expression, &mut DomainValueResolver(base))?;
207 super::defaults::bind_stored_schema_expression(context, expression, typed)?;
208 Ok(())
209}
210
211pub mod constraints;
212pub mod dependencies;
213pub mod removal;
214
215#[cfg(test)]
216mod tests;