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::DropTimeSeries(_)
216            | QueryExpr::CreateQueue(_)
217            | QueryExpr::AlterQueue(_)
218            | QueryExpr::DropQueue(_)
219            | QueryExpr::QueueSelect(_)
220            | QueryExpr::QueueCommand(_)
221            | QueryExpr::KvCommand(_)
222            | QueryExpr::ConfigCommand(_)
223            | QueryExpr::CreateTree(_)
224            | QueryExpr::DropTree(_)
225            | QueryExpr::TreeCommand(_)
226            | QueryExpr::ExplainAlter(_)
227            | QueryExpr::TransactionControl(_)
228            | QueryExpr::MaintenanceCommand(_)
229            | QueryExpr::CreateSchema(_)
230            | QueryExpr::DropSchema(_)
231            | QueryExpr::CreateSequence(_)
232            | QueryExpr::DropSequence(_)
233            | QueryExpr::CopyFrom(_)
234            | QueryExpr::CreateView(_)
235            | QueryExpr::DropView(_)
236            | QueryExpr::RefreshMaterializedView(_)
237            | QueryExpr::CreatePolicy(_)
238            | QueryExpr::DropPolicy(_)
239            | QueryExpr::CreateServer(_)
240            | QueryExpr::DropServer(_)
241            | QueryExpr::CreateForeignTable(_)
242            | QueryExpr::DropForeignTable(_)
243            | QueryExpr::Grant(_)
244            | QueryExpr::Revoke(_)
245            | QueryExpr::AlterUser(_)
246            | QueryExpr::CreateIamPolicy { .. }
247            | QueryExpr::DropIamPolicy { .. }
248            | QueryExpr::AttachPolicy { .. }
249            | QueryExpr::DetachPolicy { .. }
250            | QueryExpr::ShowPolicies { .. }
251            | QueryExpr::ShowEffectivePermissions { .. }
252            | QueryExpr::SimulatePolicy { .. }
253            | QueryExpr::CreateMigration(_)
254            | QueryExpr::ApplyMigration(_)
255            | QueryExpr::RollbackMigration(_)
256            | QueryExpr::ExplainMigration(_)
257            | QueryExpr::EventsBackfill(_)
258            | QueryExpr::EventsBackfillStatus { .. }) => other,
259        }
260    }
261
262    fn is_applicable(&self, _query: &QueryExpr) -> bool {
263        true
264    }
265}
266
267/// Simplify filter expressions
268struct SimplifyFiltersRule;
269
270impl RewriteRule for SimplifyFiltersRule {
271    fn name(&self) -> &str {
272        "SimplifyFilters"
273    }
274
275    fn apply(&self, query: QueryExpr, ctx: &mut RewriteContext) -> QueryExpr {
276        match query {
277            QueryExpr::Table(mut tq) => {
278                if let Some(filter) = effective_table_filter(&tq) {
279                    tq.filter = Some(simplify_filter(filter, ctx));
280                }
281                QueryExpr::Table(tq)
282            }
283            QueryExpr::Graph(mut gq) => {
284                if let Some(filter) = effective_graph_filter(&gq) {
285                    gq.filter = Some(simplify_filter(filter, ctx));
286                }
287                QueryExpr::Graph(gq)
288            }
289            QueryExpr::Join(mut jq) => {
290                let join_filter = effective_join_filter(&jq);
291                let left = self.apply(*jq.left, ctx);
292                let right = self.apply(*jq.right, ctx);
293                if let Some(filter) = join_filter {
294                    jq.filter = Some(simplify_filter(filter, ctx));
295                }
296                jq.left = Box::new(left);
297                jq.right = Box::new(right);
298                QueryExpr::Join(jq)
299            }
300            QueryExpr::Path(pq) => QueryExpr::Path(pq),
301            QueryExpr::Vector(vq) => {
302                // Vector queries have MetadataFilter, not AstFilter
303                // Pass through for now
304                QueryExpr::Vector(vq)
305            }
306            QueryExpr::Hybrid(mut hq) => {
307                // Simplify filters in the structured part
308                hq.structured = Box::new(self.apply(*hq.structured, ctx));
309                QueryExpr::Hybrid(hq)
310            }
311            // DML/DDL/Command statements pass through without filter simplification
312            other @ (QueryExpr::Insert(_)
313            | QueryExpr::Update(_)
314            | QueryExpr::Delete(_)
315            | QueryExpr::CreateTable(_)
316            | QueryExpr::CreateCollection(_)
317            | QueryExpr::CreateVector(_)
318            | QueryExpr::DropTable(_)
319            | QueryExpr::DropGraph(_)
320            | QueryExpr::DropVector(_)
321            | QueryExpr::DropDocument(_)
322            | QueryExpr::DropKv(_)
323            | QueryExpr::DropCollection(_)
324            | QueryExpr::Truncate(_)
325            | QueryExpr::AlterTable(_)
326            | QueryExpr::GraphCommand(_)
327            | QueryExpr::SearchCommand(_)
328            | QueryExpr::CreateIndex(_)
329            | QueryExpr::DropIndex(_)
330            | QueryExpr::ProbabilisticCommand(_)
331            | QueryExpr::Ask(_)
332            | QueryExpr::SetConfig { .. }
333            | QueryExpr::ShowConfig { .. }
334            | QueryExpr::SetSecret { .. }
335            | QueryExpr::DeleteSecret { .. }
336            | QueryExpr::ShowSecrets { .. }
337            | QueryExpr::SetTenant(_)
338            | QueryExpr::ShowTenant
339            | QueryExpr::CreateTimeSeries(_)
340            | QueryExpr::DropTimeSeries(_)
341            | QueryExpr::CreateQueue(_)
342            | QueryExpr::AlterQueue(_)
343            | QueryExpr::DropQueue(_)
344            | QueryExpr::QueueSelect(_)
345            | QueryExpr::QueueCommand(_)
346            | QueryExpr::KvCommand(_)
347            | QueryExpr::ConfigCommand(_)
348            | QueryExpr::CreateTree(_)
349            | QueryExpr::DropTree(_)
350            | QueryExpr::TreeCommand(_)
351            | QueryExpr::ExplainAlter(_)
352            | QueryExpr::TransactionControl(_)
353            | QueryExpr::MaintenanceCommand(_)
354            | QueryExpr::CreateSchema(_)
355            | QueryExpr::DropSchema(_)
356            | QueryExpr::CreateSequence(_)
357            | QueryExpr::DropSequence(_)
358            | QueryExpr::CopyFrom(_)
359            | QueryExpr::CreateView(_)
360            | QueryExpr::DropView(_)
361            | QueryExpr::RefreshMaterializedView(_)
362            | QueryExpr::CreatePolicy(_)
363            | QueryExpr::DropPolicy(_)
364            | QueryExpr::CreateServer(_)
365            | QueryExpr::DropServer(_)
366            | QueryExpr::CreateForeignTable(_)
367            | QueryExpr::DropForeignTable(_)
368            | QueryExpr::Grant(_)
369            | QueryExpr::Revoke(_)
370            | QueryExpr::AlterUser(_)
371            | QueryExpr::CreateIamPolicy { .. }
372            | QueryExpr::DropIamPolicy { .. }
373            | QueryExpr::AttachPolicy { .. }
374            | QueryExpr::DetachPolicy { .. }
375            | QueryExpr::ShowPolicies { .. }
376            | QueryExpr::ShowEffectivePermissions { .. }
377            | QueryExpr::SimulatePolicy { .. }
378            | QueryExpr::CreateMigration(_)
379            | QueryExpr::ApplyMigration(_)
380            | QueryExpr::RollbackMigration(_)
381            | QueryExpr::ExplainMigration(_)
382            | QueryExpr::EventsBackfill(_)
383            | QueryExpr::EventsBackfillStatus { .. }) => other,
384        }
385    }
386
387    fn is_applicable(&self, query: &QueryExpr) -> bool {
388        match query {
389            QueryExpr::Table(tq) => effective_table_filter(tq).is_some(),
390            QueryExpr::Graph(gq) => effective_graph_filter(gq).is_some(),
391            QueryExpr::Join(_) => true,
392            QueryExpr::Path(_) => false,
393            QueryExpr::Vector(vq) => effective_vector_filter(vq).is_some(),
394            QueryExpr::Hybrid(_) => true, // May have filters in structured part
395            // DML/DDL/Command statements are not applicable for filter simplification
396            QueryExpr::Insert(_)
397            | QueryExpr::Update(_)
398            | QueryExpr::Delete(_)
399            | QueryExpr::CreateTable(_)
400            | QueryExpr::CreateCollection(_)
401            | QueryExpr::CreateVector(_)
402            | QueryExpr::DropTable(_)
403            | QueryExpr::DropGraph(_)
404            | QueryExpr::DropVector(_)
405            | QueryExpr::DropDocument(_)
406            | QueryExpr::DropKv(_)
407            | QueryExpr::DropCollection(_)
408            | QueryExpr::Truncate(_)
409            | QueryExpr::AlterTable(_)
410            | QueryExpr::GraphCommand(_)
411            | QueryExpr::SearchCommand(_)
412            | QueryExpr::CreateIndex(_)
413            | QueryExpr::DropIndex(_)
414            | QueryExpr::ProbabilisticCommand(_)
415            | QueryExpr::Ask(_)
416            | QueryExpr::SetConfig { .. }
417            | QueryExpr::ShowConfig { .. }
418            | QueryExpr::SetSecret { .. }
419            | QueryExpr::DeleteSecret { .. }
420            | QueryExpr::ShowSecrets { .. }
421            | QueryExpr::SetTenant(_)
422            | QueryExpr::ShowTenant
423            | QueryExpr::CreateTimeSeries(_)
424            | QueryExpr::DropTimeSeries(_)
425            | QueryExpr::CreateQueue(_)
426            | QueryExpr::AlterQueue(_)
427            | QueryExpr::DropQueue(_)
428            | QueryExpr::QueueSelect(_)
429            | QueryExpr::QueueCommand(_)
430            | QueryExpr::KvCommand(_)
431            | QueryExpr::ConfigCommand(_)
432            | QueryExpr::CreateTree(_)
433            | QueryExpr::DropTree(_)
434            | QueryExpr::TreeCommand(_)
435            | QueryExpr::ExplainAlter(_)
436            | QueryExpr::TransactionControl(_)
437            | QueryExpr::MaintenanceCommand(_)
438            | QueryExpr::CreateSchema(_)
439            | QueryExpr::DropSchema(_)
440            | QueryExpr::CreateSequence(_)
441            | QueryExpr::DropSequence(_)
442            | QueryExpr::CopyFrom(_)
443            | QueryExpr::CreateView(_)
444            | QueryExpr::DropView(_)
445            | QueryExpr::RefreshMaterializedView(_)
446            | QueryExpr::CreatePolicy(_)
447            | QueryExpr::DropPolicy(_)
448            | QueryExpr::CreateServer(_)
449            | QueryExpr::DropServer(_)
450            | QueryExpr::CreateForeignTable(_)
451            | QueryExpr::DropForeignTable(_)
452            | QueryExpr::Grant(_)
453            | QueryExpr::Revoke(_)
454            | QueryExpr::AlterUser(_)
455            | QueryExpr::CreateIamPolicy { .. }
456            | QueryExpr::DropIamPolicy { .. }
457            | QueryExpr::AttachPolicy { .. }
458            | QueryExpr::DetachPolicy { .. }
459            | QueryExpr::ShowPolicies { .. }
460            | QueryExpr::ShowEffectivePermissions { .. }
461            | QueryExpr::SimulatePolicy { .. }
462            | QueryExpr::CreateMigration(_)
463            | QueryExpr::ApplyMigration(_)
464            | QueryExpr::RollbackMigration(_)
465            | QueryExpr::ExplainMigration(_)
466            | QueryExpr::EventsBackfill(_)
467            | QueryExpr::EventsBackfillStatus { .. } => false,
468        }
469    }
470}
471
472/// Push predicates down to data sources
473struct PushdownPredicatesRule;
474
475impl RewriteRule for PushdownPredicatesRule {
476    fn name(&self) -> &str {
477        "PushdownPredicates"
478    }
479
480    fn apply(&self, query: QueryExpr, ctx: &mut RewriteContext) -> QueryExpr {
481        match query {
482            QueryExpr::Join(mut jq) => {
483                // Try to push join predicates down to children
484                // This is a simplified version - real implementation would analyze
485                // which predicates can be pushed to which child
486
487                // For now, just recursively apply to children
488                jq.left = Box::new(self.apply(*jq.left, ctx));
489                jq.right = Box::new(self.apply(*jq.right, ctx));
490                ctx.stats.predicates_pushed += 1;
491                QueryExpr::Join(jq)
492            }
493            other => other,
494        }
495    }
496
497    fn is_applicable(&self, query: &QueryExpr) -> bool {
498        matches!(query, QueryExpr::Join(_))
499    }
500}
501
502/// Eliminate dead code branches
503struct EliminateDeadCodeRule;
504
505impl RewriteRule for EliminateDeadCodeRule {
506    fn name(&self) -> &str {
507        "EliminateDeadCode"
508    }
509
510    fn apply(&self, query: QueryExpr, _ctx: &mut RewriteContext) -> QueryExpr {
511        match query {
512            QueryExpr::Table(mut tq) => {
513                // Remove always-true filters
514                if let Some(filter) = effective_table_filter(&tq).as_ref() {
515                    if is_always_true(filter) {
516                        tq.filter = None;
517                    }
518                }
519                QueryExpr::Table(tq)
520            }
521            other => other,
522        }
523    }
524
525    fn is_applicable(&self, query: &QueryExpr) -> bool {
526        matches!(query, QueryExpr::Table(_))
527    }
528}
529
530/// Fold constant expressions
531struct FoldConstantsRule;
532
533impl RewriteRule for FoldConstantsRule {
534    fn name(&self) -> &str {
535        "FoldConstants"
536    }
537
538    fn apply(&self, query: QueryExpr, _ctx: &mut RewriteContext) -> QueryExpr {
539        // Constant folding is complex - for now just pass through
540        // A real implementation would evaluate constant expressions at compile time
541        query
542    }
543
544    fn is_applicable(&self, _query: &QueryExpr) -> bool {
545        true
546    }
547}
548
549// =============================================================================
550// Helper Functions
551// =============================================================================
552
553fn projection_name(proj: &Projection) -> String {
554    match proj {
555        Projection::All => "*".to_string(),
556        Projection::Column(name) => name.clone(),
557        Projection::Alias(_, alias) => alias.clone(),
558        Projection::Function(name, _) => name
559            .split_once(':')
560            .map(|(_, alias)| alias.to_string())
561            .unwrap_or_else(|| name.clone()),
562        Projection::Expression(expr, alias) => {
563            alias.clone().unwrap_or_else(|| format!("{:?}", expr))
564        }
565        Projection::Field(field, alias) => alias.clone().unwrap_or_else(|| format!("{:?}", field)),
566    }
567}
568
569fn simplify_filter(filter: AstFilter, ctx: &mut RewriteContext) -> AstFilter {
570    match filter {
571        AstFilter::And(left, right) => {
572            let left = simplify_filter(*left, ctx);
573            let right = simplify_filter(*right, ctx);
574
575            // AND with TRUE -> other side
576            if is_always_true(&left) {
577                ctx.stats.filters_simplified += 1;
578                return right;
579            }
580            if is_always_true(&right) {
581                ctx.stats.filters_simplified += 1;
582                return left;
583            }
584
585            // AND with FALSE -> FALSE
586            if is_always_false(&left) || is_always_false(&right) {
587                ctx.stats.filters_simplified += 1;
588                return AstFilter::Compare {
589                    field: FieldRef::TableColumn {
590                        table: String::new(),
591                        column: "1".to_string(),
592                    },
593                    op: CompareOp::Eq,
594                    value: Value::Integer(0),
595                };
596            }
597
598            AstFilter::And(Box::new(left), Box::new(right))
599        }
600        AstFilter::Or(left, right) => {
601            let left = simplify_filter(*left, ctx);
602            let right = simplify_filter(*right, ctx);
603
604            // OR with FALSE -> other side
605            if is_always_false(&left) {
606                ctx.stats.filters_simplified += 1;
607                return right;
608            }
609            if is_always_false(&right) {
610                ctx.stats.filters_simplified += 1;
611                return left;
612            }
613
614            // OR with TRUE -> TRUE
615            if is_always_true(&left) || is_always_true(&right) {
616                ctx.stats.filters_simplified += 1;
617                return AstFilter::Compare {
618                    field: FieldRef::TableColumn {
619                        table: String::new(),
620                        column: "1".to_string(),
621                    },
622                    op: CompareOp::Eq,
623                    value: Value::Integer(1),
624                };
625            }
626
627            AstFilter::Or(Box::new(left), Box::new(right))
628        }
629        AstFilter::Not(inner) => {
630            let inner = simplify_filter(*inner, ctx);
631
632            // NOT NOT x -> x
633            if let AstFilter::Not(double_inner) = inner {
634                ctx.stats.filters_simplified += 1;
635                return *double_inner;
636            }
637
638            AstFilter::Not(Box::new(inner))
639        }
640        other => other,
641    }
642}
643
644fn is_always_true(filter: &AstFilter) -> bool {
645    match filter {
646        AstFilter::Compare { field, op, value } => {
647            // 1 = 1 is always true
648            matches!(field, FieldRef::TableColumn { column, .. } if column == "1")
649                && matches!(op, CompareOp::Eq)
650                && matches!(value, Value::Integer(1))
651        }
652        _ => false,
653    }
654}
655
656fn is_always_false(filter: &AstFilter) -> bool {
657    match filter {
658        AstFilter::Compare { field, op, value } => {
659            // 1 = 0 is always false
660            matches!(field, FieldRef::TableColumn { column, .. } if column == "1")
661                && matches!(op, CompareOp::Eq)
662                && matches!(value, Value::Integer(0))
663        }
664        _ => false,
665    }
666}
667
668#[cfg(test)]
669mod tests {
670    use super::*;
671
672    fn make_field(name: &str) -> FieldRef {
673        FieldRef::TableColumn {
674            table: String::new(),
675            column: name.to_string(),
676        }
677    }
678
679    #[test]
680    fn test_simplify_and_with_true() {
681        let mut ctx = RewriteContext::default();
682
683        let filter = AstFilter::And(
684            Box::new(AstFilter::Compare {
685                field: make_field("1"),
686                op: CompareOp::Eq,
687                value: Value::Integer(1),
688            }),
689            Box::new(AstFilter::Compare {
690                field: make_field("x"),
691                op: CompareOp::Eq,
692                value: Value::Integer(5),
693            }),
694        );
695
696        let simplified = simplify_filter(filter, &mut ctx);
697
698        match simplified {
699            AstFilter::Compare { field, .. } => {
700                assert!(matches!(field, FieldRef::TableColumn { column, .. } if column == "x"));
701            }
702            _ => panic!("Expected Compare filter"),
703        }
704    }
705
706    #[test]
707    fn test_simplify_double_not() {
708        let mut ctx = RewriteContext::default();
709
710        let filter = AstFilter::Not(Box::new(AstFilter::Not(Box::new(AstFilter::Compare {
711            field: make_field("x"),
712            op: CompareOp::Eq,
713            value: Value::Integer(5),
714        }))));
715
716        let simplified = simplify_filter(filter, &mut ctx);
717
718        match simplified {
719            AstFilter::Compare { field, .. } => {
720                assert!(matches!(field, FieldRef::TableColumn { column, .. } if column == "x"));
721            }
722            _ => panic!("Expected Compare filter"),
723        }
724    }
725}