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                        .map(|assignment| assignment.value.clone()),
237                );
238            }
239            crate::plan::MergeWhenPlan::InsertNotMatched {
240                condition, values, ..
241            } => {
242                expressions.extend(condition.iter().cloned());
243                expressions.extend(values.iter().cloned());
244            }
245            crate::plan::MergeWhenPlan::DeleteMatched { condition }
246            | crate::plan::MergeWhenPlan::DeleteNotMatchedBySource { condition }
247            | crate::plan::MergeWhenPlan::NothingMatched { condition }
248            | crate::plan::MergeWhenPlan::NothingNotMatched { condition }
249            | crate::plan::MergeWhenPlan::NothingNotMatchedBySource { condition } => {
250                expressions.extend(condition.iter().cloned());
251            }
252        }
253    }
254    expressions.extend(
255        plan.returning
256            .iter()
257            .map(|projection| projection.expr.clone()),
258    );
259    CommandRoutineInputs {
260        ctes: plan.ctes.clone(),
261        source: Some(source),
262        expressions,
263        subqueries: plan.subqueries.clone(),
264        outer: RowSchema::default(),
265    }
266}
267
268fn insert_statement_routine_inputs(
269    context: &CatalogRoutineContext<'_, '_>,
270    plan: &InsertPlan,
271) -> Result<CommandRoutineInputs, SQLError> {
272    let mut expressions = plan.rows.iter().flatten().cloned().collect::<Vec<_>>();
273    if let Some(conflict) = &plan.on_conflict {
274        expressions.extend(conflict.expressions.iter().cloned());
275        expressions.extend(conflict.predicate.iter().map(Box::as_ref).cloned());
276        if let ConflictActionPlan::Update {
277            assignments,
278            predicate,
279        } = &conflict.action
280        {
281            expressions.extend(
282                assignments
283                    .iter()
284                    .map(|assignment| assignment.value.clone()),
285            );
286            expressions.extend(predicate.iter().map(Box::as_ref).cloned());
287        }
288    }
289    expressions.extend(
290        plan.returning
291            .iter()
292            .map(|projection| projection.expr.clone()),
293    );
294    let source = plan.source.as_ref().map(|source| SourcePlan::Subquery {
295        body: Box::new((**source).clone()),
296        alias: Some("__uqa_catalog_statement_source".into()),
297        column_aliases: Vec::new(),
298    });
299    Ok(CommandRoutineInputs {
300        ctes: plan.ctes.clone(),
301        source,
302        expressions,
303        subqueries: plan.subqueries.clone(),
304        outer: statement_target_outer_schema(
305            context,
306            &plan.table,
307            &plan.target_qualifier,
308            &plan.returning_aliases,
309        )?,
310    })
311}
312
313fn update_statement_routine_inputs(
314    context: &CatalogRoutineContext<'_, '_>,
315    plan: &UpdatePlan,
316) -> Result<CommandRoutineInputs, SQLError> {
317    let mut expressions = plan
318        .assignments
319        .iter()
320        .map(|assignment| assignment.value.clone())
321        .collect::<Vec<_>>();
322    expressions.extend(plan.predicate.iter().cloned());
323    expressions.extend(
324        plan.returning
325            .iter()
326            .map(|projection| projection.expr.clone()),
327    );
328    Ok(CommandRoutineInputs {
329        ctes: plan.ctes.clone(),
330        source: plan.source.as_deref().cloned(),
331        expressions,
332        subqueries: plan.subqueries.clone(),
333        outer: statement_target_outer_schema(
334            context,
335            &plan.table,
336            &plan.target_qualifier,
337            &plan.returning_aliases,
338        )?,
339    })
340}
341
342fn delete_statement_routine_inputs(
343    context: &CatalogRoutineContext<'_, '_>,
344    plan: &DeletePlan,
345) -> Result<CommandRoutineInputs, SQLError> {
346    let mut expressions = plan.predicate.iter().cloned().collect::<Vec<_>>();
347    expressions.extend(
348        plan.returning
349            .iter()
350            .map(|projection| projection.expr.clone()),
351    );
352    Ok(CommandRoutineInputs {
353        ctes: plan.ctes.clone(),
354        source: plan.source.as_deref().cloned(),
355        expressions,
356        subqueries: plan.subqueries.clone(),
357        outer: statement_target_outer_schema(
358            context,
359            &plan.table,
360            &plan.target_qualifier,
361            &plan.returning_aliases,
362        )?,
363    })
364}
365
366fn expression_contains_routine(expression: &ScalarExpr, subqueries: &[QueryPlan]) -> bool {
367    let mut references = Vec::new();
368    collect_scalar_routine_references(expression, subqueries, &mut references).is_ok()
369        && !references.is_empty()
370}
371
372fn statement_target_outer_schema(
373    context: &CatalogRoutineContext<'_, '_>,
374    table: &str,
375    target_qualifier: &str,
376    aliases: &crate::ast::ReturningAliases,
377) -> Result<RowSchema, SQLError> {
378    let target = crate::binding::analyze_source_plan_schema(
379        context.routines,
380        &SourcePlan::Table {
381            bound_columns: None,
382            name: table.to_string(),
383            qualifier: target_qualifier.to_string(),
384            alias: None,
385            column_aliases: Vec::new(),
386            include_descendants: true,
387        },
388        &[],
389        context.binding,
390        None,
391    )?;
392    let target = RowSchema::with_types(target.columns().to_vec(), target.column_types().to_vec());
393    Ok(crate::semantics::returning_expression_schema(
394        &target,
395        target_qualifier,
396        aliases,
397        None,
398    ))
399}
400
401pub fn collect_expression_routine_references(
402    expression: &crate::plan::ExpressionPlan,
403) -> Result<Vec<BoundRoutineReference>, SQLError> {
404    let mut references = Vec::new();
405    collect_scalar_routine_references(&expression.scalar, &expression.subqueries, &mut references)?;
406    Ok(references)
407}
408
409fn collect_query_routine_references(
410    query: &QueryPlan,
411    references: &mut Vec<BoundRoutineReference>,
412) -> Result<(), SQLError> {
413    for cte in &query.ctes {
414        collect_cte_routine_references(&cte.body, references)?;
415        if let Some(cycle) = &cte.cycle {
416            collect_scalar_routine_references(&cycle.mark_value, &[], references)?;
417            collect_scalar_routine_references(&cycle.mark_default, &[], references)?;
418        }
419    }
420    match &query.root {
421        RelationalPlan::QueryBlock(block) => {
422            if let Some(source) = &block.from {
423                collect_source_routine_references(source, &block.subqueries, references)?;
424            }
425            for projection in &block.projections {
426                collect_scalar_routine_references(&projection.expr, &block.subqueries, references)?;
427            }
428            if let Some(expression) = &block.r#where {
429                collect_scalar_routine_references(expression, &block.subqueries, references)?;
430            }
431            for expression in &block.group_by {
432                collect_scalar_routine_references(expression, &block.subqueries, references)?;
433            }
434            for expression in block.grouping_sets.iter().flatten() {
435                collect_scalar_routine_references(expression, &block.subqueries, references)?;
436            }
437            if let Some(expression) = &block.having {
438                collect_scalar_routine_references(expression, &block.subqueries, references)?;
439            }
440            for order in &block.order_by {
441                collect_scalar_routine_references(&order.expr, &block.subqueries, references)?;
442            }
443            if let Some(expression) = &block.limit {
444                collect_scalar_routine_references(expression, &block.subqueries, references)?;
445            }
446            if let Some(expression) = &block.offset {
447                collect_scalar_routine_references(expression, &block.subqueries, references)?;
448            }
449            for expression in &block.distinct_on {
450                collect_scalar_routine_references(expression, &block.subqueries, references)?;
451            }
452        }
453        RelationalPlan::SetOp {
454            left,
455            right,
456            order_by,
457            limit,
458            offset,
459            subqueries,
460            ..
461        } => {
462            collect_query_routine_references(left, references)?;
463            collect_query_routine_references(right, references)?;
464            for order in order_by {
465                collect_scalar_routine_references(&order.expr, subqueries, references)?;
466            }
467            if let Some(expression) = limit {
468                collect_scalar_routine_references(expression, subqueries, references)?;
469            }
470            if let Some(expression) = offset {
471                collect_scalar_routine_references(expression, subqueries, references)?;
472            }
473        }
474        RelationalPlan::Values { rows, subqueries } => {
475            for expression in rows.iter().flatten() {
476                collect_scalar_routine_references(expression, subqueries, references)?;
477            }
478        }
479    }
480    Ok(())
481}
482
483fn collect_source_routine_references(
484    source: &SourcePlan,
485    subqueries: &[QueryPlan],
486    references: &mut Vec<BoundRoutineReference>,
487) -> Result<(), SQLError> {
488    match source {
489        SourcePlan::Table { .. } => {}
490        SourcePlan::Join {
491            left, right, on, ..
492        } => {
493            collect_source_routine_references(left, subqueries, references)?;
494            collect_source_routine_references(right, subqueries, references)?;
495            if let Some(expression) = on {
496                collect_scalar_routine_references(expression, subqueries, references)?;
497            }
498        }
499        SourcePlan::Values { rows, .. } => {
500            for expression in rows.iter().flatten() {
501                collect_scalar_routine_references(expression, subqueries, references)?;
502            }
503        }
504        SourcePlan::Function {
505            name,
506            binding,
507            args,
508            ..
509        } => {
510            references.push(BoundRoutineReference {
511                name: name.clone(),
512                binding: binding.clone(),
513            });
514            for expression in args {
515                collect_scalar_routine_references(expression, subqueries, references)?;
516            }
517        }
518        SourcePlan::FunctionGroup { functions, .. } => {
519            for function in functions {
520                references.push(BoundRoutineReference {
521                    name: function.name.clone(),
522                    binding: function.binding.clone(),
523                });
524                for expression in &function.args {
525                    collect_scalar_routine_references(expression, subqueries, references)?;
526                }
527            }
528        }
529        SourcePlan::Subquery { body, .. } => {
530            collect_query_routine_references(body, references)?;
531        }
532    }
533    Ok(())
534}
535
536fn collect_scalar_routine_references(
537    expression: &ScalarExpr,
538    subqueries: &[QueryPlan],
539    references: &mut Vec<BoundRoutineReference>,
540) -> Result<(), SQLError> {
541    match expression {
542        ScalarExpr::Func {
543            name,
544            binding,
545            args,
546            order_by,
547            filter,
548            ..
549        } => {
550            for argument in args {
551                collect_scalar_routine_references(argument, subqueries, references)?;
552            }
553            for order in order_by {
554                collect_scalar_routine_references(&order.expr, subqueries, references)?;
555            }
556            if let Some(filter) = filter {
557                collect_scalar_routine_references(filter, subqueries, references)?;
558            }
559            references.push(BoundRoutineReference {
560                name: name.clone(),
561                binding: binding.clone(),
562            });
563        }
564        ScalarExpr::Array(items)
565        | ScalarExpr::Row(items)
566        | ScalarExpr::And(items)
567        | ScalarExpr::Or(items) => collect_many_routine_references(items, subqueries, references)?,
568        ScalarExpr::Binary { lhs, rhs, .. } => {
569            collect_scalar_routine_references(lhs, subqueries, references)?;
570            collect_scalar_routine_references(rhs, subqueries, references)?;
571        }
572        ScalarExpr::UnaryMinus(inner)
573        | ScalarExpr::Not(inner)
574        | ScalarExpr::IsNull { expr: inner, .. }
575        | ScalarExpr::Cast { expr: inner, .. } => {
576            collect_scalar_routine_references(inner, subqueries, references)?;
577        }
578        ScalarExpr::Between { expr, low, high } => {
579            collect_scalar_routine_references(expr, subqueries, references)?;
580            collect_scalar_routine_references(low, subqueries, references)?;
581            collect_scalar_routine_references(high, subqueries, references)?;
582        }
583        ScalarExpr::InList { expr, list, .. } => {
584            collect_scalar_routine_references(expr, subqueries, references)?;
585            for item in list {
586                collect_scalar_routine_references(item, subqueries, references)?;
587            }
588        }
589        ScalarExpr::WindowCall { name, args, spec } => {
590            collect_window_routine_references(name, args, spec, subqueries, references)?;
591        }
592        ScalarExpr::Case {
593            base,
594            when,
595            else_branch,
596        } => collect_case_routine_references(
597            base.as_deref(),
598            when,
599            else_branch.as_deref(),
600            subqueries,
601            references,
602        )?,
603        ScalarExpr::ScalarSubquery(index)
604        | ScalarExpr::Exists {
605            subquery: index, ..
606        } => {
607            let query = subqueries.get(*index).ok_or_else(|| {
608                SQLError::Internal(format!(
609                    "stored catalog routine binding cannot resolve subquery slot {index}"
610                ))
611            })?;
612            collect_query_routine_references(query, references)?;
613        }
614        ScalarExpr::InSubquery {
615            expr,
616            subquery: index,
617            ..
618        } => {
619            collect_scalar_routine_references(expr, subqueries, references)?;
620            let query = subqueries.get(*index).ok_or_else(|| {
621                SQLError::Internal(format!(
622                    "stored catalog routine binding cannot resolve subquery slot {index}"
623                ))
624            })?;
625            collect_query_routine_references(query, references)?;
626        }
627        ScalarExpr::Star
628        | ScalarExpr::QualifiedStar(_)
629        | ScalarExpr::Default
630        | ScalarExpr::Column(_)
631        | ScalarExpr::Position(_)
632        | ScalarExpr::InternalColumn(_)
633        | ScalarExpr::QualifiedColumn { .. }
634        | ScalarExpr::Literal(_)
635        | ScalarExpr::TypedLiteral { .. }
636        | ScalarExpr::Param(_) => {}
637    }
638    Ok(())
639}
640
641fn collect_many_routine_references(
642    expressions: &[ScalarExpr],
643    subqueries: &[QueryPlan],
644    references: &mut Vec<BoundRoutineReference>,
645) -> Result<(), SQLError> {
646    for expression in expressions {
647        collect_scalar_routine_references(expression, subqueries, references)?;
648    }
649    Ok(())
650}
651
652fn collect_case_routine_references(
653    base: Option<&ScalarExpr>,
654    when: &[(ScalarExpr, ScalarExpr)],
655    else_branch: Option<&ScalarExpr>,
656    subqueries: &[QueryPlan],
657    references: &mut Vec<BoundRoutineReference>,
658) -> Result<(), SQLError> {
659    if let Some(base) = base {
660        collect_scalar_routine_references(base, subqueries, references)?;
661    }
662    for (condition, result) in when {
663        collect_scalar_routine_references(condition, subqueries, references)?;
664        collect_scalar_routine_references(result, subqueries, references)?;
665    }
666    if let Some(branch) = else_branch {
667        collect_scalar_routine_references(branch, subqueries, references)?;
668    }
669    Ok(())
670}
671
672fn collect_window_routine_references(
673    name: &str,
674    args: &[ScalarExpr],
675    spec: &crate::ScalarWindowSpec,
676    subqueries: &[QueryPlan],
677    references: &mut Vec<BoundRoutineReference>,
678) -> Result<(), SQLError> {
679    for argument in args {
680        collect_scalar_routine_references(argument, subqueries, references)?;
681    }
682    for expression in &spec.partition_by {
683        collect_scalar_routine_references(expression, subqueries, references)?;
684    }
685    for order in &spec.order_by {
686        collect_scalar_routine_references(&order.expr, subqueries, references)?;
687    }
688    if let Some(frame) = &spec.frame {
689        for bound in [&frame.start, &frame.end] {
690            if let ScalarFrameBound::Preceding(inner) | ScalarFrameBound::Following(inner) = bound {
691                collect_scalar_routine_references(inner, subqueries, references)?;
692            }
693        }
694    }
695    references.push(BoundRoutineReference {
696        name: name.to_string(),
697        binding: None,
698    });
699    Ok(())
700}
701
702fn mark_cte_relations_bound(body: &mut crate::plan::CtePlanBody) {
703    match body {
704        crate::plan::CtePlanBody::Query(query) => mark_query_relations_bound(query),
705        crate::plan::CtePlanBody::Command(command) => {
706            match command.as_mut() {
707                CommandPlan::Insert(plan) => {
708                    plan.relations_bound = true;
709                    plan.target_relation_bound = true;
710                }
711                CommandPlan::Update(plan) => {
712                    plan.relations_bound = true;
713                    plan.target_relation_bound = true;
714                }
715                CommandPlan::Delete(plan) => {
716                    plan.relations_bound = true;
717                    plan.target_relation_bound = true;
718                }
719                _ => {}
720            }
721            if let Some(ctes) = command.ctes_mut() {
722                for cte in ctes {
723                    mark_cte_relations_bound(&mut cte.body);
724                }
725            }
726            for query in command.query_inputs_mut() {
727                mark_query_relations_bound(query);
728            }
729            if let Some(source) = command.source_input_mut() {
730                mark_source_relations_bound(source);
731            }
732        }
733    }
734}
735
736fn collect_cte_routine_references(
737    body: &crate::plan::CtePlanBody,
738    references: &mut Vec<BoundRoutineReference>,
739) -> Result<(), SQLError> {
740    match body {
741        crate::plan::CtePlanBody::Query(query) => {
742            collect_query_routine_references(query, references)
743        }
744        crate::plan::CtePlanBody::Command(command) => {
745            for cte in command.ctes() {
746                collect_cte_routine_references(&cte.body, references)?;
747                if let Some(cycle) = &cte.cycle {
748                    collect_scalar_routine_references(&cycle.mark_value, &[], references)?;
749                    collect_scalar_routine_references(&cycle.mark_default, &[], references)?;
750                }
751            }
752            for query in command.query_inputs() {
753                collect_query_routine_references(query, references)?;
754            }
755            if let Some(source) = command.source_input() {
756                collect_source_routine_references(source, command.scalar_subqueries(), references)?;
757            }
758            for expression in command.expressions() {
759                collect_scalar_routine_references(
760                    expression,
761                    command.scalar_subqueries(),
762                    references,
763                )?;
764            }
765            Ok(())
766        }
767    }
768}
769
770fn mark_query_relations_bound(query: &mut QueryPlan) {
771    query.relations_bound = true;
772    for cte in &mut query.ctes {
773        mark_cte_relations_bound(&mut cte.body);
774    }
775    match &mut query.root {
776        RelationalPlan::QueryBlock(block) => {
777            if let Some(source) = &mut block.from {
778                mark_source_relations_bound(source);
779            }
780            for subquery in &mut block.subqueries {
781                mark_query_relations_bound(subquery);
782            }
783        }
784        RelationalPlan::SetOp {
785            left,
786            right,
787            subqueries,
788            ..
789        } => {
790            mark_query_relations_bound(left);
791            mark_query_relations_bound(right);
792            for subquery in subqueries {
793                mark_query_relations_bound(subquery);
794            }
795        }
796        RelationalPlan::Values { subqueries, .. } => {
797            for subquery in subqueries {
798                mark_query_relations_bound(subquery);
799            }
800        }
801    }
802}
803
804fn mark_source_relations_bound(source: &mut SourcePlan) {
805    match source {
806        SourcePlan::Join { left, right, .. } => {
807            mark_source_relations_bound(left);
808            mark_source_relations_bound(right);
809        }
810        SourcePlan::Subquery { body, .. } => mark_query_relations_bound(body),
811        SourcePlan::Table { .. }
812        | SourcePlan::Values { .. }
813        | SourcePlan::Function { .. }
814        | SourcePlan::FunctionGroup { .. } => {}
815    }
816}
817
818pub mod analysis;