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 definition.collation_oid = Some(crate::catalog::collations::declarations::select(
47 context.catalog,
48 &definition.base,
49 definition.collation.as_deref(),
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 let pattern = crate::binding::legacy_patterns::restore_expression(default, context.catalog)
116 .map_err(SQLError::Internal)?;
117 if pattern
118 || crate::type_resolution::composite_rows::expression_requires_binding(
119 default,
120 context.catalog,
121 )?
122 {
123 super::defaults::bind_stored_schema_expression(context, default, default.clone())?;
124 changed = true;
125 }
126 }
127 for check in &mut definition.checks {
128 changed |= check.expression.upgrade_legacy_serialized_dispatches();
129 let pattern = crate::binding::legacy_patterns::restore_expression(
130 &mut check.expression,
131 context.catalog,
132 )
133 .map_err(SQLError::Internal)?;
134 if pattern
135 || crate::type_resolution::composite_rows::expression_requires_binding(
136 &check.expression,
137 context.catalog,
138 )?
139 {
140 bind_domain_check(context, &definition.base, &mut check.expression)?;
141 changed = true;
142 }
143 }
144 Ok(changed)
145}
146
147struct DomainValueResolver<'a>(&'a ColumnType);
148
149impl VariableResolver for DomainValueResolver<'_> {
150 fn resolve_name(&mut self, name: &str) -> Result<Option<ResolvedVariable>, SQLError> {
151 if name != "value" {
152 return Err(SQLError::UnknownColumn(name.into()));
153 }
154 Ok(Some(ResolvedVariable {
155 collation_oid: None,
156 value: Value::Null,
157 declared_type: Some(self.0.catalog_name()),
158 }))
159 }
160
161 fn resolve_qualified(
162 &mut self,
163 qualifier: &str,
164 _column: &str,
165 ) -> Result<Option<ResolvedVariable>, SQLError> {
166 Err(SQLError::UnknownTable(qualifier.into()))
167 }
168
169 fn resolve_param(&mut self, index: usize) -> Result<Option<ResolvedVariable>, SQLError> {
170 Err(domain_error(
171 "42P02",
172 format!("there is no parameter ${index}"),
173 ))
174 }
175}
176
177fn bind_domain_check(
178 context: &SchemaBindingContext<'_, '_>,
179 base: &ColumnType,
180 expression: &mut Expr,
181) -> Result<(), SQLError> {
182 let original = crate::plan::ExpressionPlan::lower(expression.clone());
183 let mut plan = original.clone();
184 let input = RowSchema::with_types(vec!["value".into()], vec![Some(base.clone())]);
185 let ty =
186 crate::binding::analyze_domain_check(context.catalog, &mut plan, &input, context.binding)?;
187 let sites = crate::binding::syntax_sites::expression_syntax_sites(&original, &plan)?;
188 crate::catalog::stored_ast::bind_stored_expression_sites(expression, &sites)?;
189 if let Some(mut ty) = ty.as_ref() {
190 while let ColumnType::Domain { base, .. } = ty {
191 ty = base;
192 }
193 if *ty != ColumnType::Boolean {
194 return Err(domain_error(
195 "42804",
196 format!(
197 "argument of CHECK must be type boolean, not type {}",
198 ty.regtype_name()
199 ),
200 ));
201 }
202 } else if matches!(expression, Expr::Literal(Value::Null | Value::Str(_))) {
203 crate::catalog::stored_ast::read_unknown_stored_literal(
204 crate::FunctionTypeResolver::enum_labels(context.catalog),
205 crate::FunctionTypeResolver::catalog_input_functions(context.catalog),
206 expression,
207 &ColumnType::Boolean,
208 false,
209 )?;
210 } else {
211 *expression = Expr::Cast {
212 implicit: true,
213 expr: Box::new(expression.clone()),
214 ty: "boolean".into(),
215 };
216 }
217 let typed = bind_expr(expression, &mut DomainValueResolver(base))?;
219 super::defaults::bind_stored_schema_expression(context, expression, typed)?;
220 Ok(())
221}
222
223pub mod constraints;
224pub mod dependencies;
225pub mod removal;
226
227#[cfg(test)]
228mod tests;