Skip to main content

uqa_sql/expr/composites/
constants.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Apply a descriptor change to typed catalog datums without re-reading input text or repeating domain constraints.
8
9use super::{
10    apply_attribute_change, type_contains_composite, AttributeChange, CompositeTypeCatalog,
11};
12use crate::{
13    ast::{
14        ColumnDef, ColumnType, Expr, PartitionBound, PartitionRangeDatum, PartitionSpec, Statement,
15        TableCheck,
16    },
17    plan::{QueryPlan, UnifiedPlan},
18    type_resolution::FunctionTypeResolver,
19    SQLError, ScalarExpr,
20};
21use uqa_core::Value;
22
23pub struct CompositeConstantChange<'a> {
24    pub target: u32,
25    pub change: &'a AttributeChange,
26    pub catalog: &'a dyn CompositeTypeCatalog,
27    pub types: &'a dyn FunctionTypeResolver,
28}
29
30impl CompositeConstantChange<'_> {
31    pub fn value(&self, value: &mut Value, ty: &ColumnType) -> Result<bool, SQLError> {
32        if matches!(value, Value::Null) || !type_contains_composite(ty, self.target, self.catalog)?
33        {
34            return Ok(false);
35        }
36        *value = apply_attribute_change(
37            std::mem::replace(value, Value::Null),
38            ty,
39            self.target,
40            self.change,
41            self.catalog,
42        )?;
43        Ok(true)
44    }
45
46    fn typed_value(&self, value: &mut Value, name: &str) -> Result<bool, SQLError> {
47        let Some(ty) = self.types.resolve_type_name(name)? else {
48            return if crate::ast::UserTypeIdentity::parse(name).is_some() {
49                Err(SQLError::Internal(format!(
50                    "stored constant type {name} disappeared"
51                )))
52            } else {
53                Ok(false)
54            };
55        };
56        self.value(value, &ty)
57    }
58
59    fn syntax_node(&self, node: &mut Expr) -> Result<bool, SQLError> {
60        match node {
61            Expr::TypedLiteral { value, ty } => self.typed_value(value, ty),
62            _ => Ok(false),
63        }
64    }
65
66    fn scalar_node(&self, node: &mut ScalarExpr) -> Result<bool, SQLError> {
67        match node {
68            ScalarExpr::TypedLiteral { value, ty, .. } => self.typed_value(value, ty),
69            _ => Ok(false),
70        }
71    }
72
73    pub fn expression(&self, expression: &mut Expr) -> Result<bool, SQLError> {
74        let mut changed = false;
75        crate::catalog::stored_ast::visit_stored_expression(expression, &mut |node| {
76            changed |= self.syntax_node(node)?;
77            Ok(())
78        })?;
79        Ok(changed)
80    }
81
82    pub fn statement(&self, statement: &mut Statement) -> Result<bool, SQLError> {
83        let mut changed = false;
84        crate::catalog::stored_ast::visit_stored_statement_expressions(statement, &mut |node| {
85            changed |= self.syntax_node(node)?;
86            Ok(())
87        })?;
88        Ok(changed)
89    }
90
91    pub fn query(&self, query: &mut QueryPlan) -> Result<bool, SQLError> {
92        self.plan_nodes(|visit| query.rewrite_scalar_expressions(visit))
93    }
94
95    pub fn plan(&self, plan: &mut UnifiedPlan) -> Result<bool, SQLError> {
96        self.plan_nodes(|visit| plan.rewrite_scalar_expressions(visit))
97    }
98
99    pub fn expression_plan(
100        &self,
101        plan: &mut crate::plan::ExpressionPlan,
102    ) -> Result<bool, SQLError> {
103        let mut changed = self
104            .plan_nodes(|visit| crate::plan::rewrite_scalar_expression(&mut plan.scalar, visit))?;
105        for query in &mut plan.subqueries {
106            changed |= self.query(query)?;
107        }
108        Ok(changed)
109    }
110
111    fn plan_nodes(
112        &self,
113        visit: impl FnOnce(&mut dyn FnMut(&mut ScalarExpr)),
114    ) -> Result<bool, SQLError> {
115        let mut changed = false;
116        let mut failure = None;
117        visit(&mut |node| {
118            if failure.is_some() {
119                return;
120            }
121            match self.scalar_node(node) {
122                Ok(value) => changed |= value,
123                Err(error) => failure = Some(error),
124            }
125        });
126        failure.map_or(Ok(changed), Err)
127    }
128
129    pub fn columns(&self, columns: &mut [ColumnDef]) -> Result<bool, SQLError> {
130        let mut changed = false;
131        for column in columns {
132            for expression in column.default.iter_mut().chain(column.check.iter_mut()) {
133                changed |= self.expression(expression)?;
134            }
135            if let Some(generated) = &mut column.generated {
136                changed |= self.expression(&mut generated.expression)?;
137            }
138        }
139        Ok(changed)
140    }
141
142    pub fn checks(
143        &self,
144        checks: &mut [TableCheck],
145        columns: &[ColumnDef],
146    ) -> Result<bool, SQLError> {
147        let mut changed = false;
148        for check in checks {
149            changed |= self.expression(&mut check.expr)?;
150            if let Some(partition) = &mut check.partition_constraint {
151                changed |= self.bound(&mut partition.bound, &partition.spec, columns)?;
152                changed |= self.spec(&mut partition.spec)?;
153            }
154        }
155        Ok(changed)
156    }
157
158    pub fn spec(&self, spec: &mut PartitionSpec) -> Result<bool, SQLError> {
159        let mut changed = false;
160        for key in &mut spec.keys {
161            changed |= self.expression(key)?;
162        }
163        Ok(changed)
164    }
165
166    pub fn bound(
167        &self,
168        bound: &mut PartitionBound,
169        spec: &PartitionSpec,
170        columns: &[ColumnDef],
171    ) -> Result<bool, SQLError> {
172        let mut changed = false;
173        for (position, key) in spec.keys.iter().enumerate() {
174            let ty = crate::semantics::partition::partition_key_type(self.types, key, columns)?;
175            match bound {
176                PartitionBound::List(values) => {
177                    for expression in values {
178                        changed |= self.bound_value(expression, &ty)?;
179                    }
180                }
181                PartitionBound::Range { lower, upper } => {
182                    for point in [lower.get_mut(position), upper.get_mut(position)]
183                        .into_iter()
184                        .flatten()
185                    {
186                        if let PartitionRangeDatum::Value(expression) = point {
187                            changed |= self.bound_value(expression, &ty)?;
188                        }
189                    }
190                }
191                PartitionBound::Default | PartitionBound::Hash { .. } => {}
192            }
193        }
194        Ok(changed)
195    }
196
197    fn bound_value(&self, expression: &mut Expr, ty: &ColumnType) -> Result<bool, SQLError> {
198        match expression {
199            Expr::Literal(value) | Expr::TypedLiteral { value, .. } => self.value(value, ty),
200            _ => Err(SQLError::Internal(
201                "partition bound datum was not evaluated".into(),
202            )),
203        }
204    }
205}
206
207#[cfg(test)]
208mod tests;