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 let ontology_path = base_path.join(&manifest.ontology.source);
16 let ontology_content = fs::read_to_string(&ontology_path).unwrap_or_default();
17
18 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}