Skip to main content

uqa_sql/binding/
syntax_sites.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Bound copies of stored syntax, read back in the order of that syntax. Catalog-owned expressions and statements are stored as syntax and bound by lowering a copy; binding keeps the copy's shape, so reading the lowered copy and its bound form side by side, in the order the stored syntax visitor walks the syntax, pairs every routine call, cast and `unknown` literal with the syntax node it came from. The lowered copy tells which bound nodes stand for syntax literals: variable binding may replace column references by typed placeholders, which are not syntax.
8
9use super::stored_routines::BoundRoutineReference;
10use crate::plan::CommandPlan;
11use crate::plan::{CtePlanBody, ExpressionPlan, QueryPlan, RelationalPlan, SourcePlan};
12use crate::{SQLError, ScalarExpr, ScalarFrameBound};
13use uqa_core::Value;
14
15/// An expression site of stored syntax and what binding recorded for it.
16#[derive(Debug, Clone, PartialEq)]
17pub enum ValueSite {
18    /// Positions grouped into a scalar-array comparison; the other IN items become individual comparisons.
19    Membership { array_items: Vec<usize> },
20    /// A cast, with the type name binding gave it.
21    Cast(String),
22    /// A ROW constructor bound to its original composite attribute positions.
23    CompositeRow(crate::ast::CompositeRowBinding),
24    /// An `unknown` literal that binding left unconverted.
25    Literal,
26    /// An `unknown` literal that binding converted to a typed constant.
27    Constant { value: Value, ty: String },
28    /// A cast binding wrapped around the syntax, coercing an operand to the type its operator declares, as a `RelabelType` or an implicit coercion does.
29    Relabel(String),
30    /// A function's written ordering syntax, including a legacy call whose selected binding recovered the distinction.
31    FunctionOrder(crate::ast::FunctionOrderSyntax),
32    /// Any other node of stored syntax, which keeps the sites in step with the syntax so that a relabel reaches the node it wraps.
33    Node,
34}
35
36/// The casts a node begins with.
37fn cast_depth(expression: &ScalarExpr) -> usize {
38    let mut depth = 0;
39    let mut node = expression;
40    while let ScalarExpr::Cast { expr, .. } = node {
41        depth += 1;
42        node = expr;
43    }
44    depth
45}
46
47/// Everything binding recorded for one piece of stored syntax, in syntax order.
48#[derive(Debug, Default)]
49pub struct SyntaxSites {
50    /// Routine calls, each reported after its arguments as the syntax visitor reports calls.
51    pub routines: Vec<BoundRoutineReference>,
52    /// Casts, `unknown` literals and function ordering syntax in pre-order.
53    pub values: Vec<ValueSite>,
54}
55
56/// Sites of a stored expression: `lowered` is the plan lowered from the syntax and `bound` its bound copy. The expression visitor reads a site at every node, so the casts binding adds around a node reach it as relabels.
57pub fn expression_syntax_sites(
58    lowered: &ExpressionPlan,
59    bound: &ExpressionPlan,
60) -> Result<SyntaxSites, SQLError> {
61    let mut walk = Walk::default();
62    walk.scalar(
63        &lowered.scalar,
64        &bound.scalar,
65        (&lowered.subqueries, &bound.subqueries),
66    )?;
67    Ok(walk.sites)
68}
69
70/// Sites of a stored statement lowered to a query.
71pub fn query_syntax_sites(lowered: &QueryPlan, bound: &QueryPlan) -> Result<SyntaxSites, SQLError> {
72    let mut walk = Walk::default();
73    walk.query(lowered, bound, false)?;
74    Ok(walk.sites)
75}
76
77/// Sites from the query adapter for a stored command; its final output is synthetic and has no syntax node.
78pub(super) fn command_query_syntax_sites(
79    lowered: &QueryPlan,
80    bound: &QueryPlan,
81) -> Result<SyntaxSites, SQLError> {
82    let mut walk = Walk::default();
83    walk.query(lowered, bound, true)?;
84    Ok(walk.sites)
85}
86
87fn syntax_projections(
88    projections: &[crate::plan::ProjectionPlan],
89    command_root: bool,
90) -> Result<&[crate::plan::ProjectionPlan], SQLError> {
91    if command_root {
92        projections
93            .split_last()
94            .map(|(_, syntax)| syntax)
95            .ok_or_else(|| shape_error("command output"))
96    } else {
97        Ok(projections)
98    }
99}
100
101type Subqueries<'a> = (&'a [QueryPlan], &'a [QueryPlan]);
102
103fn shape_error(what: &str) -> SQLError {
104    SQLError::Internal(format!("bound stored syntax no longer matches its {what}"))
105}
106
107fn pairs<'a, T>(
108    lowered: &'a [T],
109    bound: &'a [T],
110    what: &str,
111) -> Result<impl Iterator<Item = (&'a T, &'a T)>, SQLError> {
112    if lowered.len() != bound.len() {
113        return Err(shape_error(what));
114    }
115    Ok(lowered.iter().zip(bound))
116}
117
118fn optional_pair<'a, T>(
119    lowered: Option<&'a T>,
120    bound: Option<&'a T>,
121    what: &str,
122) -> Result<Option<(&'a T, &'a T)>, SQLError> {
123    match (lowered, bound) {
124        (Some(lowered), Some(bound)) => Ok(Some((lowered, bound))),
125        (None, None) => Ok(None),
126        _ => Err(shape_error(what)),
127    }
128}
129
130#[derive(Default)]
131struct Walk {
132    sites: SyntaxSites,
133}
134
135impl Walk {
136    fn query(
137        &mut self,
138        lowered: &QueryPlan,
139        bound: &QueryPlan,
140        command_root: bool,
141    ) -> Result<(), SQLError> {
142        for (lowered, bound) in pairs(&lowered.ctes, &bound.ctes, "common table expressions")? {
143            self.cte_body(&lowered.body, &bound.body)?;
144            if let Some((lowered, bound)) =
145                optional_pair(lowered.cycle.as_ref(), bound.cycle.as_ref(), "CYCLE clause")?
146            {
147                self.scalar(&lowered.mark_value, &bound.mark_value, (&[], &[]))?;
148                self.scalar(&lowered.mark_default, &bound.mark_default, (&[], &[]))?;
149            }
150        }
151        match (&lowered.root, &bound.root) {
152            (RelationalPlan::QueryBlock(lowered), RelationalPlan::QueryBlock(bound)) => {
153                let subqueries = (&lowered.subqueries[..], &bound.subqueries[..]);
154                if let Some((lowered, bound)) =
155                    optional_pair(lowered.from.as_ref(), bound.from.as_ref(), "FROM clause")?
156                {
157                    self.source(lowered, bound, subqueries)?;
158                }
159                for (lowered, bound) in pairs(
160                    syntax_projections(&lowered.projections, command_root)?,
161                    syntax_projections(&bound.projections, command_root)?,
162                    "select list",
163                )? {
164                    self.scalar(&lowered.expr, &bound.expr, subqueries)?;
165                }
166                self.optional(lowered.r#where.as_ref(), bound.r#where.as_ref(), subqueries)?;
167                self.scalars(&lowered.group_by, &bound.group_by, subqueries)?;
168                if lowered.grouping_sets.len() != bound.grouping_sets.len() {
169                    return Err(shape_error("grouping sets"));
170                }
171                for (lowered, bound) in lowered.grouping_sets.iter().zip(&bound.grouping_sets) {
172                    self.scalars(lowered, bound, subqueries)?;
173                }
174                self.optional(lowered.having.as_ref(), bound.having.as_ref(), subqueries)?;
175                for (lowered, bound) in pairs(&lowered.order_by, &bound.order_by, "ORDER BY")? {
176                    self.scalar(&lowered.expr, &bound.expr, subqueries)?;
177                }
178                self.optional(lowered.limit.as_ref(), bound.limit.as_ref(), subqueries)?;
179                self.optional(lowered.offset.as_ref(), bound.offset.as_ref(), subqueries)?;
180                self.scalars(&lowered.distinct_on, &bound.distinct_on, subqueries)?;
181                for (lowered, bound) in
182                    pairs(&lowered.windows, &bound.windows, "WINDOW definitions")?
183                {
184                    if lowered.name != bound.name || lowered.inherited != bound.inherited {
185                        return Err(shape_error("WINDOW identity"));
186                    }
187                    let lowered: Vec<_> = lowered.spec.expressions().collect();
188                    let bound: Vec<_> = bound.spec.expressions().collect();
189                    for (lowered, bound) in pairs(&lowered, &bound, "WINDOW expressions")? {
190                        self.scalar(lowered, bound, subqueries)?;
191                    }
192                }
193                Ok(())
194            }
195            (
196                RelationalPlan::SetOp {
197                    left,
198                    right,
199                    order_by,
200                    limit,
201                    offset,
202                    subqueries,
203                    ..
204                },
205                RelationalPlan::SetOp {
206                    left: bound_left,
207                    right: bound_right,
208                    order_by: bound_order,
209                    limit: bound_limit,
210                    offset: bound_offset,
211                    subqueries: bound_subqueries,
212                    ..
213                },
214            ) => {
215                self.query(left, bound_left, false)?;
216                self.query(right, bound_right, false)?;
217                let subqueries = (&subqueries[..], &bound_subqueries[..]);
218                for (lowered, bound) in pairs(order_by, bound_order, "ORDER BY")? {
219                    self.scalar(&lowered.expr, &bound.expr, subqueries)?;
220                }
221                self.optional(limit.as_deref(), bound_limit.as_deref(), subqueries)?;
222                self.optional(offset.as_deref(), bound_offset.as_deref(), subqueries)
223            }
224            (
225                RelationalPlan::Values { rows, subqueries },
226                RelationalPlan::Values {
227                    rows: bound_rows,
228                    subqueries: bound_subqueries,
229                },
230            ) => {
231                let subqueries = (&subqueries[..], &bound_subqueries[..]);
232                for (lowered, bound) in pairs(rows, bound_rows, "VALUES list")? {
233                    self.scalars(lowered, bound, subqueries)?;
234                }
235                Ok(())
236            }
237            _ => Err(shape_error("query")),
238        }
239    }
240
241    /// A data-modifying `WITH` query, in the order the syntax visitor walks the statement.
242    fn cte_body(&mut self, lowered: &CtePlanBody, bound: &CtePlanBody) -> Result<(), SQLError> {
243        match (lowered, bound) {
244            (CtePlanBody::Query(lowered), CtePlanBody::Query(bound)) => {
245                self.query(lowered, bound, false)
246            }
247            (CtePlanBody::Command(lowered), CtePlanBody::Command(bound)) => {
248                self.command(lowered, bound)
249            }
250            _ => Err(shape_error("WITH query")),
251        }
252    }
253
254    fn command(&mut self, lowered: &CommandPlan, bound: &CommandPlan) -> Result<(), SQLError> {
255        for (lowered, bound) in pairs(lowered.ctes(), bound.ctes(), "common table expressions")? {
256            self.cte_body(&lowered.body, &bound.body)?;
257            if let Some((lowered, bound)) =
258                optional_pair(lowered.cycle.as_ref(), bound.cycle.as_ref(), "CYCLE clause")?
259            {
260                self.scalar(&lowered.mark_value, &bound.mark_value, (&[], &[]))?;
261                self.scalar(&lowered.mark_default, &bound.mark_default, (&[], &[]))?;
262            }
263        }
264        let subqueries = (lowered.scalar_subqueries(), bound.scalar_subqueries());
265        match (lowered, bound) {
266            (CommandPlan::Insert(lowered), CommandPlan::Insert(bound)) => {
267                if let Some((lowered, bound)) = optional_pair(
268                    lowered.source.as_deref(),
269                    bound.source.as_deref(),
270                    "INSERT source",
271                )? {
272                    self.query(lowered, bound, false)?;
273                }
274            }
275            (CommandPlan::Update(_), CommandPlan::Update(_))
276            | (CommandPlan::Delete(_), CommandPlan::Delete(_))
277            | (CommandPlan::Merge(_), CommandPlan::Merge(_)) => {
278                if let Some((lowered, bound)) = optional_pair(
279                    lowered.source_input(),
280                    bound.source_input(),
281                    "command source",
282                )? {
283                    self.source(lowered, bound, subqueries)?;
284                }
285            }
286            _ => return Err(shape_error("WITH command")),
287        }
288        let lowered = syntax_expressions(lowered);
289        let bound = syntax_expressions(bound);
290        for (lowered, bound) in pairs(&lowered, &bound, "command expressions")? {
291            self.scalar(lowered, bound, subqueries)?;
292        }
293        Ok(())
294    }
295
296    fn source(
297        &mut self,
298        lowered: &SourcePlan,
299        bound: &SourcePlan,
300        subqueries: Subqueries<'_>,
301    ) -> Result<(), SQLError> {
302        match (lowered, bound) {
303            (SourcePlan::Table { .. }, SourcePlan::Table { .. }) => Ok(()),
304            (
305                SourcePlan::Join {
306                    left, right, on, ..
307                },
308                SourcePlan::Join {
309                    left: bound_left,
310                    right: bound_right,
311                    on: bound_on,
312                    ..
313                },
314            ) => {
315                self.source(left, bound_left, subqueries)?;
316                self.source(right, bound_right, subqueries)?;
317                self.optional(on.as_ref(), bound_on.as_ref(), subqueries)
318            }
319            (
320                SourcePlan::Values { rows, .. },
321                SourcePlan::Values {
322                    rows: bound_rows, ..
323                },
324            ) => {
325                for (lowered, bound) in pairs(rows, bound_rows, "VALUES list")? {
326                    self.scalars(lowered, bound, subqueries)?;
327                }
328                Ok(())
329            }
330            (
331                SourcePlan::Function { args, .. },
332                SourcePlan::Function {
333                    name,
334                    binding,
335                    args: bound_args,
336                    ..
337                },
338            ) => {
339                self.sites.routines.push(BoundRoutineReference {
340                    name: name.clone(),
341                    binding: binding.clone(),
342                });
343                self.scalars(args, bound_args, subqueries)
344            }
345            (
346                SourcePlan::FunctionGroup { functions, .. },
347                SourcePlan::FunctionGroup {
348                    functions: bound_functions,
349                    ..
350                },
351            ) => {
352                for (lowered, bound) in pairs(functions, bound_functions, "ROWS FROM list")? {
353                    self.sites.routines.push(BoundRoutineReference {
354                        name: bound.name.clone(),
355                        binding: bound.binding.clone(),
356                    });
357                    self.scalars(&lowered.args, &bound.args, subqueries)?;
358                }
359                Ok(())
360            }
361            (SourcePlan::Subquery { body, .. }, SourcePlan::Subquery { body: bound, .. }) => {
362                self.query(body, bound, false)
363            }
364            _ => Err(shape_error("FROM item")),
365        }
366    }
367
368    fn optional(
369        &mut self,
370        lowered: Option<&ScalarExpr>,
371        bound: Option<&ScalarExpr>,
372        subqueries: Subqueries<'_>,
373    ) -> Result<(), SQLError> {
374        if let Some((lowered, bound)) = optional_pair(lowered, bound, "clause")? {
375            self.scalar(lowered, bound, subqueries)?;
376        }
377        Ok(())
378    }
379
380    fn scalars(
381        &mut self,
382        lowered: &[ScalarExpr],
383        bound: &[ScalarExpr],
384        subqueries: Subqueries<'_>,
385    ) -> Result<(), SQLError> {
386        for (lowered, bound) in pairs(lowered, bound, "expression list")? {
387            self.scalar(lowered, bound, subqueries)?;
388        }
389        Ok(())
390    }
391
392    fn subquery(
393        &mut self,
394        index: usize,
395        bound_index: usize,
396        subqueries: Subqueries<'_>,
397    ) -> Result<(), SQLError> {
398        match (subqueries.0.get(index), subqueries.1.get(bound_index)) {
399            (Some(lowered), Some(bound)) => self.query(lowered, bound, false),
400            _ => Err(SQLError::Internal(format!(
401                "stored syntax cannot resolve subquery slot {index}"
402            ))),
403        }
404    }
405
406    #[expect(
407        clippy::too_many_lines,
408        reason = "one syntax-order walk covers every scalar variant"
409    )]
410    fn scalar(
411        &mut self,
412        lowered: &ScalarExpr,
413        bound: &ScalarExpr,
414        subqueries: Subqueries<'_>,
415    ) -> Result<(), SQLError> {
416        if let ScalarExpr::CompositeRow { items, binding, .. } = bound {
417            let source = match lowered {
418                ScalarExpr::Cast { expr, .. } => expr.as_ref(),
419                expression => expression,
420            };
421            if let ScalarExpr::Row(original)
422            | ScalarExpr::CompositeRow {
423                items: original, ..
424            } = source
425            {
426                self.sites
427                    .values
428                    .push(ValueSite::CompositeRow(binding.clone()));
429                return self.scalars(original, items, subqueries);
430            }
431        }
432        if let Some(shape) = crate::type_resolution::membership::stored_shape(lowered, bound) {
433            let ScalarExpr::InList {
434                expr,
435                list,
436                negated,
437            } = lowered
438            else {
439                unreachable!()
440            };
441            self.sites.values.push(ValueSite::Membership {
442                array_items: shape.array_items.clone(),
443            });
444            let rewritten = crate::type_resolution::membership::rewrite(
445                expr.clone(),
446                list.clone(),
447                *negated,
448                &shape,
449                &uqa_core::memory::ProductionControl::uncontrolled(),
450            )?;
451            return self.scalar(&rewritten, bound, subqueries);
452        }
453        // Binding wraps an operand in the casts its operator needs, outside the casts the syntax writes.
454        let mut bound = bound;
455        for _ in 0..cast_depth(bound).saturating_sub(cast_depth(lowered)) {
456            let ScalarExpr::Cast { expr, ty, .. } = bound else {
457                return Err(shape_error("relabel"));
458            };
459            self.sites.values.push(ValueSite::Relabel(ty.clone()));
460            bound = expr;
461        }
462        match lowered {
463            ScalarExpr::Literal(Value::Str(_) | Value::Null) => {
464                self.sites.values.push(match bound {
465                    ScalarExpr::TypedLiteral { value, ty, .. } => ValueSite::Constant {
466                        value: value.clone(),
467                        ty: ty.clone(),
468                    },
469                    ScalarExpr::Literal(Value::Str(_) | Value::Null) => ValueSite::Literal,
470                    _ => return Err(shape_error("literal")),
471                });
472                return Ok(());
473            }
474            ScalarExpr::Cast { .. } => {
475                let ScalarExpr::Cast { ty, .. } = bound else {
476                    return Err(shape_error("cast"));
477                };
478                self.sites.values.push(ValueSite::Cast(ty.clone()));
479            }
480            ScalarExpr::Func { order_syntax, .. } => {
481                let ScalarExpr::Func {
482                    order_syntax: bound,
483                    ..
484                } = bound
485                else {
486                    return Err(shape_error("function ordering"));
487                };
488                if !order_syntax.is_legacy() && order_syntax != bound {
489                    return Err(shape_error("function ordering"));
490                }
491                self.sites.values.push(ValueSite::FunctionOrder(*bound));
492            }
493            _ => self.sites.values.push(ValueSite::Node),
494        }
495        match (lowered, bound) {
496            (
497                ScalarExpr::Func {
498                    args,
499                    order_by,
500                    filter,
501                    ..
502                },
503                ScalarExpr::Func {
504                    name,
505                    binding,
506                    args: bound_args,
507                    order_by: bound_order,
508                    filter: bound_filter,
509                    ..
510                },
511            ) => {
512                self.scalars(args, bound_args, subqueries)?;
513                for (lowered, bound) in pairs(order_by, bound_order, "aggregate ORDER BY")? {
514                    self.scalar(&lowered.expr, &bound.expr, subqueries)?;
515                }
516                self.optional(filter.as_deref(), bound_filter.as_deref(), subqueries)?;
517                self.sites.routines.push(BoundRoutineReference {
518                    name: name.clone(),
519                    binding: binding.clone(),
520                });
521                Ok(())
522            }
523            (ScalarExpr::Array(items), ScalarExpr::Array(bound))
524            | (ScalarExpr::Row(items), ScalarExpr::Row(bound))
525            | (ScalarExpr::And(items), ScalarExpr::And(bound))
526            | (ScalarExpr::Or(items), ScalarExpr::Or(bound)) => {
527                self.scalars(items, bound, subqueries)
528            }
529            (
530                ScalarExpr::Binary { lhs, rhs, .. },
531                ScalarExpr::Binary {
532                    lhs: bound_lhs,
533                    rhs: bound_rhs,
534                    ..
535                },
536            ) => {
537                self.scalar(lhs, bound_lhs, subqueries)?;
538                self.scalar(rhs, bound_rhs, subqueries)
539            }
540            (ScalarExpr::UnaryMinus(inner), ScalarExpr::UnaryMinus(bound))
541            | (ScalarExpr::Not(inner), ScalarExpr::Not(bound))
542            | (ScalarExpr::IsNull { expr: inner, .. }, ScalarExpr::IsNull { expr: bound, .. })
543            | (ScalarExpr::Cast { expr: inner, .. }, ScalarExpr::Cast { expr: bound, .. }) => {
544                self.scalar(inner, bound, subqueries)
545            }
546            (
547                ScalarExpr::Between { expr, low, high },
548                ScalarExpr::Between {
549                    expr: bound_expr,
550                    low: bound_low,
551                    high: bound_high,
552                },
553            ) => {
554                self.scalar(expr, bound_expr, subqueries)?;
555                self.scalar(low, bound_low, subqueries)?;
556                self.scalar(high, bound_high, subqueries)
557            }
558            (
559                ScalarExpr::InList { expr, list, .. },
560                ScalarExpr::InList {
561                    expr: bound_expr,
562                    list: bound_list,
563                    ..
564                },
565            ) => {
566                self.scalar(expr, bound_expr, subqueries)?;
567                self.scalars(list, bound_list, subqueries)
568            }
569            (
570                ScalarExpr::WindowCall {
571                    args, spec, filter, ..
572                },
573                ScalarExpr::WindowCall {
574                    name,
575                    args: bound_args,
576                    spec: bound_spec,
577                    filter: bound_filter,
578                    ..
579                },
580            ) => {
581                self.scalars(args, bound_args, subqueries)?;
582                if let Some((lowered, bound)) =
583                    optional_pair(filter.as_deref(), bound_filter.as_deref(), "window FILTER")?
584                {
585                    self.scalar(lowered, bound, subqueries)?;
586                }
587                self.scalars(&spec.partition_by, &bound_spec.partition_by, subqueries)?;
588                for (lowered, bound) in
589                    pairs(&spec.order_by, &bound_spec.order_by, "window ORDER BY")?
590                {
591                    self.scalar(&lowered.expr, &bound.expr, subqueries)?;
592                }
593                if let Some((lowered, bound)) = optional_pair(
594                    spec.frame.as_ref(),
595                    bound_spec.frame.as_ref(),
596                    "window frame",
597                )? {
598                    self.frame_bound(&lowered.start, &bound.start, subqueries)?;
599                    self.frame_bound(&lowered.end, &bound.end, subqueries)?;
600                }
601                self.sites.routines.push(BoundRoutineReference {
602                    name: name.clone(),
603                    binding: None,
604                });
605                Ok(())
606            }
607            (
608                ScalarExpr::Case {
609                    base,
610                    when,
611                    else_branch,
612                },
613                ScalarExpr::Case {
614                    base: bound_base,
615                    when: bound_when,
616                    else_branch: bound_else,
617                },
618            ) => {
619                self.optional(base.as_deref(), bound_base.as_deref(), subqueries)?;
620                for ((condition, result), (bound_condition, bound_result)) in
621                    pairs(when, bound_when, "CASE arms")?
622                {
623                    self.scalar(condition, bound_condition, subqueries)?;
624                    self.scalar(result, bound_result, subqueries)?;
625                }
626                self.optional(else_branch.as_deref(), bound_else.as_deref(), subqueries)
627            }
628            (ScalarExpr::ScalarSubquery(index), ScalarExpr::ScalarSubquery(bound))
629            | (
630                ScalarExpr::Exists {
631                    subquery: index, ..
632                },
633                ScalarExpr::Exists {
634                    subquery: bound, ..
635                },
636            ) => self.subquery(*index, *bound, subqueries),
637            (
638                ScalarExpr::InSubquery {
639                    expr,
640                    subquery: index,
641                    ..
642                },
643                ScalarExpr::InSubquery {
644                    expr: bound_expr,
645                    subquery: bound,
646                    ..
647                },
648            ) => {
649                self.scalar(expr, bound_expr, subqueries)?;
650                self.subquery(*index, *bound, subqueries)
651            }
652            // Leaves: binding may replace a column by its structural reference or a typed placeholder.
653            (
654                ScalarExpr::Star
655                | ScalarExpr::QualifiedStar(_)
656                | ScalarExpr::Default
657                | ScalarExpr::Column(_)
658                | ScalarExpr::Position(_)
659                | ScalarExpr::InternalColumn(_)
660                | ScalarExpr::QualifiedColumn { .. }
661                | ScalarExpr::Literal(_)
662                | ScalarExpr::TypedLiteral { .. }
663                | ScalarExpr::Param(_),
664                _,
665            ) => Ok(()),
666            _ => Err(shape_error("expression")),
667        }
668    }
669
670    fn frame_bound(
671        &mut self,
672        lowered: &ScalarFrameBound,
673        bound: &ScalarFrameBound,
674        subqueries: Subqueries<'_>,
675    ) -> Result<(), SQLError> {
676        match (lowered, bound) {
677            (ScalarFrameBound::Preceding(lowered), ScalarFrameBound::Preceding(bound))
678            | (ScalarFrameBound::Following(lowered), ScalarFrameBound::Following(bound)) => {
679                self.scalar(lowered, bound, subqueries)
680            }
681            (
682                ScalarFrameBound::UnboundedPreceding
683                | ScalarFrameBound::UnboundedFollowing
684                | ScalarFrameBound::CurrentRow,
685                _,
686            ) => Ok(()),
687            _ => Err(shape_error("window frame")),
688        }
689    }
690}
691
692/// The scalar expressions a data-modifying `WITH` query owns in syntax order; planner-added view checks and target predicates have no syntax.
693fn syntax_expressions(command: &CommandPlan) -> Vec<&ScalarExpr> {
694    use crate::plan::{ConflictActionPlan, MergeWhenPlan};
695    let mut expressions = Vec::new();
696    match command {
697        CommandPlan::Insert(plan) => {
698            expressions.extend(
699                plan.columns
700                    .iter()
701                    .flat_map(crate::ast::AssignmentTarget::expressions),
702            );
703            expressions.extend(plan.rows.iter().flatten());
704            if let Some(conflict) = &plan.on_conflict {
705                expressions.extend(&conflict.expressions);
706                expressions.extend(conflict.predicate.as_deref());
707                if let ConflictActionPlan::Update {
708                    assignments,
709                    predicate,
710                } = &conflict.action
711                {
712                    expressions.extend(
713                        assignments
714                            .iter()
715                            .flat_map(crate::plan::AssignmentPlan::expressions),
716                    );
717                    expressions.extend(predicate.as_deref());
718                }
719            }
720            expressions.extend(plan.returning.iter().map(|projection| &projection.expr));
721        }
722        CommandPlan::Update(plan) => {
723            expressions.extend(
724                plan.assignments
725                    .iter()
726                    .flat_map(crate::plan::AssignmentPlan::expressions),
727            );
728            expressions.extend(plan.predicate.as_ref());
729            expressions.extend(plan.returning.iter().map(|projection| &projection.expr));
730        }
731        CommandPlan::Delete(plan) => {
732            expressions.extend(plan.predicate.as_ref());
733            expressions.extend(plan.returning.iter().map(|projection| &projection.expr));
734        }
735        CommandPlan::Merge(plan) => {
736            expressions.push(&plan.join_condition);
737            for clause in &plan.when_clauses {
738                match clause {
739                    MergeWhenPlan::UpdateMatched {
740                        condition,
741                        assignments,
742                    }
743                    | MergeWhenPlan::UpdateNotMatchedBySource {
744                        condition,
745                        assignments,
746                    } => {
747                        expressions.extend(condition.as_ref());
748                        expressions.extend(
749                            assignments
750                                .iter()
751                                .flat_map(crate::plan::AssignmentPlan::expressions),
752                        );
753                    }
754                    MergeWhenPlan::InsertNotMatched {
755                        condition,
756                        columns,
757                        values,
758                        ..
759                    } => {
760                        expressions.extend(condition.as_ref());
761                        expressions.extend(
762                            columns
763                                .iter()
764                                .flat_map(crate::ast::AssignmentTarget::expressions),
765                        );
766                        expressions.extend(values.iter());
767                    }
768                    MergeWhenPlan::DeleteMatched { condition }
769                    | MergeWhenPlan::DeleteNotMatchedBySource { condition }
770                    | MergeWhenPlan::NothingMatched { condition }
771                    | MergeWhenPlan::NothingNotMatched { condition }
772                    | MergeWhenPlan::NothingNotMatchedBySource { condition } => {
773                        expressions.extend(condition.as_ref());
774                    }
775                }
776            }
777            expressions.extend(plan.returning.iter().map(|projection| &projection.expr));
778        }
779        _ => {}
780    }
781    expressions
782}