Skip to main content

uni_query_functions/rewrite/rules/
btic.rs

1/// BTIC temporal rewrite rules
2///
3/// Decomposes opaque BTIC function calls into range predicates that
4/// the existing pushdown infrastructure can evaluate.
5use crate::rewrite::context::RewriteContext;
6use crate::rewrite::error::RewriteError;
7use crate::rewrite::rule::{Arity, RewriteRule};
8use uni_cypher::ast::{BinaryOp, Expr};
9
10/// Rewrite rule for `btic_contains_point`
11///
12/// Transforms: `btic_contains_point(expr, point)`
13/// Into:       `btic_lo(expr) <= point AND btic_hi(expr) > point`
14///
15/// This decomposes the opaque function call into two range predicates
16/// on the lo/hi accessors, enabling downstream predicate analysis and
17/// potential pushdown.
18pub struct BticContainsPointRule;
19
20impl RewriteRule for BticContainsPointRule {
21    fn function_name(&self) -> &str {
22        "btic_contains_point"
23    }
24
25    fn validate_args(&self, args: &[Expr]) -> Result<(), RewriteError> {
26        Arity::Exact(2).check(args.len())
27    }
28
29    fn rewrite(&self, args: Vec<Expr>, _ctx: &RewriteContext) -> Result<Expr, RewriteError> {
30        let btic_expr = args[0].clone();
31        let point = args[1].clone();
32
33        // btic_lo(expr) <= point
34        let lo_check = Expr::BinaryOp {
35            left: Box::new(Expr::FunctionCall {
36                name: "btic_lo".to_string(),
37                args: vec![btic_expr.clone()],
38                distinct: false,
39                window_spec: None,
40            }),
41            op: BinaryOp::LtEq,
42            right: Box::new(point.clone()),
43        };
44
45        // btic_hi(expr) > point
46        let hi_check = Expr::BinaryOp {
47            left: Box::new(Expr::FunctionCall {
48                name: "btic_hi".to_string(),
49                args: vec![btic_expr],
50                distinct: false,
51                window_spec: None,
52            }),
53            op: BinaryOp::Gt,
54            right: Box::new(point),
55        };
56
57        // lo_check AND hi_check
58        Ok(Expr::BinaryOp {
59            left: Box::new(lo_check),
60            op: BinaryOp::And,
61            right: Box::new(hi_check),
62        })
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69    use crate::rewrite::context::RewriteContext;
70    use uni_cypher::ast::CypherLiteral;
71
72    #[test]
73    fn test_btic_contains_point_rewrite() {
74        let rule = BticContainsPointRule;
75        let ctx = RewriteContext::default();
76
77        let args = vec![
78            Expr::Property(Box::new(Expr::Variable("n".into())), "valid_at".into()),
79            Expr::Literal(CypherLiteral::Integer(489_024_000_000)),
80        ];
81
82        assert!(rule.validate_args(&args).is_ok());
83
84        let result = rule.rewrite(args, &ctx).unwrap();
85
86        // Should produce: btic_lo(n.valid_at) <= 489024000000 AND btic_hi(n.valid_at) > 489024000000
87        match result {
88            Expr::BinaryOp {
89                op: BinaryOp::And, ..
90            } => {} // Correct shape
91            other => panic!("expected AND expression, got: {other:?}"),
92        }
93    }
94
95    #[test]
96    fn test_btic_contains_point_wrong_arity() {
97        let rule = BticContainsPointRule;
98        let args = vec![Expr::Variable("x".into())];
99        assert!(rule.validate_args(&args).is_err());
100    }
101}