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}