Skip to main content

aurora_db/parser/
executor_utils.rs

1use crate::error::{AqlError, ErrorCode, Result};
2use crate::parser::ast;
3
4/// Filter with compiled regexes for performance
5#[derive(Debug, Clone)]
6pub enum CompiledFilter {
7    Eq(String, ast::Value),
8    Ne(String, ast::Value),
9    Gt(String, ast::Value),
10    Gte(String, ast::Value),
11    Lt(String, ast::Value),
12    Lte(String, ast::Value),
13    In(String, ast::Value),
14    NotIn(String, ast::Value),
15    Contains(String, ast::Value),
16    StartsWith(String, ast::Value),
17    EndsWith(String, ast::Value),
18    Matches(String, regex::Regex),
19    IsNull(String),
20    IsNotNull(String),
21    And(Vec<CompiledFilter>),
22    Or(Vec<CompiledFilter>),
23    Not(Box<CompiledFilter>),
24}
25
26/// Compile an AQL filter into a CompiledFilter
27pub fn compile_filter(filter: &ast::Filter) -> Result<CompiledFilter> {
28    match filter {
29        ast::Filter::Eq(f, v) => Ok(CompiledFilter::Eq(f.clone(), v.clone())),
30        ast::Filter::Ne(f, v) => Ok(CompiledFilter::Ne(f.clone(), v.clone())),
31        ast::Filter::Gt(f, v) => Ok(CompiledFilter::Gt(f.clone(), v.clone())),
32        ast::Filter::Gte(f, v) => Ok(CompiledFilter::Gte(f.clone(), v.clone())),
33        ast::Filter::Lt(f, v) => Ok(CompiledFilter::Lt(f.clone(), v.clone())),
34        ast::Filter::Lte(f, v) => Ok(CompiledFilter::Lte(f.clone(), v.clone())),
35        ast::Filter::In(f, v) => Ok(CompiledFilter::In(f.clone(), v.clone())),
36        ast::Filter::NotIn(f, v) => Ok(CompiledFilter::NotIn(f.clone(), v.clone())),
37        ast::Filter::Contains(f, v) => Ok(CompiledFilter::Contains(f.clone(), v.clone())),
38        ast::Filter::StartsWith(f, v) => Ok(CompiledFilter::StartsWith(f.clone(), v.clone())),
39        ast::Filter::EndsWith(f, v) => Ok(CompiledFilter::EndsWith(f.clone(), v.clone())),
40        ast::Filter::Matches(f, v) => {
41            if let ast::Value::String(pattern) = v {
42                // Use RegexBuilder with size limits to prevent ReDoS attacks
43                let re = regex::RegexBuilder::new(pattern)
44                    .size_limit(10_000_000) // 10MB compiled regex size limit
45                    .dfa_size_limit(2_000_000) // 2MB DFA size limit
46                    .build()
47                    .map_err(|e| {
48                        AqlError::new(
49                            ErrorCode::SecurityError,
50                            format!(
51                                "Regex pattern '{}' is too complex or invalid: {}",
52                                pattern, e
53                            ),
54                        )
55                    })?;
56                Ok(CompiledFilter::Matches(f.clone(), re))
57            } else {
58                Err(AqlError::new(
59                    ErrorCode::InvalidInput,
60                    "Matches filter requires a string pattern",
61                ))
62            }
63        }
64        ast::Filter::IsNull(f) => Ok(CompiledFilter::IsNull(f.clone())),
65        ast::Filter::IsNotNull(f) => Ok(CompiledFilter::IsNotNull(f.clone())),
66        ast::Filter::And(filters) => {
67            let compiled = filters
68                .iter()
69                .map(compile_filter)
70                .collect::<Result<Vec<_>>>()?;
71            Ok(CompiledFilter::And(compiled))
72        }
73        ast::Filter::Or(filters) => {
74            let compiled = filters
75                .iter()
76                .map(compile_filter)
77                .collect::<Result<Vec<_>>>()?;
78            Ok(CompiledFilter::Or(compiled))
79        }
80        ast::Filter::Not(filter) => {
81            let compiled = compile_filter(filter)?;
82            Ok(CompiledFilter::Not(Box::new(compiled)))
83        }
84    }
85}