ggen_core/codegen/
watch_cache_integration.rs

1use crate::codegen::IncrementalCache;
2use crate::graph::Graph;
3use crate::manifest::GgenManifest;
4use ggen_utils::error::Result;
5use std::fs;
6use std::path::Path;
7
8pub struct WatchCacheIntegration;
9
10impl WatchCacheIntegration {
11    pub fn detect_affected_rules(
12        manifest: &GgenManifest, base_path: &Path, cache: &IncrementalCache,
13    ) -> Result<AffectedRulesAnalysis> {
14        // Read current ontology content
15        let ontology_path = base_path.join(&manifest.ontology.source);
16        let ontology_content = fs::read_to_string(&ontology_path).unwrap_or_default();
17
18        // Check what changed (use empty graph for inference state)
19        let empty_graph =
20            Graph::new().map_err(|e| ggen_utils::error::Error::new(&e.to_string()))?;
21        let invalidation = cache.check_invalidation(manifest, &ontology_content, &empty_graph);
22
23        let mut affected_rules = vec![];
24        let mut high_impact_rules = vec![];
25        let mut unaffected_rules = vec![];
26
27        for rule in &manifest.generation.rules {
28            let should_rerun = invalidation.changed_rules.iter().any(|r| r == &rule.name);
29
30            if should_rerun {
31                affected_rules.push(rule.name.clone());
32
33                if !manifest.ontology.imports.is_empty() {
34                    high_impact_rules.push(rule.name.clone());
35                }
36            } else {
37                unaffected_rules.push(rule.name.clone());
38            }
39        }
40
41        Ok(AffectedRulesAnalysis {
42            manifest_changed: invalidation.manifest_changed,
43            ontology_changed: invalidation.ontology_changed,
44            inference_state_changed: invalidation.inference_state_changed,
45            affected_rule_count: affected_rules.len(),
46            unaffected_rule_count: unaffected_rules.len(),
47            affected_rules,
48            high_impact_rules,
49            unaffected_rules,
50            rerun_all: invalidation.should_rerun_all,
51        })
52    }
53
54    pub fn get_rule_execution_order(
55        analysis: &AffectedRulesAnalysis, manifest: &GgenManifest,
56    ) -> Vec<String> {
57        if analysis.rerun_all {
58            return manifest
59                .generation
60                .rules
61                .iter()
62                .map(|r| r.name.clone())
63                .collect();
64        }
65
66        let mut ordered = vec![];
67
68        for rule in &manifest.generation.rules {
69            if analysis.affected_rules.contains(&rule.name) {
70                ordered.push(rule.name.clone());
71            }
72        }
73
74        ordered
75    }
76
77    pub fn estimate_speedup(analysis: &AffectedRulesAnalysis, total_rules: usize) -> f64 {
78        if total_rules == 0 {
79            return 1.0;
80        }
81
82        let unaffected_ratio = analysis.unaffected_rule_count as f64 / total_rules as f64;
83        1.0 + (unaffected_ratio * 4.0)
84    }
85}
86
87pub struct AffectedRulesAnalysis {
88    pub manifest_changed: bool,
89    pub ontology_changed: bool,
90    pub inference_state_changed: bool,
91    pub affected_rule_count: usize,
92    pub unaffected_rule_count: usize,
93    pub affected_rules: Vec<String>,
94    pub high_impact_rules: Vec<String>,
95    pub unaffected_rules: Vec<String>,
96    pub rerun_all: bool,
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn test_speedup_calculation_no_changes() {
105        let analysis = AffectedRulesAnalysis {
106            manifest_changed: false,
107            ontology_changed: false,
108            inference_state_changed: false,
109            affected_rule_count: 0,
110            unaffected_rule_count: 10,
111            affected_rules: vec![],
112            high_impact_rules: vec![],
113            unaffected_rules: (0..10).map(|i| format!("rule-{}", i)).collect(),
114            rerun_all: false,
115        };
116
117        let speedup = WatchCacheIntegration::estimate_speedup(&analysis, 10);
118        assert!(speedup > 1.0);
119    }
120
121    #[test]
122    fn test_speedup_calculation_all_changed() {
123        let analysis = AffectedRulesAnalysis {
124            manifest_changed: true,
125            ontology_changed: false,
126            inference_state_changed: false,
127            affected_rule_count: 10,
128            unaffected_rule_count: 0,
129            affected_rules: (0..10).map(|i| format!("rule-{}", i)).collect(),
130            high_impact_rules: vec![],
131            unaffected_rules: vec![],
132            rerun_all: true,
133        };
134
135        let speedup = WatchCacheIntegration::estimate_speedup(&analysis, 10);
136        assert_eq!(speedup, 1.0);
137    }
138
139    #[test]
140    fn test_rule_execution_order_partial() {
141        use std::collections::BTreeMap;
142
143        let mut manifest = GgenManifest {
144            project: crate::manifest::ProjectConfig {
145                name: "test".to_string(),
146                version: "1.0.0".to_string(),
147                description: None,
148            },
149            ontology: crate::manifest::OntologyConfig {
150                source: "ontology.ttl".into(),
151                imports: vec![],
152                base_iri: None,
153                prefixes: BTreeMap::new(),
154            },
155            inference: crate::manifest::InferenceConfig::default(),
156            generation: crate::manifest::GenerationConfig {
157                rules: vec![],
158                output_dir: "output".into(),
159                require_audit_trail: false,
160                determinism_salt: None,
161                max_sparql_timeout_ms: 5000,
162            },
163            validation: crate::manifest::ValidationConfig::default(),
164        };
165
166        manifest.generation.rules = vec![
167            crate::manifest::GenerationRule {
168                name: "rule-1".to_string(),
169                query: crate::manifest::QuerySource::Inline {
170                    inline: "q1".to_string(),
171                },
172                template: crate::manifest::TemplateSource::Inline {
173                    inline: "t1".to_string(),
174                },
175                output_file: "out1.rs".to_string(),
176                skip_empty: false,
177                mode: Default::default(),
178                when: None,
179            },
180            crate::manifest::GenerationRule {
181                name: "rule-2".to_string(),
182                query: crate::manifest::QuerySource::Inline {
183                    inline: "q2".to_string(),
184                },
185                template: crate::manifest::TemplateSource::Inline {
186                    inline: "t2".to_string(),
187                },
188                output_file: "out2.rs".to_string(),
189                skip_empty: false,
190                mode: Default::default(),
191                when: None,
192            },
193        ];
194
195        let analysis = AffectedRulesAnalysis {
196            manifest_changed: false,
197            ontology_changed: true,
198            inference_state_changed: false,
199            affected_rule_count: 1,
200            unaffected_rule_count: 1,
201            affected_rules: vec!["rule-1".to_string()],
202            high_impact_rules: vec!["rule-1".to_string()],
203            unaffected_rules: vec!["rule-2".to_string()],
204            rerun_all: false,
205        };
206
207        let order = WatchCacheIntegration::get_rule_execution_order(&analysis, &manifest);
208        assert_eq!(order, vec!["rule-1"]);
209    }
210}