Skip to main content

zerodds_sql_filter/
ast.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! AST data types for content-filter expressions.
5
6use alloc::boxed::Box;
7use alloc::string::String;
8use alloc::vec::Vec;
9
10/// An evaluable scalar value. OMG allows more types (char, etc.) — we
11/// deliberately map narrowly to the most common cases here.
12#[derive(Debug, Clone, PartialEq)]
13pub enum Value {
14    /// String literal or string field.
15    String(String),
16    /// Signed 64-bit integer (maps Rust `i8..i64` + `u8..u32`).
17    Int(i64),
18    /// Double precision (for `f32` + `f64` fields).
19    Float(f64),
20    /// Boolean.
21    Bool(bool),
22}
23
24/// Comparison operators.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum CmpOp {
27    /// `=`
28    Eq,
29    /// `!=` / `<>`
30    Neq,
31    /// `<`
32    Lt,
33    /// `<=`
34    Le,
35    /// `>`
36    Gt,
37    /// `>=`
38    Ge,
39    /// `LIKE` — wildcard comparison (only for strings).
40    Like,
41}
42
43/// A filter expression. A recursive tree, produced by [`crate::parse`].
44#[derive(Debug, Clone, PartialEq)]
45pub enum Expr {
46    /// Combined `AND` expression.
47    And(Box<Expr>, Box<Expr>),
48    /// Kombinierter `OR`-Ausdruck.
49    Or(Box<Expr>, Box<Expr>),
50    /// `NOT`-Negation.
51    Not(Box<Expr>),
52    /// Comparison between two operands.
53    Cmp {
54        /// Linker Operand.
55        lhs: Operand,
56        /// Operator.
57        op: CmpOp,
58        /// Rechter Operand.
59        rhs: Operand,
60    },
61    /// `field BETWEEN low AND high` (spec §B.2.1 BetweenPredicate).
62    /// Equivalent to `field >= low AND field <= high`. `negated = true`
63    /// for `NOT BETWEEN`.
64    Between {
65        /// Field operand (left of BETWEEN).
66        field: Operand,
67        /// Lower bound (inclusive).
68        low: Operand,
69        /// Upper bound (inclusive).
70        high: Operand,
71        /// `NOT BETWEEN`?
72        negated: bool,
73    },
74}
75
76/// A comparison operand: literal, field reference or parameter.
77#[derive(Debug, Clone, PartialEq)]
78pub enum Operand {
79    /// Constant literal.
80    Literal(Value),
81    /// Field access, possibly dotted (`a.b.c`).
82    Field(String),
83    /// Positional parameter `%N`.
84    Param(u32),
85}
86
87impl Expr {
88    /// Recursive sub-expression count (useful for tests / metrics).
89    #[must_use]
90    pub fn node_count(&self) -> usize {
91        match self {
92            Self::And(a, b) | Self::Or(a, b) => 1 + a.node_count() + b.node_count(),
93            Self::Not(inner) => 1 + inner.node_count(),
94            Self::Cmp { .. } => 1,
95            Self::Between { .. } => 1,
96        }
97    }
98
99    /// Collects all parameter indices that occur in the expression.
100    #[must_use]
101    pub fn collect_param_indices(&self) -> Vec<u32> {
102        /// zerodds-lint: recursion-depth = parse-tree-depth (≤ 64 via
103        /// parser input caps; no unbounded recursion possible).
104        fn walk(e: &Expr, out: &mut Vec<u32>) {
105            match e {
106                Expr::And(a, b) | Expr::Or(a, b) => {
107                    walk(a, out);
108                    walk(b, out);
109                }
110                Expr::Not(inner) => walk(inner, out),
111                Expr::Cmp { lhs, rhs, .. } => {
112                    if let Operand::Param(i) = lhs {
113                        out.push(*i);
114                    }
115                    if let Operand::Param(i) = rhs {
116                        out.push(*i);
117                    }
118                }
119                Expr::Between {
120                    field, low, high, ..
121                } => {
122                    for op in [field, low, high] {
123                        if let Operand::Param(i) = op {
124                            out.push(*i);
125                        }
126                    }
127                }
128            }
129        }
130        let mut out = Vec::new();
131        walk(self, &mut out);
132        out
133    }
134}