Skip to main content

teaql_core/
expr.rs

1use crate::{EntityDescriptor, SelectQuery, Value};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub enum BinaryOp {
5    Eq,
6    Ne,
7    Gt,
8    Gte,
9    Lt,
10    Lte,
11    Like,
12    NotLike,
13    In,
14    NotIn,
15    InLarge,
16    NotInLarge,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum ExprFunction {
21    Soundex,
22    Gbk,
23    Count,
24    Sum,
25    Avg,
26    Min,
27    Max,
28    Stddev,
29    StddevPop,
30    VarSamp,
31    VarPop,
32    BitAnd,
33    BitOr,
34    BitXor,
35}
36
37#[derive(Debug, Clone, PartialEq)]
38pub enum Expr {
39    Column(String),
40    Value(Value),
41    /// Compiler provenance for typed LIKE helpers. `pattern` is the unchanged
42    /// execution value; `original` is used only for classified intent projection.
43    /// Raw `like`/`not_like` expressions deliberately do not create this leaf.
44    #[doc(hidden)]
45    LikePattern {
46        pattern: String,
47        original: String,
48    },
49    Function {
50        function: ExprFunction,
51        args: Vec<Expr>,
52    },
53    Binary {
54        left: Box<Expr>,
55        op: BinaryOp,
56        right: Box<Expr>,
57    },
58    SubQuery {
59        left: Box<Expr>,
60        op: BinaryOp,
61        entity: EntityDescriptor,
62        query: Box<SelectQuery>,
63    },
64    Between {
65        expr: Box<Expr>,
66        lower: Box<Expr>,
67        upper: Box<Expr>,
68    },
69    IsNull(Box<Expr>),
70    IsNotNull(Box<Expr>),
71    And(Vec<Expr>),
72    Or(Vec<Expr>),
73    Not(Box<Expr>),
74}
75
76impl Expr {
77    /// Match the long-standing Java runtime policy: collections larger than
78    /// twenty values use the dialect-specific large-set representation.
79    pub const LARGE_IN_THRESHOLD: usize = 20;
80
81    pub fn column(name: impl Into<String>) -> Self {
82        Self::Column(name.into())
83    }
84
85    pub fn value(value: impl Into<Value>) -> Self {
86        Self::Value(value.into())
87    }
88
89    pub fn function(function: ExprFunction, args: impl IntoIterator<Item = Expr>) -> Self {
90        Self::Function {
91            function,
92            args: args.into_iter().collect(),
93        }
94    }
95
96    pub fn soundex(expr: Expr) -> Self {
97        Self::function(ExprFunction::Soundex, [expr])
98    }
99
100    pub fn gbk(expr: Expr) -> Self {
101        Self::function(ExprFunction::Gbk, [expr])
102    }
103
104    pub fn count_all() -> Self {
105        Self::function(ExprFunction::Count, [])
106    }
107
108    pub fn count_expr(expr: Expr) -> Self {
109        Self::function(ExprFunction::Count, [expr])
110    }
111
112    pub fn sum_expr(expr: Expr) -> Self {
113        Self::function(ExprFunction::Sum, [expr])
114    }
115
116    pub fn avg_expr(expr: Expr) -> Self {
117        Self::function(ExprFunction::Avg, [expr])
118    }
119
120    pub fn min_expr(expr: Expr) -> Self {
121        Self::function(ExprFunction::Min, [expr])
122    }
123
124    pub fn max_expr(expr: Expr) -> Self {
125        Self::function(ExprFunction::Max, [expr])
126    }
127
128    pub fn stddev_expr(expr: Expr) -> Self {
129        Self::function(ExprFunction::Stddev, [expr])
130    }
131
132    pub fn stddev_pop_expr(expr: Expr) -> Self {
133        Self::function(ExprFunction::StddevPop, [expr])
134    }
135
136    pub fn var_samp_expr(expr: Expr) -> Self {
137        Self::function(ExprFunction::VarSamp, [expr])
138    }
139
140    pub fn var_pop_expr(expr: Expr) -> Self {
141        Self::function(ExprFunction::VarPop, [expr])
142    }
143
144    pub fn bit_and_expr(expr: Expr) -> Self {
145        Self::function(ExprFunction::BitAnd, [expr])
146    }
147
148    pub fn bit_or_expr(expr: Expr) -> Self {
149        Self::function(ExprFunction::BitOr, [expr])
150    }
151
152    pub fn bit_xor_expr(expr: Expr) -> Self {
153        Self::function(ExprFunction::BitXor, [expr])
154    }
155
156    pub fn sound_like(column: impl Into<String>, value: impl Into<Value>) -> Self {
157        Self::binary(
158            Self::soundex(Self::column(column)),
159            BinaryOp::Eq,
160            Self::soundex(Self::value(value)),
161        )
162    }
163
164    pub fn eq(column: impl Into<String>, value: impl Into<Value>) -> Self {
165        Self::binary(Self::column(column), BinaryOp::Eq, Self::value(value))
166    }
167
168    pub fn ne(column: impl Into<String>, value: impl Into<Value>) -> Self {
169        Self::binary(Self::column(column), BinaryOp::Ne, Self::value(value))
170    }
171
172    pub fn gt(column: impl Into<String>, value: impl Into<Value>) -> Self {
173        Self::binary(Self::column(column), BinaryOp::Gt, Self::value(value))
174    }
175
176    pub fn gte(column: impl Into<String>, value: impl Into<Value>) -> Self {
177        Self::binary(Self::column(column), BinaryOp::Gte, Self::value(value))
178    }
179
180    pub fn lt(column: impl Into<String>, value: impl Into<Value>) -> Self {
181        Self::binary(Self::column(column), BinaryOp::Lt, Self::value(value))
182    }
183
184    pub fn lte(column: impl Into<String>, value: impl Into<Value>) -> Self {
185        Self::binary(Self::column(column), BinaryOp::Lte, Self::value(value))
186    }
187
188    pub fn like(column: impl Into<String>, pattern: impl Into<String>) -> Self {
189        Self::binary(
190            Self::column(column),
191            BinaryOp::Like,
192            Self::value(pattern.into()),
193        )
194    }
195
196    pub fn not_like(column: impl Into<String>, pattern: impl Into<String>) -> Self {
197        Self::binary(
198            Self::column(column),
199            BinaryOp::NotLike,
200            Self::value(pattern.into()),
201        )
202    }
203
204    pub fn contain(column: impl Into<String>, value: impl Into<String>) -> Self {
205        Self::typed_like(column, value.into(), BinaryOp::Like, "%", "%")
206    }
207
208    pub fn not_contain(column: impl Into<String>, value: impl Into<String>) -> Self {
209        Self::typed_like(column, value.into(), BinaryOp::NotLike, "%", "%")
210    }
211
212    pub fn begin_with(column: impl Into<String>, value: impl Into<String>) -> Self {
213        Self::typed_like(column, value.into(), BinaryOp::Like, "", "%")
214    }
215
216    pub fn not_begin_with(column: impl Into<String>, value: impl Into<String>) -> Self {
217        Self::typed_like(column, value.into(), BinaryOp::NotLike, "", "%")
218    }
219
220    pub fn end_with(column: impl Into<String>, value: impl Into<String>) -> Self {
221        Self::typed_like(column, value.into(), BinaryOp::Like, "%", "")
222    }
223
224    pub fn not_end_with(column: impl Into<String>, value: impl Into<String>) -> Self {
225        Self::typed_like(column, value.into(), BinaryOp::NotLike, "%", "")
226    }
227
228    fn typed_like(
229        column: impl Into<String>,
230        original: String,
231        op: BinaryOp,
232        prefix: &str,
233        suffix: &str,
234    ) -> Self {
235        Self::binary(
236            Self::column(column),
237            op,
238            Self::LikePattern {
239                pattern: format!("{prefix}{original}{suffix}"),
240                original,
241            },
242        )
243    }
244
245    pub fn binary(left: Expr, op: BinaryOp, right: Expr) -> Self {
246        Self::Binary {
247            left: Box::new(left),
248            op,
249            right: Box::new(right),
250        }
251    }
252
253    pub fn compare_columns(
254        left_column: impl Into<String>,
255        op: BinaryOp,
256        right_column: impl Into<String>,
257    ) -> Self {
258        Self::binary(Self::column(left_column), op, Self::column(right_column))
259    }
260
261    pub fn in_list(column: impl Into<String>, values: impl IntoIterator<Item = Value>) -> Self {
262        let values = values.into_iter().collect::<Vec<_>>();
263        Self::binary(
264            Self::column(column),
265            if values.len() > Self::LARGE_IN_THRESHOLD {
266                BinaryOp::InLarge
267            } else {
268                BinaryOp::In
269            },
270            Self::Value(Value::List(values)),
271        )
272    }
273
274    pub fn not_in_list(column: impl Into<String>, values: impl IntoIterator<Item = Value>) -> Self {
275        let values = values.into_iter().collect::<Vec<_>>();
276        Self::binary(
277            Self::column(column),
278            if values.len() > Self::LARGE_IN_THRESHOLD {
279                BinaryOp::NotInLarge
280            } else {
281                BinaryOp::NotIn
282            },
283            Self::Value(Value::List(values)),
284        )
285    }
286
287    pub fn in_large(column: impl Into<String>, values: impl IntoIterator<Item = Value>) -> Self {
288        Self::binary(
289            Self::column(column),
290            BinaryOp::InLarge,
291            Self::Value(Value::List(values.into_iter().collect())),
292        )
293    }
294
295    pub fn not_in_large(
296        column: impl Into<String>,
297        values: impl IntoIterator<Item = Value>,
298    ) -> Self {
299        Self::binary(
300            Self::column(column),
301            BinaryOp::NotInLarge,
302            Self::Value(Value::List(values.into_iter().collect())),
303        )
304    }
305
306    pub fn in_subquery(
307        column: impl Into<String>,
308        entity: EntityDescriptor,
309        query: SelectQuery,
310        field: impl Into<String>,
311    ) -> Self {
312        Self::subquery(Self::column(column), BinaryOp::In, entity, query, field)
313    }
314
315    pub fn not_in_subquery(
316        column: impl Into<String>,
317        entity: EntityDescriptor,
318        query: SelectQuery,
319        field: impl Into<String>,
320    ) -> Self {
321        Self::subquery(Self::column(column), BinaryOp::NotIn, entity, query, field)
322    }
323
324    pub fn subquery(
325        left: Expr,
326        op: BinaryOp,
327        entity: EntityDescriptor,
328        mut query: SelectQuery,
329        field: impl Into<String>,
330    ) -> Self {
331        let field = field.into();
332        // SQL `NOT IN` becomes UNKNOWN for every outer row when the projected
333        // set contains NULL. Relation non-existence and negative matching must
334        // ignore orphan child rows instead of poisoning the entire predicate.
335        if matches!(op, BinaryOp::NotIn | BinaryOp::NotInLarge) {
336            query = query.and_filter(Self::is_not_null(field.clone()));
337        }
338        query.projection = vec![field];
339        Self::SubQuery {
340            left: Box::new(left),
341            op,
342            entity,
343            query: Box::new(query),
344        }
345    }
346
347    pub fn between(
348        column: impl Into<String>,
349        lower: impl Into<Value>,
350        upper: impl Into<Value>,
351    ) -> Self {
352        Self::Between {
353            expr: Box::new(Self::column(column)),
354            lower: Box::new(Self::value(lower)),
355            upper: Box::new(Self::value(upper)),
356        }
357    }
358
359    pub fn is_null(column: impl Into<String>) -> Self {
360        Self::IsNull(Box::new(Self::column(column)))
361    }
362
363    pub fn is_not_null(column: impl Into<String>) -> Self {
364        Self::IsNotNull(Box::new(Self::column(column)))
365    }
366
367    pub fn and(parts: impl IntoIterator<Item = Expr>) -> Self {
368        let mut unique = Vec::new();
369        for part in parts {
370            if !unique.contains(&part) {
371                unique.push(part);
372            }
373        }
374        Self::And(unique)
375    }
376
377    pub fn or(parts: impl IntoIterator<Item = Expr>) -> Self {
378        let mut unique = Vec::new();
379        for part in parts {
380            if !unique.contains(&part) {
381                unique.push(part);
382            }
383        }
384        Self::Or(unique)
385    }
386
387    pub fn negate(expr: Expr) -> Self {
388        Self::Not(Box::new(expr))
389    }
390
391    pub fn and_expr(self, other: Expr) -> Self {
392        if self == other {
393            return self;
394        }
395        match self {
396            Self::And(mut parts) => {
397                if !parts.contains(&other) {
398                    parts.push(other);
399                }
400                Self::And(parts)
401            }
402            expr => Self::And(vec![expr, other]),
403        }
404    }
405
406    pub fn or_expr(self, other: Expr) -> Self {
407        if self == other {
408            return self;
409        }
410        match self {
411            Self::Or(mut parts) => {
412                if !parts.contains(&other) {
413                    parts.push(other);
414                }
415                Self::Or(parts)
416            }
417            expr => Self::Or(vec![expr, other]),
418        }
419    }
420}