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