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