Skip to main content

uni_query/query/rewrite/
context.rs

1/// Rewrite context and configuration
2use crate::query::rewrite::error::RewriteError;
3use std::collections::HashMap;
4
5/// Contextual information available during query rewriting
6///
7/// The context provides information about the current query environment,
8/// including variable scope, schema metadata, and configuration options.
9#[derive(Default)]
10pub struct RewriteContext {
11    /// Variables currently in scope (from MATCH, WITH, etc.)
12    pub scope: HashMap<String, VariableInfo>,
13
14    /// Rewrite statistics (for observability)
15    pub stats: RewriteStats,
16
17    /// Configuration flags
18    pub config: RewriteConfig,
19}
20
21impl RewriteContext {
22    /// Create a new rewrite context with default configuration
23    pub fn new() -> Self {
24        Self::default()
25    }
26
27    /// Create a new rewrite context with custom configuration
28    pub fn with_config(config: RewriteConfig) -> Self {
29        Self {
30            scope: HashMap::new(),
31            stats: RewriteStats::default(),
32            config,
33        }
34    }
35
36    /// Get information about a variable in scope
37    pub fn get_variable(&self, name: &str) -> Option<&VariableInfo> {
38        self.scope.get(name)
39    }
40
41    /// Add a variable to the scope
42    pub fn add_variable(&mut self, name: String, info: VariableInfo) {
43        self.scope.insert(name, info);
44    }
45}
46
47/// Information about a variable in scope
48#[derive(Debug, Clone)]
49pub struct VariableInfo {
50    /// Variable name
51    pub name: String,
52
53    /// Label (for nodes) or None
54    pub label: Option<String>,
55
56    /// Whether this is an edge (true) or node (false)
57    pub is_edge: bool,
58
59    /// Known properties with their types
60    pub properties: HashMap<String, PropertyType>,
61}
62
63/// Property type information
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub enum PropertyType {
66    /// String property
67    String,
68
69    /// Integer property
70    Integer,
71
72    /// Float property
73    Float,
74
75    /// Boolean property
76    Boolean,
77
78    /// DateTime property
79    DateTime,
80
81    /// List of values
82    List,
83
84    /// Map/object
85    Map,
86
87    /// Unknown or dynamic type
88    Unknown,
89}
90
91/// Statistics collected during rewriting
92#[derive(Debug, Default, Clone)]
93pub struct RewriteStats {
94    /// Total number of function calls visited
95    pub functions_visited: usize,
96
97    /// Number of functions successfully rewritten
98    pub functions_rewritten: usize,
99
100    /// Number of functions that fell back to scalar execution
101    pub functions_skipped: usize,
102
103    /// Errors encountered during rewriting (non-fatal)
104    pub errors: Vec<RewriteError>,
105
106    /// Per-rule statistics
107    pub rule_stats: HashMap<String, RuleStats>,
108}
109
110impl RewriteStats {
111    /// Get or create rule stats entry
112    fn rule_stats_mut(&mut self, rule_name: &str) -> &mut RuleStats {
113        self.rule_stats.entry(rule_name.to_string()).or_default()
114    }
115
116    /// Record a successful rewrite for a rule
117    pub fn record_success(&mut self, rule_name: &str) {
118        self.functions_rewritten += 1;
119        self.rule_stats_mut(rule_name).record_success();
120    }
121
122    /// Record a failed rewrite for a rule
123    pub fn record_failure(&mut self, rule_name: &str, error: RewriteError) {
124        self.functions_skipped += 1;
125        self.rule_stats_mut(rule_name).record_failure(error.clone());
126        self.errors.push(error);
127    }
128
129    /// Record a visited function
130    pub fn record_visit(&mut self) {
131        self.functions_visited += 1;
132    }
133}
134
135/// Per-rule statistics
136#[derive(Debug, Default, Clone)]
137pub struct RuleStats {
138    /// Number of times this rule was attempted
139    pub attempts: usize,
140
141    /// Number of successful rewrites
142    pub successes: usize,
143
144    /// Failure counts by error type
145    pub failures: HashMap<String, usize>,
146}
147
148impl RuleStats {
149    fn record_success(&mut self) {
150        self.attempts += 1;
151        self.successes += 1;
152    }
153
154    fn record_failure(&mut self, error: RewriteError) {
155        self.attempts += 1;
156        let error_key = format!("{error:?}");
157        *self.failures.entry(error_key).or_default() += 1;
158    }
159}
160
161/// Configuration options for query rewriting
162#[derive(Debug, Clone)]
163pub struct RewriteConfig {
164    /// Enable temporal function rewrites
165    pub enable_temporal: bool,
166
167    /// Enable spatial function rewrites (future)
168    pub enable_spatial: bool,
169
170    /// Enable property access rewrites (future)
171    pub enable_property: bool,
172
173    /// Whether to fall back to scalar execution on rewrite failure
174    pub fallback_to_scalar: bool,
175
176    /// Enable verbose logging of rewrite operations
177    pub verbose_logging: bool,
178}
179
180impl Default for RewriteConfig {
181    fn default() -> Self {
182        Self {
183            enable_temporal: true,
184            enable_spatial: false,
185            enable_property: false,
186            fallback_to_scalar: true,
187            verbose_logging: false,
188        }
189    }
190}
191
192impl RewriteConfig {
193    /// Create a config with all rewrites enabled
194    pub fn all_enabled() -> Self {
195        Self {
196            enable_temporal: true,
197            enable_spatial: true,
198            enable_property: true,
199            fallback_to_scalar: true,
200            verbose_logging: false,
201        }
202    }
203
204    /// Create a config with all rewrites disabled
205    pub fn all_disabled() -> Self {
206        Self {
207            enable_temporal: false,
208            enable_spatial: false,
209            enable_property: false,
210            fallback_to_scalar: true,
211            verbose_logging: false,
212        }
213    }
214
215    /// Enable verbose logging
216    pub fn with_verbose_logging(mut self) -> Self {
217        self.verbose_logging = true;
218        self
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225
226    #[test]
227    fn test_context_default() {
228        let ctx = RewriteContext::default();
229        assert!(ctx.scope.is_empty());
230        assert_eq!(ctx.stats.functions_visited, 0);
231        assert!(ctx.config.enable_temporal);
232    }
233
234    #[test]
235    fn test_stats_recording() {
236        let mut stats = RewriteStats::default();
237
238        stats.record_success("test.func");
239        assert_eq!(stats.functions_rewritten, 1);
240        assert_eq!(stats.rule_stats.get("test.func").unwrap().successes, 1);
241
242        stats.record_failure(
243            "test.func",
244            RewriteError::NotApplicable {
245                reason: "test".into(),
246            },
247        );
248        assert_eq!(stats.functions_skipped, 1);
249        assert_eq!(stats.errors.len(), 1);
250    }
251
252    #[test]
253    fn test_config_builders() {
254        let all_enabled = RewriteConfig::all_enabled();
255        assert!(all_enabled.enable_temporal);
256        assert!(all_enabled.enable_spatial);
257        assert!(all_enabled.enable_property);
258
259        let all_disabled = RewriteConfig::all_disabled();
260        assert!(!all_disabled.enable_temporal);
261        assert!(!all_disabled.enable_spatial);
262        assert!(!all_disabled.enable_property);
263    }
264}