uni_query_functions/rewrite/mod.rs
1/// Query rewriting framework
2///
3/// This module provides a general-purpose framework for transforming function calls
4/// into equivalent predicate expressions at compile time. The framework enables:
5///
6/// - Full predicate pushdown to storage
7/// - Index utilization
8/// - Extensible plugin architecture for adding new rewrite rules
9///
10/// # Architecture
11///
12/// The framework consists of:
13///
14/// - **RewriteRule trait**: Interface for implementing rewrite transformations
15/// - **RewriteRegistry**: Global registry of all rewrite rules
16/// - **ExpressionWalker**: Traverses expression trees and applies rules
17/// - **RewriteContext**: Contextual information during rewriting
18///
19/// # Example Usage
20///
21/// ```ignore
22/// use uni_query::rewrite::rewrite_query;
23///
24/// // Rewrite a complete query
25/// let rewritten = rewrite_query(query)?;
26/// ```
27///
28/// # Adding New Rules
29///
30/// See `rules/README.md` for a guide on implementing custom rewrite rules.
31pub mod context;
32pub mod error;
33pub mod function_rename;
34pub mod registry;
35pub mod rule;
36pub mod rules;
37pub mod walker;
38
39use context::{RewriteContext, RewriteStats};
40use error::RewriteError;
41use registry::RewriteRegistry;
42use walker::ExpressionWalker;
43
44use std::sync::OnceLock;
45
46/// Global registry of rewrite rules, initialized once on first use
47static GLOBAL_REGISTRY: OnceLock<RewriteRegistry> = OnceLock::new();
48
49/// Get the global rewrite registry, initializing it if needed
50fn get_or_init_registry() -> &'static RewriteRegistry {
51 GLOBAL_REGISTRY.get_or_init(|| {
52 tracing::info!("Initializing query rewrite framework");
53 RewriteRegistry::with_builtin_rules()
54 })
55}
56
57/// Log rewrite statistics if any functions were visited
58fn log_rewrite_stats(stats: &RewriteStats) {
59 if stats.functions_visited > 0 {
60 tracing::info!(
61 "Rewrite pass complete: {} functions visited, {} rewritten, {} skipped",
62 stats.functions_visited,
63 stats.functions_rewritten,
64 stats.functions_skipped
65 );
66
67 if !stats.errors.is_empty() {
68 tracing::debug!("Rewrite errors: {:?}", stats.errors);
69 }
70 }
71}
72
73/// Rewrite a complete query
74///
75/// This is the main entry point for applying query rewrites. It walks the
76/// entire query tree and applies registered rewrite rules to all function calls.
77///
78/// # Arguments
79///
80/// * `query` - The query to rewrite
81///
82/// # Returns
83///
84/// The rewritten query with function calls transformed into predicates.
85///
86/// # Example
87///
88/// ```ignore
89/// let query = parse_cypher("MATCH (p)-[e:EMPLOYED_BY]->(c) WHERE uni.temporal.validAt(e, 'start', 'end', datetime('2021-06-15')) RETURN c")?;
90/// let rewritten = rewrite_query(query)?;
91/// // The validAt function will be transformed into: e.start <= ... AND (e.end IS NULL OR e.end >= ...)
92/// ```
93pub fn rewrite_query(
94 query: uni_cypher::ast::Query,
95) -> Result<uni_cypher::ast::Query, RewriteError> {
96 let registry = get_or_init_registry();
97 let context = RewriteContext::default();
98
99 let mut walker = ExpressionWalker::new(registry, context);
100 let rewritten_query = walker.rewrite_query(query);
101
102 log_rewrite_stats(&walker.context().stats);
103
104 Ok(rewritten_query)
105}