Skip to main content

uqa_sql/binding/
stored_routines.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Exact routine binding for catalog-owned statements.
8
9use crate::ast::FunctionBinding;
10use crate::plan::{
11    AccessPathPlan, CommandPlan, ComputePlan, ConflictActionPlan, CtePlan, DeletePlan, InsertPlan,
12    JoinExecutionStrategy, MergePlan, ProjectionPlan, QueryBlockPlan, QueryPlan, RelationalPlan,
13    SourcePlan, UnifiedPlan, UpdatePlan,
14};
15use crate::SQLError;
16use crate::{RowSchema, ScalarExpr, ScalarFrameBound};
17
18use crate::{binding::context::BindingContext, routines::RoutineResolution};
19use uqa_core::Value;
20
21/// Routine metadata and immutable namespace inputs for a stored statement.
22pub struct CatalogRoutineContext<'a, 'q> {
23    pub routines: &'a dyn RoutineResolution,
24    pub binding: &'a BindingContext<'q>,
25}
26
27pub struct BoundStatementRoutines {
28    pub query: Option<QueryPlan>,
29    pub references: Vec<BoundRoutineReference>,
30}
31
32#[derive(Debug, Clone)]
33pub struct BoundRoutineReference {
34    pub name: String,
35    pub binding: Option<FunctionBinding>,
36}
37
38struct CommandRoutineInputs {
39    ctes: Vec<CtePlan>,
40    source: Option<SourcePlan>,
41    expressions: Vec<ScalarExpr>,
42    subqueries: Vec<QueryPlan>,
43    outer: RowSchema,
44}
45
46pub fn bind_catalog_statement_routines(
47    context: &CatalogRoutineContext<'_, '_>,
48    plan: &UnifiedPlan,
49) -> Result<BoundStatementRoutines, SQLError> {
50    let query = match plan {
51        UnifiedPlan::Query(query) => {
52            let mut query = (**query).clone();
53            mark_query_relations_bound(&mut query);
54            crate::binding::bind_query_plan_routines_for_storage(
55                context.routines,
56                &mut query,
57                &[],
58                context.binding,
59                None,
60            )?;
61            Some(query)
62        }
63        UnifiedPlan::Command(command) => bind_command_statement_routines(context, command)?,
64    };
65    let mut references = Vec::new();
66    if let Some(query) = &query {
67        collect_query_routine_references(query, &mut references)?;
68    }
69    Ok(BoundStatementRoutines { query, references })
70}
71
72pub fn mark_catalog_statement_relations_bound(plan: &mut UnifiedPlan) -> Result<(), SQLError> {
73    match plan {
74        UnifiedPlan::Query(query) => mark_query_relations_bound(query),
75        UnifiedPlan::Command(command) => match command.as_mut() {
76            CommandPlan::Insert(plan) => {
77                plan.relations_bound = true;
78                for cte in &mut plan.ctes {
79                    mark_cte_relations_bound(&mut cte.body);
80                }
81                if let Some(source) = &mut plan.source {
82                    mark_query_relations_bound(source);
83                }
84                for subquery in &mut plan.subqueries {
85                    mark_query_relations_bound(subquery);
86                }
87            }
88            CommandPlan::Update(plan) => {
89                plan.relations_bound = true;
90                for cte in &mut plan.ctes {
91                    mark_cte_relations_bound(&mut cte.body);
92                }
93                if let Some(source) = &mut plan.source {
94                    mark_source_relations_bound(source);
95                }
96                for subquery in &mut plan.subqueries {
97                    mark_query_relations_bound(subquery);
98                }
99            }
100            CommandPlan::Delete(plan) => {
101                plan.relations_bound = true;
102                for cte in &mut plan.ctes {
103                    mark_cte_relations_bound(&mut cte.body);
104                }
105                if let Some(source) = &mut plan.source {
106                    mark_source_relations_bound(source);
107                }
108                for subquery in &mut plan.subqueries {
109                    mark_query_relations_bound(subquery);
110                }
111            }
112            CommandPlan::Notify { .. } => {}
113            CommandPlan::Merge(plan) => {
114                for cte in &mut plan.ctes {
115                    mark_cte_relations_bound(&mut cte.body);
116                }
117                mark_source_relations_bound(&mut plan.source);
118                for subquery in &mut plan.subqueries {
119                    mark_query_relations_bound(subquery);
120                }
121            }
122            _ => {
123                return Err(SQLError::Internal(
124                    "catalog-owned statement lowered to an unsupported command".into(),
125                ));
126            }
127        },
128    }
129    Ok(())
130}
131
132fn bind_command_statement_routines(
133    context: &CatalogRoutineContext<'_, '_>,
134    command: &CommandPlan,
135) -> Result<Option<QueryPlan>, SQLError> {
136    let Some(inputs) = command_statement_routine_inputs(context, command)? else {
137        return Ok(None);
138    };
139    let projections = inputs
140        .expressions
141        .into_iter()
142        .filter(|expression| expression_contains_routine(expression, &inputs.subqueries))
143        .filter(|expression| !matches!(expression, ScalarExpr::Default))
144        .map(|expr| ProjectionPlan { expr, alias: None })
145        .chain(std::iter::once(ProjectionPlan {
146            expr: ScalarExpr::Literal(Value::Int(1)),
147            alias: None,
148        }))
149        .collect();
150    let mut query = QueryPlan {
151        relations_bound: true,
152        ctes: inputs.ctes,
153        root: RelationalPlan::QueryBlock(Box::new(QueryBlockPlan {
154            projections,
155            from: inputs.source,
156            r#where: None,
157            compute: ComputePlan::Project,
158            group_by: Vec::new(),
159            grouping_sets: Vec::new(),
160            group_distinct: false,
161            having: None,
162            order_by: Vec::new(),
163            limit: None,
164            with_ties: false,
165            offset: None,
166            distinct: false,
167            distinct_on: Vec::new(),
168            subqueries: inputs.subqueries,
169            access: AccessPathPlan::Row,
170            locking: Vec::new(),
171        })),
172    };
173    mark_query_relations_bound(&mut query);
174    crate::binding::bind_query_plan_routines_for_storage(
175        context.routines,
176        &mut query,
177        &[],
178        context.binding,
179        Some(&inputs.outer),
180    )?;
181    Ok(Some(query))
182}
183
184fn command_statement_routine_inputs(
185    context: &CatalogRoutineContext<'_, '_>,
186    command: &CommandPlan,
187) -> Result<Option<CommandRoutineInputs>, SQLError> {
188    match command {
189        CommandPlan::Insert(plan) => insert_statement_routine_inputs(context, plan).map(Some),
190        CommandPlan::Update(plan) => update_statement_routine_inputs(context, plan).map(Some),
191        CommandPlan::Delete(plan) => delete_statement_routine_inputs(context, plan).map(Some),
192        CommandPlan::Merge(plan) => Ok(Some(merge_statement_routine_inputs(plan))),
193        CommandPlan::Notify { .. } => Ok(None),
194        _ => Err(SQLError::Internal(
195            "catalog-owned statement lowered to an unsupported command".into(),
196        )),
197    }
198}
199
200fn merge_statement_routine_inputs(plan: &MergePlan) -> CommandRoutineInputs {
201    let target = SourcePlan::Table {
202        bound_columns: None,
203        name: plan.target.clone(),
204        qualifier: plan.target_qualifier.clone(),
205        alias: plan.target_alias.clone(),
206        column_aliases: Vec::new(),
207        include_descendants: plan.include_descendants,
208    };
209    let source = SourcePlan::Join {
210        left: Box::new(target),
211        right: plan.source.clone(),
212        kind: crate::ast::JoinKind::Cross,
213        on: None,
214        using: None,
215        natural: false,
216        alias: None,
217        column_aliases: Vec::new(),
218        lateral: false,
219        strategy: JoinExecutionStrategy::default(),
220    };
221    let mut expressions = vec![plan.join_condition.clone()];
222    for clause in &plan.when_clauses {
223        match clause {
224            crate::plan::MergeWhenPlan::UpdateMatched {
225                condition,
226                assignments,
227            }
228            | crate::plan::MergeWhenPlan::UpdateNotMatchedBySource {
229                condition,
230                assignments,
231            } => {
232                expressions.extend(condition.iter().cloned());
233                expressions.extend(
234                    assignments
235                        .iter()
236                        .flat_map(crate::plan::AssignmentPlan::expressions)
237                        .cloned(),
238                );
239            }
240            crate::plan::MergeWhenPlan::InsertNotMatched {
241                condition,
242                columns,
243                values,
244                ..
245            } => {
246                expressions.extend(condition.iter().cloned());
247                expressions.extend(
248                    columns
249                        .iter()
250                        .flat_map(crate::ast::AssignmentTarget::expressions)
251                        .cloned(),
252                );
253                expressions.extend(values.iter().cloned());
254            }
255            crate::plan::MergeWhenPlan::DeleteMatched { condition }
256            | crate::plan::MergeWhenPlan::DeleteNotMatchedBySource { condition }
257            | crate::plan::MergeWhenPlan::NothingMatched { condition }
258            | crate::plan::MergeWhenPlan::NothingNotMatched { condition }
259            | crate::plan::MergeWhenPlan::NothingNotMatchedBySource { condition } => {
260                expressions.extend(condition.iter().cloned());
261            }
262        }
263    }
264    expressions.extend(
265        plan.returning
266            .iter()
267            .map(|projection| projection.expr.clone()),
268    );
269    CommandRoutineInputs {
270        ctes: plan.ctes.clone(),
271        source: Some(source),
272        expressions,
273        subqueries: plan.subqueries.clone(),
274        outer: RowSchema::default(),
275    }
276}
277
278fn insert_statement_routine_inputs(
279    context: &CatalogRoutineContext<'_, '_>,
280    plan: &InsertPlan,
281) -> Result<CommandRoutineInputs, SQLError> {
282    let mut expressions = plan
283        .columns
284        .iter()
285        .flat_map(crate::ast::AssignmentTarget::expressions)
286        .chain(plan.rows.iter().flatten())
287        .cloned()
288        .collect::<Vec<_>>();
289    if let Some(conflict) = &plan.on_conflict {
290        expressions.extend(conflict.expressions.iter().cloned());
291        expressions.extend(conflict.predicate.iter().map(Box::as_ref).cloned());
292        if let ConflictActionPlan::Update {
293            assignments,
294            predicate,
295        } = &conflict.action
296        {
297            expressions.extend(
298                assignments
299                    .iter()
300                    .flat_map(crate::plan::AssignmentPlan::expressions)
301                    .cloned(),
302            );
303            expressions.extend(predicate.iter().map(Box::as_ref).cloned());
304        }
305    }
306    expressions.extend(
307        plan.returning
308            .iter()
309            .map(|projection| projection.expr.clone()),
310    );
311    let source = plan.source.as_ref().map(|source| SourcePlan::Subquery {
312        body: Box::new((**source).clone()),
313        alias: Some("__uqa_catalog_statement_source".into()),
314        column_aliases: Vec::new(),
315    });
316    Ok(CommandRoutineInputs {
317        ctes: plan.ctes.clone(),
318        source,
319        expressions,
320        subqueries: plan.subqueries.clone(),
321        outer: statement_target_outer_schema(
322            context,
323            &plan.table,
324            &plan.target_qualifier,
325            &plan.returning_aliases,
326        )?,
327    })
328}
329
330fn update_statement_routine_inputs(
331    context: &CatalogRoutineContext<'_, '_>,
332    plan: &UpdatePlan,
333) -> Result<CommandRoutineInputs, SQLError> {
334    let mut expressions = plan
335        .assignments
336        .iter()
337        .flat_map(crate::plan::AssignmentPlan::expressions)
338        .cloned()
339        .collect::<Vec<_>>();
340    expressions.extend(plan.predicate.iter().cloned());
341    expressions.extend(
342        plan.returning
343            .iter()
344            .map(|projection| projection.expr.clone()),
345    );
346    Ok(CommandRoutineInputs {
347        ctes: plan.ctes.clone(),
348        source: plan.source.as_deref().cloned(),
349        expressions,
350        subqueries: plan.subqueries.clone(),
351        outer: statement_target_outer_schema(
352            context,
353            &plan.table,
354            &plan.target_qualifier,
355            &plan.returning_aliases,
356        )?,
357    })
358}
359
360fn delete_statement_routine_inputs(
361    context: &CatalogRoutineContext<'_, '_>,
362    plan: &DeletePlan,
363) -> Result<CommandRoutineInputs, SQLError> {
364    let mut expressions = plan.predicate.iter().cloned().collect::<Vec<_>>();
365    expressions.extend(
366        plan.returning
367            .iter()
368            .map(|projection| projection.expr.clone()),
369    );
370    Ok(CommandRoutineInputs {
371        ctes: plan.ctes.clone(),
372        source: plan.source.as_deref().cloned(),
373        expressions,
374        subqueries: plan.subqueries.clone(),
375        outer: statement_target_outer_schema(
376            context,
377            &plan.table,
378            &plan.target_qualifier,
379            &plan.returning_aliases,
380        )?,
381    })
382}
383
384fn expression_contains_routine(expression: &ScalarExpr, subqueries: &[QueryPlan]) -> bool {
385    let mut references = Vec::new();
386    collect_scalar_routine_references(expression, subqueries, &mut references).is_ok()
387        && !references.is_empty()
388}
389
390fn statement_target_outer_schema(
391    context: &CatalogRoutineContext<'_, '_>,
392    table: &str,
393    target_qualifier: &str,
394    aliases: &crate::ast::ReturningAliases,
395) -> Result<RowSchema, SQLError> {
396    let target = crate::binding::analyze_source_plan_schema(
397        context.routines,
398        &SourcePlan::Table {
399            bound_columns: None,
400            name: table.to_string(),
401            qualifier: target_qualifier.to_string(),
402            alias: None,
403            column_aliases: Vec::new(),
404            include_descendants: true,
405        },
406        &[],
407        context.binding,
408        None,
409    )?;
410    let target = RowSchema::with_types(target.columns().to_vec(), target.column_types().to_vec());
411    Ok(crate::semantics::returning_expression_schema(
412        &target,
413        target_qualifier,
414        aliases,
415        None,
416    ))
417}
418
419pub fn collect_expression_routine_references(
420    expression: &crate::plan::ExpressionPlan,
421) -> Result<Vec<BoundRoutineReference>, SQLError> {
422    let mut references = Vec::new();
423    collect_scalar_routine_references(&expression.scalar, &expression.subqueries, &mut references)?;
424    Ok(references)
425}
426
427fn collect_query_routine_references(
428    query: &QueryPlan,
429    references: &mut Vec<BoundRoutineReference>,
430) -> Result<(), SQLError> {
431    for cte in &query.ctes {
432        collect_cte_routine_references(&cte.body, references)?;
433        if let Some(cycle) = &cte.cycle {
434            collect_scalar_routine_references(&cycle.mark_value, &[], references)?;
435            collect_scalar_routine_references(&cycle.mark_default, &[], references)?;
436        }
437    }
438    match &query.root {
439        RelationalPlan::QueryBlock(block) => {
440            if let Some(source) = &block.from {
441                collect_source_routine_references(source, &block.subqueries, references)?;
442            }
443            for projection in &block.projections {
444                collect_scalar_routine_references(&projection.expr, &block.subqueries, references)?;
445            }
446            if let Some(expression) = &block.r#where {
447                collect_scalar_routine_references(expression, &block.subqueries, references)?;
448            }
449            for expression in &block.group_by {
450                collect_scalar_routine_references(expression, &block.subqueries, references)?;
451            }
452            for expression in block.grouping_sets.iter().flatten() {
453                collect_scalar_routine_references(expression, &block.subqueries, references)?;
454            }
455            if let Some(expression) = &block.having {
456                collect_scalar_routine_references(expression, &block.subqueries, references)?;
457            }
458            for order in &block.order_by {
459                collect_scalar_routine_references(&order.expr, &block.subqueries, references)?;
460            }
461            if let Some(expression) = &block.limit {
462                collect_scalar_routine_references(expression, &block.subqueries, references)?;
463            }
464            if let Some(expression) = &block.offset {
465                collect_scalar_routine_references(expression, &block.subqueries, references)?;
466            }
467            for expression in &block.distinct_on {
468                collect_scalar_routine_references(expression, &block.subqueries, references)?;
469            }
470        }
471        RelationalPlan::SetOp {
472            left,
473            right,
474            order_by,
475            limit,
476            offset,
477            subqueries,
478            ..
479        } => {
480            collect_query_routine_references(left, references)?;
481            collect_query_routine_references(right, references)?;
482            for order in order_by {
483                collect_scalar_routine_references(&order.expr, subqueries, references)?;
484            }
485            if let Some(expression) = limit {
486                collect_scalar_routine_references(expression, subqueries, references)?;
487            }
488            if let Some(expression) = offset {
489                collect_scalar_routine_references(expression, subqueries, references)?;
490            }
491        }
492        RelationalPlan::Values { rows, subqueries } => {
493            for expression in rows.iter().flatten() {
494                collect_scalar_routine_references(expression, subqueries, references)?;
495            }
496        }
497    }
498    Ok(())
499}
500
501fn collect_source_routine_references(
502    source: &SourcePlan,
503    subqueries: &[QueryPlan],
504    references: &mut Vec<BoundRoutineReference>,
505) -> Result<(), SQLError> {
506    match source {
507        SourcePlan::Table { .. } => {}
508        SourcePlan::Join {
509            left, right, on, ..
510        } => {
511            collect_source_routine_references(left, subqueries, references)?;
512            collect_source_routine_references(right, subqueries, references)?;
513            if let Some(expression) = on {
514                collect_scalar_routine_references(expression, subqueries, references)?;
515            }
516        }
517        SourcePlan::Values { rows, .. } => {
518            for expression in rows.iter().flatten() {
519                collect_scalar_routine_references(expression, subqueries, references)?;
520            }
521        }
522        SourcePlan::Function {
523            name,
524            binding,
525            args,
526            ..
527        } => {
528            references.push(BoundRoutineReference {
529                name: name.clone(),
530                binding: binding.clone(),
531            });
532            for expression in args {
533                collect_scalar_routine_references(expression, subqueries, references)?;
534            }
535        }
536        SourcePlan::FunctionGroup { functions, .. } => {
537            for function in functions {
538                references.push(BoundRoutineReference {
539                    name: function.name.clone(),
540                    binding: function.binding.clone(),
541                });
542                for expression in &function.args {
543                    collect_scalar_routine_references(expression, subqueries, references)?;
544                }
545            }
546        }
547        SourcePlan::Subquery { body, .. } => {
548            collect_query_routine_references(body, references)?;
549        }
550    }
551    Ok(())
552}
553
554fn collect_scalar_routine_references(
555    expression: &ScalarExpr,
556    subqueries: &[QueryPlan],
557    references: &mut Vec<BoundRoutineReference>,
558) -> Result<(), SQLError> {
559    match expression {
560        ScalarExpr::Func {
561            name,
562            binding,
563            args,
564            order_by,
565            filter,
566            ..
567        } => {
568            for argument in args {
569                collect_scalar_routine_references(argument, subqueries, references)?;
570            }
571            for order in order_by {
572                collect_scalar_routine_references(&order.expr, subqueries, references)?;
573            }
574            if let Some(filter) = filter {
575                collect_scalar_routine_references(filter, subqueries, references)?;
576            }
577            references.push(BoundRoutineReference {
578                name: name.clone(),
579                binding: binding.clone(),
580            });
581        }
582        ScalarExpr::Array(items)
583        | ScalarExpr::Row(items)
584        | ScalarExpr::And(items)
585        | ScalarExpr::Or(items) => collect_many_routine_references(items, subqueries, references)?,
586        ScalarExpr::Binary { lhs, rhs, .. } => {
587            collect_scalar_routine_references(lhs, subqueries, references)?;
588            collect_scalar_routine_references(rhs, subqueries, references)?;
589        }
590        ScalarExpr::UnaryMinus(inner)
591        | ScalarExpr::Not(inner)
592        | ScalarExpr::IsNull { expr: inner, .. }
593        | ScalarExpr::Cast { expr: inner, .. } => {
594            collect_scalar_routine_references(inner, subqueries, references)?;
595        }
596        ScalarExpr::Between { expr, low, high } => {
597            collect_scalar_routine_references(expr, subqueries, references)?;
598            collect_scalar_routine_references(low, subqueries, references)?;
599            collect_scalar_routine_references(high, subqueries, references)?;
600        }
601        ScalarExpr::InList { expr, list, .. } => {
602            collect_scalar_routine_references(expr, subqueries, references)?;
603            for item in list {
604                collect_scalar_routine_references(item, subqueries, references)?;
605            }
606        }
607        ScalarExpr::WindowCall { name, args, spec } => {
608            collect_window_routine_references(name, args, spec, subqueries, references)?;
609        }
610        ScalarExpr::Case {
611            base,
612            when,
613            else_branch,
614        } => collect_case_routine_references(
615            base.as_deref(),
616            when,
617            else_branch.as_deref(),
618            subqueries,
619            references,
620        )?,
621        ScalarExpr::ScalarSubquery(index)
622        | ScalarExpr::Exists {
623            subquery: index, ..
624        } => {
625            let query = subqueries.get(*index).ok_or_else(|| {
626                SQLError::Internal(format!(
627                    "stored catalog routine binding cannot resolve subquery slot {index}"
628                ))
629            })?;
630            collect_query_routine_references(query, references)?;
631        }
632        ScalarExpr::InSubquery {
633            expr,
634            subquery: index,
635            ..
636        } => {
637            collect_scalar_routine_references(expr, subqueries, references)?;
638            let query = subqueries.get(*index).ok_or_else(|| {
639                SQLError::Internal(format!(
640                    "stored catalog routine binding cannot resolve subquery slot {index}"
641                ))
642            })?;
643            collect_query_routine_references(query, references)?;
644        }
645        ScalarExpr::Star
646        | ScalarExpr::QualifiedStar(_)
647        | ScalarExpr::Default
648        | ScalarExpr::Column(_)
649        | ScalarExpr::Position(_)
650        | ScalarExpr::InternalColumn(_)
651        | ScalarExpr::QualifiedColumn { .. }
652        | ScalarExpr::Literal(_)
653        | ScalarExpr::TypedLiteral { .. }
654        | ScalarExpr::Param(_) => {}
655    }
656    Ok(())
657}
658
659fn collect_many_routine_references(
660    expressions: &[ScalarExpr],
661    subqueries: &[QueryPlan],
662    references: &mut Vec<BoundRoutineReference>,
663) -> Result<(), SQLError> {
664    for expression in expressions {
665        collect_scalar_routine_references(expression, subqueries, references)?;
666    }
667    Ok(())
668}
669
670fn collect_case_routine_references(
671    base: Option<&ScalarExpr>,
672    when: &[(ScalarExpr, ScalarExpr)],
673    else_branch: Option<&ScalarExpr>,
674    subqueries: &[QueryPlan],
675    references: &mut Vec<BoundRoutineReference>,
676) -> Result<(), SQLError> {
677    if let Some(base) = base {
678        collect_scalar_routine_references(base, subqueries, references)?;
679    }
680    for (condition, result) in when {
681        collect_scalar_routine_references(condition, subqueries, references)?;
682        collect_scalar_routine_references(result, subqueries, references)?;
683    }
684    if let Some(branch) = else_branch {
685        collect_scalar_routine_references(branch, subqueries, references)?;
686    }
687    Ok(())
688}
689
690fn collect_window_routine_references(
691    name: &str,
692    args: &[ScalarExpr],
693    spec: &crate::ScalarWindowSpec,
694    subqueries: &[QueryPlan],
695    references: &mut Vec<BoundRoutineReference>,
696) -> Result<(), SQLError> {
697    for argument in args {
698        collect_scalar_routine_references(argument, subqueries, references)?;
699    }
700    for expression in &spec.partition_by {
701        collect_scalar_routine_references(expression, subqueries, references)?;
702    }
703    for order in &spec.order_by {
704        collect_scalar_routine_references(&order.expr, subqueries, references)?;
705    }
706    if let Some(frame) = &spec.frame {
707        for bound in [&frame.start, &frame.end] {
708            if let ScalarFrameBound::Preceding(inner) | ScalarFrameBound::Following(inner) = bound {
709                collect_scalar_routine_references(inner, subqueries, references)?;
710            }
711        }
712    }
713    references.push(BoundRoutineReference {
714        name: name.to_string(),
715        binding: None,
716    });
717    Ok(())
718}
719
720fn mark_cte_relations_bound(body: &mut crate::plan::CtePlanBody) {
721    match body {
722        crate::plan::CtePlanBody::Query(query) => mark_query_relations_bound(query),
723        crate::plan::CtePlanBody::Command(command) => {
724            match command.as_mut() {
725                CommandPlan::Insert(plan) => {
726                    plan.relations_bound = true;
727                    plan.target_relation_bound = true;
728                }
729                CommandPlan::Update(plan) => {
730                    plan.relations_bound = true;
731                    plan.target_relation_bound = true;
732                }
733                CommandPlan::Delete(plan) => {
734                    plan.relations_bound = true;
735                    plan.target_relation_bound = true;
736                }
737                _ => {}
738            }
739            if let Some(ctes) = command.ctes_mut() {
740                for cte in ctes {
741                    mark_cte_relations_bound(&mut cte.body);
742                }
743            }
744            for query in command.query_inputs_mut() {
745                mark_query_relations_bound(query);
746            }
747            if let Some(source) = command.source_input_mut() {
748                mark_source_relations_bound(source);
749            }
750        }
751    }
752}
753
754fn collect_cte_routine_references(
755    body: &crate::plan::CtePlanBody,
756    references: &mut Vec<BoundRoutineReference>,
757) -> Result<(), SQLError> {
758    match body {
759        crate::plan::CtePlanBody::Query(query) => {
760            collect_query_routine_references(query, references)
761        }
762        crate::plan::CtePlanBody::Command(command) => {
763            for cte in command.ctes() {
764                collect_cte_routine_references(&cte.body, references)?;
765                if let Some(cycle) = &cte.cycle {
766                    collect_scalar_routine_references(&cycle.mark_value, &[], references)?;
767                    collect_scalar_routine_references(&cycle.mark_default, &[], references)?;
768                }
769            }
770            for query in command.query_inputs() {
771                collect_query_routine_references(query, references)?;
772            }
773            if let Some(source) = command.source_input() {
774                collect_source_routine_references(source, command.scalar_subqueries(), references)?;
775            }
776            for expression in command.expressions() {
777                collect_scalar_routine_references(
778                    expression,
779                    command.scalar_subqueries(),
780                    references,
781                )?;
782            }
783            Ok(())
784        }
785    }
786}
787
788fn mark_query_relations_bound(query: &mut QueryPlan) {
789    query.relations_bound = true;
790    for cte in &mut query.ctes {
791        mark_cte_relations_bound(&mut cte.body);
792    }
793    match &mut query.root {
794        RelationalPlan::QueryBlock(block) => {
795            if let Some(source) = &mut block.from {
796                mark_source_relations_bound(source);
797            }
798            for subquery in &mut block.subqueries {
799                mark_query_relations_bound(subquery);
800            }
801        }
802        RelationalPlan::SetOp {
803            left,
804            right,
805            subqueries,
806            ..
807        } => {
808            mark_query_relations_bound(left);
809            mark_query_relations_bound(right);
810            for subquery in subqueries {
811                mark_query_relations_bound(subquery);
812            }
813        }
814        RelationalPlan::Values { subqueries, .. } => {
815            for subquery in subqueries {
816                mark_query_relations_bound(subquery);
817            }
818        }
819    }
820}
821
822fn mark_source_relations_bound(source: &mut SourcePlan) {
823    match source {
824        SourcePlan::Join { left, right, .. } => {
825            mark_source_relations_bound(left);
826            mark_source_relations_bound(right);
827        }
828        SourcePlan::Subquery { body, .. } => mark_query_relations_bound(body),
829        SourcePlan::Table { .. }
830        | SourcePlan::Values { .. }
831        | SourcePlan::Function { .. }
832        | SourcePlan::FunctionGroup { .. } => {}
833    }
834}
835
836pub mod analysis;