Skip to main content

reddb_server/storage/query/planner/
rewriter.rs

1//! Query Rewriter
2//!
3//! Multi-pass AST transformation system inspired by Neo4j's query rewriting.
4//!
5//! # Rewrite Passes
6//!
7//! 1. **Normalize**: Standardize AST structure
8//! 2. **InjectCachedProperties**: Cache property lookups at compile time
9//! 3. **SimplifyFilters**: Combine and simplify filter expressions
10//! 4. **PushdownPredicates**: Move filters closer to data source
11//! 5. **ValidateFunctions**: Check function calls against schema
12
13use crate::storage::query::ast::{
14    CompareOp, FieldRef, Filter as AstFilter, JoinQuery, Projection, QueryExpr,
15};
16use crate::storage::query::sql_lowering::{
17    effective_graph_filter, effective_join_filter, effective_table_filter, effective_vector_filter,
18};
19use crate::storage::schema::Value;
20
21/// Context for rewrite operations
22#[derive(Debug, Clone, Default)]
23pub struct RewriteContext {
24    /// Property cache for compile-time lookups
25    pub property_cache: Vec<CachedProperty>,
26    /// Validation errors encountered
27    pub errors: Vec<String>,
28    /// Warnings generated
29    pub warnings: Vec<String>,
30    /// Statistics about rewrites
31    pub stats: RewriteStats,
32}
33
34/// A cached property lookup
35#[derive(Debug, Clone)]
36pub struct CachedProperty {
37    /// Source alias (table or node)
38    pub source: String,
39    /// Property name
40    pub property: String,
41    /// Cached value if known at compile time
42    pub cached_value: Option<String>,
43}
44
45/// Statistics about rewrite passes
46#[derive(Debug, Clone, Default)]
47pub struct RewriteStats {
48    /// Number of filters simplified
49    pub filters_simplified: u32,
50    /// Number of predicates pushed down
51    pub predicates_pushed: u32,
52    /// Number of properties cached
53    pub properties_cached: u32,
54    /// Number of expressions normalized
55    pub expressions_normalized: u32,
56}
57
58/// A rewrite rule that transforms query expressions
59pub trait RewriteRule: Send + Sync {
60    /// Rule name for debugging
61    fn name(&self) -> &str;
62
63    /// Apply the rule to a query expression
64    fn apply(&self, query: QueryExpr, ctx: &mut RewriteContext) -> QueryExpr;
65
66    /// Check if this rule is applicable to the query
67    fn is_applicable(&self, query: &QueryExpr) -> bool;
68}
69
70/// Query rewriter with pluggable rules
71pub struct QueryRewriter {
72    /// Ordered list of rewrite rules
73    rules: Vec<Box<dyn RewriteRule>>,
74    /// Maximum number of rewrite iterations
75    max_iterations: usize,
76}
77
78impl QueryRewriter {
79    /// Create a new rewriter with default rules
80    pub fn new() -> Self {
81        let rules: Vec<Box<dyn RewriteRule>> = vec![
82            Box::new(NormalizeRule),
83            Box::new(SimplifyFiltersRule),
84            Box::new(PushdownPredicatesRule),
85            Box::new(EliminateDeadCodeRule),
86            Box::new(FoldConstantsRule),
87        ];
88
89        Self {
90            rules,
91            max_iterations: 10,
92        }
93    }
94
95    /// Add a custom rewrite rule
96    pub fn add_rule(&mut self, rule: Box<dyn RewriteRule>) {
97        self.rules.push(rule);
98    }
99
100    /// Rewrite a query expression
101    pub fn rewrite(&self, query: QueryExpr) -> QueryExpr {
102        let mut ctx = RewriteContext::default();
103        self.rewrite_with_context(query, &mut ctx)
104    }
105
106    /// Rewrite with access to context
107    pub fn rewrite_with_context(
108        &self,
109        mut query: QueryExpr,
110        ctx: &mut RewriteContext,
111    ) -> QueryExpr {
112        // Apply rules iteratively until fixed point
113        for _iteration in 0..self.max_iterations {
114            let original = format!("{:?}", query);
115
116            for rule in &self.rules {
117                if rule.is_applicable(&query) {
118                    query = rule.apply(query, ctx);
119                }
120            }
121
122            // Check for fixed point
123            if format!("{:?}", query) == original {
124                break;
125            }
126        }
127
128        query
129    }
130}
131
132impl Default for QueryRewriter {
133    fn default() -> Self {
134        Self::new()
135    }
136}
137
138// =============================================================================
139// Built-in Rewrite Rules
140// =============================================================================
141
142/// Normalize AST structure
143struct NormalizeRule;
144
145impl RewriteRule for NormalizeRule {
146    fn name(&self) -> &str {
147        "Normalize"
148    }
149
150    fn apply(&self, query: QueryExpr, ctx: &mut RewriteContext) -> QueryExpr {
151        match query {
152            QueryExpr::Table(mut tq) => {
153                // Normalize column order
154                tq.columns.sort_by(|a, b| {
155                    let a_name = projection_name(a);
156                    let b_name = projection_name(b);
157                    a_name.cmp(&b_name)
158                });
159                ctx.stats.expressions_normalized += 1;
160                QueryExpr::Table(tq)
161            }
162            QueryExpr::Graph(gq) => {
163                // Graph queries don't need normalization currently
164                QueryExpr::Graph(gq)
165            }
166            QueryExpr::Join(jq) => {
167                // Recursively normalize children
168                let left = self.apply(*jq.left, ctx);
169                let right = self.apply(*jq.right, ctx);
170                QueryExpr::Join(JoinQuery {
171                    left: Box::new(left),
172                    right: Box::new(right),
173                    ..jq
174                })
175            }
176            QueryExpr::Path(pq) => QueryExpr::Path(pq),
177            QueryExpr::Vector(vq) => {
178                // Vector queries don't need normalization currently
179                QueryExpr::Vector(vq)
180            }
181            QueryExpr::Hybrid(mut hq) => {
182                // Normalize the structured part
183                hq.structured = Box::new(self.apply(*hq.structured, ctx));
184                QueryExpr::Hybrid(hq)
185            }
186            // DML/DDL/Command statements pass through without normalization
187            other @ (QueryExpr::Insert(_)
188            | QueryExpr::Update(_)
189            | QueryExpr::Delete(_)
190            | QueryExpr::CreateTable(_)
191            | QueryExpr::CreateCollection(_)
192            | QueryExpr::CreateVector(_)
193            | QueryExpr::DropTable(_)
194            | QueryExpr::DropGraph(_)
195            | QueryExpr::DropVector(_)
196            | QueryExpr::DropDocument(_)
197            | QueryExpr::DropKv(_)
198            | QueryExpr::DropCollection(_)
199            | QueryExpr::Truncate(_)
200            | QueryExpr::AlterTable(_)
201            | QueryExpr::GraphCommand(_)
202            | QueryExpr::SearchCommand(_)
203            | QueryExpr::CreateIndex(_)
204            | QueryExpr::DropIndex(_)
205            | QueryExpr::ProbabilisticCommand(_)
206            | QueryExpr::Ask(_)
207            | QueryExpr::SetConfig { .. }
208            | QueryExpr::ShowConfig { .. }
209            | QueryExpr::SetSecret { .. }
210            | QueryExpr::DeleteSecret { .. }
211            | QueryExpr::ShowSecrets { .. }
212            | QueryExpr::SetTenant(_)
213            | QueryExpr::ShowTenant
214            | QueryExpr::CreateTimeSeries(_)
215            | QueryExpr::CreateMetric(_)
216            | QueryExpr::AlterMetric(_)
217            | QueryExpr::CreateSlo(_)
218            | QueryExpr::DropTimeSeries(_)
219            | QueryExpr::CreateQueue(_)
220            | QueryExpr::AlterQueue(_)
221            | QueryExpr::DropQueue(_)
222            | QueryExpr::QueueSelect(_)
223            | QueryExpr::QueueCommand(_)
224            | QueryExpr::KvCommand(_)
225            | QueryExpr::ConfigCommand(_)
226            | QueryExpr::CreateTree(_)
227            | QueryExpr::DropTree(_)
228            | QueryExpr::TreeCommand(_)
229            | QueryExpr::ExplainAlter(_)
230            | QueryExpr::TransactionControl(_)
231            | QueryExpr::MaintenanceCommand(_)
232            | QueryExpr::CreateSchema(_)
233            | QueryExpr::DropSchema(_)
234            | QueryExpr::CreateSequence(_)
235            | QueryExpr::DropSequence(_)
236            | QueryExpr::CopyFrom(_)
237            | QueryExpr::CreateView(_)
238            | QueryExpr::DropView(_)
239            | QueryExpr::RefreshMaterializedView(_)
240            | QueryExpr::CreatePolicy(_)
241            | QueryExpr::DropPolicy(_)
242            | QueryExpr::CreateServer(_)
243            | QueryExpr::DropServer(_)
244            | QueryExpr::CreateForeignTable(_)
245            | QueryExpr::DropForeignTable(_)
246            | QueryExpr::Grant(_)
247            | QueryExpr::Revoke(_)
248            | QueryExpr::AlterUser(_)
249            | QueryExpr::CreateIamPolicy { .. }
250            | QueryExpr::DropIamPolicy { .. }
251            | QueryExpr::AttachPolicy { .. }
252            | QueryExpr::DetachPolicy { .. }
253            | QueryExpr::ShowPolicies { .. }
254            | QueryExpr::ShowEffectivePermissions { .. }
255            | QueryExpr::RankOf(_)
256            | QueryExpr::ApproxRankOf(_)
257            | QueryExpr::RankRange(_)
258            | QueryExpr::SimulatePolicy { .. }
259            | QueryExpr::LintPolicy { .. }
260            | QueryExpr::MigratePolicyMode { .. }
261            | QueryExpr::CreateMigration(_)
262            | QueryExpr::ApplyMigration(_)
263            | QueryExpr::RollbackMigration(_)
264            | QueryExpr::ExplainMigration(_)
265            | QueryExpr::EventsBackfill(_)
266            | QueryExpr::EventsBackfillStatus { .. }) => other,
267        }
268    }
269
270    fn is_applicable(&self, _query: &QueryExpr) -> bool {
271        true
272    }
273}
274
275/// Simplify filter expressions
276struct SimplifyFiltersRule;
277
278impl RewriteRule for SimplifyFiltersRule {
279    fn name(&self) -> &str {
280        "SimplifyFilters"
281    }
282
283    fn apply(&self, query: QueryExpr, ctx: &mut RewriteContext) -> QueryExpr {
284        match query {
285            QueryExpr::Table(mut tq) => {
286                if let Some(filter) = effective_table_filter(&tq) {
287                    tq.filter = Some(simplify_filter(filter, ctx));
288                }
289                QueryExpr::Table(tq)
290            }
291            QueryExpr::Graph(mut gq) => {
292                if let Some(filter) = effective_graph_filter(&gq) {
293                    gq.filter = Some(simplify_filter(filter, ctx));
294                }
295                QueryExpr::Graph(gq)
296            }
297            QueryExpr::Join(mut jq) => {
298                let join_filter = effective_join_filter(&jq);
299                let left = self.apply(*jq.left, ctx);
300                let right = self.apply(*jq.right, ctx);
301                if let Some(filter) = join_filter {
302                    jq.filter = Some(simplify_filter(filter, ctx));
303                }
304                jq.left = Box::new(left);
305                jq.right = Box::new(right);
306                QueryExpr::Join(jq)
307            }
308            QueryExpr::Path(pq) => QueryExpr::Path(pq),
309            QueryExpr::Vector(vq) => {
310                // Vector queries have MetadataFilter, not AstFilter
311                // Pass through for now
312                QueryExpr::Vector(vq)
313            }
314            QueryExpr::Hybrid(mut hq) => {
315                // Simplify filters in the structured part
316                hq.structured = Box::new(self.apply(*hq.structured, ctx));
317                QueryExpr::Hybrid(hq)
318            }
319            // DML/DDL/Command statements pass through without filter simplification
320            other @ (QueryExpr::Insert(_)
321            | QueryExpr::Update(_)
322            | QueryExpr::Delete(_)
323            | QueryExpr::CreateTable(_)
324            | QueryExpr::CreateCollection(_)
325            | QueryExpr::CreateVector(_)
326            | QueryExpr::DropTable(_)
327            | QueryExpr::DropGraph(_)
328            | QueryExpr::DropVector(_)
329            | QueryExpr::DropDocument(_)
330            | QueryExpr::DropKv(_)
331            | QueryExpr::DropCollection(_)
332            | QueryExpr::Truncate(_)
333            | QueryExpr::AlterTable(_)
334            | QueryExpr::GraphCommand(_)
335            | QueryExpr::SearchCommand(_)
336            | QueryExpr::CreateIndex(_)
337            | QueryExpr::DropIndex(_)
338            | QueryExpr::ProbabilisticCommand(_)
339            | QueryExpr::Ask(_)
340            | QueryExpr::SetConfig { .. }
341            | QueryExpr::ShowConfig { .. }
342            | QueryExpr::SetSecret { .. }
343            | QueryExpr::DeleteSecret { .. }
344            | QueryExpr::ShowSecrets { .. }
345            | QueryExpr::SetTenant(_)
346            | QueryExpr::ShowTenant
347            | QueryExpr::CreateTimeSeries(_)
348            | QueryExpr::CreateMetric(_)
349            | QueryExpr::AlterMetric(_)
350            | QueryExpr::CreateSlo(_)
351            | QueryExpr::DropTimeSeries(_)
352            | QueryExpr::CreateQueue(_)
353            | QueryExpr::AlterQueue(_)
354            | QueryExpr::DropQueue(_)
355            | QueryExpr::QueueSelect(_)
356            | QueryExpr::QueueCommand(_)
357            | QueryExpr::KvCommand(_)
358            | QueryExpr::ConfigCommand(_)
359            | QueryExpr::CreateTree(_)
360            | QueryExpr::DropTree(_)
361            | QueryExpr::TreeCommand(_)
362            | QueryExpr::ExplainAlter(_)
363            | QueryExpr::TransactionControl(_)
364            | QueryExpr::MaintenanceCommand(_)
365            | QueryExpr::CreateSchema(_)
366            | QueryExpr::DropSchema(_)
367            | QueryExpr::CreateSequence(_)
368            | QueryExpr::DropSequence(_)
369            | QueryExpr::CopyFrom(_)
370            | QueryExpr::CreateView(_)
371            | QueryExpr::DropView(_)
372            | QueryExpr::RefreshMaterializedView(_)
373            | QueryExpr::CreatePolicy(_)
374            | QueryExpr::DropPolicy(_)
375            | QueryExpr::CreateServer(_)
376            | QueryExpr::DropServer(_)
377            | QueryExpr::CreateForeignTable(_)
378            | QueryExpr::DropForeignTable(_)
379            | QueryExpr::Grant(_)
380            | QueryExpr::Revoke(_)
381            | QueryExpr::AlterUser(_)
382            | QueryExpr::CreateIamPolicy { .. }
383            | QueryExpr::DropIamPolicy { .. }
384            | QueryExpr::AttachPolicy { .. }
385            | QueryExpr::DetachPolicy { .. }
386            | QueryExpr::ShowPolicies { .. }
387            | QueryExpr::ShowEffectivePermissions { .. }
388            | QueryExpr::RankOf(_)
389            | QueryExpr::ApproxRankOf(_)
390            | QueryExpr::RankRange(_)
391            | QueryExpr::SimulatePolicy { .. }
392            | QueryExpr::LintPolicy { .. }
393            | QueryExpr::MigratePolicyMode { .. }
394            | QueryExpr::CreateMigration(_)
395            | QueryExpr::ApplyMigration(_)
396            | QueryExpr::RollbackMigration(_)
397            | QueryExpr::ExplainMigration(_)
398            | QueryExpr::EventsBackfill(_)
399            | QueryExpr::EventsBackfillStatus { .. }) => other,
400        }
401    }
402
403    fn is_applicable(&self, query: &QueryExpr) -> bool {
404        match query {
405            QueryExpr::Table(tq) => effective_table_filter(tq).is_some(),
406            QueryExpr::Graph(gq) => effective_graph_filter(gq).is_some(),
407            QueryExpr::Join(_) => true,
408            QueryExpr::Path(_) => false,
409            QueryExpr::Vector(vq) => effective_vector_filter(vq).is_some(),
410            QueryExpr::Hybrid(_) => true, // May have filters in structured part
411            // DML/DDL/Command statements are not applicable for filter simplification
412            QueryExpr::Insert(_)
413            | QueryExpr::Update(_)
414            | QueryExpr::Delete(_)
415            | QueryExpr::CreateTable(_)
416            | QueryExpr::CreateCollection(_)
417            | QueryExpr::CreateVector(_)
418            | QueryExpr::DropTable(_)
419            | QueryExpr::DropGraph(_)
420            | QueryExpr::DropVector(_)
421            | QueryExpr::DropDocument(_)
422            | QueryExpr::DropKv(_)
423            | QueryExpr::DropCollection(_)
424            | QueryExpr::Truncate(_)
425            | QueryExpr::AlterTable(_)
426            | QueryExpr::GraphCommand(_)
427            | QueryExpr::SearchCommand(_)
428            | QueryExpr::CreateIndex(_)
429            | QueryExpr::DropIndex(_)
430            | QueryExpr::ProbabilisticCommand(_)
431            | QueryExpr::Ask(_)
432            | QueryExpr::SetConfig { .. }
433            | QueryExpr::ShowConfig { .. }
434            | QueryExpr::SetSecret { .. }
435            | QueryExpr::DeleteSecret { .. }
436            | QueryExpr::ShowSecrets { .. }
437            | QueryExpr::SetTenant(_)
438            | QueryExpr::ShowTenant
439            | QueryExpr::CreateTimeSeries(_)
440            | QueryExpr::CreateMetric(_)
441            | QueryExpr::AlterMetric(_)
442            | QueryExpr::CreateSlo(_)
443            | QueryExpr::DropTimeSeries(_)
444            | QueryExpr::CreateQueue(_)
445            | QueryExpr::AlterQueue(_)
446            | QueryExpr::DropQueue(_)
447            | QueryExpr::QueueSelect(_)
448            | QueryExpr::QueueCommand(_)
449            | QueryExpr::KvCommand(_)
450            | QueryExpr::ConfigCommand(_)
451            | QueryExpr::CreateTree(_)
452            | QueryExpr::DropTree(_)
453            | QueryExpr::TreeCommand(_)
454            | QueryExpr::ExplainAlter(_)
455            | QueryExpr::TransactionControl(_)
456            | QueryExpr::MaintenanceCommand(_)
457            | QueryExpr::CreateSchema(_)
458            | QueryExpr::DropSchema(_)
459            | QueryExpr::CreateSequence(_)
460            | QueryExpr::DropSequence(_)
461            | QueryExpr::CopyFrom(_)
462            | QueryExpr::CreateView(_)
463            | QueryExpr::DropView(_)
464            | QueryExpr::RefreshMaterializedView(_)
465            | QueryExpr::CreatePolicy(_)
466            | QueryExpr::DropPolicy(_)
467            | QueryExpr::CreateServer(_)
468            | QueryExpr::DropServer(_)
469            | QueryExpr::CreateForeignTable(_)
470            | QueryExpr::DropForeignTable(_)
471            | QueryExpr::Grant(_)
472            | QueryExpr::Revoke(_)
473            | QueryExpr::AlterUser(_)
474            | QueryExpr::CreateIamPolicy { .. }
475            | QueryExpr::DropIamPolicy { .. }
476            | QueryExpr::AttachPolicy { .. }
477            | QueryExpr::DetachPolicy { .. }
478            | QueryExpr::ShowPolicies { .. }
479            | QueryExpr::ShowEffectivePermissions { .. }
480            | QueryExpr::RankOf(_)
481            | QueryExpr::ApproxRankOf(_)
482            | QueryExpr::RankRange(_)
483            | QueryExpr::SimulatePolicy { .. }
484            | QueryExpr::LintPolicy { .. }
485            | QueryExpr::MigratePolicyMode { .. }
486            | QueryExpr::CreateMigration(_)
487            | QueryExpr::ApplyMigration(_)
488            | QueryExpr::RollbackMigration(_)
489            | QueryExpr::ExplainMigration(_)
490            | QueryExpr::EventsBackfill(_)
491            | QueryExpr::EventsBackfillStatus { .. } => false,
492        }
493    }
494}
495
496/// Push predicates down to data sources
497struct PushdownPredicatesRule;
498
499impl RewriteRule for PushdownPredicatesRule {
500    fn name(&self) -> &str {
501        "PushdownPredicates"
502    }
503
504    fn apply(&self, query: QueryExpr, ctx: &mut RewriteContext) -> QueryExpr {
505        match query {
506            QueryExpr::Join(mut jq) => {
507                // Try to push join predicates down to children
508                // This is a simplified version - real implementation would analyze
509                // which predicates can be pushed to which child
510
511                // For now, just recursively apply to children
512                jq.left = Box::new(self.apply(*jq.left, ctx));
513                jq.right = Box::new(self.apply(*jq.right, ctx));
514                ctx.stats.predicates_pushed += 1;
515                QueryExpr::Join(jq)
516            }
517            other => other,
518        }
519    }
520
521    fn is_applicable(&self, query: &QueryExpr) -> bool {
522        matches!(query, QueryExpr::Join(_))
523    }
524}
525
526/// Eliminate dead code branches
527struct EliminateDeadCodeRule;
528
529impl RewriteRule for EliminateDeadCodeRule {
530    fn name(&self) -> &str {
531        "EliminateDeadCode"
532    }
533
534    fn apply(&self, query: QueryExpr, _ctx: &mut RewriteContext) -> QueryExpr {
535        match query {
536            QueryExpr::Table(mut tq) => {
537                // Remove always-true filters
538                if let Some(filter) = effective_table_filter(&tq).as_ref() {
539                    if is_always_true(filter) {
540                        tq.filter = None;
541                    }
542                }
543                QueryExpr::Table(tq)
544            }
545            other => other,
546        }
547    }
548
549    fn is_applicable(&self, query: &QueryExpr) -> bool {
550        matches!(query, QueryExpr::Table(_))
551    }
552}
553
554/// Fold constant expressions
555struct FoldConstantsRule;
556
557impl RewriteRule for FoldConstantsRule {
558    fn name(&self) -> &str {
559        "FoldConstants"
560    }
561
562    fn apply(&self, query: QueryExpr, _ctx: &mut RewriteContext) -> QueryExpr {
563        // Constant folding is complex - for now just pass through
564        // A real implementation would evaluate constant expressions at compile time
565        query
566    }
567
568    fn is_applicable(&self, _query: &QueryExpr) -> bool {
569        true
570    }
571}
572
573// =============================================================================
574// Helper Functions
575// =============================================================================
576
577fn projection_name(proj: &Projection) -> String {
578    match proj {
579        Projection::All => "*".to_string(),
580        Projection::Column(name) => name.clone(),
581        Projection::Alias(_, alias) => alias.clone(),
582        Projection::Function(name, _) => name
583            .split_once(':')
584            .map(|(_, alias)| alias.to_string())
585            .unwrap_or_else(|| name.clone()),
586        Projection::Expression(expr, alias) => {
587            alias.clone().unwrap_or_else(|| format!("{:?}", expr))
588        }
589        Projection::Field(field, alias) => alias.clone().unwrap_or_else(|| format!("{:?}", field)),
590        Projection::Window { name, alias, .. } => alias.clone().unwrap_or_else(|| name.clone()),
591    }
592}
593
594fn simplify_filter(filter: AstFilter, ctx: &mut RewriteContext) -> AstFilter {
595    match filter {
596        AstFilter::And(left, right) => {
597            let left = simplify_filter(*left, ctx);
598            let right = simplify_filter(*right, ctx);
599
600            // AND with TRUE -> other side
601            if is_always_true(&left) {
602                ctx.stats.filters_simplified += 1;
603                return right;
604            }
605            if is_always_true(&right) {
606                ctx.stats.filters_simplified += 1;
607                return left;
608            }
609
610            // AND with FALSE -> FALSE
611            if is_always_false(&left) || is_always_false(&right) {
612                ctx.stats.filters_simplified += 1;
613                return AstFilter::Compare {
614                    field: FieldRef::TableColumn {
615                        table: String::new(),
616                        column: "1".to_string(),
617                    },
618                    op: CompareOp::Eq,
619                    value: Value::Integer(0),
620                };
621            }
622
623            AstFilter::And(Box::new(left), Box::new(right))
624        }
625        AstFilter::Or(left, right) => {
626            let left = simplify_filter(*left, ctx);
627            let right = simplify_filter(*right, ctx);
628
629            // OR with FALSE -> other side
630            if is_always_false(&left) {
631                ctx.stats.filters_simplified += 1;
632                return right;
633            }
634            if is_always_false(&right) {
635                ctx.stats.filters_simplified += 1;
636                return left;
637            }
638
639            // OR with TRUE -> TRUE
640            if is_always_true(&left) || is_always_true(&right) {
641                ctx.stats.filters_simplified += 1;
642                return AstFilter::Compare {
643                    field: FieldRef::TableColumn {
644                        table: String::new(),
645                        column: "1".to_string(),
646                    },
647                    op: CompareOp::Eq,
648                    value: Value::Integer(1),
649                };
650            }
651
652            AstFilter::Or(Box::new(left), Box::new(right))
653        }
654        AstFilter::Not(inner) => {
655            let inner = simplify_filter(*inner, ctx);
656
657            // NOT NOT x -> x
658            if let AstFilter::Not(double_inner) = inner {
659                ctx.stats.filters_simplified += 1;
660                return *double_inner;
661            }
662
663            AstFilter::Not(Box::new(inner))
664        }
665        other => other,
666    }
667}
668
669fn is_always_true(filter: &AstFilter) -> bool {
670    match filter {
671        AstFilter::Compare { field, op, value } => {
672            // 1 = 1 is always true
673            matches!(field, FieldRef::TableColumn { column, .. } if column == "1")
674                && matches!(op, CompareOp::Eq)
675                && matches!(value, Value::Integer(1))
676        }
677        _ => false,
678    }
679}
680
681fn is_always_false(filter: &AstFilter) -> bool {
682    match filter {
683        AstFilter::Compare { field, op, value } => {
684            // 1 = 0 is always false
685            matches!(field, FieldRef::TableColumn { column, .. } if column == "1")
686                && matches!(op, CompareOp::Eq)
687                && matches!(value, Value::Integer(0))
688        }
689        _ => false,
690    }
691}
692
693#[cfg(test)]
694mod tests {
695    use super::*;
696
697    fn make_field(name: &str) -> FieldRef {
698        FieldRef::TableColumn {
699            table: String::new(),
700            column: name.to_string(),
701        }
702    }
703
704    #[test]
705    fn test_simplify_and_with_true() {
706        let mut ctx = RewriteContext::default();
707
708        let filter = AstFilter::And(
709            Box::new(AstFilter::Compare {
710                field: make_field("1"),
711                op: CompareOp::Eq,
712                value: Value::Integer(1),
713            }),
714            Box::new(AstFilter::Compare {
715                field: make_field("x"),
716                op: CompareOp::Eq,
717                value: Value::Integer(5),
718            }),
719        );
720
721        let simplified = simplify_filter(filter, &mut ctx);
722
723        match simplified {
724            AstFilter::Compare { field, .. } => {
725                assert!(matches!(field, FieldRef::TableColumn { column, .. } if column == "x"));
726            }
727            _ => panic!("Expected Compare filter"),
728        }
729    }
730
731    #[test]
732    fn test_simplify_double_not() {
733        let mut ctx = RewriteContext::default();
734
735        let filter = AstFilter::Not(Box::new(AstFilter::Not(Box::new(AstFilter::Compare {
736            field: make_field("x"),
737            op: CompareOp::Eq,
738            value: Value::Integer(5),
739        }))));
740
741        let simplified = simplify_filter(filter, &mut ctx);
742
743        match simplified {
744            AstFilter::Compare { field, .. } => {
745                assert!(matches!(field, FieldRef::TableColumn { column, .. } if column == "x"));
746            }
747            _ => panic!("Expected Compare filter"),
748        }
749    }
750}