1#![forbid(unsafe_code)]
3
4use std::ops::Bound;
5
6#[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 #[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 #[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 #[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#[derive(Debug, Clone)]
38pub struct Filter<'a, F> {
39 pub field_id: F,
40 pub op: Operator<'a>,
41}
42
43#[derive(Debug, Clone)]
45pub enum Operator<'a> {
46 Equals(&'a [u8]),
48
49 Range {
50 lower: Bound<&'a [u8]>,
51 upper: Bound<&'a [u8]>,
52 },
53
54 GreaterThan(&'a [u8]),
56 GreaterThanOrEquals(&'a [u8]),
57 LessThan(&'a [u8]),
58 LessThanOrEquals(&'a [u8]),
59
60 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}