Skip to main content

uqa_sql/ir/
mod.rs

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