Skip to main content

uqa_execution/scalar/
mod.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! AST-independent scalar physical IR shared by the planner and executors.
8
9mod call_arguments;
10mod context;
11mod evaluator;
12mod subquery;
13mod traversal;
14
15use uqa_core::Value;
16use uqa_sql::ast::{BinaryOp, FrameMode, FunctionBinding, InternalColumnRef, NullsOrder};
17
18/// Index into the query children owned by the enclosing expression plan.
19pub type SubqueryId = usize;
20
21#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
22pub enum ScalarExpr {
23    Star,
24    QualifiedStar(String),
25    Default,
26    Column(String),
27    /// Logical position in an already-bound physical row schema. This variant is introduced only after relational binding so duplicate SQL labels remain independently addressable.
28    Position(usize),
29    /// Structural executor-only attribute, resolved independently of SQL relation and column names.
30    InternalColumn(InternalColumnRef),
31    QualifiedColumn {
32        qualifier: String,
33        column: String,
34    },
35    Literal(Value),
36    Param(usize),
37    Func {
38        name: String,
39        #[serde(default, skip_serializing_if = "Option::is_none")]
40        binding: Option<FunctionBinding>,
41        args: Vec<Self>,
42        distinct: bool,
43        order_by: Vec<ScalarOrder>,
44        filter: Option<Box<Self>>,
45    },
46    Array(Vec<Self>),
47    Row(Vec<Self>),
48    Binary {
49        op: BinaryOp,
50        lhs: Box<Self>,
51        rhs: Box<Self>,
52    },
53    UnaryMinus(Box<Self>),
54    Not(Box<Self>),
55    And(Vec<Self>),
56    Or(Vec<Self>),
57    IsNull {
58        expr: Box<Self>,
59        negated: bool,
60    },
61    Between {
62        expr: Box<Self>,
63        low: Box<Self>,
64        high: Box<Self>,
65    },
66    InList {
67        expr: Box<Self>,
68        list: Vec<Self>,
69        negated: bool,
70    },
71    WindowCall {
72        name: String,
73        args: Vec<Self>,
74        spec: ScalarWindowSpec,
75    },
76    Case {
77        base: Option<Box<Self>>,
78        when: Vec<(Self, Self)>,
79        else_branch: Option<Box<Self>>,
80    },
81    Cast {
82        expr: Box<Self>,
83        ty: String,
84    },
85    ScalarSubquery(SubqueryId),
86    Exists {
87        subquery: SubqueryId,
88        negated: bool,
89    },
90    InSubquery {
91        expr: Box<Self>,
92        subquery: SubqueryId,
93        negated: bool,
94    },
95}
96
97#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
98pub struct ScalarOrder {
99    pub expr: ScalarExpr,
100    pub descending: bool,
101    pub nulls: Option<NullsOrder>,
102}
103
104#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
105pub struct ScalarWindowSpec {
106    pub partition_by: Vec<ScalarExpr>,
107    pub order_by: Vec<ScalarOrder>,
108    pub frame: Option<ScalarWindowFrame>,
109}
110
111#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
112pub struct ScalarWindowFrame {
113    pub mode: FrameMode,
114    pub start: ScalarFrameBound,
115    pub end: ScalarFrameBound,
116}
117
118#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
119pub enum ScalarFrameBound {
120    UnboundedPreceding,
121    UnboundedFollowing,
122    CurrentRow,
123    Preceding(Box<ScalarExpr>),
124    Following(Box<ScalarExpr>),
125}
126
127impl ScalarExpr {
128    #[must_use]
129    pub fn qualified_column(qualifier: impl Into<String>, column: impl Into<String>) -> Self {
130        Self::QualifiedColumn {
131            qualifier: qualifier.into(),
132            column: column.into(),
133        }
134    }
135}
136
137pub use call_arguments::{
138    eval_call_arguments, scalar_call_argument, scalar_call_arguments,
139    validate_scalar_call_arguments, ScalarCallArgument,
140};
141pub use context::ScalarEvalContext;
142pub use evaluator::eval_scalar;
143pub(crate) use evaluator::scalar_integer_binary_width;
144pub use subquery::{ScalarSubqueryRunner, SubqueryResult};
145
146#[cfg(test)]
147mod tests;