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    /// An already-coerced runtime datum with its declared SQL type. Variable binding emits this leaf so reading a domain value does not repeat its constraints.
180    #[doc(hidden)]
181    TypedLiteral {
182        value: Value,
183        ty: String,
184    },
185    /// A positional bind parameter (`$1`, `$2`, ...).
186    Param(usize),
187    /// `text_match(...)`, `knn_match(...)`, etc. - dispatched through
188    /// the function registry.
189    Func {
190        name: String,
191        #[serde(default, skip_serializing_if = "Option::is_none")]
192        binding: Option<FunctionBinding>,
193        args: Vec<Expr>,
194        /// `func(DISTINCT expr)` - only meaningful for aggregate
195        /// functions. Mirrors `PostgreSQL`'s `agg_distinct`.
196        distinct: bool,
197        /// `func(expr ORDER BY ...)` - only meaningful for ordered
198        /// aggregates (`STRING_AGG`, `ARRAY_AGG`, `PERCENTILE_*`).
199        order_by: Vec<OrderBy>,
200        /// `func(...) FILTER (WHERE expr)` - aggregate-level row filter.
201        filter: Option<Box<Expr>>,
202    },
203    /// `ARRAY[1.0, 2.0, ...]` literal - currently restricted to numeric
204    /// elements (vectors).
205    Array(Vec<Expr>),
206    /// Anonymous SQL row constructor (`ROW(...)` or `(a, b)`).
207    Row(Vec<Expr>),
208    /// `lhs op rhs` - comparison or arithmetic.
209    Binary {
210        op: BinaryOp,
211        lhs: Box<Expr>,
212        rhs: Box<Expr>,
213    },
214    /// `PostgreSQL` prefix `-`, kept distinct from binary subtraction so the
215    /// operand's declared numeric width and overflow behavior survive lowering.
216    UnaryMinus(Box<Expr>),
217    /// `NOT expr`.
218    Not(Box<Expr>),
219    /// `cond_1 AND cond_2 AND ...` (n-ary).
220    And(Vec<Expr>),
221    /// `cond_1 OR cond_2 OR ...` (n-ary).
222    Or(Vec<Expr>),
223    /// `expr IS NULL` / `expr IS NOT NULL`.
224    IsNull {
225        expr: Box<Expr>,
226        negated: bool,
227    },
228    /// `expr BETWEEN low AND high`.
229    Between {
230        expr: Box<Expr>,
231        low: Box<Expr>,
232        high: Box<Expr>,
233    },
234    /// `expr IN (a, b, c)` literal list.
235    InList {
236        expr: Box<Expr>,
237        list: Vec<Expr>,
238        negated: bool,
239    },
240    /// `func(args) OVER (PARTITION BY ... ORDER BY ...)`.
241    WindowCall {
242        name: String,
243        args: Vec<Expr>,
244        spec: WindowSpec,
245    },
246    /// `CASE [base] WHEN cond THEN result ... [ELSE default] END`.
247    /// `base` lifts simple-form `CASE expr WHEN val THEN ...` into an
248    /// optional comparison anchor; searched-form `CASE WHEN cond ...`
249    /// leaves it `None`.
250    Case {
251        base: Option<Box<Expr>>,
252        when: Vec<(Expr, Expr)>,
253        else_branch: Option<Box<Expr>>,
254    },
255    /// `CAST(expr AS type)`. The type name is preserved verbatim so
256    /// the evaluator can apply the correct coercion.
257    Cast {
258        expr: Box<Expr>,
259        ty: String,
260    },
261    /// `(SELECT ...)` scalar subquery: yields a single row / single
262    /// column value at evaluation time.
263    ScalarSubquery(Box<SelectStmt>),
264    /// `EXISTS (SELECT ...)` -- truthy when the body produces at
265    /// least one row.
266    Exists {
267        body: Box<SelectStmt>,
268        negated: bool,
269    },
270    /// `expr [NOT] IN (SELECT ...)` set membership against a
271    /// subquery. Evaluator runs the body once per top-level
272    /// expression and tests membership.
273    InSubquery {
274        expr: Box<Expr>,
275        body: Box<SelectStmt>,
276        negated: bool,
277    },
278}
279
280impl Expr {
281    pub fn qualified_column(qualifier: impl Into<String>, column: impl Into<String>) -> Self {
282        Self::QualifiedColumn {
283            qualifier: qualifier.into(),
284            column: column.into(),
285        }
286    }
287
288    /// Upgrade compiler-owned function markers deserialized from catalogs
289    /// written by releases through 0.1.6.
290    #[doc(hidden)]
291    #[expect(
292        clippy::too_many_lines,
293        reason = "exhaustive AST migration preserves every serialized variant"
294    )]
295    pub fn upgrade_legacy_serialized_dispatches(&mut self) -> bool {
296        let mut changed = false;
297        match self {
298            Self::Func {
299                name,
300                binding,
301                args,
302                order_by,
303                filter,
304                ..
305            } => {
306                for argument in args {
307                    changed |= argument.upgrade_legacy_serialized_dispatches();
308                }
309                for order in order_by {
310                    changed |= order.expr.upgrade_legacy_serialized_dispatches();
311                }
312                if let Some(filter) = filter {
313                    changed |= filter.upgrade_legacy_serialized_dispatches();
314                }
315                changed |=
316                    super::FunctionBinding::upgrade_legacy_serialized_dispatch(name, binding);
317            }
318            Self::Array(items) | Self::Row(items) | Self::And(items) | Self::Or(items) => {
319                for item in items {
320                    changed |= item.upgrade_legacy_serialized_dispatches();
321                }
322            }
323            Self::Binary { lhs, rhs, .. } => {
324                changed |= lhs.upgrade_legacy_serialized_dispatches();
325                changed |= rhs.upgrade_legacy_serialized_dispatches();
326            }
327            Self::UnaryMinus(inner)
328            | Self::Not(inner)
329            | Self::IsNull { expr: inner, .. }
330            | Self::Cast { expr: inner, .. } => {
331                changed |= inner.upgrade_legacy_serialized_dispatches();
332            }
333            Self::Between { expr, low, high } => {
334                changed |= expr.upgrade_legacy_serialized_dispatches();
335                changed |= low.upgrade_legacy_serialized_dispatches();
336                changed |= high.upgrade_legacy_serialized_dispatches();
337            }
338            Self::InList { expr, list, .. } => {
339                changed |= expr.upgrade_legacy_serialized_dispatches();
340                for item in list {
341                    changed |= item.upgrade_legacy_serialized_dispatches();
342                }
343            }
344            Self::WindowCall { args, spec, .. } => {
345                for argument in args {
346                    changed |= argument.upgrade_legacy_serialized_dispatches();
347                }
348                for partition in &mut spec.partition_by {
349                    changed |= partition.upgrade_legacy_serialized_dispatches();
350                }
351                for order in &mut spec.order_by {
352                    changed |= order.expr.upgrade_legacy_serialized_dispatches();
353                }
354                if let Some(frame) = &mut spec.frame {
355                    for bound in [&mut frame.start, &mut frame.end] {
356                        match bound {
357                            FrameBound::Preceding(expression)
358                            | FrameBound::Following(expression) => {
359                                changed |= expression.upgrade_legacy_serialized_dispatches();
360                            }
361                            FrameBound::UnboundedPreceding
362                            | FrameBound::UnboundedFollowing
363                            | FrameBound::CurrentRow => {}
364                        }
365                    }
366                }
367            }
368            Self::Case {
369                base,
370                when,
371                else_branch,
372            } => {
373                if let Some(base) = base {
374                    changed |= base.upgrade_legacy_serialized_dispatches();
375                }
376                for (condition, result) in when {
377                    changed |= condition.upgrade_legacy_serialized_dispatches();
378                    changed |= result.upgrade_legacy_serialized_dispatches();
379                }
380                if let Some(branch) = else_branch {
381                    changed |= branch.upgrade_legacy_serialized_dispatches();
382                }
383            }
384            Self::InSubquery { expr, body, .. } => {
385                changed |= expr.upgrade_legacy_serialized_dispatches();
386                changed |= body.upgrade_legacy_serialized_dispatches();
387            }
388            Self::ScalarSubquery(body) | Self::Exists { body, .. } => {
389                changed |= body.upgrade_legacy_serialized_dispatches();
390            }
391            Self::Default
392            | Self::Star
393            | Self::QualifiedStar(_)
394            | Self::Column(_)
395            | Self::QualifiedColumn { .. }
396            | Self::InternalColumn(_)
397            | Self::Literal(_)
398            | Self::TypedLiteral { .. }
399            | Self::Param(_) => {}
400        }
401        changed
402    }
403
404    /// True when this expression tree contains a window function call.
405    #[must_use]
406    pub fn contains_window(&self) -> bool {
407        self.any_node(&|node| matches!(node, Self::WindowCall { .. }))
408    }
409
410    /// True when this expression tree contains a built-in aggregate call.
411    #[must_use]
412    pub fn contains_aggregate(&self) -> bool {
413        self.any_node(
414            &|node| matches!(node, Self::Func { name, .. } if is_builtin_aggregate_function(name)),
415        )
416    }
417
418    /// True when this expression contains a column whose owning relation can only be determined after catalog schemas have been bound.
419    #[must_use]
420    pub fn contains_unqualified_column(&self) -> bool {
421        self.any_node(&|node| matches!(node, Self::Column(_)))
422    }
423
424    /// True when this expression contains a function whose strictness cannot be decided without an engine catalog.
425    #[must_use]
426    pub fn contains_function_with_unknown_strictness(&self) -> bool {
427        self.any_node(&|node| {
428            matches!(
429                node,
430                Self::Func {
431                    name,
432                    args,
433                    binding,
434                    ..
435                } if crate::expr::bound_scalar_function_strictness(
436                    name,
437                    binding.as_ref(),
438                    args.len(),
439                )
440                .is_none()
441            )
442        })
443    }
444
445    /// Whether `hit` matches this node or any scalar node below it. Subquery bodies are opaque because they own independent query trees.
446    #[must_use]
447    pub fn any_node(&self, hit: &dyn Fn(&Self) -> bool) -> bool {
448        if hit(self) {
449            return true;
450        }
451        match self {
452            Self::Func {
453                args,
454                order_by,
455                filter,
456                ..
457            } => {
458                args.iter().any(|arg| arg.any_node(hit))
459                    || order_by.iter().any(|order| order.expr.any_node(hit))
460                    || filter.as_deref().is_some_and(|filter| filter.any_node(hit))
461            }
462            Self::Array(items) | Self::Row(items) | Self::And(items) | Self::Or(items) => {
463                items.iter().any(|item| item.any_node(hit))
464            }
465            Self::UnaryMinus(expr) | Self::Not(expr) | Self::Cast { expr, .. } => {
466                expr.any_node(hit)
467            }
468            Self::Binary { lhs, rhs, .. } => lhs.any_node(hit) || rhs.any_node(hit),
469            Self::IsNull { expr, .. } | Self::InSubquery { expr, .. } => expr.any_node(hit),
470            Self::Between { expr, low, high } => {
471                expr.any_node(hit) || low.any_node(hit) || high.any_node(hit)
472            }
473            Self::InList { expr, list, .. } => {
474                expr.any_node(hit) || list.iter().any(|item| item.any_node(hit))
475            }
476            Self::Case {
477                base,
478                when,
479                else_branch,
480            } => {
481                base.as_deref().is_some_and(|base| base.any_node(hit))
482                    || when
483                        .iter()
484                        .any(|(condition, result)| condition.any_node(hit) || result.any_node(hit))
485                    || else_branch
486                        .as_deref()
487                        .is_some_and(|branch| branch.any_node(hit))
488            }
489            Self::WindowCall { .. }
490            | Self::Star
491            | Self::QualifiedStar(_)
492            | Self::Default
493            | Self::Column(_)
494            | Self::QualifiedColumn { .. }
495            | Self::InternalColumn(_)
496            | Self::Literal(_)
497            | Self::TypedLiteral { .. }
498            | Self::Param(_)
499            | Self::ScalarSubquery(_)
500            | Self::Exists { .. } => false,
501        }
502    }
503}
504
505fn upgrade_exprs(expressions: &mut [Expr]) -> bool {
506    expressions.iter_mut().fold(false, |changed, expression| {
507        expression.upgrade_legacy_serialized_dispatches() | changed
508    })
509}
510
511fn upgrade_rows(rows: &mut [Vec<Expr>]) -> bool {
512    rows.iter_mut()
513        .fold(false, |changed, row| upgrade_exprs(row) | changed)
514}
515
516fn upgrade_optional(expression: &mut Option<Expr>) -> bool {
517    expression
518        .as_mut()
519        .is_some_and(Expr::upgrade_legacy_serialized_dispatches)
520}
521
522fn upgrade_projections(projections: &mut [Projection]) -> bool {
523    projections.iter_mut().fold(false, |changed, projection| {
524        projection.expr.upgrade_legacy_serialized_dispatches() | changed
525    })
526}
527
528fn upgrade_assignments(assignments: &mut [(String, Expr)]) -> bool {
529    assignments
530        .iter_mut()
531        .fold(false, |changed, (_, expression)| {
532            expression.upgrade_legacy_serialized_dispatches() | changed
533        })
534}
535
536fn upgrade_ctes(ctes: &mut [CTE]) -> bool {
537    ctes.iter_mut().fold(false, |mut changed, cte| {
538        if let Some(cycle) = &mut cte.cycle {
539            changed |= cycle.mark_value.upgrade_legacy_serialized_dispatches();
540            changed |= cycle.mark_default.upgrade_legacy_serialized_dispatches();
541        }
542        let mut statement = cte.body.clone().into_statement();
543        let body_changed = statement.upgrade_legacy_serialized_dispatches();
544        if body_changed {
545            cte.body = super::CteBody::try_from(statement)
546                .expect("dispatch migration preserves the CTE statement kind");
547        }
548        changed | body_changed
549    })
550}
551
552impl FromClause {
553    fn upgrade_legacy_serialized_dispatches(&mut self) -> bool {
554        match self {
555            Self::Table { .. } => false,
556            Self::Join {
557                left, right, on, ..
558            } => {
559                left.upgrade_legacy_serialized_dispatches()
560                    | right.upgrade_legacy_serialized_dispatches()
561                    | upgrade_optional(on)
562            }
563            Self::Values { rows, .. } => upgrade_rows(rows),
564            Self::Function { args, .. } => upgrade_exprs(args),
565            Self::FunctionGroup { functions, .. } => {
566                functions.iter_mut().fold(false, |changed, function| {
567                    upgrade_exprs(&mut function.args) | changed
568                })
569            }
570            Self::Subquery { body, .. } => body.upgrade_legacy_serialized_dispatches(),
571        }
572    }
573}
574
575impl SelectStmt {
576    /// Upgrade every legacy compiler dispatch marker in this complete query tree.
577    #[doc(hidden)]
578    pub fn upgrade_legacy_serialized_dispatches(&mut self) -> bool {
579        let mut changed = upgrade_projections(&mut self.projections);
580        changed |= upgrade_rows(&mut self.values);
581        if let Some(from) = &mut self.from {
582            changed |= from.upgrade_legacy_serialized_dispatches();
583        }
584        changed |= upgrade_optional(&mut self.r#where);
585        changed |= upgrade_exprs(&mut self.group_by);
586        for grouping_set in &mut self.grouping_sets {
587            changed |= upgrade_exprs(grouping_set);
588        }
589        changed |= upgrade_optional(&mut self.having);
590        for order in &mut self.order_by {
591            changed |= order.expr.upgrade_legacy_serialized_dispatches();
592        }
593        changed |= upgrade_optional(&mut self.limit);
594        changed |= upgrade_optional(&mut self.offset);
595        changed |= upgrade_ctes(&mut self.with);
596        if let Some(set_operation) = &mut self.set_op {
597            if let Some(left) = &mut set_operation.left {
598                changed |= left.upgrade_legacy_serialized_dispatches();
599            }
600            changed |= set_operation.right.upgrade_legacy_serialized_dispatches();
601            for order in &mut set_operation.combined_order_by {
602                changed |= order.expr.upgrade_legacy_serialized_dispatches();
603            }
604            changed |= upgrade_optional(&mut set_operation.combined_limit);
605            changed |= upgrade_optional(&mut set_operation.combined_offset);
606        }
607        changed | upgrade_exprs(&mut self.distinct_on)
608    }
609}
610
611impl MergeWhen {
612    fn upgrade_legacy_serialized_dispatches(&mut self) -> bool {
613        match self {
614            Self::UpdateMatched {
615                condition,
616                assignments,
617            }
618            | Self::UpdateNotMatchedBySource {
619                condition,
620                assignments,
621            } => upgrade_optional(condition) | upgrade_assignments(assignments),
622            Self::InsertNotMatched {
623                condition, values, ..
624            } => upgrade_optional(condition) | upgrade_exprs(values),
625            Self::DeleteMatched { condition }
626            | Self::DeleteNotMatchedBySource { condition }
627            | Self::NothingMatched { condition }
628            | Self::NothingNotMatched { condition }
629            | Self::NothingNotMatchedBySource { condition } => upgrade_optional(condition),
630        }
631    }
632}
633
634impl Statement {
635    /// Upgrade legacy compiler dispatch markers without reparsing SQL or changing catalog-bound relation identities.
636    #[doc(hidden)]
637    #[expect(
638        clippy::too_many_lines,
639        reason = "exhaustive AST migration preserves every serialized variant"
640    )]
641    pub fn upgrade_legacy_serialized_dispatches(&mut self) -> bool {
642        match self {
643            Self::Select(select) => select.upgrade_legacy_serialized_dispatches(),
644            Self::CreateDomain(domain) => {
645                let mut changed = upgrade_optional(&mut domain.default);
646                for check in &mut domain.checks {
647                    changed |= check.expression.upgrade_legacy_serialized_dispatches();
648                }
649                changed
650            }
651            Self::Insert(insert) => {
652                let mut changed = upgrade_ctes(&mut insert.with);
653                changed |= upgrade_rows(&mut insert.rows);
654                if let Some(source) = &mut insert.select_source {
655                    changed |= source.upgrade_legacy_serialized_dispatches();
656                }
657                if let Some(conflict) = &mut insert.on_conflict {
658                    for expression in &mut conflict.expressions {
659                        changed |= expression.upgrade_legacy_serialized_dispatches();
660                    }
661                    changed |= conflict
662                        .predicate
663                        .as_deref_mut()
664                        .is_some_and(Expr::upgrade_legacy_serialized_dispatches);
665                    if let OnConflictAction::Update {
666                        assignments,
667                        r#where,
668                    } = &mut conflict.action
669                    {
670                        changed |= upgrade_assignments(assignments);
671                        changed |= r#where
672                            .as_deref_mut()
673                            .is_some_and(Expr::upgrade_legacy_serialized_dispatches);
674                    }
675                }
676                changed | upgrade_projections(&mut insert.returning)
677            }
678            Self::Update(update) => {
679                let mut changed = upgrade_assignments(&mut update.assignments);
680                changed |= upgrade_optional(&mut update.r#where);
681                changed |= upgrade_ctes(&mut update.with);
682                if let Some(from) = &mut update.from {
683                    changed |= from.upgrade_legacy_serialized_dispatches();
684                }
685                changed | upgrade_projections(&mut update.returning)
686            }
687            Self::Delete(delete) => {
688                let mut changed = upgrade_optional(&mut delete.r#where);
689                changed |= upgrade_ctes(&mut delete.with);
690                if let Some(using) = &mut delete.using {
691                    changed |= using.upgrade_legacy_serialized_dispatches();
692                }
693                changed | upgrade_projections(&mut delete.returning)
694            }
695            Self::CreateView { body, .. }
696            | Self::CreateMaterializedView { body, .. }
697            | Self::CreateTableAs { body, .. } => body.upgrade_legacy_serialized_dispatches(),
698            Self::DeclareCursor(cursor) => cursor.query.upgrade_legacy_serialized_dispatches(),
699            Self::Explain { body, .. } | Self::Prepare { body, .. } => {
700                body.upgrade_legacy_serialized_dispatches()
701            }
702            Self::Execute { params, .. } | Self::Call { args: params, .. } => upgrade_exprs(params),
703            Self::Values { rows } => upgrade_rows(rows),
704            Self::Merge(merge) => {
705                let mut changed = upgrade_ctes(&mut merge.with);
706                changed |= merge.source.upgrade_legacy_serialized_dispatches();
707                changed |= merge.join_condition.upgrade_legacy_serialized_dispatches();
708                for clause in &mut merge.when_clauses {
709                    changed |= clause.upgrade_legacy_serialized_dispatches();
710                }
711                changed | upgrade_projections(&mut merge.returning)
712            }
713            Self::CreateFunction(definition) => {
714                let mut changed = definition
715                    .params
716                    .iter_mut()
717                    .fold(false, |changed, parameter| {
718                        parameter
719                            .default
720                            .as_mut()
721                            .is_some_and(Expr::upgrade_legacy_serialized_dispatches)
722                            | changed
723                    });
724                if let FunctionBody::Statements(statements) = &mut definition.body {
725                    for statement in statements {
726                        changed |= statement.upgrade_legacy_serialized_dispatches();
727                    }
728                }
729                changed
730            }
731            Self::CreateTrigger(trigger) => upgrade_optional(&mut trigger.when),
732            Self::CreateRule(rule) => {
733                let mut changed = upgrade_optional(&mut rule.condition);
734                for action in &mut rule.actions {
735                    changed |= action.upgrade_legacy_serialized_dispatches();
736                }
737                changed
738            }
739            Self::CreateTable(_)
740            | Self::CreateTableIfNotExists(_)
741            | Self::CreateIndex(_)
742            | Self::Drop(_)
743            | Self::AlterTable(_)
744            | Self::AlterForeignTable(_)
745            | Self::AlterView(_)
746            | Self::RefreshMaterializedView { .. }
747            | Self::CreateSchema { .. }
748            | Self::AlterSchemaOwner { .. }
749            | Self::Notify { .. }
750            | Self::Listen { .. }
751            | Self::Unlisten { .. }
752            | Self::SetVariable { .. }
753            | Self::ResetVariable { .. }
754            | Self::ResetAllVariables
755            | Self::SetConstraints { .. }
756            | Self::ShowVariable { .. }
757            | Self::Discard { .. }
758            | Self::Load { .. }
759            | Self::Analyze { .. }
760            | Self::Vacuum(_)
761            | Self::Truncate { .. }
762            | Self::Transaction(_)
763            | Self::FetchCursor(_)
764            | Self::CloseCursor { .. }
765            | Self::CreateSequence(_)
766            | Self::AlterSequence(_)
767            | Self::Deallocate { .. }
768            | Self::CreateForeignServer(_)
769            | Self::CreateForeignTable(_)
770            | Self::CreateForeignTableIfNotExists(_)
771            | Self::DropFunction(_)
772            | Self::AlterRoutine(_)
773            | Self::AlterRoutineOwner(_)
774            | Self::RenameRoutine(_)
775            | Self::GrantRoutine(_)
776            | Self::GrantTable(_)
777            | Self::GrantSequence(_)
778            | Self::GrantDatabase(_)
779            | Self::GrantSchema(_)
780            | Self::GrantRole(_)
781            | Self::CreateRole(_)
782            | Self::AlterRole(_)
783            | Self::DropRole(_)
784            | Self::DropTrigger(_)
785            | Self::DropRule(_)
786            | Self::DoBlock { .. } => false,
787        }
788    }
789}
790
791/// Return whether `name` is a built-in aggregate understood by the planner.
792#[must_use]
793pub fn is_builtin_aggregate_function(name: &str) -> bool {
794    matches!(
795        name.to_ascii_lowercase().as_str(),
796        "count"
797            | "sum"
798            | "avg"
799            | "min"
800            | "max"
801            | "string_agg"
802            | "array_agg"
803            | "bool_and"
804            | "bool_or"
805            | "stddev"
806            | "stddev_samp"
807            | "stddev_pop"
808            | "variance"
809            | "var_samp"
810            | "var_pop"
811            | "percentile_cont"
812            | "percentile_disc"
813            | "mode"
814            | "json_agg"
815            | "jsonb_agg"
816            | "json_object_agg"
817            | "jsonb_object_agg"
818    )
819}
820
821#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
822pub enum BinaryOp {
823    Equal,
824    NotEqual,
825    Less,
826    LessEqual,
827    Greater,
828    GreaterEqual,
829    Add,
830    Subtract,
831    Multiply,
832    Divide,
833}
834
835/// `Expr` restricted to value-producing forms used by `INSERT` rows.
836pub type ValueExpr = Expr;