Skip to main content

uqa_sql/ast/
expressions.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7use serde::{Deserialize, Serialize};
8use std::sync::atomic::{AtomicU64, Ordering};
9use uqa_core::Value;
10
11use super::{
12    FromClause, FunctionBinding, FunctionBody, MergeWhen, OnConflictAction, SelectStmt, Statement,
13    CTE,
14};
15
16/// Query-local identity for an executor-only row source. Parser-produced SQL
17/// never contains this identity, so internal row carriers cannot collide with
18/// user relation aliases.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
20#[doc(hidden)]
21pub struct InternalRelationId(u64);
22
23impl InternalRelationId {
24    /// Allocate an opaque relation identity for an engine-injected row source.
25    #[must_use]
26    pub fn allocate() -> Self {
27        static NEXT_ID: AtomicU64 = AtomicU64::new(1);
28        let id = NEXT_ID
29            .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
30                current.checked_add(1)
31            })
32            .expect("internal relation identity space exhausted");
33        Self(id)
34    }
35
36    /// Address one zero-based attribute of this internal relation.
37    #[must_use]
38    pub fn column(self, attribute: usize) -> InternalColumnRef {
39        InternalColumnRef {
40            relation: self,
41            attribute: u32::try_from(attribute).expect("internal relation attribute exceeds u32"),
42        }
43    }
44
45    #[must_use]
46    pub const fn raw(self) -> u64 {
47        self.0
48    }
49
50    #[must_use]
51    pub const fn from_raw(raw: u64) -> Self {
52        Self(raw)
53    }
54}
55
56/// Structural reference to an executor-only relation attribute. This is the
57/// UQA analogue of PostgreSQL's `Var(varno, varattno)` identity: it is never
58/// resolved through SQL text names.
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
60#[doc(hidden)]
61pub struct InternalColumnRef {
62    relation: InternalRelationId,
63    attribute: u32,
64}
65
66impl InternalColumnRef {
67    #[must_use]
68    pub const fn relation(self) -> InternalRelationId {
69        self.relation
70    }
71
72    #[must_use]
73    pub const fn attribute(self) -> usize {
74        self.attribute as usize
75    }
76
77    #[must_use]
78    pub const fn from_raw(relation: u64, attribute: u32) -> Self {
79        Self {
80            relation: InternalRelationId::from_raw(relation),
81            attribute,
82        }
83    }
84}
85
86#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
87pub struct Projection {
88    pub expr: Expr,
89    pub alias: Option<String>,
90}
91
92#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
93pub struct OrderBy {
94    pub expr: Expr,
95    pub descending: bool,
96    /// `NULLS FIRST` / `NULLS LAST` placement. `None` means the
97    /// SQL-standard default - `NULLS LAST` for ASC and `NULLS FIRST`
98    /// for DESC. Mirrors `PostgreSQL` semantics.
99    pub nulls: Option<NullsOrder>,
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
103pub enum NullsOrder {
104    First,
105    Last,
106}
107
108#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
109pub struct WindowSpec {
110    /// Named window referenced by this specification while the SQL compiler resolves a `WINDOW` clause. Compiler-produced plans clear this field before lowering into the unified scalar IR.
111    #[serde(default, skip_serializing_if = "Option::is_none")]
112    pub reference: Option<WindowReference>,
113    pub partition_by: Vec<Expr>,
114    pub order_by: Vec<OrderBy>,
115    /// `ROWS` / `RANGE` frame, or `None` when not specified (defaults
116    /// to `RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW`).
117    pub frame: Option<WindowFrame>,
118}
119
120#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
121pub struct WindowReference {
122    pub name: String,
123    pub kind: WindowReferenceKind,
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
127pub enum WindowReferenceKind {
128    /// `OVER window_name` uses the named definition directly, including its frame.
129    Direct,
130    /// `OVER (window_name ...)` or `WINDOW child AS (parent ...)` copies and may extend a frameless definition.
131    Copy,
132}
133
134#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
135pub struct WindowFrame {
136    pub mode: FrameMode,
137    pub start: FrameBound,
138    pub end: FrameBound,
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
142pub enum FrameMode {
143    Rows,
144    Range,
145    Groups,
146}
147
148#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
149pub enum FrameBound {
150    UnboundedPreceding,
151    UnboundedFollowing,
152    CurrentRow,
153    Preceding(Box<Expr>),
154    Following(Box<Expr>),
155}
156
157/// Scalar expression nodes the compiler handles.
158#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
159pub enum Expr {
160    Star,
161    /// Relation-qualified wildcard projection (`table.*` or `alias.*`).
162    QualifiedStar(String),
163    /// `DEFAULT` in an INSERT/UPDATE assignment. This is a mutation marker,
164    /// not a scalar value, and must be resolved against the target column
165    /// before expression evaluation.
166    Default,
167    /// Unqualified column reference (`col`).
168    Column(String),
169    /// Qualified column reference (`table.col` or `alias.col`).
170    QualifiedColumn {
171        qualifier: String,
172        column: String,
173    },
174    /// Engine-injected structural column reference. SQL parsing never emits
175    /// this variant and SQL name binding must not rewrite it.
176    #[doc(hidden)]
177    InternalColumn(InternalColumnRef),
178    Literal(Value),
179    /// A positional bind parameter (`$1`, `$2`, ...).
180    Param(usize),
181    /// `text_match(...)`, `knn_match(...)`, etc. - dispatched through
182    /// the function registry.
183    Func {
184        name: String,
185        #[serde(default, skip_serializing_if = "Option::is_none")]
186        binding: Option<FunctionBinding>,
187        args: Vec<Expr>,
188        /// `func(DISTINCT expr)` - only meaningful for aggregate
189        /// functions. Mirrors `PostgreSQL`'s `agg_distinct`.
190        distinct: bool,
191        /// `func(expr ORDER BY ...)` - only meaningful for ordered
192        /// aggregates (`STRING_AGG`, `ARRAY_AGG`, `PERCENTILE_*`).
193        order_by: Vec<OrderBy>,
194        /// `func(...) FILTER (WHERE expr)` - aggregate-level row filter.
195        filter: Option<Box<Expr>>,
196    },
197    /// `ARRAY[1.0, 2.0, ...]` literal - currently restricted to numeric
198    /// elements (vectors).
199    Array(Vec<Expr>),
200    /// Anonymous SQL row constructor (`ROW(...)` or `(a, b)`).
201    Row(Vec<Expr>),
202    /// `lhs op rhs` - comparison or arithmetic.
203    Binary {
204        op: BinaryOp,
205        lhs: Box<Expr>,
206        rhs: Box<Expr>,
207    },
208    /// `PostgreSQL` prefix `-`, kept distinct from binary subtraction so the
209    /// operand's declared numeric width and overflow behavior survive lowering.
210    UnaryMinus(Box<Expr>),
211    /// `NOT expr`.
212    Not(Box<Expr>),
213    /// `cond_1 AND cond_2 AND ...` (n-ary).
214    And(Vec<Expr>),
215    /// `cond_1 OR cond_2 OR ...` (n-ary).
216    Or(Vec<Expr>),
217    /// `expr IS NULL` / `expr IS NOT NULL`.
218    IsNull {
219        expr: Box<Expr>,
220        negated: bool,
221    },
222    /// `expr BETWEEN low AND high`.
223    Between {
224        expr: Box<Expr>,
225        low: Box<Expr>,
226        high: Box<Expr>,
227    },
228    /// `expr IN (a, b, c)` literal list.
229    InList {
230        expr: Box<Expr>,
231        list: Vec<Expr>,
232        negated: bool,
233    },
234    /// `func(args) OVER (PARTITION BY ... ORDER BY ...)`.
235    WindowCall {
236        name: String,
237        args: Vec<Expr>,
238        spec: WindowSpec,
239    },
240    /// `CASE [base] WHEN cond THEN result ... [ELSE default] END`.
241    /// `base` lifts simple-form `CASE expr WHEN val THEN ...` into an
242    /// optional comparison anchor; searched-form `CASE WHEN cond ...`
243    /// leaves it `None`.
244    Case {
245        base: Option<Box<Expr>>,
246        when: Vec<(Expr, Expr)>,
247        else_branch: Option<Box<Expr>>,
248    },
249    /// `CAST(expr AS type)`. The type name is preserved verbatim so
250    /// the evaluator can apply the correct coercion.
251    Cast {
252        expr: Box<Expr>,
253        ty: String,
254    },
255    /// `(SELECT ...)` scalar subquery: yields a single row / single
256    /// column value at evaluation time.
257    ScalarSubquery(Box<SelectStmt>),
258    /// `EXISTS (SELECT ...)` -- truthy when the body produces at
259    /// least one row.
260    Exists {
261        body: Box<SelectStmt>,
262        negated: bool,
263    },
264    /// `expr [NOT] IN (SELECT ...)` set membership against a
265    /// subquery. Evaluator runs the body once per top-level
266    /// expression and tests membership.
267    InSubquery {
268        expr: Box<Expr>,
269        body: Box<SelectStmt>,
270        negated: bool,
271    },
272}
273
274impl Expr {
275    pub fn qualified_column(qualifier: impl Into<String>, column: impl Into<String>) -> Self {
276        Self::QualifiedColumn {
277            qualifier: qualifier.into(),
278            column: column.into(),
279        }
280    }
281
282    /// Upgrade compiler-owned function markers deserialized from catalogs
283    /// written by releases through 0.1.6.
284    #[doc(hidden)]
285    pub fn upgrade_legacy_serialized_dispatches(&mut self) -> bool {
286        let mut changed = false;
287        match self {
288            Self::Func {
289                name,
290                binding,
291                args,
292                order_by,
293                filter,
294                ..
295            } => {
296                for argument in args {
297                    changed |= argument.upgrade_legacy_serialized_dispatches();
298                }
299                for order in order_by {
300                    changed |= order.expr.upgrade_legacy_serialized_dispatches();
301                }
302                if let Some(filter) = filter {
303                    changed |= filter.upgrade_legacy_serialized_dispatches();
304                }
305                changed |=
306                    super::FunctionBinding::upgrade_legacy_serialized_dispatch(name, binding);
307            }
308            Self::Array(items) | Self::Row(items) | Self::And(items) | Self::Or(items) => {
309                for item in items {
310                    changed |= item.upgrade_legacy_serialized_dispatches();
311                }
312            }
313            Self::Binary { lhs, rhs, .. } => {
314                changed |= lhs.upgrade_legacy_serialized_dispatches();
315                changed |= rhs.upgrade_legacy_serialized_dispatches();
316            }
317            Self::UnaryMinus(inner)
318            | Self::Not(inner)
319            | Self::IsNull { expr: inner, .. }
320            | Self::Cast { expr: inner, .. } => {
321                changed |= inner.upgrade_legacy_serialized_dispatches();
322            }
323            Self::Between { expr, low, high } => {
324                changed |= expr.upgrade_legacy_serialized_dispatches();
325                changed |= low.upgrade_legacy_serialized_dispatches();
326                changed |= high.upgrade_legacy_serialized_dispatches();
327            }
328            Self::InList { expr, list, .. } => {
329                changed |= expr.upgrade_legacy_serialized_dispatches();
330                for item in list {
331                    changed |= item.upgrade_legacy_serialized_dispatches();
332                }
333            }
334            Self::WindowCall { args, spec, .. } => {
335                for argument in args {
336                    changed |= argument.upgrade_legacy_serialized_dispatches();
337                }
338                for partition in &mut spec.partition_by {
339                    changed |= partition.upgrade_legacy_serialized_dispatches();
340                }
341                for order in &mut spec.order_by {
342                    changed |= order.expr.upgrade_legacy_serialized_dispatches();
343                }
344                if let Some(frame) = &mut spec.frame {
345                    for bound in [&mut frame.start, &mut frame.end] {
346                        match bound {
347                            FrameBound::Preceding(expression)
348                            | FrameBound::Following(expression) => {
349                                changed |= expression.upgrade_legacy_serialized_dispatches();
350                            }
351                            FrameBound::UnboundedPreceding
352                            | FrameBound::UnboundedFollowing
353                            | FrameBound::CurrentRow => {}
354                        }
355                    }
356                }
357            }
358            Self::Case {
359                base,
360                when,
361                else_branch,
362            } => {
363                if let Some(base) = base {
364                    changed |= base.upgrade_legacy_serialized_dispatches();
365                }
366                for (condition, result) in when {
367                    changed |= condition.upgrade_legacy_serialized_dispatches();
368                    changed |= result.upgrade_legacy_serialized_dispatches();
369                }
370                if let Some(branch) = else_branch {
371                    changed |= branch.upgrade_legacy_serialized_dispatches();
372                }
373            }
374            Self::InSubquery { expr, body, .. } => {
375                changed |= expr.upgrade_legacy_serialized_dispatches();
376                changed |= body.upgrade_legacy_serialized_dispatches();
377            }
378            Self::ScalarSubquery(body) | Self::Exists { body, .. } => {
379                changed |= body.upgrade_legacy_serialized_dispatches();
380            }
381            Self::Default
382            | Self::Star
383            | Self::QualifiedStar(_)
384            | Self::Column(_)
385            | Self::QualifiedColumn { .. }
386            | Self::InternalColumn(_)
387            | Self::Literal(_)
388            | Self::Param(_) => {}
389        }
390        changed
391    }
392
393    /// True when this expression tree contains a window function call.
394    #[must_use]
395    pub fn contains_window(&self) -> bool {
396        self.any_node(&|node| matches!(node, Self::WindowCall { .. }))
397    }
398
399    /// True when this expression tree contains a built-in aggregate call.
400    #[must_use]
401    pub fn contains_aggregate(&self) -> bool {
402        self.any_node(
403            &|node| matches!(node, Self::Func { name, .. } if is_builtin_aggregate_function(name)),
404        )
405    }
406
407    /// True when this expression contains a column whose owning relation can only be determined after catalog schemas have been bound.
408    #[must_use]
409    pub fn contains_unqualified_column(&self) -> bool {
410        self.any_node(&|node| matches!(node, Self::Column(_)))
411    }
412
413    /// True when this expression contains a function whose strictness cannot be decided without an engine catalog.
414    #[must_use]
415    pub fn contains_function_with_unknown_strictness(&self) -> bool {
416        self.any_node(&|node| {
417            matches!(
418                node,
419                Self::Func {
420                    name,
421                    args,
422                    binding,
423                    ..
424                } if crate::expr::bound_scalar_function_strictness(
425                    name,
426                    binding.as_ref(),
427                    args.len(),
428                )
429                .is_none()
430            )
431        })
432    }
433
434    /// Whether `hit` matches this node or any scalar node below it. Subquery bodies are opaque because they own independent query trees.
435    #[must_use]
436    pub fn any_node(&self, hit: &dyn Fn(&Self) -> bool) -> bool {
437        if hit(self) {
438            return true;
439        }
440        match self {
441            Self::Func {
442                args,
443                order_by,
444                filter,
445                ..
446            } => {
447                args.iter().any(|arg| arg.any_node(hit))
448                    || order_by.iter().any(|order| order.expr.any_node(hit))
449                    || filter.as_deref().is_some_and(|filter| filter.any_node(hit))
450            }
451            Self::Array(items) | Self::Row(items) | Self::And(items) | Self::Or(items) => {
452                items.iter().any(|item| item.any_node(hit))
453            }
454            Self::UnaryMinus(expr) | Self::Not(expr) | Self::Cast { expr, .. } => {
455                expr.any_node(hit)
456            }
457            Self::Binary { lhs, rhs, .. } => lhs.any_node(hit) || rhs.any_node(hit),
458            Self::IsNull { expr, .. } | Self::InSubquery { expr, .. } => expr.any_node(hit),
459            Self::Between { expr, low, high } => {
460                expr.any_node(hit) || low.any_node(hit) || high.any_node(hit)
461            }
462            Self::InList { expr, list, .. } => {
463                expr.any_node(hit) || list.iter().any(|item| item.any_node(hit))
464            }
465            Self::Case {
466                base,
467                when,
468                else_branch,
469            } => {
470                base.as_deref().is_some_and(|base| base.any_node(hit))
471                    || when
472                        .iter()
473                        .any(|(condition, result)| condition.any_node(hit) || result.any_node(hit))
474                    || else_branch
475                        .as_deref()
476                        .is_some_and(|branch| branch.any_node(hit))
477            }
478            Self::WindowCall { .. }
479            | Self::Star
480            | Self::QualifiedStar(_)
481            | Self::Default
482            | Self::Column(_)
483            | Self::QualifiedColumn { .. }
484            | Self::InternalColumn(_)
485            | Self::Literal(_)
486            | Self::Param(_)
487            | Self::ScalarSubquery(_)
488            | Self::Exists { .. } => false,
489        }
490    }
491}
492
493fn upgrade_exprs(expressions: &mut [Expr]) -> bool {
494    expressions.iter_mut().fold(false, |changed, expression| {
495        expression.upgrade_legacy_serialized_dispatches() | changed
496    })
497}
498
499fn upgrade_rows(rows: &mut [Vec<Expr>]) -> bool {
500    rows.iter_mut()
501        .fold(false, |changed, row| upgrade_exprs(row) | changed)
502}
503
504fn upgrade_optional(expression: &mut Option<Expr>) -> bool {
505    expression
506        .as_mut()
507        .is_some_and(Expr::upgrade_legacy_serialized_dispatches)
508}
509
510fn upgrade_projections(projections: &mut [Projection]) -> bool {
511    projections.iter_mut().fold(false, |changed, projection| {
512        projection.expr.upgrade_legacy_serialized_dispatches() | changed
513    })
514}
515
516fn upgrade_assignments(assignments: &mut [(String, Expr)]) -> bool {
517    assignments
518        .iter_mut()
519        .fold(false, |changed, (_, expression)| {
520            expression.upgrade_legacy_serialized_dispatches() | changed
521        })
522}
523
524fn upgrade_ctes(ctes: &mut [CTE]) -> bool {
525    ctes.iter_mut().fold(false, |mut changed, cte| {
526        if let Some(cycle) = &mut cte.cycle {
527            changed |= cycle.mark_value.upgrade_legacy_serialized_dispatches();
528            changed |= cycle.mark_default.upgrade_legacy_serialized_dispatches();
529        }
530        changed | cte.query.upgrade_legacy_serialized_dispatches()
531    })
532}
533
534impl FromClause {
535    fn upgrade_legacy_serialized_dispatches(&mut self) -> bool {
536        match self {
537            Self::Table { .. } => false,
538            Self::Join {
539                left, right, on, ..
540            } => {
541                left.upgrade_legacy_serialized_dispatches()
542                    | right.upgrade_legacy_serialized_dispatches()
543                    | upgrade_optional(on)
544            }
545            Self::Values { rows, .. } => upgrade_rows(rows),
546            Self::Function { args, .. } => upgrade_exprs(args),
547            Self::FunctionGroup { functions, .. } => {
548                functions.iter_mut().fold(false, |changed, function| {
549                    upgrade_exprs(&mut function.args) | changed
550                })
551            }
552            Self::Subquery { body, .. } => body.upgrade_legacy_serialized_dispatches(),
553        }
554    }
555}
556
557impl SelectStmt {
558    /// Upgrade every legacy compiler dispatch marker in this complete query tree.
559    #[doc(hidden)]
560    pub fn upgrade_legacy_serialized_dispatches(&mut self) -> bool {
561        let mut changed = upgrade_projections(&mut self.projections);
562        changed |= upgrade_rows(&mut self.values);
563        if let Some(from) = &mut self.from {
564            changed |= from.upgrade_legacy_serialized_dispatches();
565        }
566        changed |= upgrade_optional(&mut self.r#where);
567        changed |= upgrade_exprs(&mut self.group_by);
568        for grouping_set in &mut self.grouping_sets {
569            changed |= upgrade_exprs(grouping_set);
570        }
571        changed |= upgrade_optional(&mut self.having);
572        for order in &mut self.order_by {
573            changed |= order.expr.upgrade_legacy_serialized_dispatches();
574        }
575        changed |= upgrade_optional(&mut self.limit);
576        changed |= upgrade_optional(&mut self.offset);
577        changed |= upgrade_ctes(&mut self.with);
578        if let Some(set_operation) = &mut self.set_op {
579            if let Some(left) = &mut set_operation.left {
580                changed |= left.upgrade_legacy_serialized_dispatches();
581            }
582            changed |= set_operation.right.upgrade_legacy_serialized_dispatches();
583            for order in &mut set_operation.combined_order_by {
584                changed |= order.expr.upgrade_legacy_serialized_dispatches();
585            }
586            changed |= upgrade_optional(&mut set_operation.combined_limit);
587            changed |= upgrade_optional(&mut set_operation.combined_offset);
588        }
589        changed | upgrade_exprs(&mut self.distinct_on)
590    }
591}
592
593impl MergeWhen {
594    fn upgrade_legacy_serialized_dispatches(&mut self) -> bool {
595        match self {
596            Self::UpdateMatched {
597                condition,
598                assignments,
599            }
600            | Self::UpdateNotMatchedBySource {
601                condition,
602                assignments,
603            } => upgrade_optional(condition) | upgrade_assignments(assignments),
604            Self::InsertNotMatched {
605                condition, values, ..
606            } => upgrade_optional(condition) | upgrade_exprs(values),
607            Self::DeleteMatched { condition }
608            | Self::DeleteNotMatchedBySource { condition }
609            | Self::NothingMatched { condition }
610            | Self::NothingNotMatched { condition }
611            | Self::NothingNotMatchedBySource { condition } => upgrade_optional(condition),
612        }
613    }
614}
615
616impl Statement {
617    /// Upgrade legacy compiler dispatch markers without reparsing SQL or changing catalog-bound relation identities.
618    #[doc(hidden)]
619    pub fn upgrade_legacy_serialized_dispatches(&mut self) -> bool {
620        match self {
621            Self::Select(select) => select.upgrade_legacy_serialized_dispatches(),
622            Self::Insert(insert) => {
623                let mut changed = upgrade_ctes(&mut insert.with);
624                changed |= upgrade_rows(&mut insert.rows);
625                if let Some(source) = &mut insert.select_source {
626                    changed |= source.upgrade_legacy_serialized_dispatches();
627                }
628                if let Some(conflict) = &mut insert.on_conflict {
629                    if let OnConflictAction::Update {
630                        assignments,
631                        r#where,
632                    } = &mut conflict.action
633                    {
634                        changed |= upgrade_assignments(assignments);
635                        changed |= upgrade_optional(r#where);
636                    }
637                }
638                changed | upgrade_projections(&mut insert.returning)
639            }
640            Self::Update(update) => {
641                let mut changed = upgrade_assignments(&mut update.assignments);
642                changed |= upgrade_optional(&mut update.r#where);
643                changed |= upgrade_ctes(&mut update.with);
644                if let Some(from) = &mut update.from {
645                    changed |= from.upgrade_legacy_serialized_dispatches();
646                }
647                changed | upgrade_projections(&mut update.returning)
648            }
649            Self::Delete(delete) => {
650                let mut changed = upgrade_optional(&mut delete.r#where);
651                changed |= upgrade_ctes(&mut delete.with);
652                if let Some(using) = &mut delete.using {
653                    changed |= using.upgrade_legacy_serialized_dispatches();
654                }
655                changed | upgrade_projections(&mut delete.returning)
656            }
657            Self::CreateView { body, .. }
658            | Self::CreateMaterializedView { body, .. }
659            | Self::CreateTableAs { body, .. } => body.upgrade_legacy_serialized_dispatches(),
660            Self::DeclareCursor(cursor) => cursor.query.upgrade_legacy_serialized_dispatches(),
661            Self::Explain { body, .. } | Self::Prepare { body, .. } => {
662                body.upgrade_legacy_serialized_dispatches()
663            }
664            Self::Execute { params, .. } | Self::Call { args: params, .. } => upgrade_exprs(params),
665            Self::Values { rows } => upgrade_rows(rows),
666            Self::Merge(merge) => {
667                let mut changed = merge.source.upgrade_legacy_serialized_dispatches();
668                changed |= merge.join_condition.upgrade_legacy_serialized_dispatches();
669                for clause in &mut merge.when_clauses {
670                    changed |= clause.upgrade_legacy_serialized_dispatches();
671                }
672                changed | upgrade_projections(&mut merge.returning)
673            }
674            Self::CreateFunction(definition) => {
675                let mut changed = definition
676                    .params
677                    .iter_mut()
678                    .fold(false, |changed, parameter| {
679                        parameter
680                            .default
681                            .as_mut()
682                            .is_some_and(Expr::upgrade_legacy_serialized_dispatches)
683                            | changed
684                    });
685                if let FunctionBody::Statements(statements) = &mut definition.body {
686                    for statement in statements {
687                        changed |= statement.upgrade_legacy_serialized_dispatches();
688                    }
689                }
690                changed
691            }
692            Self::CreateTrigger(trigger) => upgrade_optional(&mut trigger.when),
693            Self::CreateRule(rule) => {
694                let mut changed = upgrade_optional(&mut rule.condition);
695                for action in &mut rule.actions {
696                    changed |= action.upgrade_legacy_serialized_dispatches();
697                }
698                changed
699            }
700            Self::CreateTable(_)
701            | Self::CreateIndex(_)
702            | Self::Drop(_)
703            | Self::AlterTable(_)
704            | Self::AlterViewOptions(_)
705            | Self::RefreshMaterializedView { .. }
706            | Self::CreateSchema { .. }
707            | Self::SetVariable { .. }
708            | Self::ResetVariable { .. }
709            | Self::ResetAllVariables
710            | Self::SetConstraints { .. }
711            | Self::ShowVariable { .. }
712            | Self::Discard { .. }
713            | Self::Load { .. }
714            | Self::Analyze { .. }
715            | Self::Vacuum(_)
716            | Self::Truncate { .. }
717            | Self::Transaction(_)
718            | Self::FetchCursor(_)
719            | Self::CloseCursor { .. }
720            | Self::CreateSequence(_)
721            | Self::AlterSequence(_)
722            | Self::Deallocate { .. }
723            | Self::CreateForeignServer(_)
724            | Self::CreateForeignTable(_)
725            | Self::DropFunction(_)
726            | Self::AlterRoutine(_)
727            | Self::AlterRoutineOwner(_)
728            | Self::GrantRoutine(_)
729            | Self::CreateRole(_)
730            | Self::AlterRole(_)
731            | Self::DropRole(_)
732            | Self::DropTrigger(_)
733            | Self::DropRule(_)
734            | Self::DoBlock { .. } => false,
735        }
736    }
737}
738
739/// Return whether `name` is a built-in aggregate understood by the planner.
740#[must_use]
741pub fn is_builtin_aggregate_function(name: &str) -> bool {
742    matches!(
743        name.to_ascii_lowercase().as_str(),
744        "count"
745            | "sum"
746            | "avg"
747            | "min"
748            | "max"
749            | "string_agg"
750            | "array_agg"
751            | "bool_and"
752            | "bool_or"
753            | "stddev"
754            | "stddev_samp"
755            | "stddev_pop"
756            | "variance"
757            | "var_samp"
758            | "var_pop"
759            | "percentile_cont"
760            | "percentile_disc"
761            | "mode"
762            | "json_agg"
763            | "jsonb_agg"
764            | "json_object_agg"
765            | "jsonb_object_agg"
766    )
767}
768
769#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
770pub enum BinaryOp {
771    Equal,
772    NotEqual,
773    Less,
774    LessEqual,
775    Greater,
776    GreaterEqual,
777    Add,
778    Subtract,
779    Multiply,
780    Divide,
781}
782
783/// `Expr` restricted to value-producing forms used by `INSERT` rows.
784pub type ValueExpr = Expr;