Skip to main content

robin_sparkless_core/
expr.rs

1//! Engine-agnostic expression IR. All backends interpret this; root and core only use ExprIr.
2
3use serde::{Deserialize, Serialize};
4
5/// Literal value in an expression (engine-agnostic).
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub enum LiteralValue {
8    I64(i64),
9    F64(f64),
10    I32(i32),
11    Str(String),
12    Bool(bool),
13    Null,
14}
15
16/// Expression IR: a single, serializable tree that backends convert to their native Expr.
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub enum ExprIr {
19    /// Column reference: `col("name")`
20    Column(String),
21    /// Literal value
22    Lit(LiteralValue),
23
24    // --- Binary comparison (left, right) ---
25    Eq(Box<ExprIr>, Box<ExprIr>),
26    Ne(Box<ExprIr>, Box<ExprIr>),
27    Gt(Box<ExprIr>, Box<ExprIr>),
28    Ge(Box<ExprIr>, Box<ExprIr>),
29    Lt(Box<ExprIr>, Box<ExprIr>),
30    Le(Box<ExprIr>, Box<ExprIr>),
31    EqNullSafe(Box<ExprIr>, Box<ExprIr>),
32
33    // --- Logical ---
34    And(Box<ExprIr>, Box<ExprIr>),
35    Or(Box<ExprIr>, Box<ExprIr>),
36    Not(Box<ExprIr>),
37
38    // --- Arithmetic ---
39    Add(Box<ExprIr>, Box<ExprIr>),
40    Sub(Box<ExprIr>, Box<ExprIr>),
41    Mul(Box<ExprIr>, Box<ExprIr>),
42    Div(Box<ExprIr>, Box<ExprIr>),
43
44    // --- Other binary ---
45    Between {
46        left: Box<ExprIr>,
47        lower: Box<ExprIr>,
48        upper: Box<ExprIr>,
49    },
50    IsIn(Box<ExprIr>, Box<ExprIr>),
51
52    // --- Unary ---
53    IsNull(Box<ExprIr>),
54    IsNotNull(Box<ExprIr>),
55
56    // --- Conditional ---
57    When {
58        condition: Box<ExprIr>,
59        then_expr: Box<ExprIr>,
60        otherwise: Box<ExprIr>,
61    },
62
63    /// Function call: name and args (e.g. sum, count, upper, substring, cast).
64    Call {
65        name: String,
66        args: Vec<ExprIr>,
67    },
68}
69
70// ---------- Builder helpers ----------
71
72/// Column reference.
73pub fn col(name: &str) -> ExprIr {
74    ExprIr::Column(name.to_string())
75}
76
77pub fn lit_i64(n: i64) -> ExprIr {
78    ExprIr::Lit(LiteralValue::I64(n))
79}
80
81pub fn lit_i32(n: i32) -> ExprIr {
82    ExprIr::Lit(LiteralValue::I32(n))
83}
84
85pub fn lit_f64(n: f64) -> ExprIr {
86    ExprIr::Lit(LiteralValue::F64(n))
87}
88
89pub fn lit_str(s: &str) -> ExprIr {
90    ExprIr::Lit(LiteralValue::Str(s.to_string()))
91}
92
93pub fn lit_bool(b: bool) -> ExprIr {
94    ExprIr::Lit(LiteralValue::Bool(b))
95}
96
97pub fn lit_null() -> ExprIr {
98    ExprIr::Lit(LiteralValue::Null)
99}
100
101/// Generic function call (for the long tail of functions).
102pub fn call(name: &str, args: Vec<ExprIr>) -> ExprIr {
103    ExprIr::Call {
104        name: name.to_string(),
105        args,
106    }
107}
108
109/// When-then-otherwise builder.
110pub struct WhenBuilder {
111    condition: ExprIr,
112}
113
114impl WhenBuilder {
115    pub fn then(self, then_expr: ExprIr) -> WhenThenBuilder {
116        WhenThenBuilder {
117            condition: self.condition,
118            then_expr,
119        }
120    }
121}
122
123pub struct WhenThenBuilder {
124    condition: ExprIr,
125    then_expr: ExprIr,
126}
127
128impl WhenThenBuilder {
129    pub fn otherwise(self, otherwise: ExprIr) -> ExprIr {
130        ExprIr::When {
131            condition: Box::new(self.condition),
132            then_expr: Box::new(self.then_expr),
133            otherwise: Box::new(otherwise),
134        }
135    }
136}
137
138/// Start a when(condition).then(...).otherwise(...) chain.
139pub fn when(condition: ExprIr) -> WhenBuilder {
140    WhenBuilder { condition }
141}
142
143// ---------- Common binary ops as ExprIr builders ----------
144
145pub fn eq(a: ExprIr, b: ExprIr) -> ExprIr {
146    ExprIr::Eq(Box::new(a), Box::new(b))
147}
148
149pub fn ne(a: ExprIr, b: ExprIr) -> ExprIr {
150    ExprIr::Ne(Box::new(a), Box::new(b))
151}
152
153pub fn gt(a: ExprIr, b: ExprIr) -> ExprIr {
154    ExprIr::Gt(Box::new(a), Box::new(b))
155}
156
157pub fn ge(a: ExprIr, b: ExprIr) -> ExprIr {
158    ExprIr::Ge(Box::new(a), Box::new(b))
159}
160
161pub fn lt(a: ExprIr, b: ExprIr) -> ExprIr {
162    ExprIr::Lt(Box::new(a), Box::new(b))
163}
164
165pub fn le(a: ExprIr, b: ExprIr) -> ExprIr {
166    ExprIr::Le(Box::new(a), Box::new(b))
167}
168
169pub fn and_(a: ExprIr, b: ExprIr) -> ExprIr {
170    ExprIr::And(Box::new(a), Box::new(b))
171}
172
173pub fn or_(a: ExprIr, b: ExprIr) -> ExprIr {
174    ExprIr::Or(Box::new(a), Box::new(b))
175}
176
177pub fn not_(a: ExprIr) -> ExprIr {
178    ExprIr::Not(Box::new(a))
179}
180
181pub fn is_null(a: ExprIr) -> ExprIr {
182    ExprIr::IsNull(Box::new(a))
183}
184
185pub fn between(left: ExprIr, lower: ExprIr, upper: ExprIr) -> ExprIr {
186    ExprIr::Between {
187        left: Box::new(left),
188        lower: Box::new(lower),
189        upper: Box::new(upper),
190    }
191}
192
193pub fn is_in(left: ExprIr, right: ExprIr) -> ExprIr {
194    ExprIr::IsIn(Box::new(left), Box::new(right))
195}
196
197// ---------- Aggregation builders (ExprIr::Call) ----------
198
199pub fn sum(expr: ExprIr) -> ExprIr {
200    ExprIr::Call {
201        name: "sum".to_string(),
202        args: vec![expr],
203    }
204}
205
206pub fn count(expr: ExprIr) -> ExprIr {
207    ExprIr::Call {
208        name: "count".to_string(),
209        args: vec![expr],
210    }
211}
212
213pub fn min(expr: ExprIr) -> ExprIr {
214    ExprIr::Call {
215        name: "min".to_string(),
216        args: vec![expr],
217    }
218}
219
220pub fn max(expr: ExprIr) -> ExprIr {
221    ExprIr::Call {
222        name: "max".to_string(),
223        args: vec![expr],
224    }
225}
226
227pub fn mean(expr: ExprIr) -> ExprIr {
228    ExprIr::Call {
229        name: "mean".to_string(),
230        args: vec![expr],
231    }
232}
233
234/// Alias an expression with a new output name.
235pub fn alias(expr: ExprIr, name: &str) -> ExprIr {
236    ExprIr::Call {
237        name: "alias".to_string(),
238        args: vec![expr, lit_str(name)],
239    }
240}