1use std::collections::HashMap;
73
74use super::filter::{Filter, Predicate};
75use reddb_types::Value;
76
77#[derive(Debug, Clone, PartialEq, Eq)]
80pub enum CompileError {
81 UnknownColumn(String),
82}
83
84impl std::fmt::Display for CompileError {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 match self {
87 CompileError::UnknownColumn(c) => {
88 write!(f, "compiled filter: unknown column '{c}'")
89 }
90 }
91 }
92}
93
94impl std::error::Error for CompileError {}
95
96#[derive(Debug, Clone)]
102pub enum CompiledOp {
103 Predicate {
108 col_idx: usize,
109 predicate: Predicate,
110 },
111 AndN(usize),
114 OrN(usize),
117 Not,
119}
120
121#[derive(Debug, Clone)]
126pub struct CompiledFilter {
127 ops: Vec<CompiledOp>,
128}
129
130impl CompiledFilter {
131 pub fn compile(filter: &Filter, schema: &HashMap<String, usize>) -> Result<Self, CompileError> {
136 let mut ops = Vec::new();
137 compile_into(filter, schema, &mut ops)?;
138 Ok(Self { ops })
139 }
140
141 pub fn op_count(&self) -> usize {
144 self.ops.len()
145 }
146
147 pub fn evaluate(&self, slot: &[Value]) -> bool {
158 let mut stack: Vec<bool> = Vec::with_capacity(8);
161 for op in &self.ops {
162 match op {
163 CompiledOp::Predicate { col_idx, predicate } => {
164 let value = slot.get(*col_idx).unwrap_or(&Value::Null);
165 let result = predicate.evaluate(value);
171 stack.push(result);
172 }
173 CompiledOp::AndN(n) => {
174 let take = (*n).min(stack.len());
175 let new_len = stack.len() - take;
176 let result = stack[new_len..].iter().all(|b| *b);
177 stack.truncate(new_len);
178 stack.push(result);
179 }
180 CompiledOp::OrN(n) => {
181 let take = (*n).min(stack.len());
182 let new_len = stack.len() - take;
183 let result = stack[new_len..].iter().any(|b| *b);
184 stack.truncate(new_len);
185 stack.push(result);
186 }
187 CompiledOp::Not => {
188 let v = stack.pop().unwrap_or(true);
189 stack.push(!v);
190 }
191 }
192 }
193 stack.pop().unwrap_or(true)
196 }
197}
198
199fn compile_into(
200 filter: &Filter,
201 schema: &HashMap<String, usize>,
202 ops: &mut Vec<CompiledOp>,
203) -> Result<(), CompileError> {
204 match filter {
205 Filter::Predicate(p) => {
206 let col_idx = schema
207 .get(&p.column)
208 .copied()
209 .ok_or_else(|| CompileError::UnknownColumn(p.column.clone()))?;
210 ops.push(CompiledOp::Predicate {
211 col_idx,
212 predicate: p.clone(),
213 });
214 }
215 Filter::And(filters) => {
216 for f in filters {
217 compile_into(f, schema, ops)?;
218 }
219 ops.push(CompiledOp::AndN(filters.len()));
220 }
221 Filter::Or(filters) => {
222 for f in filters {
223 compile_into(f, schema, ops)?;
224 }
225 ops.push(CompiledOp::OrN(filters.len()));
226 }
227 Filter::Not(inner) => {
228 compile_into(inner, schema, ops)?;
229 ops.push(CompiledOp::Not);
230 }
231 }
232 Ok(())
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238 use crate::query::filter::Predicate;
239
240 fn schema(cols: &[&str]) -> HashMap<String, usize> {
241 cols.iter()
242 .enumerate()
243 .map(|(i, c)| (c.to_string(), i))
244 .collect()
245 }
246
247 fn slot(values: Vec<Value>) -> Vec<Value> {
248 values
249 }
250
251 #[test]
254 fn compile_simple_eq() {
255 let s = schema(&["a"]);
256 let f = Filter::Predicate(Predicate::eq("a", Value::Integer(5)));
257 let c = CompiledFilter::compile(&f, &s).unwrap();
258 assert_eq!(c.op_count(), 1);
260 }
261
262 #[test]
263 fn compile_and_two() {
264 let s = schema(&["a", "b"]);
265 let f = Filter::And(vec![
266 Filter::Predicate(Predicate::eq("a", Value::Integer(1))),
267 Filter::Predicate(Predicate::eq("b", Value::Integer(2))),
268 ]);
269 let c = CompiledFilter::compile(&f, &s).unwrap();
270 assert_eq!(c.op_count(), 3);
272 }
273
274 #[test]
275 fn compile_or_three() {
276 let s = schema(&["a"]);
277 let f = Filter::Or(vec![
278 Filter::Predicate(Predicate::eq("a", Value::Integer(1))),
279 Filter::Predicate(Predicate::eq("a", Value::Integer(2))),
280 Filter::Predicate(Predicate::eq("a", Value::Integer(3))),
281 ]);
282 let c = CompiledFilter::compile(&f, &s).unwrap();
283 assert_eq!(c.op_count(), 4); }
285
286 #[test]
287 fn compile_not_wraps_inner() {
288 let s = schema(&["a"]);
289 let f = Filter::Not(Box::new(Filter::Predicate(Predicate::eq(
290 "a",
291 Value::Integer(1),
292 ))));
293 let c = CompiledFilter::compile(&f, &s).unwrap();
294 assert_eq!(c.op_count(), 2); }
296
297 #[test]
298 fn compile_unknown_column_errors() {
299 let s = schema(&["a"]);
300 let f = Filter::Predicate(Predicate::eq("missing", Value::Integer(1)));
301 let err = CompiledFilter::compile(&f, &s).unwrap_err();
302 assert_eq!(err, CompileError::UnknownColumn("missing".to_string()));
303 }
304
305 #[test]
308 fn eval_eq_true() {
309 let s = schema(&["age"]);
310 let c = CompiledFilter::compile(
311 &Filter::Predicate(Predicate::eq("age", Value::Integer(42))),
312 &s,
313 )
314 .unwrap();
315 assert!(c.evaluate(&slot(vec![Value::Integer(42)])));
316 }
317
318 #[test]
319 fn eval_eq_false() {
320 let s = schema(&["age"]);
321 let c = CompiledFilter::compile(
322 &Filter::Predicate(Predicate::eq("age", Value::Integer(42))),
323 &s,
324 )
325 .unwrap();
326 assert!(!c.evaluate(&slot(vec![Value::Integer(7)])));
327 }
328
329 #[test]
330 fn eval_and_true_when_all_true() {
331 let s = schema(&["a", "b"]);
332 let f = Filter::And(vec![
333 Filter::Predicate(Predicate::eq("a", Value::Integer(1))),
334 Filter::Predicate(Predicate::eq("b", Value::Integer(2))),
335 ]);
336 let c = CompiledFilter::compile(&f, &s).unwrap();
337 assert!(c.evaluate(&slot(vec![Value::Integer(1), Value::Integer(2)])));
338 }
339
340 #[test]
341 fn eval_and_false_when_one_false() {
342 let s = schema(&["a", "b"]);
343 let f = Filter::And(vec![
344 Filter::Predicate(Predicate::eq("a", Value::Integer(1))),
345 Filter::Predicate(Predicate::eq("b", Value::Integer(2))),
346 ]);
347 let c = CompiledFilter::compile(&f, &s).unwrap();
348 assert!(!c.evaluate(&slot(vec![Value::Integer(1), Value::Integer(99)])));
349 }
350
351 #[test]
352 fn eval_or_true_when_one_true() {
353 let s = schema(&["a"]);
354 let f = Filter::Or(vec![
355 Filter::Predicate(Predicate::eq("a", Value::Integer(1))),
356 Filter::Predicate(Predicate::eq("a", Value::Integer(2))),
357 ]);
358 let c = CompiledFilter::compile(&f, &s).unwrap();
359 assert!(c.evaluate(&slot(vec![Value::Integer(2)])));
360 }
361
362 #[test]
363 fn eval_not_flips_result() {
364 let s = schema(&["a"]);
365 let inner = Filter::Predicate(Predicate::eq("a", Value::Integer(1)));
366 let f = Filter::Not(Box::new(inner));
367 let c = CompiledFilter::compile(&f, &s).unwrap();
368 assert!(!c.evaluate(&slot(vec![Value::Integer(1)])));
369 assert!(c.evaluate(&slot(vec![Value::Integer(2)])));
370 }
371
372 #[test]
373 fn eval_is_null_on_null_value() {
374 let s = schema(&["a"]);
375 let c = CompiledFilter::compile(&Filter::Predicate(Predicate::is_null("a")), &s).unwrap();
376 assert!(c.evaluate(&slot(vec![Value::Null])));
377 assert!(!c.evaluate(&slot(vec![Value::Integer(1)])));
378 }
379
380 #[test]
381 fn eval_range_predicate() {
382 let s = schema(&["age"]);
383 let c = CompiledFilter::compile(
384 &Filter::Predicate(Predicate::between(
385 "age",
386 Value::Integer(18),
387 Value::Integer(65),
388 )),
389 &s,
390 )
391 .unwrap();
392 assert!(c.evaluate(&slot(vec![Value::Integer(30)])));
393 assert!(!c.evaluate(&slot(vec![Value::Integer(10)])));
394 assert!(!c.evaluate(&slot(vec![Value::Integer(70)])));
395 }
396
397 #[test]
398 fn eval_nested_and_or_not() {
399 let s = schema(&["a", "b", "c"]);
401 let f = Filter::Or(vec![
402 Filter::And(vec![
403 Filter::Predicate(Predicate::eq("a", Value::Integer(1))),
404 Filter::Predicate(Predicate::eq("b", Value::Integer(2))),
405 ]),
406 Filter::Not(Box::new(Filter::Predicate(Predicate::eq(
407 "c",
408 Value::Integer(3),
409 )))),
410 ]);
411 let c = CompiledFilter::compile(&f, &s).unwrap();
412
413 assert!(c.evaluate(&slot(vec![
415 Value::Integer(1),
416 Value::Integer(2),
417 Value::Integer(99),
418 ])));
419 assert!(c.evaluate(&slot(vec![
421 Value::Integer(99),
422 Value::Integer(99),
423 Value::Integer(99),
424 ])));
425 assert!(!c.evaluate(&slot(vec![
427 Value::Integer(99),
428 Value::Integer(99),
429 Value::Integer(3),
430 ])));
431 }
432
433 #[test]
436 fn compiled_matches_legacy_for_simple_filters() {
437 let s = schema(&["a", "b"]);
438
439 let f = Filter::Or(vec![
441 Filter::And(vec![
442 Filter::Predicate(Predicate::eq("a", Value::Integer(1))),
443 Filter::Predicate(Predicate::gt("b", Value::Integer(5))),
444 ]),
445 Filter::Predicate(Predicate::is_null("a")),
446 ]);
447 let compiled = CompiledFilter::compile(&f, &s).unwrap();
448
449 let cases: Vec<(Vec<Value>, bool)> = vec![
450 (vec![Value::Integer(1), Value::Integer(10)], true),
452 (vec![Value::Integer(1), Value::Integer(5)], false),
454 (vec![Value::Null, Value::Integer(0)], true),
456 (vec![Value::Integer(2), Value::Integer(10)], false),
458 ];
459
460 for (slot_vals, expected) in cases {
461 let got_compiled = compiled.evaluate(&slot_vals);
463 let row: HashMap<String, Value> = vec!["a", "b"]
465 .into_iter()
466 .zip(slot_vals.iter().cloned())
467 .map(|(k, v)| (k.to_string(), v))
468 .collect();
469 let got_legacy = f.evaluate(&|c| row.get(c).cloned());
470 assert_eq!(
471 got_compiled, got_legacy,
472 "compiled and legacy disagree on row {:?}",
473 slot_vals
474 );
475 assert_eq!(got_compiled, expected, "wrong answer for {:?}", slot_vals);
476 }
477 }
478
479 #[test]
480 fn compiled_matches_legacy_for_random_filters() {
481 let s = schema(&["a", "b", "c"]);
485 let mut seed: u64 = 0x9E3779B97F4A7C15;
486 let mut next = || {
487 seed = seed
488 .wrapping_mul(6364136223846793005)
489 .wrapping_add(1442695040888963407);
490 seed
491 };
492
493 for _ in 0..1000 {
494 let n = (next() % 3) as usize;
495 let filter = make_random_filter(&mut next, n);
496 let compiled = match CompiledFilter::compile(&filter, &s) {
497 Ok(c) => c,
498 Err(_) => continue,
499 };
500
501 let row_vals = vec![
502 Value::Integer((next() % 10) as i64),
503 Value::Integer((next() % 10) as i64),
504 Value::Integer((next() % 10) as i64),
505 ];
506 let row: HashMap<String, Value> = vec!["a", "b", "c"]
507 .into_iter()
508 .zip(row_vals.iter().cloned())
509 .map(|(k, v)| (k.to_string(), v))
510 .collect();
511
512 let got_compiled = compiled.evaluate(&row_vals);
513 let got_legacy = filter.evaluate(&|c| row.get(c).cloned());
514 assert_eq!(
515 got_compiled, got_legacy,
516 "fuzz disagreement: filter={:?} row={:?}",
517 filter, row_vals
518 );
519 }
520 }
521
522 fn make_random_filter(next: &mut impl FnMut() -> u64, depth: usize) -> Filter {
523 if depth == 0 {
524 let col = ["a", "b", "c"][(next() % 3) as usize];
526 let lit = Value::Integer((next() % 10) as i64);
527 let op = next() % 5;
528 return match op {
529 0 => Filter::Predicate(Predicate::eq(col, lit)),
530 1 => Filter::Predicate(Predicate::ne(col, lit)),
531 2 => Filter::Predicate(Predicate::lt(col, lit)),
532 3 => Filter::Predicate(Predicate::gt(col, lit)),
533 _ => Filter::Predicate(Predicate::ge(col, lit)),
534 };
535 }
536 match next() % 3 {
537 0 => Filter::And(vec![
538 make_random_filter(next, depth - 1),
539 make_random_filter(next, depth - 1),
540 ]),
541 1 => Filter::Or(vec![
542 make_random_filter(next, depth - 1),
543 make_random_filter(next, depth - 1),
544 ]),
545 _ => Filter::Not(Box::new(make_random_filter(next, depth - 1))),
546 }
547 }
548
549 #[test]
550 fn evaluate_handles_oversized_index_as_null() {
551 let s = schema(&["a", "b", "out_of_range"]);
555 let f = Filter::Predicate(Predicate::eq("out_of_range", Value::Integer(1)));
556 let c = CompiledFilter::compile(&f, &s).unwrap();
557 let short_slot = vec![Value::Integer(0), Value::Integer(0)];
559 assert!(!c.evaluate(&short_slot));
561 }
562
563 #[test]
564 fn empty_filter_returns_true() {
565 let s = schema(&["a"]);
567 let c = CompiledFilter::compile(&Filter::And(vec![]), &s).unwrap();
568 assert!(c.evaluate(&slot(vec![Value::Integer(1)])));
569 }
570}