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