Skip to main content

zerodds_sql_filter/
ast.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! AST-Datentypen für Content-Filter-Expressions.
5
6use alloc::boxed::Box;
7use alloc::string::String;
8use alloc::vec::Vec;
9
10/// Ein evaluierbarer skalarer Wert. OMG erlaubt mehr Typen (char, etc.)
11/// — wir mappen hier bewusst eng auf die häufigsten Fälle.
12#[derive(Debug, Clone, PartialEq)]
13pub enum Value {
14    /// String-Literal oder String-Feld.
15    String(String),
16    /// Signed 64-bit Integer (mappt Rust `i8..i64` + `u8..u32`).
17    Int(i64),
18    /// Doppelte Genauigkeit (für `f32` + `f64`-Felder).
19    Float(f64),
20    /// Boolean.
21    Bool(bool),
22}
23
24/// Vergleichs-Operatoren.
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-Vergleich (nur für Strings).
40    Like,
41}
42
43/// Ein Filter-Ausdruck. Rekursiver Baum, erzeugt vom [`crate::parse`].
44#[derive(Debug, Clone, PartialEq)]
45pub enum Expr {
46    /// Kombinierter `AND`-Ausdruck.
47    And(Box<Expr>, Box<Expr>),
48    /// Kombinierter `OR`-Ausdruck.
49    Or(Box<Expr>, Box<Expr>),
50    /// `NOT`-Negation.
51    Not(Box<Expr>),
52    /// Vergleich zwischen zwei Operanden.
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 zu `field >= low AND field <= high`. `negated = true`
63    /// fuer `NOT BETWEEN`.
64    Between {
65        /// Feld-Operand (links vom BETWEEN).
66        field: Operand,
67        /// Untere Grenze (inklusive).
68        low: Operand,
69        /// Obere Grenze (inklusive).
70        high: Operand,
71        /// `NOT BETWEEN`?
72        negated: bool,
73    },
74}
75
76/// Ein Vergleichs-Operand: Literal, Feld-Referenz oder Parameter.
77#[derive(Debug, Clone, PartialEq)]
78pub enum Operand {
79    /// Konstantes Literal.
80    Literal(Value),
81    /// Feldzugriff, ggf. dotted (`a.b.c`).
82    Field(String),
83    /// Positional Parameter `%N`.
84    Param(u32),
85}
86
87impl Expr {
88    /// Rekursive Sub-Ausdruck-Anzahl (nützlich für Tests / Metriken).
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    /// Sammelt alle Parameter-Indices, die in der Expression vorkommen.
100    #[must_use]
101    pub fn collect_param_indices(&self) -> Vec<u32> {
102        /// zerodds-lint: recursion-depth = parse-tree-depth (≤ 64 durch
103        /// Parser-Input-Caps; keine unbounded Rekursion moeglich).
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}