term_guard/optimizer/
analyzer.rs

1//! Query analysis for constraint optimization.
2
3use crate::core::Constraint;
4use crate::prelude::TermError;
5use std::collections::HashMap;
6use std::sync::Arc;
7
8/// Information about a constraint's query pattern.
9#[derive(Debug, Clone)]
10pub struct ConstraintAnalysis {
11    /// The constraint name
12    pub name: String,
13    /// The constraint reference
14    pub constraint: Arc<dyn Constraint>,
15    /// The table being queried (usually "data")
16    pub table_name: String,
17    /// Type of aggregations used (COUNT, SUM, etc.)
18    pub aggregations: Vec<AggregationType>,
19    /// Columns referenced in the query
20    pub columns: Vec<String>,
21    /// Whether the query has WHERE clauses
22    pub has_predicates: bool,
23    /// Whether the query can be combined with others
24    pub is_combinable: bool,
25}
26
27/// Types of aggregations used in constraints.
28#[derive(Debug, Clone, PartialEq, Eq, Hash)]
29pub enum AggregationType {
30    Count,
31    CountDistinct,
32    Sum,
33    Avg,
34    Min,
35    Max,
36    StdDev,
37    Variance,
38}
39
40/// Analyzes constraints to identify optimization opportunities.
41#[derive(Debug)]
42pub struct QueryAnalyzer {
43    /// Cache of analysis results
44    cache: HashMap<String, ConstraintAnalysis>,
45}
46
47impl QueryAnalyzer {
48    /// Creates a new query analyzer.
49    pub fn new() -> Self {
50        Self {
51            cache: HashMap::new(),
52        }
53    }
54
55    /// Analyzes a set of constraints.
56    pub fn analyze(
57        &mut self,
58        constraints: &[(String, Arc<dyn Constraint>)],
59    ) -> Result<Vec<ConstraintAnalysis>, TermError> {
60        let mut analyses = Vec::new();
61
62        for (name, constraint) in constraints {
63            // Check cache first
64            if let Some(cached) = self.cache.get(name) {
65                analyses.push(cached.clone());
66                continue;
67            }
68
69            // Analyze the constraint
70            let analysis = self.analyze_constraint(name.clone(), constraint.clone())?;
71
72            // Cache the result
73            self.cache.insert(name.clone(), analysis.clone());
74            analyses.push(analysis);
75        }
76
77        Ok(analyses)
78    }
79
80    /// Analyzes a single constraint.
81    pub fn analyze_constraint(
82        &self,
83        name: String,
84        constraint: Arc<dyn Constraint>,
85    ) -> Result<ConstraintAnalysis, TermError> {
86        // For now, we'll use heuristics based on constraint names
87        // In a real implementation, we'd parse the SQL or use constraint metadata
88
89        let constraint_name = constraint.name();
90
91        // Determine aggregations based on constraint type
92        let aggregations = match constraint_name {
93            "completeness" => vec![AggregationType::Count],
94            "uniqueness" => vec![AggregationType::Count, AggregationType::CountDistinct],
95            "compliance" => vec![AggregationType::Count],
96            "min" => vec![AggregationType::Min],
97            "max" => vec![AggregationType::Max],
98            "mean" => vec![AggregationType::Avg],
99            "sum" => vec![AggregationType::Sum],
100            "standard_deviation" => vec![AggregationType::StdDev],
101            "quantile" => vec![AggregationType::Count], // Simplified
102            "entropy" => vec![AggregationType::Count],  // Simplified
103            "mutual_information" => vec![AggregationType::Count], // Simplified
104            "histogram" => vec![AggregationType::Count],
105            _ => vec![AggregationType::Count], // Default
106        };
107
108        // Extract column information (simplified for now)
109        let columns = self.extract_columns(constraint_name);
110
111        // Determine if query has predicates
112        let has_predicates = matches!(
113            constraint_name,
114            "compliance" | "pattern_match" | "containment"
115        );
116
117        // Most constraints can be combined except for complex ones
118        let is_combinable = !matches!(
119            constraint_name,
120            "quantile" | "entropy" | "mutual_information" | "anomaly_detection"
121        );
122
123        Ok(ConstraintAnalysis {
124            name,
125            constraint,
126            table_name: "data".to_string(),
127            aggregations,
128            columns,
129            has_predicates,
130            is_combinable,
131        })
132    }
133
134    /// Extracts column names from constraint (simplified heuristic).
135    fn extract_columns(&self, constraint_name: &str) -> Vec<String> {
136        // In a real implementation, we'd parse the constraint's configuration
137        // For now, return a placeholder
138        match constraint_name {
139            "completeness" | "uniqueness" | "min" | "max" | "mean" | "sum" => {
140                vec!["column".to_string()] // Placeholder
141            }
142            "mutual_information" => {
143                vec!["column1".to_string(), "column2".to_string()]
144            }
145            _ => vec![],
146        }
147    }
148
149    /// Clears the analysis cache.
150    pub fn clear_cache(&mut self) {
151        self.cache.clear();
152    }
153}
154
155impl Default for QueryAnalyzer {
156    fn default() -> Self {
157        Self::new()
158    }
159}
160
161// TODO: Fix tests once Completeness constraint is made public
162#[cfg(test)]
163mod tests {
164    use super::*;
165    // use crate::constraints::completeness::Completeness;
166
167    #[test]
168    fn test_analyzer_creation() {
169        let analyzer = QueryAnalyzer::new();
170        assert!(analyzer.cache.is_empty());
171    }
172
173    // TODO: Re-enable once Completeness is made public
174    // #[tokio::test]
175    // async fn test_constraint_analysis() {
176    //     let mut analyzer = QueryAnalyzer::new();
177    //
178    //     let constraint = Arc::new(Completeness::new("test_column")) as Arc<dyn Constraint>;
179    //     let constraints = vec![("test".to_string(), constraint)];
180    //
181    //     let analyses = analyzer.analyze(&constraints).unwrap();
182    //     assert_eq!(analyses.len(), 1);
183    //
184    //     let analysis = &analyses[0];
185    //     assert_eq!(analysis.name, "test");
186    //     assert_eq!(analysis.table_name, "data");
187    //     assert!(analysis.is_combinable);
188    //     assert!(!analysis.has_predicates);
189    // }
190}