Skip to main content

uqa_sql/plpgsql/
binding.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Variable binding across expressions, queries, and statements.
8
9use super::{Expr, FromClause, MergeWhen, Projection, Result, SelectStmt, Statement, Value, CTE};
10use crate::ast::InternalColumnRef;
11
12/// Runtime datum value together with the concrete SQL type declared by PL/pgSQL. The type is optional for composite fields and pseudo-types whose runtime carrier already identifies their category.
13#[derive(Debug, Clone)]
14pub struct ResolvedVariable {
15    pub value: Value,
16    pub declared_type: Option<String>,
17    /// Selected implicit collation of a declared variable or event field. Absence preserves the declared type's default.
18    pub collation_oid: Option<u32>,
19}
20
21impl ResolvedVariable {
22    #[must_use]
23    pub fn untyped(value: Value) -> Self {
24        Self {
25            value,
26            declared_type: None,
27            collation_oid: None,
28        }
29    }
30
31    pub(crate) fn into_expression(self) -> Expr {
32        match self.declared_type {
33            Some(ty) => Expr::TypedLiteral {
34                collation_oid: self.collation_oid,
35                composite_source: None,
36                value: self.value,
37                ty,
38            },
39            None => Expr::Literal(self.value),
40        }
41    }
42}
43
44/// Resolves routine variables while a compiled expression / statement
45/// is being specialized for one execution.
46pub trait VariableResolver {
47    /// Concrete parameter metadata for a declared datum; catalog implementations
48    /// preserve user-type identity instead of inferring from a carrier value.
49    fn parameter_type(&self, name: &str) -> Option<crate::ColumnType> {
50        crate::ColumnType::from_sql_name(name).ok()
51    }
52
53    /// Current value of an unqualified name. `Ok(None)` leaves the
54    /// column reference for the engine to resolve.
55    fn resolve_name(&mut self, name: &str) -> Result<Option<ResolvedVariable>>;
56    /// Current value of `qualifier.column` (record field access).
57    fn resolve_qualified(
58        &mut self,
59        qualifier: &str,
60        column: &str,
61    ) -> Result<Option<ResolvedVariable>>;
62    /// Value of a positional `$n` reference (function arguments).
63    fn resolve_param(&mut self, index: usize) -> Result<Option<ResolvedVariable>>;
64
65    /// Optional expression-level rewrite hook. The default preserves the variable-substitution behavior used by PL/pgSQL while allowing catalog lifecycle code to rewrite a reference without fabricating a literal value.
66    fn rewrite_name(&mut self, name: &str) -> Result<Option<Expr>> {
67        Ok(self
68            .resolve_name(name)?
69            .map(ResolvedVariable::into_expression))
70    }
71
72    /// Expression-level counterpart of [`Self::resolve_qualified`].
73    fn rewrite_qualified(&mut self, qualifier: &str, column: &str) -> Result<Option<Expr>> {
74        Ok(self
75            .resolve_qualified(qualifier, column)?
76            .map(ResolvedVariable::into_expression))
77    }
78
79    /// Expand `qualifier.*` when the containing SQL construct treats it as a
80    /// list item. Scalar binding deliberately does not call this hook because
81    /// `PostgreSQL` treats the same syntax as a composite whole-row value in
82    /// scalar contexts.
83    fn rewrite_qualified_star(&mut self, _qualifier: &str) -> Result<Option<Vec<Expr>>> {
84        Ok(None)
85    }
86
87    /// Resolve `qualifier.*` when it appears in a scalar context and therefore denotes one composite whole-row value rather than a projection list.
88    fn rewrite_qualified_whole_row(&mut self, _qualifier: &str) -> Result<Option<Expr>> {
89        Ok(None)
90    }
91
92    /// Expression-level counterpart of [`Self::resolve_param`].
93    fn rewrite_param(&mut self, index: usize) -> Result<Option<Expr>> {
94        Ok(self
95            .resolve_param(index)?
96            .map(ResolvedVariable::into_expression))
97    }
98
99    /// Observe or replace an executor-only structural column reference. SQL
100    /// variable resolvers normally leave these untouched.
101    fn rewrite_internal(&mut self, _column: InternalColumnRef) -> Result<Option<Expr>> {
102        Ok(None)
103    }
104}
105
106/// Rewrite an expression, substituting resolvable variable references
107/// with literals. References the resolver declines stay untouched.
108#[expect(
109    clippy::too_many_lines,
110    reason = "PL/pgSQL lowering preserves parser order and datum validation"
111)]
112pub fn bind_expr(expr: &Expr, r: &mut dyn VariableResolver) -> Result<Expr> {
113    Ok(match expr {
114        Expr::Column(name) => match r.rewrite_name(name)? {
115            Some(value) => value,
116            None => expr.clone(),
117        },
118        Expr::QualifiedColumn {
119            qualifier, column, ..
120        } => match r.rewrite_qualified(qualifier, column)? {
121            Some(value) => value,
122            None => expr.clone(),
123        },
124        Expr::Param(index) => match r.rewrite_param(*index)? {
125            Some(value) => value,
126            None => expr.clone(),
127        },
128        Expr::InternalColumn(column) => match r.rewrite_internal(*column)? {
129            Some(value) => value,
130            None => expr.clone(),
131        },
132        Expr::QualifiedStar(qualifier) => r
133            .rewrite_qualified_whole_row(qualifier)?
134            .unwrap_or_else(|| expr.clone()),
135        Expr::Default | Expr::Literal(_) | Expr::TypedLiteral { .. } | Expr::Star => expr.clone(),
136        Expr::Func {
137            order_syntax,
138            name,
139            binding,
140            args,
141            distinct,
142            order_by,
143            filter,
144        } => Expr::Func {
145            order_syntax: *order_syntax,
146            name: name.clone(),
147            binding: binding.clone(),
148            args: bind_exprs(args, r)?,
149            distinct: *distinct,
150            order_by: bind_order_by(order_by, r)?,
151            filter: match filter {
152                Some(f) => Some(Box::new(bind_expr(f, r)?)),
153                None => None,
154            },
155        },
156        Expr::Array(items) => Expr::Array(bind_exprs(items, r)?),
157        Expr::Row(items) => Expr::Row(bind_exprs(items, r)?),
158        Expr::CompositeRow { items, binding } => Expr::CompositeRow {
159            items: bind_exprs(items, r)?,
160            binding: binding.clone(),
161        },
162        Expr::Binary { op, lhs, rhs } => Expr::Binary {
163            op: *op,
164            lhs: Box::new(bind_expr(lhs, r)?),
165            rhs: Box::new(bind_expr(rhs, r)?),
166        },
167        Expr::UnaryMinus(inner) => Expr::UnaryMinus(Box::new(bind_expr(inner, r)?)),
168        Expr::Not(inner) => Expr::Not(Box::new(bind_expr(inner, r)?)),
169        Expr::And(items) => Expr::And(bind_exprs(items, r)?),
170        Expr::Or(items) => Expr::Or(bind_exprs(items, r)?),
171        Expr::IsNull { expr, negated } => Expr::IsNull {
172            expr: Box::new(bind_expr(expr, r)?),
173            negated: *negated,
174        },
175        Expr::Between { expr, low, high } => Expr::Between {
176            expr: Box::new(bind_expr(expr, r)?),
177            low: Box::new(bind_expr(low, r)?),
178            high: Box::new(bind_expr(high, r)?),
179        },
180        Expr::InList {
181            expr,
182            list,
183            negated,
184        } => Expr::InList {
185            expr: Box::new(bind_expr(expr, r)?),
186            list: bind_exprs(list, r)?,
187            negated: *negated,
188        },
189        Expr::WindowCall {
190            name,
191            args,
192            spec,
193            filter,
194            modifiers,
195        } => Expr::WindowCall {
196            modifiers: *modifiers,
197            name: name.clone(),
198            args: bind_exprs(args, r)?,
199            spec: Box::new(bind_window_spec(spec, r)?),
200            filter: match filter {
201                Some(f) => Some(Box::new(bind_expr(f, r)?)),
202                None => None,
203            },
204        },
205        Expr::Case {
206            base,
207            when,
208            else_branch,
209        } => Expr::Case {
210            base: match base {
211                Some(b) => Some(Box::new(bind_expr(b, r)?)),
212                None => None,
213            },
214            when: when
215                .iter()
216                .map(|(c, v)| Ok((bind_expr(c, r)?, bind_expr(v, r)?)))
217                .collect::<Result<Vec<_>>>()?,
218            else_branch: match else_branch {
219                Some(e) => Some(Box::new(bind_expr(e, r)?)),
220                None => None,
221            },
222        },
223        Expr::Cast { expr, ty, implicit } => Expr::Cast {
224            implicit: *implicit,
225            expr: Box::new(bind_expr(expr, r)?),
226            ty: ty.clone(),
227        },
228        Expr::ScalarSubquery(body) => Expr::ScalarSubquery(Box::new(bind_select(body, r)?)),
229        Expr::Exists { body, negated } => Expr::Exists {
230            body: Box::new(bind_select(body, r)?),
231            negated: *negated,
232        },
233        Expr::InSubquery {
234            expr,
235            body,
236            negated,
237        } => Expr::InSubquery {
238            expr: Box::new(bind_expr(expr, r)?),
239            body: Box::new(bind_select(body, r)?),
240            negated: *negated,
241        },
242    })
243}
244
245/// A frame offset may name a PL/pgSQL variable, which becomes a parameter of the query.
246fn bind_frame_bound(
247    bound: &crate::ast::FrameBound,
248    r: &mut dyn VariableResolver,
249) -> Result<crate::ast::FrameBound> {
250    use crate::ast::FrameBound;
251    Ok(match bound {
252        FrameBound::Preceding(offset) => FrameBound::Preceding(Box::new(bind_expr(offset, r)?)),
253        FrameBound::Following(offset) => FrameBound::Following(Box::new(bind_expr(offset, r)?)),
254        FrameBound::UnboundedPreceding => FrameBound::UnboundedPreceding,
255        FrameBound::UnboundedFollowing => FrameBound::UnboundedFollowing,
256        FrameBound::CurrentRow => FrameBound::CurrentRow,
257    })
258}
259
260pub(super) fn bind_exprs(exprs: &[Expr], r: &mut dyn VariableResolver) -> Result<Vec<Expr>> {
261    exprs.iter().map(|e| bind_expr(e, r)).collect()
262}
263
264pub(super) fn bind_opt_expr(
265    expr: Option<&Expr>,
266    r: &mut dyn VariableResolver,
267) -> Result<Option<Expr>> {
268    match expr {
269        Some(e) => Ok(Some(bind_expr(e, r)?)),
270        None => Ok(None),
271    }
272}
273
274pub(super) fn bind_order_by(
275    items: &[crate::ast::OrderBy],
276    r: &mut dyn VariableResolver,
277) -> Result<Vec<crate::ast::OrderBy>> {
278    items
279        .iter()
280        .map(|o| {
281            Ok(crate::ast::OrderBy {
282                expr: bind_expr(&o.expr, r)?,
283                descending: o.descending,
284                nulls: o.nulls,
285            })
286        })
287        .collect()
288}
289
290pub(super) fn bind_projections(
291    items: &[Projection],
292    r: &mut dyn VariableResolver,
293) -> Result<Vec<Projection>> {
294    items
295        .iter()
296        .map(|p| {
297            Ok(Projection {
298                expr: bind_expr(&p.expr, r)?,
299                alias: p.alias.clone(),
300            })
301        })
302        .collect()
303}
304
305pub(super) fn bind_assignments(
306    items: &[(crate::ast::AssignmentTargets, Expr)],
307    r: &mut dyn VariableResolver,
308) -> Result<Vec<(crate::ast::AssignmentTargets, Expr)>> {
309    items
310        .iter()
311        .map(|(target, expression)| {
312            Ok((
313                {
314                    let mut group = target.clone();
315                    for target in group.targets_mut() {
316                        *target = bind_assignment_target(target, r)?;
317                    }
318                    group
319                },
320                bind_expr(expression, r)?,
321            ))
322        })
323        .collect()
324}
325
326fn bind_assignment_target(
327    target: &crate::ast::AssignmentTarget,
328    resolver: &mut dyn VariableResolver,
329) -> Result<crate::ast::AssignmentTarget> {
330    let mut target = target.clone();
331    for expression in target.expressions_mut() {
332        *expression = bind_expr(expression, resolver)?;
333    }
334    Ok(target)
335}
336
337pub(super) fn bind_ctes(items: &[CTE], r: &mut dyn VariableResolver) -> Result<Vec<CTE>> {
338    items
339        .iter()
340        .map(|cte| {
341            Ok(CTE {
342                name: cte.name.clone(),
343                columns: cte.columns.clone(),
344                recursive: cte.recursive,
345                materialization: cte.materialization,
346                search: cte.search.clone(),
347                cycle: cte
348                    .cycle
349                    .as_ref()
350                    .map(|cycle| -> Result<crate::ast::CteCycleClause> {
351                        Ok(crate::ast::CteCycleClause {
352                            columns: cycle.columns.clone(),
353                            mark_column: cycle.mark_column.clone(),
354                            mark_value: bind_expr(&cycle.mark_value, r)?,
355                            mark_default: bind_expr(&cycle.mark_default, r)?,
356                            path_column: cycle.path_column.clone(),
357                        })
358                    })
359                    .transpose()?,
360                body: crate::ast::CteBody::try_from(bind_statement(
361                    &cte.body.clone().into_statement(),
362                    r,
363                )?)?,
364            })
365        })
366        .collect()
367}
368
369pub(super) fn bind_rows(
370    rows: &[Vec<Expr>],
371    r: &mut dyn VariableResolver,
372) -> Result<Vec<Vec<Expr>>> {
373    rows.iter().map(|row| bind_exprs(row, r)).collect()
374}
375
376fn bind_window_spec(
377    spec: &crate::ast::WindowSpec,
378    r: &mut dyn VariableResolver,
379) -> Result<crate::ast::WindowSpec> {
380    Ok(crate::ast::WindowSpec {
381        definition: spec.definition,
382        raw_definition: None,
383        reference: spec.reference.clone(),
384        partition_by: bind_exprs(&spec.partition_by, r)?,
385        order_by: bind_order_by(&spec.order_by, r)?,
386        frame: spec
387            .frame
388            .as_ref()
389            .map(|frame| -> Result<crate::ast::WindowFrame> {
390                Ok(crate::ast::WindowFrame {
391                    start: bind_frame_bound(&frame.start, r)?,
392                    end: bind_frame_bound(&frame.end, r)?,
393                    ..frame.clone()
394                })
395            })
396            .transpose()?,
397    })
398}
399
400/// Rewrite a `SELECT` body, substituting resolvable variables.
401pub fn bind_select(stmt: &SelectStmt, r: &mut dyn VariableResolver) -> Result<SelectStmt> {
402    Ok(SelectStmt {
403        windows: stmt
404            .windows
405            .iter()
406            .map(|window| {
407                Ok(crate::ast::WindowDefinition {
408                    name: window.name.clone(),
409                    inherited: window.inherited,
410                    spec: bind_window_spec(&window.spec, r)?,
411                })
412            })
413            .collect::<Result<Vec<_>>>()?,
414        projections: bind_projections(&stmt.projections, r)?,
415        values: bind_rows(&stmt.values, r)?,
416        from: match stmt.from.as_ref() {
417            Some(f) => Some(bind_from(f, r)?),
418            None => None,
419        },
420        r#where: bind_opt_expr(stmt.r#where.as_ref(), r)?,
421        group_by: bind_exprs(&stmt.group_by, r)?,
422        grouping_sets: stmt
423            .grouping_sets
424            .iter()
425            .map(|set| bind_exprs(set, r))
426            .collect::<Result<Vec<_>>>()?,
427        group_distinct: stmt.group_distinct,
428        having: bind_opt_expr(stmt.having.as_ref(), r)?,
429        order_by: bind_order_by(&stmt.order_by, r)?,
430        limit: bind_opt_expr(stmt.limit.as_ref(), r)?,
431        with_ties: stmt.with_ties,
432        offset: bind_opt_expr(stmt.offset.as_ref(), r)?,
433        with: bind_ctes(&stmt.with, r)?,
434        set_op: match stmt.set_op.as_ref() {
435            Some(op) => Some(Box::new(crate::ast::SetOp {
436                kind: op.kind,
437                all: op.all,
438                left: op
439                    .left
440                    .as_ref()
441                    .map(|left| bind_select(left, r).map(Box::new))
442                    .transpose()?,
443                right: bind_select(&op.right, r)?,
444                combined_order_by: bind_order_by(&op.combined_order_by, r)?,
445                combined_limit: bind_opt_expr(op.combined_limit.as_ref(), r)?,
446                combined_with_ties: op.combined_with_ties,
447                combined_offset: bind_opt_expr(op.combined_offset.as_ref(), r)?,
448            })),
449            None => None,
450        },
451        distinct: stmt.distinct,
452        distinct_on: bind_exprs(&stmt.distinct_on, r)?,
453        locking: stmt.locking.clone(),
454    })
455}
456
457pub(super) fn bind_from(from: &FromClause, r: &mut dyn VariableResolver) -> Result<FromClause> {
458    Ok(match from {
459        FromClause::Table { .. } => from.clone(),
460        FromClause::Join {
461            left,
462            right,
463            kind,
464            on,
465            using,
466            natural,
467            alias,
468            column_aliases,
469            lateral,
470        } => FromClause::Join {
471            left: Box::new(bind_from(left, r)?),
472            right: Box::new(bind_from(right, r)?),
473            kind: *kind,
474            on: bind_opt_expr(on.as_ref(), r)?,
475            using: using.clone(),
476            natural: *natural,
477            alias: alias.clone(),
478            column_aliases: column_aliases.clone(),
479            lateral: *lateral,
480        },
481        FromClause::Values {
482            rows,
483            alias,
484            column_aliases,
485            internal_relation,
486            internal_column_types,
487            internal_column_collations,
488        } => FromClause::Values {
489            rows: bind_rows(rows, r)?,
490            alias: alias.clone(),
491            column_aliases: column_aliases.clone(),
492            internal_relation: *internal_relation,
493            internal_column_types: internal_column_types.clone(),
494            internal_column_collations: internal_column_collations.clone(),
495        },
496        FromClause::Function {
497            name,
498            binding,
499            output_name,
500            relations,
501            args,
502            alias,
503            column_aliases,
504            ordinality,
505            column_types,
506        } => FromClause::Function {
507            name: name.clone(),
508            binding: binding.clone(),
509            output_name: output_name.clone(),
510            relations: relations.clone(),
511            args: bind_exprs(args, r)?,
512            alias: alias.clone(),
513            column_aliases: column_aliases.clone(),
514            ordinality: *ordinality,
515            column_types: column_types.clone(),
516        },
517        FromClause::FunctionGroup {
518            functions,
519            alias,
520            column_aliases,
521            ordinality,
522        } => FromClause::FunctionGroup {
523            functions: functions
524                .iter()
525                .map(|function| {
526                    Ok(crate::ast::TableFunction {
527                        name: function.name.clone(),
528                        binding: function.binding.clone(),
529                        output_name: function.output_name.clone(),
530                        relations: function.relations.clone(),
531                        args: bind_exprs(&function.args, r)?,
532                        column_aliases: function.column_aliases.clone(),
533                        column_types: function.column_types.clone(),
534                    })
535                })
536                .collect::<Result<Vec<_>>>()?,
537            alias: alias.clone(),
538            column_aliases: column_aliases.clone(),
539            ordinality: *ordinality,
540        },
541        FromClause::Subquery {
542            body,
543            alias,
544            column_aliases,
545        } => FromClause::Subquery {
546            body: Box::new(bind_select(body, r)?),
547            alias: alias.clone(),
548            column_aliases: column_aliases.clone(),
549        },
550    })
551}
552
553/// Rewrite a full statement, substituting resolvable variables in
554/// every expression position. Statements without expression payloads
555/// pass through unchanged.
556#[expect(
557    clippy::too_many_lines,
558    reason = "PL/pgSQL lowering preserves parser order and datum validation"
559)]
560pub fn bind_statement(stmt: &Statement, r: &mut dyn VariableResolver) -> Result<Statement> {
561    Ok(match stmt {
562        Statement::Select(body) => Statement::Select(Box::new(bind_select(body, r)?)),
563        Statement::Insert(insert) => {
564            let mut out = insert.clone();
565            out.columns = insert
566                .columns
567                .iter()
568                .map(|target| bind_assignment_target(target, r))
569                .collect::<Result<_>>()?;
570            out.with = bind_ctes(&insert.with, r)?;
571            out.rows = bind_rows(&insert.rows, r)?;
572            out.select_source = match insert.select_source.as_ref() {
573                Some(body) => Some(Box::new(bind_select(body, r)?)),
574                None => None,
575            };
576            out.on_conflict = match insert.on_conflict.as_ref() {
577                Some(oc) => Some(crate::ast::OnConflict {
578                    predicate: bind_opt_expr(oc.predicate.as_deref(), r)?.map(Box::new),
579                    constraint: oc.constraint.clone(),
580                    conflict_columns: oc.conflict_columns.clone(),
581                    expressions: oc
582                        .expressions
583                        .iter()
584                        .map(|expr| bind_expr(expr, r))
585                        .collect::<Result<Vec<_>>>()?,
586                    action: match &oc.action {
587                        crate::ast::OnConflictAction::Nothing => {
588                            crate::ast::OnConflictAction::Nothing
589                        }
590                        crate::ast::OnConflictAction::Update {
591                            assignments,
592                            r#where,
593                        } => crate::ast::OnConflictAction::Update {
594                            assignments: bind_assignments(assignments, r)?,
595                            r#where: bind_opt_expr(r#where.as_deref(), r)?.map(Box::new),
596                        },
597                    },
598                }),
599                None => None,
600            };
601            out.returning = bind_projections(&insert.returning, r)?;
602            Statement::Insert(out)
603        }
604        Statement::Update(update) => {
605            let mut out = update.clone();
606            out.assignments = bind_assignments(&update.assignments, r)?;
607            out.r#where = bind_opt_expr(update.r#where.as_ref(), r)?;
608            out.with = bind_ctes(&update.with, r)?;
609            out.from = match update.from.as_ref() {
610                Some(f) => Some(bind_from(f, r)?),
611                None => None,
612            };
613            out.returning = bind_projections(&update.returning, r)?;
614            Statement::Update(out)
615        }
616        Statement::Delete(delete) => {
617            let mut out = delete.clone();
618            out.r#where = bind_opt_expr(delete.r#where.as_ref(), r)?;
619            out.with = bind_ctes(&delete.with, r)?;
620            out.using = match delete.using.as_ref() {
621                Some(f) => Some(bind_from(f, r)?),
622                None => None,
623            };
624            out.returning = bind_projections(&delete.returning, r)?;
625            Statement::Delete(out)
626        }
627        Statement::Values { rows } => Statement::Values {
628            rows: bind_rows(rows, r)?,
629        },
630        Statement::CreateTableAs {
631            name,
632            if_not_exists,
633            column_names,
634            with_no_data,
635            select_into,
636            persistence,
637            on_commit,
638            body,
639        } => Statement::CreateTableAs {
640            name: name.clone(),
641            if_not_exists: *if_not_exists,
642            column_names: column_names.clone(),
643            with_no_data: *with_no_data,
644            select_into: *select_into,
645            persistence: *persistence,
646            on_commit: *on_commit,
647            body: Box::new(bind_select(body, r)?),
648        },
649        Statement::CreateMaterializedView {
650            name,
651            column_names,
652            if_not_exists,
653            with_no_data,
654            options,
655            body,
656        } => Statement::CreateMaterializedView {
657            name: name.clone(),
658            column_names: column_names.clone(),
659            if_not_exists: *if_not_exists,
660            with_no_data: *with_no_data,
661            options: options.clone(),
662            body: Box::new(bind_select(body, r)?),
663        },
664        Statement::Explain {
665            analyze,
666            verbose,
667            format,
668            body,
669        } => Statement::Explain {
670            analyze: *analyze,
671            verbose: *verbose,
672            format: format.clone(),
673            body: Box::new(bind_statement(body, r)?),
674        },
675        Statement::DeclareCursor(cursor) => {
676            let mut out = cursor.clone();
677            out.query = Box::new(bind_select(&cursor.query, r)?);
678            Statement::DeclareCursor(out)
679        }
680        Statement::Merge(merge) => {
681            let mut out = merge.clone();
682            out.with = bind_ctes(&merge.with, r)?;
683            out.source = bind_from(&merge.source, r)?;
684            out.join_condition = bind_expr(&merge.join_condition, r)?;
685            out.when_clauses = merge
686                .when_clauses
687                .iter()
688                .map(|w| bind_merge_when(w, r))
689                .collect::<Result<Vec<_>>>()?;
690            out.returning = bind_projections(&merge.returning, r)?;
691            Statement::Merge(out)
692        }
693        Statement::Call { name, args } => Statement::Call {
694            name: name.clone(),
695            args: bind_exprs(args, r)?,
696        },
697        other => other.clone(),
698    })
699}
700
701pub(super) fn bind_merge_when(when: &MergeWhen, r: &mut dyn VariableResolver) -> Result<MergeWhen> {
702    Ok(match when {
703        MergeWhen::UpdateMatched {
704            condition,
705            assignments,
706        } => MergeWhen::UpdateMatched {
707            condition: bind_opt_expr(condition.as_ref(), r)?,
708            assignments: bind_assignments(assignments, r)?,
709        },
710        MergeWhen::DeleteMatched { condition } => MergeWhen::DeleteMatched {
711            condition: bind_opt_expr(condition.as_ref(), r)?,
712        },
713        MergeWhen::UpdateNotMatchedBySource {
714            condition,
715            assignments,
716        } => MergeWhen::UpdateNotMatchedBySource {
717            condition: bind_opt_expr(condition.as_ref(), r)?,
718            assignments: bind_assignments(assignments, r)?,
719        },
720        MergeWhen::DeleteNotMatchedBySource { condition } => MergeWhen::DeleteNotMatchedBySource {
721            condition: bind_opt_expr(condition.as_ref(), r)?,
722        },
723        MergeWhen::InsertNotMatched {
724            condition,
725            columns,
726            overriding,
727            values,
728        } => MergeWhen::InsertNotMatched {
729            condition: bind_opt_expr(condition.as_ref(), r)?,
730            columns: columns
731                .iter()
732                .map(|target| bind_assignment_target(target, r))
733                .collect::<Result<_>>()?,
734            overriding: *overriding,
735            values: bind_exprs(values, r)?,
736        },
737        MergeWhen::NothingMatched { condition } => MergeWhen::NothingMatched {
738            condition: bind_opt_expr(condition.as_ref(), r)?,
739        },
740        MergeWhen::NothingNotMatched { condition } => MergeWhen::NothingNotMatched {
741            condition: bind_opt_expr(condition.as_ref(), r)?,
742        },
743        MergeWhen::NothingNotMatchedBySource { condition } => {
744            MergeWhen::NothingNotMatchedBySource {
745                condition: bind_opt_expr(condition.as_ref(), r)?,
746            }
747        }
748    })
749}