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::{FunctionBinding, SelectStmt};
12
13/// Query-local identity for an executor-only row source. Parser-produced SQL never contains this identity, so internal row carriers cannot collide with user relation aliases.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
15#[doc(hidden)]
16pub struct InternalRelationId(u64);
17
18impl InternalRelationId {
19    /// Allocate an opaque relation identity for an engine-injected row source.
20    #[must_use]
21    pub fn allocate() -> Self {
22        static NEXT_ID: AtomicU64 = AtomicU64::new(1);
23        let id = NEXT_ID
24            .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
25                current.checked_add(1)
26            })
27            .expect("internal relation identity space exhausted");
28        Self(id)
29    }
30
31    /// Address one zero-based attribute of this internal relation.
32    #[must_use]
33    pub fn column(self, attribute: usize) -> InternalColumnRef {
34        InternalColumnRef {
35            relation: self,
36            attribute: u32::try_from(attribute).expect("internal relation attribute exceeds u32"),
37        }
38    }
39
40    #[must_use]
41    pub const fn raw(self) -> u64 {
42        self.0
43    }
44
45    #[must_use]
46    pub const fn from_raw(raw: u64) -> Self {
47        Self(raw)
48    }
49}
50
51/// Structural reference to an executor-only relation attribute. This is the UQA analogue of PostgreSQL's `Var(varno, varattno)` identity: it is never resolved through SQL text names.
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
53#[doc(hidden)]
54pub struct InternalColumnRef {
55    relation: InternalRelationId,
56    attribute: u32,
57}
58
59impl InternalColumnRef {
60    #[must_use]
61    pub const fn relation(self) -> InternalRelationId {
62        self.relation
63    }
64
65    #[must_use]
66    pub const fn attribute(self) -> usize {
67        self.attribute as usize
68    }
69
70    #[must_use]
71    pub const fn from_raw(relation: u64, attribute: u32) -> Self {
72        Self {
73            relation: InternalRelationId::from_raw(relation),
74            attribute,
75        }
76    }
77}
78
79#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
80pub struct Projection {
81    pub expr: Expr,
82    pub alias: Option<String>,
83}
84
85#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
86pub struct OrderBy {
87    pub expr: Expr,
88    pub descending: bool,
89    /// `NULLS FIRST` / `NULLS LAST` placement. `None` means the
90    /// SQL-standard default - `NULLS LAST` for ASC and `NULLS FIRST`
91    /// for DESC. Mirrors `PostgreSQL` semantics.
92    pub nulls: Option<NullsOrder>,
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
96pub enum NullsOrder {
97    First,
98    Last,
99}
100
101/// One query-local window definition. The specification owns only its written clauses; inheritance is resolved through an earlier named definition.
102#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
103pub struct WindowDefinition<S = WindowSpec> {
104    pub name: Option<String>,
105    pub inherited: Option<usize>,
106    pub spec: S,
107}
108
109/// Compiler-only equality key for a raw window declaration; not part of stored SQL.
110#[derive(Debug, Clone, PartialEq)]
111pub struct WindowDefinitionSyntax(pub(crate) serde_json::Value);
112
113#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
114pub struct WindowSpec {
115    /// Raw syntax survives only until the compiler selects its query-local definition.
116    #[serde(skip)]
117    pub raw_definition: Option<WindowDefinitionSyntax>,
118    /// Canonical definition in the enclosing query block. Older stored inline specifications have no slot.
119    #[serde(default, skip_serializing_if = "Option::is_none")]
120    pub definition: Option<usize>,
121    /// 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.
122    #[serde(default, skip_serializing_if = "Option::is_none")]
123    pub reference: Option<WindowReference>,
124    pub partition_by: Vec<Expr>,
125    pub order_by: Vec<OrderBy>,
126    /// `ROWS` / `RANGE` frame, or `None` when not specified (defaults
127    /// to `RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW`).
128    pub frame: Option<WindowFrame>,
129}
130
131impl WindowSpec {
132    /// Scalar roots owned by this specification, excluding separately stored inherited clauses.
133    pub fn expressions_mut(&mut self) -> impl Iterator<Item = &mut Expr> {
134        self.partition_by
135            .iter_mut()
136            .chain(self.order_by.iter_mut().map(|order| &mut order.expr))
137            .chain(
138                self.frame
139                    .iter_mut()
140                    .flat_map(|frame| [&mut frame.start, &mut frame.end])
141                    .filter_map(|bound| match bound {
142                        FrameBound::Preceding(value) | FrameBound::Following(value) => {
143                            Some(value.as_mut())
144                        }
145                        _ => None,
146                    }),
147            )
148    }
149}
150
151#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
152pub struct WindowReference {
153    pub name: String,
154    pub kind: WindowReferenceKind,
155}
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
158pub enum WindowReferenceKind {
159    /// `OVER window_name` uses the named definition directly, including its frame.
160    Direct,
161    /// `OVER (window_name ...)` or `WINDOW child AS (parent ...)` copies and may extend a frameless definition.
162    Copy,
163}
164
165#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
166pub struct WindowFrame {
167    pub mode: FrameMode,
168    pub start: FrameBound,
169    pub end: FrameBound,
170    /// Whether the frame was written `BETWEEN start AND end`. A frame that names only its start, such as `ROWS UNBOUNDED PRECEDING`, ends at the current row, and the definition is deparsed as it was written.
171    #[serde(default = "super::default_true")]
172    pub between: bool,
173    #[serde(default, skip_serializing_if = "FrameExclusion::is_no_others")]
174    pub exclusion: FrameExclusion,
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
178pub enum FrameMode {
179    Rows,
180    Range,
181    Groups,
182}
183
184/// The written function-call form, retained independently of overload binding. The durable `order_syntax` field keeps its original name for stored-expression compatibility.
185#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
186pub enum FunctionCallSyntax {
187    #[default]
188    Legacy,
189    Ordinary,
190    WithinGroup,
191    Extract,
192}
193
194/// The original public name, preserved for callers and stored ordering metadata.
195pub type FunctionOrderSyntax = FunctionCallSyntax;
196
197impl FunctionCallSyntax {
198    #[must_use]
199    pub const fn is_legacy(&self) -> bool {
200        matches!(self, Self::Legacy)
201    }
202}
203
204/// The aggregate modifiers written on a window call, which `ParseFuncOrColumn` rejects after it has resolved the function. The arguments of a call written with `WITHIN GROUP` include its ordering expressions.
205#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
206pub struct WindowCallModifiers {
207    pub distinct: bool,
208    /// An aggregate `ORDER BY` inside the argument list.
209    pub ordered: bool,
210    pub within_group: bool,
211}
212
213impl WindowCallModifiers {
214    #[must_use]
215    pub const fn is_empty(&self) -> bool {
216        !self.distinct && !self.ordered && !self.within_group
217    }
218}
219
220/// The frame exclusion clause: the rows of the frame that a window function or aggregate does not see.
221#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
222pub enum FrameExclusion {
223    #[default]
224    NoOthers,
225    CurrentRow,
226    /// The current row and its peers.
227    Group,
228    /// The peers of the current row, but not the current row itself.
229    Ties,
230}
231
232impl FrameExclusion {
233    #[must_use]
234    pub const fn is_no_others(&self) -> bool {
235        matches!(self, Self::NoOthers)
236    }
237
238    /// The clause as `pg_get_viewdef` spells it, or `None` for `EXCLUDE NO OTHERS`, which it omits.
239    #[must_use]
240    pub const fn sql(self) -> Option<&'static str> {
241        match self {
242            Self::NoOthers => None,
243            Self::CurrentRow => Some("EXCLUDE CURRENT ROW"),
244            Self::Group => Some("EXCLUDE GROUP"),
245            Self::Ties => Some("EXCLUDE TIES"),
246        }
247    }
248}
249
250#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
251pub enum FrameBound {
252    UnboundedPreceding,
253    UnboundedFollowing,
254    CurrentRow,
255    Preceding(Box<Expr>),
256    Following(Box<Expr>),
257}
258
259/// Scalar expression nodes the compiler handles.
260#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
261pub enum Expr {
262    Star,
263    /// Relation-qualified wildcard projection (`table.*` or `alias.*`).
264    QualifiedStar(String),
265    /// `DEFAULT` in an INSERT/UPDATE assignment is a mutation marker resolved against the target column before expression evaluation.
266    Default,
267    /// Unqualified column reference (`col`).
268    Column(String),
269    /// Qualified column reference (`table.col` or `alias.col`).
270    QualifiedColumn {
271        qualifier: String,
272        column: String,
273    },
274    /// Structural column reference emitted internally rather than by SQL parsing; SQL name binding must not rewrite it.
275    #[doc(hidden)]
276    InternalColumn(InternalColumnRef),
277    Literal(Value),
278    /// 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.
279    #[doc(hidden)]
280    TypedLiteral {
281        value: Value,
282        ty: String,
283    },
284    /// A positional bind parameter (`$1`, `$2`, ...).
285    Param(usize),
286    /// Function call dispatched through the function registry.
287    Func {
288        name: String,
289        #[serde(default, skip_serializing_if = "Option::is_none")]
290        binding: Option<FunctionBinding>,
291        args: Vec<Expr>,
292        /// `func(DISTINCT expr)` - only meaningful for aggregate
293        /// functions. Mirrors `PostgreSQL`'s `agg_distinct`.
294        distinct: bool,
295        /// Ordering expressions from either `func(expr ORDER BY ...)` or `func(expr) WITHIN GROUP (ORDER BY ...)`; `order_syntax` preserves their written location.
296        order_by: Vec<OrderBy>,
297        #[serde(default, skip_serializing_if = "FunctionOrderSyntax::is_legacy")]
298        order_syntax: FunctionOrderSyntax,
299        /// `func(...) FILTER (WHERE expr)` - aggregate-level row filter.
300        filter: Option<Box<Expr>>,
301    },
302    /// SQL array constructor.
303    Array(Vec<Expr>),
304    /// Anonymous SQL row constructor (`ROW(...)` or `(a, b)`).
305    Row(Vec<Expr>),
306    /// A stored typed row with creation-time attribute positions and already selected field conversions.
307    CompositeRow {
308        items: Vec<Expr>,
309        binding: super::CompositeRowBinding,
310    },
311    /// `lhs op rhs` - comparison or arithmetic.
312    Binary {
313        op: BinaryOp,
314        lhs: Box<Expr>,
315        rhs: Box<Expr>,
316    },
317    /// `PostgreSQL` prefix `-`, kept distinct from binary subtraction so the
318    /// operand's declared numeric width and overflow behavior survive lowering.
319    UnaryMinus(Box<Expr>),
320    /// `NOT expr`.
321    Not(Box<Expr>),
322    /// `cond_1 AND cond_2 AND ...` (n-ary).
323    And(Vec<Expr>),
324    /// `cond_1 OR cond_2 OR ...` (n-ary).
325    Or(Vec<Expr>),
326    /// `expr IS NULL` / `expr IS NOT NULL`.
327    IsNull {
328        expr: Box<Expr>,
329        negated: bool,
330    },
331    /// `expr BETWEEN low AND high`.
332    Between {
333        expr: Box<Expr>,
334        low: Box<Expr>,
335        high: Box<Expr>,
336    },
337    /// `expr IN (a, b, c)` literal list.
338    InList {
339        expr: Box<Expr>,
340        list: Vec<Expr>,
341        negated: bool,
342    },
343    /// `func(args) [FILTER (WHERE condition)] OVER (PARTITION BY ... ORDER BY ...)`. Only aggregates accept `FILTER`.
344    WindowCall {
345        name: String,
346        args: Vec<Expr>,
347        spec: Box<WindowSpec>,
348        #[serde(default, skip_serializing_if = "Option::is_none")]
349        filter: Option<Box<Expr>>,
350        #[serde(default, skip_serializing_if = "WindowCallModifiers::is_empty")]
351        modifiers: WindowCallModifiers,
352    },
353    /// `CASE [base] WHEN cond THEN result ... [ELSE default] END`.
354    /// `base` lifts simple-form `CASE expr WHEN val THEN ...` into an
355    /// optional comparison anchor; searched-form `CASE WHEN cond ...`
356    /// leaves it `None`.
357    Case {
358        base: Option<Box<Expr>>,
359        when: Vec<(Expr, Expr)>,
360        else_branch: Option<Box<Expr>>,
361    },
362    /// `CAST(expr AS type)`. The type name is preserved verbatim so
363    /// the evaluator can apply the correct coercion.
364    Cast {
365        /// Set by analysis for an implicit coercion retained in stored syntax.
366        #[serde(default, skip_serializing_if = "std::ops::Not::not")]
367        implicit: bool,
368        expr: Box<Expr>,
369        ty: String,
370    },
371    /// `(SELECT ...)` query expression: ordinary scalar consumers select one
372    /// column; a multiple-column SET target consumes the positional result.
373    ScalarSubquery(Box<SelectStmt>),
374    /// `EXISTS (SELECT ...)` -- truthy when the body produces at
375    /// least one row.
376    Exists {
377        body: Box<SelectStmt>,
378        negated: bool,
379    },
380    /// `expr [NOT] IN (SELECT ...)` set membership against a
381    /// subquery. Evaluator runs the body once per top-level
382    /// expression and tests membership.
383    InSubquery {
384        expr: Box<Expr>,
385        body: Box<SelectStmt>,
386        negated: bool,
387    },
388}
389
390impl Expr {
391    pub fn qualified_column(qualifier: impl Into<String>, column: impl Into<String>) -> Self {
392        Self::QualifiedColumn {
393            qualifier: qualifier.into(),
394            column: column.into(),
395        }
396    }
397
398    /// Upgrade compiler-owned function markers deserialized from catalogs
399    /// written by releases through 0.1.6.
400    #[doc(hidden)]
401    #[expect(
402        clippy::too_many_lines,
403        reason = "exhaustive AST migration preserves every serialized variant"
404    )]
405    pub fn upgrade_legacy_serialized_dispatches(&mut self) -> bool {
406        let mut changed = false;
407        match self {
408            Self::Func {
409                name,
410                binding,
411                args,
412                order_by,
413                filter,
414                ..
415            } => {
416                for argument in args {
417                    changed |= argument.upgrade_legacy_serialized_dispatches();
418                }
419                for order in order_by {
420                    changed |= order.expr.upgrade_legacy_serialized_dispatches();
421                }
422                if let Some(filter) = filter {
423                    changed |= filter.upgrade_legacy_serialized_dispatches();
424                }
425                changed |=
426                    super::FunctionBinding::upgrade_legacy_serialized_dispatch(name, binding);
427            }
428            Self::Array(items)
429            | Self::Row(items)
430            | Self::CompositeRow { items, .. }
431            | Self::And(items)
432            | Self::Or(items) => {
433                for item in items {
434                    changed |= item.upgrade_legacy_serialized_dispatches();
435                }
436            }
437            Self::Binary { lhs, rhs, .. } => {
438                changed |= lhs.upgrade_legacy_serialized_dispatches();
439                changed |= rhs.upgrade_legacy_serialized_dispatches();
440            }
441            Self::UnaryMinus(inner)
442            | Self::Not(inner)
443            | Self::IsNull { expr: inner, .. }
444            | Self::Cast { expr: inner, .. } => {
445                changed |= inner.upgrade_legacy_serialized_dispatches();
446            }
447            Self::Between { expr, low, high } => {
448                changed |= expr.upgrade_legacy_serialized_dispatches();
449                changed |= low.upgrade_legacy_serialized_dispatches();
450                changed |= high.upgrade_legacy_serialized_dispatches();
451            }
452            Self::InList { expr, list, .. } => {
453                changed |= expr.upgrade_legacy_serialized_dispatches();
454                for item in list {
455                    changed |= item.upgrade_legacy_serialized_dispatches();
456                }
457            }
458            Self::WindowCall {
459                args, spec, filter, ..
460            } => {
461                for argument in args {
462                    changed |= argument.upgrade_legacy_serialized_dispatches();
463                }
464                if let Some(filter) = filter {
465                    changed |= filter.upgrade_legacy_serialized_dispatches();
466                }
467                for partition in &mut spec.partition_by {
468                    changed |= partition.upgrade_legacy_serialized_dispatches();
469                }
470                for order in &mut spec.order_by {
471                    changed |= order.expr.upgrade_legacy_serialized_dispatches();
472                }
473                if let Some(frame) = &mut spec.frame {
474                    for bound in [&mut frame.start, &mut frame.end] {
475                        match bound {
476                            FrameBound::Preceding(expression)
477                            | FrameBound::Following(expression) => {
478                                changed |= expression.upgrade_legacy_serialized_dispatches();
479                            }
480                            FrameBound::UnboundedPreceding
481                            | FrameBound::UnboundedFollowing
482                            | FrameBound::CurrentRow => {}
483                        }
484                    }
485                }
486            }
487            Self::Case {
488                base,
489                when,
490                else_branch,
491            } => {
492                if let Some(base) = base {
493                    changed |= base.upgrade_legacy_serialized_dispatches();
494                }
495                for (condition, result) in when {
496                    changed |= condition.upgrade_legacy_serialized_dispatches();
497                    changed |= result.upgrade_legacy_serialized_dispatches();
498                }
499                if let Some(branch) = else_branch {
500                    changed |= branch.upgrade_legacy_serialized_dispatches();
501                }
502            }
503            Self::InSubquery { expr, body, .. } => {
504                changed |= expr.upgrade_legacy_serialized_dispatches();
505                changed |= body.upgrade_legacy_serialized_dispatches();
506            }
507            Self::ScalarSubquery(body) | Self::Exists { body, .. } => {
508                changed |= body.upgrade_legacy_serialized_dispatches();
509            }
510            Self::Default
511            | Self::Star
512            | Self::QualifiedStar(_)
513            | Self::Column(_)
514            | Self::QualifiedColumn { .. }
515            | Self::InternalColumn(_)
516            | Self::Literal(_)
517            | Self::TypedLiteral { .. }
518            | Self::Param(_) => {}
519        }
520        changed
521    }
522
523    /// True when this expression tree contains a window function call.
524    #[must_use]
525    pub fn contains_window(&self) -> bool {
526        self.any_node(&|node| matches!(node, Self::WindowCall { .. }))
527    }
528
529    /// True when this expression tree contains a built-in aggregate call.
530    #[must_use]
531    pub fn contains_aggregate(&self) -> bool {
532        self.any_node(
533            &|node| matches!(node, Self::Func { name, .. } if is_builtin_aggregate_function(name)),
534        )
535    }
536
537    /// True when this expression contains a column whose owning relation can only be determined after catalog schemas have been bound.
538    #[must_use]
539    pub fn contains_unqualified_column(&self) -> bool {
540        self.any_node(&|node| matches!(node, Self::Column(_)))
541    }
542
543    /// True when this expression contains a function whose strictness cannot be decided without an engine catalog.
544    #[must_use]
545    pub fn contains_function_with_unknown_strictness(&self) -> bool {
546        self.any_node(&|node| {
547            matches!(
548                node,
549                Self::Func {
550                    name,
551                    args,
552                    binding,
553                    ..
554                } if crate::expr::bound_scalar_function_strictness(
555                    name,
556                    binding.as_ref(),
557                    args.len(),
558                )
559                .is_none()
560            )
561        })
562    }
563
564    /// Whether `hit` matches this node or any scalar node below it. Subquery bodies are opaque because they own independent query trees.
565    #[must_use]
566    pub fn any_node(&self, hit: &dyn Fn(&Self) -> bool) -> bool {
567        if hit(self) {
568            return true;
569        }
570        match self {
571            Self::Func {
572                args,
573                order_by,
574                filter,
575                ..
576            } => {
577                args.iter().any(|arg| arg.any_node(hit))
578                    || order_by.iter().any(|order| order.expr.any_node(hit))
579                    || filter.as_deref().is_some_and(|filter| filter.any_node(hit))
580            }
581            Self::Array(items)
582            | Self::Row(items)
583            | Self::CompositeRow { items, .. }
584            | Self::And(items)
585            | Self::Or(items) => items.iter().any(|item| item.any_node(hit)),
586            Self::UnaryMinus(expr) | Self::Not(expr) | Self::Cast { expr, .. } => {
587                expr.any_node(hit)
588            }
589            Self::Binary { lhs, rhs, .. } => lhs.any_node(hit) || rhs.any_node(hit),
590            Self::IsNull { expr, .. } | Self::InSubquery { expr, .. } => expr.any_node(hit),
591            Self::Between { expr, low, high } => {
592                expr.any_node(hit) || low.any_node(hit) || high.any_node(hit)
593            }
594            Self::InList { expr, list, .. } => {
595                expr.any_node(hit) || list.iter().any(|item| item.any_node(hit))
596            }
597            Self::Case {
598                base,
599                when,
600                else_branch,
601            } => {
602                base.as_deref().is_some_and(|base| base.any_node(hit))
603                    || when
604                        .iter()
605                        .any(|(condition, result)| condition.any_node(hit) || result.any_node(hit))
606                    || else_branch
607                        .as_deref()
608                        .is_some_and(|branch| branch.any_node(hit))
609            }
610            Self::WindowCall { .. }
611            | Self::Star
612            | Self::QualifiedStar(_)
613            | Self::Default
614            | Self::Column(_)
615            | Self::QualifiedColumn { .. }
616            | Self::InternalColumn(_)
617            | Self::Literal(_)
618            | Self::TypedLiteral { .. }
619            | Self::Param(_)
620            | Self::ScalarSubquery(_)
621            | Self::Exists { .. } => false,
622        }
623    }
624}
625
626/// Return whether `name` is a built-in aggregate understood by the planner.
627#[must_use]
628pub fn is_builtin_aggregate_function(name: &str) -> bool {
629    matches!(
630        name.to_ascii_lowercase().as_str(),
631        "count"
632            | "sum"
633            | "avg"
634            | "min"
635            | "max"
636            | "string_agg"
637            | "array_agg"
638            | "bool_and"
639            | "bool_or"
640            | "stddev"
641            | "stddev_samp"
642            | "stddev_pop"
643            | "variance"
644            | "var_samp"
645            | "var_pop"
646            | "percentile_cont"
647            | "percentile_disc"
648            | "mode"
649            | "json_agg"
650            | "jsonb_agg"
651            | "json_object_agg"
652            | "jsonb_object_agg"
653    )
654}
655
656#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
657pub enum BinaryOp {
658    Equal,
659    NotEqual,
660    Less,
661    LessEqual,
662    Greater,
663    GreaterEqual,
664    Add,
665    Subtract,
666    Multiply,
667    Divide,
668}
669
670/// `Expr` restricted to value-producing forms used by `INSERT` rows.
671pub type ValueExpr = Expr;