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