Skip to main content

uqa_sql/binding/
routine_binding.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Persistent exact routine identity binding for catalog-owned query plans.
8
9use super::{
10    cte_references_own_name, extend_cte_generated_schema, extend_recursive_cte_binding_schema,
11    operator_join_relation_schemas, overlay_outer_schema, rename_schema, BindingContext,
12    ColumnType, QueryPlan, RelationalPlan, RowSchema, SQLError, SQLParam, ScalarExpr, SchemaScope,
13    SourcePlan,
14};
15use crate::ast::FunctionBinding;
16use crate::plan::ExpressionPlan;
17use crate::routines::RoutineResolution;
18use crate::{ColumnIdentity, FunctionTypeResolver};
19
20impl SchemaScope {
21    pub(super) fn with_stored_outer_internal_aliases(&self, schema: &RowSchema) -> RowSchema {
22        self.stored_expression_outer.as_ref().map_or_else(
23            || schema.clone(),
24            |outer| {
25                if schema.physical_width() < outer.physical_width() {
26                    schema.clone()
27                } else {
28                    RowSchema::with_trailing_internal_aliases(schema, outer)
29                }
30            },
31        )
32    }
33
34    fn canonicalize_stored_outer_columns(&self, expression: &mut ScalarExpr, schema: &RowSchema) {
35        let Some(outer) = self.stored_expression_outer.as_ref() else {
36            return;
37        };
38        let Some(outer_start) = schema.physical_width().checked_sub(outer.physical_width()) else {
39            return;
40        };
41        crate::plan::rewrite_scalar_expression(expression, &mut |node| {
42            let lookup = match node {
43                ScalarExpr::Column(column) => ColumnIdentity::unqualified(column.as_str()),
44                ScalarExpr::QualifiedColumn { qualifier, column } => {
45                    ColumnIdentity::qualified(qualifier.as_str(), column.as_str())
46                }
47                _ => return,
48            };
49            let Some(slot) = schema.physical_slot_for_identity(&lookup) else {
50                return;
51            };
52            if slot < outer_start {
53                return;
54            }
55            let outer_slot = slot - outer_start;
56            let Some(column) = outer.unique_internal_column_for_slot(outer_slot) else {
57                return;
58            };
59            *node = ScalarExpr::InternalColumn(column);
60        });
61    }
62
63    fn bind_cte_routines_for_storage(
64        &mut self,
65        routines: &dyn RoutineResolution,
66        body: &mut crate::plan::CtePlanBody,
67        params: &[SQLParam],
68        outer: Option<&RowSchema>,
69    ) -> Result<RowSchema, SQLError> {
70        let crate::plan::CtePlanBody::Command(command) = body else {
71            return self.bind_query_routines_for_storage(
72                routines,
73                body.query_mut().expect("query CTE body"),
74                params,
75                outer,
76            );
77        };
78        self.bind_command_routines_for_storage(routines, command, params, outer)?;
79        self.bind_command_returning(routines, command, params)
80    }
81
82    pub(super) fn bind_cte_routine_schemas(
83        &mut self,
84        routines: &dyn RoutineResolution,
85        ctes: &mut [crate::plan::CtePlan],
86        params: &[SQLParam],
87        outer: Option<&RowSchema>,
88    ) -> Result<Vec<(String, bool, Option<RowSchema>)>, SQLError> {
89        let ordered_names = crate::semantics::ordered_cte_plans(ctes)?
90            .into_iter()
91            .map(|cte| cte.name.clone())
92            .collect::<Vec<_>>();
93        let mut previous = Vec::with_capacity(ordered_names.len());
94        for name in ordered_names {
95            let position = ctes
96                .iter()
97                .position(|cte| cte.name == name)
98                .ok_or_else(|| SQLError::Internal(format!("ordered CTE `{name}` disappeared")))?;
99            let self_recursive = cte_references_own_name(&ctes[position]);
100            if let Some(cycle) = ctes[position].cycle.as_mut() {
101                let schema = RowSchema::default();
102                self.bind_scalar_routines_for_storage(
103                    routines,
104                    &mut cycle.mark_value,
105                    &schema,
106                    &[],
107                    params,
108                )?;
109                self.bind_scalar_routines_for_storage(
110                    routines,
111                    &mut cycle.mark_default,
112                    &schema,
113                    &[],
114                    params,
115                )?;
116            }
117            let provisional = if self_recursive {
118                self.bind_recursive_seed(
119                    routines,
120                    ctes[position]
121                        .body
122                        .query()
123                        .ok_or_else(|| SQLError::Routine {
124                            sqlstate: "42P19".into(),
125                            message: format!(
126                                "recursive query \"{}\" must not contain data-modifying statements",
127                                ctes[position].name
128                            ),
129                        })?,
130                    params,
131                    outer,
132                )?
133            } else {
134                self.bind_cte_routines_for_storage(
135                    routines,
136                    &mut ctes[position].body,
137                    params,
138                    outer,
139                )?
140            };
141            let columns = ctes[position].columns.clone();
142            let provisional = rename_schema(&provisional, &columns, None);
143            let provisional = if self_recursive {
144                extend_recursive_cte_binding_schema(routines, &ctes[position], provisional, params)?
145            } else {
146                extend_cte_generated_schema(routines, &ctes[position], provisional, params)?
147            };
148            previous.push((
149                name.clone(),
150                self.set_cte_returning(&ctes[position]),
151                self.ctes.insert(name.clone(), provisional),
152            ));
153            if self_recursive {
154                let complete = self.bind_cte_routines_for_storage(
155                    routines,
156                    &mut ctes[position].body,
157                    params,
158                    outer,
159                )?;
160                let complete = rename_schema(&complete, &columns, None);
161                let complete =
162                    extend_cte_generated_schema(routines, &ctes[position], complete, params)?;
163                self.ctes.insert(name, complete);
164            }
165        }
166
167        Ok(previous)
168    }
169
170    pub(super) fn bind_query_routines_for_storage(
171        &mut self,
172        routines: &dyn RoutineResolution,
173        plan: &mut QueryPlan,
174        params: &[SQLParam],
175        outer: Option<&RowSchema>,
176    ) -> Result<RowSchema, SQLError> {
177        let previous = self.bind_cte_routine_schemas(routines, &mut plan.ctes, params, outer)?;
178
179        let result = self.bind_root_routines_for_storage(routines, &mut plan.root, params, outer);
180        self.restore_cte_schemas(previous);
181        result
182    }
183
184    #[expect(
185        clippy::too_many_lines,
186        reason = "preserves SELECT schema and row identity"
187    )]
188    fn bind_root_routines_for_storage(
189        &mut self,
190        routines: &dyn RoutineResolution,
191        root: &mut RelationalPlan,
192        params: &[SQLParam],
193        outer: Option<&RowSchema>,
194    ) -> Result<RowSchema, SQLError> {
195        match root {
196            RelationalPlan::QueryBlock(block) => {
197                // The sources' own expressions resolve before the sources are analyzed, so that an analysis sees the names a nested query resolved, such as its output names in GROUP BY.
198                if let Some(source) = block.from.as_mut() {
199                    self.bind_source_routines_for_storage(
200                        routines,
201                        source,
202                        &block.subqueries,
203                        params,
204                        outer,
205                    )?;
206                }
207                let source_schema = match block.from.as_mut() {
208                    Some(source) => self.bind_source_for_execution(
209                        routines,
210                        source,
211                        &block.subqueries,
212                        params,
213                        outer,
214                    )?,
215                    None => RowSchema::default(),
216                };
217                let expression_schema = overlay_outer_schema(&source_schema, outer);
218                for subquery in &mut block.subqueries {
219                    self.bind_query_routines_for_storage(
220                        routines,
221                        subquery,
222                        params,
223                        Some(&expression_schema),
224                    )?;
225                }
226            }
227            RelationalPlan::SetOp {
228                left,
229                right,
230                subqueries,
231                ..
232            } => {
233                self.bind_query_routines_for_storage(routines, left, params, outer)?;
234                self.bind_query_routines_for_storage(routines, right, params, outer)?;
235                for subquery in subqueries {
236                    self.bind_query_routines_for_storage(routines, subquery, params, outer)?;
237                }
238            }
239            RelationalPlan::Values { subqueries, .. } => {
240                for subquery in subqueries {
241                    self.bind_query_routines_for_storage(routines, subquery, params, outer)?;
242                }
243            }
244        }
245
246        let set_output = match &*root {
247            RelationalPlan::SetOp { .. } => {
248                Some(self.bind_root(routines, root, params, outer, false)?)
249            }
250            RelationalPlan::QueryBlock(_) | RelationalPlan::Values { .. } => None,
251        };
252        match root {
253            RelationalPlan::QueryBlock(block) => {
254                if block.from.is_none()
255                    && block
256                        .projections
257                        .iter()
258                        .any(|projection| matches!(projection.expr, ScalarExpr::Star))
259                    && outer.is_none()
260                {
261                    return Err(SQLError::Routine {
262                        sqlstate: "42601".into(),
263                        message: "SELECT * with no tables specified is not valid".into(),
264                    });
265                }
266                let source_schema = block.from.as_ref().map_or_else(
267                    || Ok(RowSchema::default()),
268                    |source| self.bind_source(routines, source, &block.subqueries, params, outer),
269                )?;
270                let expression_schema = overlay_outer_schema(&source_schema, outer);
271                if let Some(filter) = block.r#where.as_mut() {
272                    self.bind_scalar_routines_for_storage(
273                        routines,
274                        filter,
275                        &expression_schema,
276                        &block.subqueries,
277                        params,
278                    )?;
279                }
280                let labels = super::routine_parameters::column_labels(&block.projections);
281                for projection in &mut block.projections {
282                    self.bind_scalar_routines_for_storage(
283                        routines,
284                        &mut projection.expr,
285                        &expression_schema,
286                        &block.subqueries,
287                        params,
288                    )?;
289                }
290                super::routine_parameters::keep_column_labels(&mut block.projections, labels);
291                if self.preserve_syntax_shape {
292                    // Stored syntax keeps `*` and output-name references as written; expanding a copy still reports their errors.
293                    let mut expanded = (**block).clone();
294                    expanded.projections = crate::semantics::expand_bound_projection_stars(
295                        &block.projections,
296                        &source_schema,
297                    )?;
298                    self.bind_grouping_variable_sites(&mut expanded, &source_schema);
299                    crate::semantics::grouping_sets::bind_grouping_names(
300                        routines,
301                        &mut expanded,
302                        &source_schema,
303                        None,
304                        params,
305                    )?;
306                } else {
307                    block.projections = crate::semantics::expand_bound_projection_stars(
308                        &block.projections,
309                        &source_schema,
310                    )?;
311                    self.bind_grouping_variable_sites(block, &source_schema);
312                    crate::semantics::grouping_sets::bind_grouping_names(
313                        routines,
314                        block,
315                        &source_schema,
316                        None,
317                        params,
318                    )?;
319                }
320                let output_names = self.output_names(&block.projections);
321                for expression in block
322                    .group_by
323                    .iter_mut()
324                    .chain(block.grouping_sets.iter_mut().flatten())
325                {
326                    // A preserved GROUP BY output name wins over a routine parameter when no local input column takes it. Its expression was already validated in the expanded copy.
327                    if self.preserve_syntax_shape
328                        && matches!(expression, ScalarExpr::Column(name)
329                            if !source_schema.has_unqualified_column(name)
330                                && !source_schema.column_is_ambiguous(name)
331                                && output_names.contains(name))
332                    {
333                        continue;
334                    }
335                    self.bind_scalar_routines_for_storage(
336                        routines,
337                        expression,
338                        &expression_schema,
339                        &block.subqueries,
340                        params,
341                    )?;
342                }
343                if let Some(having) = block.having.as_mut() {
344                    self.bind_scalar_routines_for_storage(
345                        routines,
346                        having,
347                        &expression_schema,
348                        &block.subqueries,
349                        params,
350                    )?;
351                }
352                // A bare name in ORDER BY or DISTINCT ON names an output column before any input column or parameter, as `findTargetlistEntrySQL92` resolves it.
353                let names_output = |expression: &ScalarExpr| match expression {
354                    ScalarExpr::Column(name) => output_names.contains(name),
355                    _ => false,
356                };
357                for order in &mut block.order_by {
358                    if names_output(&order.expr)
359                        || self.output_takes_variable_site(&order.expr, &output_names)
360                    {
361                        continue;
362                    }
363                    self.bind_scalar_routines_for_storage(
364                        routines,
365                        &mut order.expr,
366                        &expression_schema,
367                        &block.subqueries,
368                        params,
369                    )?;
370                }
371                if let Some(limit) = block.limit.as_mut() {
372                    self.bind_scalar_routines_for_storage(
373                        routines,
374                        limit,
375                        &expression_schema,
376                        &block.subqueries,
377                        params,
378                    )?;
379                }
380                if let Some(offset) = block.offset.as_mut() {
381                    self.bind_scalar_routines_for_storage(
382                        routines,
383                        offset,
384                        &expression_schema,
385                        &block.subqueries,
386                        params,
387                    )?;
388                }
389                for expression in &mut block.distinct_on {
390                    if names_output(expression)
391                        || self.output_takes_variable_site(expression, &output_names)
392                    {
393                        continue;
394                    }
395                    self.bind_scalar_routines_for_storage(
396                        routines,
397                        expression,
398                        &expression_schema,
399                        &block.subqueries,
400                        params,
401                    )?;
402                }
403                for expression in block
404                    .windows
405                    .iter_mut()
406                    .flat_map(|window| window.spec.expressions_mut())
407                {
408                    self.bind_scalar_routines_for_storage(
409                        routines,
410                        expression,
411                        &expression_schema,
412                        &block.subqueries,
413                        params,
414                    )?;
415                }
416            }
417            RelationalPlan::SetOp {
418                order_by,
419                limit,
420                offset,
421                subqueries,
422                ..
423            } => {
424                let output = overlay_outer_schema(
425                    set_output
426                        .as_ref()
427                        .expect("set-operation output schema was bound before routine expressions"),
428                    outer,
429                );
430                let output = &output;
431                for order in order_by {
432                    self.bind_scalar_routines_for_storage(
433                        routines,
434                        &mut order.expr,
435                        output,
436                        subqueries,
437                        params,
438                    )?;
439                }
440                if let Some(limit) = limit {
441                    self.bind_scalar_routines_for_storage(
442                        routines, limit, output, subqueries, params,
443                    )?;
444                }
445                if let Some(offset) = offset {
446                    self.bind_scalar_routines_for_storage(
447                        routines, offset, output, subqueries, params,
448                    )?;
449                }
450            }
451            RelationalPlan::Values { rows, subqueries } => {
452                let input = outer.cloned().unwrap_or_default();
453                for expression in rows.iter_mut().flatten() {
454                    self.bind_scalar_routines_for_storage(
455                        routines, expression, &input, subqueries, params,
456                    )?;
457                }
458            }
459        }
460        self.bind_root(routines, root, params, outer, false)
461    }
462
463    #[expect(
464        clippy::too_many_lines,
465        reason = "preserves SELECT schema and row identity"
466    )]
467    pub(super) fn bind_source_routines_for_storage(
468        &mut self,
469        engine: &dyn RoutineResolution,
470        source: &mut SourcePlan,
471        subqueries: &[QueryPlan],
472        params: &[SQLParam],
473        outer: Option<&RowSchema>,
474    ) -> Result<(), SQLError> {
475        match source {
476            SourcePlan::Join {
477                left,
478                right,
479                on,
480                lateral,
481                ..
482            } => {
483                self.bind_source_routines_for_storage(engine, left, subqueries, params, outer)?;
484                let left_schema = self.bind_source(engine, left, subqueries, params, outer)?;
485                let implicit_lateral_function = matches!(
486                    right.as_ref(),
487                    SourcePlan::Function { .. } | SourcePlan::FunctionGroup { .. }
488                );
489                let right_scope = (*lateral || implicit_lateral_function)
490                    .then(|| overlay_outer_schema(&left_schema, outer));
491                let right_outer = right_scope.as_ref().or(outer);
492                self.bind_source_routines_for_storage(
493                    engine,
494                    right,
495                    subqueries,
496                    params,
497                    right_outer,
498                )?;
499                if let Some(on) = on {
500                    let right_schema =
501                        self.bind_source(engine, right, subqueries, params, right_outer)?;
502                    let input = RowSchema::join(&left_schema, &right_schema, std::iter::empty());
503                    let input = overlay_outer_schema(&input, outer);
504                    self.bind_scalar_routines_for_storage(engine, on, &input, subqueries, params)?;
505                }
506                Ok(())
507            }
508            SourcePlan::Subquery { body, .. } => self
509                .bind_query_routines_for_storage(engine, body, params, outer)
510                .map(|_| ()),
511            SourcePlan::Values { rows, .. } => {
512                let input = outer.cloned().unwrap_or_default();
513                for expression in rows.iter_mut().flatten() {
514                    self.bind_scalar_routines_for_storage(
515                        engine, expression, &input, subqueries, params,
516                    )?;
517                }
518                Ok(())
519            }
520            SourcePlan::Function {
521                name,
522                relations,
523                args,
524                ..
525            } => {
526                let local = crate::semantics::builtin_function_dispatch_name(name);
527                if crate::registry::is_operator_join_table_function(&local) {
528                    let (left, right) = operator_join_relation_schemas(
529                        &self.catalog,
530                        &self.resolution,
531                        relations.as_ref(),
532                    )?;
533                    let constant = RowSchema::default();
534                    for (position, expression) in args.iter_mut().enumerate() {
535                        let input = match position {
536                            0 => &left,
537                            1 => &right,
538                            _ => &constant,
539                        };
540                        self.bind_scalar_routines_for_storage(
541                            engine, expression, input, subqueries, params,
542                        )?;
543                    }
544                    return Ok(());
545                }
546                let input = outer.cloned().unwrap_or_default();
547                for expression in args {
548                    self.bind_scalar_routines_for_storage(
549                        engine, expression, &input, subqueries, params,
550                    )?;
551                }
552                Ok(())
553            }
554            SourcePlan::FunctionGroup { functions, .. } => {
555                for function in functions {
556                    let local = crate::semantics::builtin_function_dispatch_name(&function.name);
557                    if crate::registry::is_operator_join_table_function(&local) {
558                        let (left, right) = operator_join_relation_schemas(
559                            &self.catalog,
560                            &self.resolution,
561                            function.relations.as_ref(),
562                        )?;
563                        let constant = RowSchema::default();
564                        for (position, expression) in function.args.iter_mut().enumerate() {
565                            let input = match position {
566                                0 => &left,
567                                1 => &right,
568                                _ => &constant,
569                            };
570                            self.bind_scalar_routines_for_storage(
571                                engine, expression, input, subqueries, params,
572                            )?;
573                        }
574                        continue;
575                    }
576                    let input = outer.cloned().unwrap_or_default();
577                    for expression in &mut function.args {
578                        self.bind_scalar_routines_for_storage(
579                            engine, expression, &input, subqueries, params,
580                        )?;
581                    }
582                }
583                Ok(())
584            }
585            SourcePlan::Table { .. } => Ok(()),
586        }
587    }
588
589    pub(super) fn bind_scalar_routines_for_storage(
590        &mut self,
591        engine: &dyn RoutineResolution,
592        expression: &mut ScalarExpr,
593        schema: &RowSchema,
594        subqueries: &[QueryPlan],
595        params: &[SQLParam],
596    ) -> Result<(), SQLError> {
597        let schema = self.with_stored_outer_internal_aliases(schema);
598        let schema = &schema;
599        self.canonicalize_stored_outer_columns(expression, schema);
600        self.expand_stored_whole_rows(expression, schema);
601        self.canonicalize_routine_parameters(expression, schema);
602        self.resolve_variable_sites(expression, schema);
603        match self.scalar_binding {
604            super::ScalarBindingMode::References => return Ok(()),
605            super::ScalarBindingMode::CompositeInputs => {
606                return self.retain_composite_inputs_in_scope(
607                    engine, expression, schema, subqueries, params,
608                )
609            }
610            super::ScalarBindingMode::Stored => {}
611        }
612        let mut failure = None;
613        crate::plan::rewrite_scalar_expression(expression, &mut |expression| {
614            if failure.is_some() {
615                return;
616            }
617            if matches!(expression, ScalarExpr::Func { order_syntax, name, binding, order_by, .. }
618                if super::ordered_calls::uses_ordered_arguments(*order_syntax, name, binding.as_ref(), order_by.len())
619                    || super::ordered_calls::is_ordered_set(name))
620            {
621                if let Err(error) = self.bind_ordered_function_for_storage(
622                    engine, expression, schema, subqueries, params,
623                ) {
624                    failure = Some(error);
625                }
626                return;
627            }
628            let ScalarExpr::Func {
629                name,
630                binding,
631                args,
632                ..
633            } = expression
634            else {
635                return;
636            };
637            if let Some(dispatch) = binding.as_ref().and_then(|binding| binding.dispatch) {
638                if let crate::ast::FunctionDispatch::NumericOperator(operator) = dispatch {
639                    let selected = (|| {
640                        let resolver = self.query_function_type_resolver_for_subqueries(
641                            engine, args, schema, subqueries, params,
642                        )?;
643                        let (_, types, _) = crate::function_call_argument_signature(
644                            args,
645                            schema,
646                            params,
647                            Some(&resolver),
648                        )?;
649                        crate::type_resolution::numeric_operator_types(operator, &types)
650                    })();
651                    match selected {
652                        Ok(selected) => {
653                            binding
654                                .as_mut()
655                                .expect("structural operator binding")
656                                .argument_types = selected
657                                .arguments
658                                .iter()
659                                .map(crate::ColumnType::sql_name)
660                                .collect();
661                        }
662                        Err(error) => failure = Some(error),
663                    }
664                }
665                return;
666            }
667            if let Err(error) = self.bind_scalar_function_for_storage(
668                engine, name, binding, args, schema, subqueries, params,
669            ) {
670                failure = Some(error);
671            }
672        });
673        failure.map_or(Ok(()), Err)?;
674        self.bind_stored_scalar_types(engine, expression, schema, subqueries, params)?;
675        self.record_composite_dependencies(engine, expression, schema, subqueries, params)
676    }
677
678    /// Name user-defined types by OID identity and keep the enum constants that binding coerces from `unknown` literals by label identity, as `PostgreSQL` stores type and label OIDs in analyzed expressions.
679    fn bind_stored_scalar_types(
680        &mut self,
681        engine: &dyn RoutineResolution,
682        expression: &mut ScalarExpr,
683        schema: &RowSchema,
684        subqueries: &[QueryPlan],
685        params: &[SQLParam],
686    ) -> Result<(), SQLError> {
687        super::stored_types::bind_scalar_type_identities(expression, &mut |name| {
688            engine.resolve_type_name(name)
689        })?;
690        let resolver = self.query_function_type_resolver_for_subqueries(
691            engine,
692            std::slice::from_ref(expression),
693            schema,
694            subqueries,
695            params,
696        )?;
697        // The coercions binding adds to an operator's operands, the constants it reads from `unknown` literals and the relabels of `oid` alias operands, are stored, as `PostgreSQL` stores them in an analyzed expression.
698        crate::type_resolution::store_operand_coercions(expression, schema, params, &resolver)?;
699        if !crate::type_resolution::contains_unknown_literal(expression) {
700            return Ok(());
701        }
702        crate::type_resolution::fold_stored_enum_constants(expression, schema, params, &resolver)
703            .map(drop)
704    }
705
706    #[expect(
707        clippy::too_many_arguments,
708        reason = "keeps execution context inputs aligned"
709    )]
710    fn bind_scalar_function_for_storage(
711        &mut self,
712        engine: &dyn RoutineResolution,
713        name: &str,
714        binding: &mut Option<FunctionBinding>,
715        args: &[ScalarExpr],
716        schema: &RowSchema,
717        subqueries: &[QueryPlan],
718        params: &[SQLParam],
719    ) -> Result<(), SQLError> {
720        let resolver = self.query_function_type_resolver_for_subqueries(
721            engine, args, schema, subqueries, params,
722        )?;
723        let (argument_names, argument_types, explicit_variadic) =
724            crate::function_call_argument_signature(args, schema, params, Some(&resolver))?;
725        let selected = if crate::is_fixed_builtin(name) {
726            crate::resolve_fixed_builtin_call(
727                name,
728                binding.as_ref(),
729                &argument_names,
730                &argument_types,
731                explicit_variadic,
732                Some(&resolver),
733            )?
734            .map(|resolved| resolved.selected)
735        } else if let Some(array) = crate::type_resolution::resolve_array_transform_call(
736            name,
737            binding.as_ref(),
738            args,
739            &argument_types,
740            explicit_variadic,
741            &resolver,
742        )? {
743            array.overload
744        } else {
745            resolver.resolve_function_overload(
746                name,
747                binding.as_ref(),
748                &argument_names,
749                &argument_types,
750                explicit_variadic,
751            )?
752        };
753        if let Some(selected) = selected {
754            *binding = Some(selected.binding);
755        }
756        Ok(())
757    }
758}
759
760pub fn bind_query_plan_routines_for_storage(
761    engine: &dyn RoutineResolution,
762    plan: &mut QueryPlan,
763    params: &[SQLParam],
764    ctes: &BindingContext,
765    outer: Option<&RowSchema>,
766) -> Result<RowSchema, SQLError> {
767    SchemaScope::for_analysis(ctes)?.bind_query_routines_for_storage(engine, plan, params, outer)
768}
769
770/// Bind a copy of a query lowered from stored syntax, keeping its shape so every bound identity can be carried back to that syntax.
771pub fn bind_syntax_query_plan_routines(
772    engine: &dyn RoutineResolution,
773    plan: &mut QueryPlan,
774    params: &[SQLParam],
775    ctes: &BindingContext,
776    outer: Option<&RowSchema>,
777) -> Result<RowSchema, SQLError> {
778    let mut scope = SchemaScope::for_analysis(ctes)?;
779    scope.preserve_syntax_shape = true;
780    scope.bind_query_routines_for_storage(engine, plan, params, outer)
781}
782
783/// Bind every routine call owned by a stored scalar expression and validate its complete query-valued descendants against the expression's row scope. The plan keeps the shape of the syntax it was lowered from.
784pub fn bind_expression_plan_routines_for_storage(
785    engine: &dyn RoutineResolution,
786    plan: &mut ExpressionPlan,
787    params: &[SQLParam],
788    ctes: &BindingContext,
789    schema: &RowSchema,
790) -> Result<Option<ColumnType>, SQLError> {
791    let mut scope = SchemaScope::for_analysis(ctes)?;
792    scope.preserve_syntax_shape = true;
793    scope.stored_expression_outer = Some(schema.clone());
794    for subquery in &mut plan.subqueries {
795        scope.bind_query_routines_for_storage(engine, subquery, params, Some(schema))?;
796    }
797    scope.bind_scalar_routines_for_storage(
798        engine,
799        &mut plan.scalar,
800        schema,
801        &plan.subqueries,
802        params,
803    )?;
804    scope.bind_expression_type(engine, &plan.scalar, schema, &plan.subqueries, params)
805}