1use crate::{
10 ast::{ColumnDef, Expr, FunctionBinding, FunctionDispatch, PatternOperator, TableCheck},
11 plan::QueryPlan,
12 FunctionTypeResolver, ScalarExpr,
13};
14
15type PatternCall<'a, T> = (
16 &'a mut String,
17 &'a mut Option<FunctionBinding>,
18 &'a mut Vec<T>,
19);
20
21trait PatternNode: Sized {
22 fn call(&mut self) -> Option<PatternCall<'_, Self>>;
23 fn negated(&mut self) -> Option<&mut Self>;
24 fn native(dispatch: FunctionDispatch, arguments: Vec<Self>) -> Self;
25 fn null() -> Self;
26}
27
28macro_rules! pattern_node {
29 ($node:ty) => {
30 impl PatternNode for $node {
31 fn call(&mut self) -> Option<PatternCall<'_, Self>> {
32 match self {
33 Self::Func {
34 name,
35 binding,
36 args,
37 distinct: false,
38 order_by,
39 filter: None,
40 ..
41 } if order_by.is_empty() => Some((name, binding, args)),
42 _ => None,
43 }
44 }
45
46 fn negated(&mut self) -> Option<&mut Self> {
47 match self {
48 Self::Not(inner) => Some(inner),
49 _ => None,
50 }
51 }
52
53 fn native(dispatch: FunctionDispatch, args: Vec<Self>) -> Self {
54 let binding = FunctionBinding::dispatched(dispatch);
55 Self::Func {
56 name: binding.name.clone(),
57 binding: Some(binding),
58 args,
59 order_syntax: crate::ast::FunctionCallSyntax::Ordinary,
60 distinct: false,
61 order_by: Vec::new(),
62 filter: None,
63 }
64 }
65
66 fn null() -> Self {
67 Self::Literal(uqa_core::Value::Null)
68 }
69 }
70 };
71}
72
73pattern_node!(Expr);
74pattern_node!(ScalarExpr);
75
76fn restore_call<T: PatternNode>(
77 expression: &mut T,
78 negated: bool,
79 resolver: &dyn FunctionTypeResolver,
80) -> bool {
81 let Some((name, binding, arguments)) = expression.call() else {
82 return false;
83 };
84 if binding.as_ref().is_some_and(|binding| {
85 !binding.builtin
86 || binding.dispatch.is_some()
87 || binding.object_id.is_some()
88 || binding.resolution_error.is_some()
89 || !binding.argument_types.is_empty()
90 }) || binding.is_none() && resolver.has_untyped_function(name)
91 {
92 return false;
93 }
94 let (operator, similar) = match name.as_str() {
95 "like" if (2..=3).contains(&arguments.len()) => (
96 if negated {
97 PatternOperator::NotLike
98 } else {
99 PatternOperator::Like
100 },
101 false,
102 ),
103 "ilike" if (2..=3).contains(&arguments.len()) => (
104 if negated {
105 PatternOperator::NotILike
106 } else {
107 PatternOperator::ILike
108 },
109 false,
110 ),
111 "similar_to" if (2..=3).contains(&arguments.len()) => (
112 if negated {
113 PatternOperator::NotRegex
114 } else {
115 PatternOperator::Regex
116 },
117 true,
118 ),
119 "regex_match_op" if arguments.len() == 2 => (
120 if negated {
121 PatternOperator::NotRegex
122 } else {
123 PatternOperator::Regex
124 },
125 false,
126 ),
127 "regex_imatch_op" if arguments.len() == 2 => (
128 if negated {
129 PatternOperator::NotRegexInsensitive
130 } else {
131 PatternOperator::RegexInsensitive
132 },
133 false,
134 ),
135 _ => return false,
136 };
137 if similar || arguments.len() == 3 {
138 let helper_arguments = arguments.split_off(1);
139 arguments.push(T::native(
140 FunctionDispatch::PatternEscape { similar },
141 helper_arguments,
142 ));
143 }
144 let restored = FunctionBinding::dispatched(FunctionDispatch::Pattern {
145 operator,
146 oid: None,
147 });
148 name.clone_from(&restored.name);
149 *binding = Some(restored);
150 true
151}
152
153fn restore_node<T: PatternNode>(
154 expression: &mut T,
155 negative_only: bool,
156 resolver: &dyn FunctionTypeResolver,
157) -> bool {
158 if let Some(inner) = expression.negated() {
159 if restore_call(inner, true, resolver) {
160 *expression = std::mem::replace(inner, T::null());
161 return true;
162 }
163 }
164 !negative_only && restore_call(expression, false, resolver)
165}
166
167pub fn restore_query(plan: &mut QueryPlan, resolver: &dyn FunctionTypeResolver) -> bool {
169 let mut changed = false;
170 for negative_only in [true, false] {
171 plan.rewrite_scalar_expressions(&mut |expression| {
172 changed |= restore_node(expression, negative_only, resolver);
173 });
174 }
175 changed
176}
177
178pub fn restore_expression_plan(
180 plan: &mut crate::plan::ExpressionPlan,
181 resolver: &dyn FunctionTypeResolver,
182) -> bool {
183 let mut changed = false;
184 for negative_only in [true, false] {
185 crate::plan::rewrite_scalar_expression(&mut plan.scalar, &mut |expression| {
186 changed |= restore_node(expression, negative_only, resolver);
187 });
188 }
189 for subquery in &mut plan.subqueries {
190 changed |= restore_query(subquery, resolver);
191 }
192 changed
193}
194
195pub fn restore_statement(
197 statement: &mut crate::ast::Statement,
198 resolver: &dyn FunctionTypeResolver,
199) -> Result<bool, crate::SQLError> {
200 let mut changed = false;
201 crate::catalog::stored_ast::visit_stored_statement_expressions(statement, &mut |expression| {
202 changed |= restore_node(expression, false, resolver);
203 Ok(())
204 })?;
205 Ok(changed)
206}
207
208pub fn restore_expression(
210 expression: &mut Expr,
211 resolver: &dyn FunctionTypeResolver,
212) -> Result<bool, String> {
213 let mut changed = false;
214 crate::catalog::stored_ast::visit_stored_expression(expression, &mut |expression| {
215 changed |= restore_node(expression, false, resolver);
216 Ok(())
217 })
218 .map_err(|error| error.to_string())?;
219 Ok(changed)
220}
221
222#[derive(Default)]
223pub struct SchemaPatternMigration {
224 pub changed: bool,
225 pub generated: bool,
226}
227
228pub fn restore_schema(
230 columns: &mut [ColumnDef],
231 checks: &mut [TableCheck],
232 resolver: &dyn FunctionTypeResolver,
233) -> Result<SchemaPatternMigration, String> {
234 let mut migration = SchemaPatternMigration::default();
235 for column in columns {
236 for expression in [&mut column.default, &mut column.check]
237 .into_iter()
238 .flatten()
239 {
240 migration.changed |= restore_expression(expression, resolver)?;
241 }
242 if let Some(generated) = &mut column.generated {
243 migration.generated |= restore_expression(&mut generated.expression, resolver)?;
244 }
245 }
246 for check in checks {
247 migration.changed |= restore_expression(&mut check.expr, resolver)?;
248 }
249 migration.changed |= migration.generated;
250 Ok(migration)
251}
252
253pub(crate) fn requires_binding(binding: &FunctionBinding) -> bool {
254 matches!(
255 binding.dispatch,
256 Some(FunctionDispatch::Pattern { oid: None, .. })
257 ) || matches!(
258 binding.dispatch,
259 Some(FunctionDispatch::PatternEscape { .. })
260 ) && binding.argument_types.is_empty()
261}
262
263#[cfg(test)]
264mod tests;