Skip to main content

uqa_sql/catalog/stored_ast/
routines.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Routine-reference operations over stored catalog ASTs.
8
9use super::{BTreeSet, Expr, RelationIdentity, SQLError, Statement, StoredAstVisitor};
10
11pub fn bind_stored_statement_routines(
12    statement: &mut Statement,
13    references: &[crate::binding::stored_routines::BoundRoutineReference],
14) -> Result<bool, SQLError> {
15    let mut ignore_relation = |_: &mut String| -> Result<(), SQLError> { Ok(()) };
16    let mut references = references.iter();
17    let mut changed = false;
18    let mut bind = |name: &mut String,
19                    binding: Option<&mut Option<crate::ast::FunctionBinding>>| {
20        let reference = references.next().ok_or_else(|| {
21            SQLError::Internal(format!(
22                "stored catalog routine binding has no entry for call `{name}`"
23            ))
24        })?;
25        changed |= apply_routine_reference(name, binding, reference)?;
26        Ok(())
27    };
28    StoredAstVisitor {
29        source: None,
30        merge: None,
31        expression: None,
32        ty: None,
33        relation: &mut ignore_relation,
34        routine: &mut bind,
35    }
36    .bind_statement(statement)?;
37    if let Some(reference) = references.next() {
38        return Err(SQLError::Internal(format!(
39            "stored catalog routine binding entry `{}` has no matching call",
40            reference.name
41        )));
42    }
43    Ok(changed)
44}
45
46pub fn rewrite_statement_routine_identity(
47    statement: &mut Statement,
48    target: &crate::ast::FunctionBinding,
49    new_name: &str,
50) -> Result<bool, SQLError> {
51    let mut changed = false;
52    let mut ignore_relation = |_: &mut String| -> Result<(), SQLError> { Ok(()) };
53    let mut rewrite = |name: &mut String,
54                       binding: Option<&mut Option<crate::ast::FunctionBinding>>|
55     -> Result<(), SQLError> {
56        let Some(binding) = binding.and_then(Option::as_mut) else {
57            return Ok(());
58        };
59        if crate::routines::function_binding_matches(binding, target) {
60            *name = new_name.to_string();
61            binding.name = new_name.to_string();
62            changed = true;
63        }
64        Ok(())
65    };
66    StoredAstVisitor {
67        source: None,
68        merge: None,
69        expression: None,
70        ty: None,
71        relation: &mut ignore_relation,
72        routine: &mut rewrite,
73    }
74    .bind_statement(statement)?;
75    Ok(changed)
76}
77
78pub fn rewrite_expression_routine_identity(
79    expression: &mut Expr,
80    target: &crate::ast::FunctionBinding,
81    new_name: &str,
82) -> Result<bool, SQLError> {
83    let mut changed = false;
84    let mut ignore_relation = |_: &mut String| -> Result<(), SQLError> { Ok(()) };
85    let mut rewrite = |name: &mut String,
86                       binding: Option<&mut Option<crate::ast::FunctionBinding>>|
87     -> Result<(), SQLError> {
88        let Some(binding) = binding.and_then(Option::as_mut) else {
89            return Ok(());
90        };
91        if crate::routines::function_binding_matches(binding, target) {
92            *name = new_name.to_string();
93            binding.name = new_name.to_string();
94            changed = true;
95        }
96        Ok(())
97    };
98    StoredAstVisitor {
99        source: None,
100        merge: None,
101        expression: None,
102        ty: None,
103        relation: &mut ignore_relation,
104        routine: &mut rewrite,
105    }
106    .bind_expr(expression, &BTreeSet::new())?;
107    Ok(changed)
108}
109
110pub fn bind_stored_expression_routines(
111    expression: &mut Expr,
112    references: &[crate::binding::stored_routines::BoundRoutineReference],
113) -> Result<bool, SQLError> {
114    let mut ignore_relation = |_: &mut String| -> Result<(), SQLError> { Ok(()) };
115    let mut references = references.iter();
116    let mut changed = false;
117    let mut bind = |name: &mut String,
118                    binding: Option<&mut Option<crate::ast::FunctionBinding>>| {
119        let reference = references.next().ok_or_else(|| {
120            SQLError::Internal(format!(
121                "stored catalog routine binding has no entry for call `{name}`"
122            ))
123        })?;
124        changed |= apply_routine_reference(name, binding, reference)?;
125        Ok(())
126    };
127    StoredAstVisitor {
128        source: None,
129        merge: None,
130        expression: None,
131        ty: None,
132        relation: &mut ignore_relation,
133        routine: &mut bind,
134    }
135    .bind_expr(expression, &BTreeSet::new())?;
136    if let Some(reference) = references.next() {
137        return Err(SQLError::Internal(format!(
138            "stored catalog routine binding entry `{}` has no matching call",
139            reference.name
140        )));
141    }
142    Ok(changed)
143}
144
145pub fn statement_references_routine_identity(
146    statement: &Statement,
147    target: &crate::ast::FunctionBinding,
148) -> Result<bool, SQLError> {
149    let mut statement = statement.clone();
150    let mut found = false;
151    let mut ignore_relation = |_: &mut String| -> Result<(), SQLError> { Ok(()) };
152    let mut inspect = |_: &mut String,
153                       binding: Option<&mut Option<crate::ast::FunctionBinding>>|
154     -> Result<(), SQLError> {
155        found |= binding
156            .and_then(|binding| binding.as_ref())
157            .is_some_and(|binding| crate::routines::function_binding_matches(binding, target));
158        Ok(())
159    };
160    StoredAstVisitor {
161        source: None,
162        merge: None,
163        expression: None,
164        ty: None,
165        relation: &mut ignore_relation,
166        routine: &mut inspect,
167    }
168    .bind_statement(&mut statement)?;
169    Ok(found)
170}
171
172pub fn expression_references_routine_identity(
173    expression: &Expr,
174    target: &crate::ast::FunctionBinding,
175) -> Result<bool, SQLError> {
176    let mut expression = expression.clone();
177    let mut found = false;
178    let mut ignore_relation = |_: &mut String| -> Result<(), SQLError> { Ok(()) };
179    let mut inspect = |_: &mut String,
180                       binding: Option<&mut Option<crate::ast::FunctionBinding>>|
181     -> Result<(), SQLError> {
182        found |= binding
183            .and_then(|binding| binding.as_ref())
184            .is_some_and(|binding| crate::routines::function_binding_matches(binding, target));
185        Ok(())
186    };
187    StoredAstVisitor {
188        source: None,
189        merge: None,
190        expression: None,
191        ty: None,
192        relation: &mut ignore_relation,
193        routine: &mut inspect,
194    }
195    .bind_expr(&mut expression, &BTreeSet::new())?;
196    Ok(found)
197}
198
199fn apply_routine_reference(
200    name: &mut String,
201    binding: Option<&mut Option<crate::ast::FunctionBinding>>,
202    reference: &crate::binding::stored_routines::BoundRoutineReference,
203) -> Result<bool, SQLError> {
204    let (_, local_name) = RelationIdentity::parse_reference(name).map_err(|error| {
205        SQLError::Internal(format!("decode stored catalog routine `{name}`: {error}"))
206    })?;
207    let (_, reference_local_name) =
208        RelationIdentity::parse_reference(&reference.name).map_err(|error| {
209            SQLError::Internal(format!(
210                "decode bound catalog routine `{}`: {error}",
211                reference.name
212            ))
213        })?;
214    if local_name != reference_local_name {
215        return Err(SQLError::Internal(format!(
216            "stored catalog routine call `{name}` does not match bound call `{}`",
217            reference.name
218        )));
219    }
220    let Some(exact) = &reference.binding else {
221        return Ok(false);
222    };
223    let mut changed = false;
224    if !exact.builtin && name != &exact.name {
225        name.clone_from(&exact.name);
226        changed = true;
227    }
228    if let Some(binding) = binding {
229        if binding.as_ref() != Some(exact) {
230            *binding = Some(exact.clone());
231            changed = true;
232        }
233    }
234    Ok(changed)
235}