llkv_expr/
expr.rs

1//! Buffer-only predicate AST.
2#![forbid(unsafe_code)]
3
4use std::ops::Bound;
5
6/// Logical expression over predicates.
7#[derive(Clone, Debug)]
8pub enum Expr<'a, F> {
9    And(Vec<Expr<'a, F>>),
10    Or(Vec<Expr<'a, F>>),
11    Not(Box<Expr<'a, F>>),
12    Pred(Filter<'a, F>),
13}
14
15impl<'a, F> Expr<'a, F> {
16    /// Build an AND of filters.
17    #[inline]
18    pub fn all_of(fs: Vec<Filter<'a, F>>) -> Expr<'a, F> {
19        Expr::And(fs.into_iter().map(Expr::Pred).collect())
20    }
21
22    /// Build an OR of filters.
23    #[inline]
24    pub fn any_of(fs: Vec<Filter<'a, F>>) -> Expr<'a, F> {
25        Expr::Or(fs.into_iter().map(Expr::Pred).collect())
26    }
27
28    /// Wrap an expression in a logical NOT.
29    #[allow(clippy::should_implement_trait)]
30    #[inline]
31    pub fn not(e: Expr<'a, F>) -> Expr<'a, F> {
32        Expr::Not(Box::new(e))
33    }
34}
35
36/// Single predicate against a field. Byte semantics are adapter-defined.
37#[derive(Debug, Clone)]
38pub struct Filter<'a, F> {
39    pub field_id: F,
40    pub op: Operator<'a>,
41}
42
43/// Comparison/matching operators over raw byte slices.
44#[derive(Debug, Clone)]
45pub enum Operator<'a> {
46    // Equality
47    Equals(&'a [u8]),
48
49    Range {
50        lower: Bound<&'a [u8]>,
51        upper: Bound<&'a [u8]>,
52    },
53
54    // Simple comparisons (can be implemented as special cases of Range if needed)
55    GreaterThan(&'a [u8]),
56    GreaterThanOrEquals(&'a [u8]),
57    LessThan(&'a [u8]),
58    LessThanOrEquals(&'a [u8]),
59
60    // Set & pattern matching
61    In(&'a [&'a [u8]]),
62    StartsWith(&'a [u8]),
63    EndsWith(&'a [u8]),
64    Contains(&'a [u8]),
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70    type TestFieldId = u32;
71    use std::ops::Bound;
72    use std::ptr;
73
74    #[test]
75    fn build_simple_exprs() {
76        let f1 = Filter {
77            field_id: 1,
78            op: Operator::Equals(b"abc"),
79        };
80        let f2 = Filter {
81            field_id: 2,
82            op: Operator::LessThan(b"zzz"),
83        };
84        let all = Expr::all_of(vec![f1.clone(), f2.clone()]);
85        let any = Expr::any_of(vec![f1.clone(), f2.clone()]);
86        let not_all = Expr::not(all);
87        match any {
88            Expr::Or(v) => assert_eq!(v.len(), 2),
89            _ => panic!("expected Or"),
90        }
91        match not_all {
92            Expr::Not(inner) => match *inner {
93                Expr::And(v) => assert_eq!(v.len(), 2),
94                _ => panic!("expected And inside Not"),
95            },
96            _ => panic!("expected Not"),
97        }
98    }
99
100    #[test]
101    fn complex_nested_shape() {
102        // f1: id=1 == "a"
103        // f2: id=2 <  "zzz"
104        // f3: id=3 in ["x","y","z"]
105        // f4: id=4 starts_with "pre"
106        let f1 = Filter {
107            field_id: 1u32,
108            op: Operator::Equals(b"a"),
109        };
110        let f2 = Filter {
111            field_id: 2u32,
112            op: Operator::LessThan(b"zzz"),
113        };
114        let f3 = Filter {
115            field_id: 3u32,
116            op: Operator::In(&[b"x", b"y", b"z"]),
117        };
118        let f4 = Filter {
119            field_id: 4u32,
120            op: Operator::StartsWith(b"pre"),
121        };
122
123        // ( f1 AND ( f2 OR NOT f3 ) )  OR  ( NOT f1 AND f4 )
124        let left = Expr::And(vec![
125            Expr::Pred(f1.clone()),
126            Expr::Or(vec![
127                Expr::Pred(f2.clone()),
128                Expr::not(Expr::Pred(f3.clone())),
129            ]),
130        ]);
131        let right = Expr::And(vec![
132            Expr::not(Expr::Pred(f1.clone())),
133            Expr::Pred(f4.clone()),
134        ]);
135        let top = Expr::Or(vec![left, right]);
136
137        // Shape checks
138        match top {
139            Expr::Or(branches) => {
140                assert_eq!(branches.len(), 2);
141                match &branches[0] {
142                    Expr::And(v) => {
143                        assert_eq!(v.len(), 2);
144                        // AND: [Pred(f1), OR(...)]
145                        match &v[0] {
146                            Expr::Pred(Filter { field_id, .. }) => assert_eq!(*field_id, 1),
147                            _ => panic!("expected Pred(f1) in left-AND[0]"),
148                        }
149                        match &v[1] {
150                            Expr::Or(or_vec) => {
151                                assert_eq!(or_vec.len(), 2);
152                                match &or_vec[0] {
153                                    Expr::Pred(Filter { field_id, .. }) => assert_eq!(*field_id, 2),
154                                    _ => panic!("expected Pred(f2) in left-AND[1].OR[0]"),
155                                }
156                                match &or_vec[1] {
157                                    Expr::Not(inner) => match inner.as_ref() {
158                                        Expr::Pred(Filter { field_id, .. }) => {
159                                            assert_eq!(*field_id, 3)
160                                        }
161                                        _ => panic!("expected Not(Pred(f3)) in left-AND[1].OR[1]"),
162                                    },
163                                    _ => panic!("expected Not(...) in left-AND[1].OR[1]"),
164                                }
165                            }
166                            _ => panic!("expected OR in left-AND[1]"),
167                        }
168                    }
169                    _ => panic!("expected AND on left branch of top OR"),
170                }
171                match &branches[1] {
172                    Expr::And(v) => {
173                        assert_eq!(v.len(), 2);
174                        // AND: [Not(f1), Pred(f4)]
175                        match &v[0] {
176                            Expr::Not(inner) => match inner.as_ref() {
177                                Expr::Pred(Filter { field_id, .. }) => assert_eq!(*field_id, 1),
178                                _ => panic!("expected Not(Pred(f1)) in right-AND[0]"),
179                            },
180                            _ => panic!("expected Not(...) in right-AND[0]"),
181                        }
182                        match &v[1] {
183                            Expr::Pred(Filter { field_id, .. }) => assert_eq!(*field_id, 4),
184                            _ => panic!("expected Pred(f4) in right-AND[1]"),
185                        }
186                    }
187                    _ => panic!("expected AND on right branch of top OR"),
188                }
189            }
190            _ => panic!("expected top-level OR"),
191        }
192    }
193
194    #[test]
195    fn range_bounds_roundtrip() {
196        // [aaa, bbb)
197        let f = Filter {
198            field_id: 7u32,
199            op: Operator::Range {
200                lower: Bound::Included(b"aaa"),
201                upper: Bound::Excluded(b"bbb"),
202            },
203        };
204
205        match f.op {
206            Operator::Range { lower, upper } => {
207                match lower {
208                    Bound::Included(b) => assert_eq!(b, b"aaa"),
209                    _ => panic!("lower bound should be Included"),
210                }
211                match upper {
212                    Bound::Excluded(b) => assert_eq!(b, b"bbb"),
213                    _ => panic!("upper bound should be Excluded"),
214                }
215            }
216            _ => panic!("expected Range operator"),
217        }
218    }
219
220    #[test]
221    fn helper_builders_preserve_structure_and_order() {
222        let f1 = Filter {
223            field_id: 1u32,
224            op: Operator::Equals(b"a"),
225        };
226        let f2 = Filter {
227            field_id: 2u32,
228            op: Operator::Equals(b"b"),
229        };
230        let f3 = Filter {
231            field_id: 3u32,
232            op: Operator::Equals(b"c"),
233        };
234
235        let and_expr = Expr::all_of(vec![f1.clone(), f2.clone(), f3.clone()]);
236        match and_expr {
237            Expr::And(v) => {
238                assert_eq!(v.len(), 3);
239                // Expect Pred(1), Pred(2), Pred(3) in order
240                match &v[0] {
241                    Expr::Pred(Filter { field_id, .. }) => assert_eq!(*field_id, 1),
242                    _ => panic!(),
243                }
244                match &v[1] {
245                    Expr::Pred(Filter { field_id, .. }) => assert_eq!(*field_id, 2),
246                    _ => panic!(),
247                }
248                match &v[2] {
249                    Expr::Pred(Filter { field_id, .. }) => assert_eq!(*field_id, 3),
250                    _ => panic!(),
251                }
252            }
253            _ => panic!("expected And"),
254        }
255
256        let or_expr = Expr::any_of(vec![f3.clone(), f2.clone(), f1.clone()]);
257        match or_expr {
258            Expr::Or(v) => {
259                assert_eq!(v.len(), 3);
260                // Expect Pred(3), Pred(2), Pred(1) in order
261                match &v[0] {
262                    Expr::Pred(Filter { field_id, .. }) => assert_eq!(*field_id, 3),
263                    _ => panic!(),
264                }
265                match &v[1] {
266                    Expr::Pred(Filter { field_id, .. }) => assert_eq!(*field_id, 2),
267                    _ => panic!(),
268                }
269                match &v[2] {
270                    Expr::Pred(Filter { field_id, .. }) => assert_eq!(*field_id, 1),
271                    _ => panic!(),
272                }
273            }
274            _ => panic!("expected Or"),
275        }
276
277        // empty lists are allowed => empty And/Or
278        match Expr::<TestFieldId>::all_of(vec![]) {
279            Expr::And(v) => assert!(v.is_empty()),
280            _ => panic!(),
281        }
282        match Expr::<TestFieldId>::any_of(vec![]) {
283            Expr::Or(v) => assert!(v.is_empty()),
284            _ => panic!(),
285        }
286
287        // not wraps exactly once
288        let pred = Expr::Pred(f1.clone());
289        let not_pred = Expr::not(pred);
290        match not_pred {
291            Expr::Not(inner) => match *inner {
292                Expr::Pred(Filter { field_id, .. }) => assert_eq!(field_id, 1),
293                _ => panic!("Not should contain the original Pred"),
294            },
295            _ => panic!("expected Not"),
296        }
297    }
298
299    #[test]
300    fn set_and_pattern_ops_hold_borrowed_slices() {
301        // Using 'static byte literals to check pointer identity is preserved.
302        let a: &'static [u8] = b"aa";
303        let b_: &'static [u8] = b"bb";
304        let c: &'static [u8] = b"cc";
305
306        let f_in = Filter {
307            field_id: 9u32,
308            op: Operator::In(&[a, b_, c]),
309        };
310        match f_in.op {
311            Operator::In(arr) => {
312                assert_eq!(arr.len(), 3);
313                // Pointer identity check
314                assert!(ptr::eq(arr[0].as_ptr(), a.as_ptr()));
315                assert!(ptr::eq(arr[1].as_ptr(), b_.as_ptr()));
316                assert!(ptr::eq(arr[2].as_ptr(), c.as_ptr()));
317            }
318            _ => panic!("expected In"),
319        }
320
321        let f_sw = Filter {
322            field_id: 10u32,
323            op: Operator::StartsWith(b"pre"),
324        };
325        let f_ew = Filter {
326            field_id: 11u32,
327            op: Operator::EndsWith(b"suf"),
328        };
329        let f_ct = Filter {
330            field_id: 12u32,
331            op: Operator::Contains(b"mid"),
332        };
333
334        match f_sw.op {
335            Operator::StartsWith(b) => assert_eq!(b, b"pre"),
336            _ => panic!(),
337        }
338        match f_ew.op {
339            Operator::EndsWith(b) => assert_eq!(b, b"suf"),
340            _ => panic!(),
341        }
342        match f_ct.op {
343            Operator::Contains(b) => assert_eq!(b, b"mid"),
344            _ => panic!(),
345        }
346    }
347
348    #[test]
349    fn generic_field_id_works_with_strings() {
350        // Demonstrate F = &'static str
351        let f1 = Filter {
352            field_id: "name",
353            op: Operator::Equals(b"alice"),
354        };
355        let f2 = Filter {
356            field_id: "status",
357            op: Operator::GreaterThanOrEquals(b"active"),
358        };
359        let expr = Expr::all_of(vec![f1.clone(), f2.clone()]);
360
361        match expr {
362            Expr::And(v) => {
363                assert_eq!(v.len(), 2);
364                match &v[0] {
365                    Expr::Pred(Filter { field_id, .. }) => assert_eq!(*field_id, "name"),
366                    _ => panic!("expected Pred(name)"),
367                }
368                match &v[1] {
369                    Expr::Pred(Filter { field_id, .. }) => assert_eq!(*field_id, "status"),
370                    _ => panic!("expected Pred(status)"),
371                }
372            }
373            _ => panic!("expected And"),
374        }
375    }
376
377    #[test]
378    fn very_deep_not_chain() {
379        // Build Not(Not(...Not(Pred)...)) of depth 64
380        let base = Expr::Pred(Filter {
381            field_id: 42u32,
382            op: Operator::Equals(b"x"),
383        });
384        let mut expr = base;
385        for _ in 0..64 {
386            expr = Expr::not(expr);
387        }
388
389        // Count nested NOTs
390        let mut count = 0usize;
391        let mut cur = &expr;
392        loop {
393            match cur {
394                Expr::Not(inner) => {
395                    count += 1;
396                    cur = inner;
397                }
398                Expr::Pred(Filter { field_id, .. }) => {
399                    assert_eq!(*field_id, 42);
400                    break;
401                }
402                _ => panic!("unexpected node inside deep NOT chain"),
403            }
404        }
405        assert_eq!(count, 64);
406    }
407}