Skip to main content

omena_query_core/
lib.rs

1//! Core query runtime primitives below the public `omena-query` facade.
2//!
3//! This crate owns producer-fragment summaries and expression-domain runtime
4//! state. `omena-query` re-exports these surfaces, but no longer needs to depend
5//! directly on each lower-level producer crate for this part of the dataflow.
6
7use std::collections::{BTreeMap, BTreeSet};
8
9pub use engine_input_producers::{
10    ClassExpressionInputV2, EngineInputV2, ExpressionDomainCallSiteFlowAnalysisV0,
11    ExpressionDomainControlFlowAnalysisV0, ExpressionDomainFlowAnalysisV0,
12    ExpressionDomainProvenanceExplanationsV0, ExpressionDomainReducedProductIterationV0,
13    ExpressionSemanticsCanonicalProducerSignalV0, ExpressionSemanticsQueryFragmentsV0, PositionV2,
14    RangeV2, SelectorUsageCanonicalProducerSignalV0, SelectorUsageQueryFragmentsV0,
15    SourceAnalysisInputV2, SourceDocumentV2, SourceResolutionCanonicalProducerSignalV0,
16    SourceResolutionQueryFragmentsV0, StringTypeFactsV2, StyleAnalysisInputV2, StyleDocumentV2,
17    StyleSelectorV2, TypeFactEntryV2,
18};
19use engine_input_producers::{
20    collect_expression_domain_flow_graphs,
21    summarize_expression_domain_call_site_flow_analysis_input,
22    summarize_expression_domain_control_flow_analysis_input,
23    summarize_expression_domain_flow_analysis_input,
24    summarize_expression_domain_provenance_explanations_input,
25    summarize_expression_domain_reduced_product_iteration_input,
26    summarize_expression_semantics_canonical_producer_signal_input,
27    summarize_expression_semantics_query_fragments_input,
28    summarize_selector_usage_canonical_producer_signal_input,
29    summarize_selector_usage_query_fragments_input,
30};
31pub use omena_abstract_value::{
32    AbstractClassValueV0, AbstractPropertyValueV0, AbstractValueDomainSummaryV0, CascadeContextV0,
33    CascadeValueFamilyMemberV0, ClassValueFlowAnalysisV0, ClassValueFlowIncrementalAnalysisV0,
34    Lin01ProvenanceSemiringV0, LinearProvenancePathV0, LinearProvenanceV0,
35    NaturalCountProvenanceSemiringV0, ProvenanceSemiringLawReportV0, ReducedClassValueProductV0,
36    SelectorProjectionCertaintyV0, derive_cascade_restriction_maps_v0,
37    summarize_cascade_value_family_v0, verify_provenance_semiring_laws_on_fixtures,
38};
39use omena_abstract_value::{
40    analyze_class_value_flow_incremental_with_database, project_abstract_value_selectors,
41    summarize_omena_abstract_value_domain, summarize_reduced_class_value_product,
42};
43use omena_incremental::OmenaIncrementalDatabaseV0;
44pub use omena_refinement::{
45    CascadeDimensionalRefinementBridgeV0, RefinementPropertyPredicateV0,
46    summarize_cascade_dimensional_refinement_bridge_v0,
47};
48pub use omena_resolver::OmenaResolverSourceResolutionRuntimeIndexV0;
49use omena_resolver::{
50    summarize_omena_resolver_canonical_producer_signal, summarize_omena_resolver_query_fragments,
51    summarize_omena_resolver_source_resolution_runtime,
52};
53use serde::Serialize;
54
55pub const OMENA_QUERY_CURRENT_SCHEMA_VERSION: &str = "0";
56pub const OMENA_QUERY_CURRENT_SCHEMA_VERSION_LABEL: &str = "V0";
57
58#[derive(Debug, Serialize)]
59#[serde(rename_all = "camelCase")]
60pub struct OmenaQueryFragmentBundleV0 {
61    pub schema_version: &'static str,
62    pub product: &'static str,
63    pub input_version: String,
64    pub expression_semantics: ExpressionSemanticsQueryFragmentsV0,
65    pub source_resolution: SourceResolutionQueryFragmentsV0,
66    pub selector_usage: SelectorUsageQueryFragmentsV0,
67}
68
69#[derive(Debug, Serialize)]
70#[serde(rename_all = "camelCase")]
71pub struct OmenaQueryExpressionDomainIncrementalFlowAnalysisV0 {
72    pub schema_version: &'static str,
73    pub product: &'static str,
74    pub input_version: String,
75    pub revision: u64,
76    pub graph_count: usize,
77    pub dirty_graph_count: usize,
78    pub reused_graph_count: usize,
79    pub analyses: Vec<OmenaQueryExpressionDomainIncrementalFlowAnalysisEntryV0>,
80}
81
82#[derive(Debug, Serialize)]
83#[serde(rename_all = "camelCase")]
84pub struct OmenaQueryExpressionDomainIncrementalFlowAnalysisEntryV0 {
85    pub graph_id: String,
86    pub file_path: String,
87    pub analysis: ClassValueFlowIncrementalAnalysisV0,
88}
89
90#[derive(Debug, Serialize)]
91#[serde(rename_all = "camelCase")]
92pub struct OmenaQueryExpressionDomainSelectorProjectionV0 {
93    pub schema_version: &'static str,
94    pub product: &'static str,
95    pub input_version: String,
96    pub projection_count: usize,
97    pub projections: Vec<OmenaQueryExpressionDomainSelectorProjectionEntryV0>,
98}
99
100#[derive(Debug, Serialize)]
101#[serde(rename_all = "camelCase")]
102pub struct OmenaQueryExpressionDomainSelectorProjectionEntryV0 {
103    pub graph_id: String,
104    pub file_path: String,
105    pub node_id: String,
106    pub target_style_paths: Vec<String>,
107    pub value_kind: &'static str,
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub reduced_product: Option<ReducedClassValueProductV0>,
110    pub selector_names: Vec<String>,
111    pub certainty: SelectorProjectionCertaintyV0,
112}
113
114#[derive(Default)]
115pub struct OmenaQueryExpressionDomainFlowRuntimeV0 {
116    revision: u64,
117    databases_by_graph_id: BTreeMap<String, OmenaIncrementalDatabaseV0>,
118    previous_analyses_by_graph_id: BTreeMap<String, ClassValueFlowAnalysisV0>,
119}
120
121impl OmenaQueryExpressionDomainFlowRuntimeV0 {
122    pub fn revision(&self) -> u64 {
123        self.revision
124    }
125
126    pub fn graph_count(&self) -> usize {
127        self.databases_by_graph_id.len()
128    }
129
130    pub fn analyze_input(
131        &mut self,
132        input: &EngineInputV2,
133    ) -> OmenaQueryExpressionDomainIncrementalFlowAnalysisV0 {
134        self.revision += 1;
135        let revision = self.revision;
136        let flow_graphs = collect_expression_domain_flow_graphs(input);
137        let live_graph_ids = flow_graphs
138            .iter()
139            .map(|entry| entry.graph_id.clone())
140            .collect::<BTreeSet<_>>();
141
142        self.databases_by_graph_id
143            .retain(|graph_id, _| live_graph_ids.contains(graph_id));
144        self.previous_analyses_by_graph_id
145            .retain(|graph_id, _| live_graph_ids.contains(graph_id));
146
147        let analyses = flow_graphs
148            .into_iter()
149            .map(|entry| {
150                let database = self
151                    .databases_by_graph_id
152                    .entry(entry.graph_id.clone())
153                    .or_default();
154                let previous_analysis = self.previous_analyses_by_graph_id.get(&entry.graph_id);
155                let analysis = analyze_class_value_flow_incremental_with_database(
156                    &entry.graph,
157                    database,
158                    previous_analysis,
159                    revision,
160                );
161                self.previous_analyses_by_graph_id
162                    .insert(entry.graph_id.clone(), analysis.analysis.clone());
163
164                OmenaQueryExpressionDomainIncrementalFlowAnalysisEntryV0 {
165                    graph_id: entry.graph_id,
166                    file_path: entry.file_path,
167                    analysis,
168                }
169            })
170            .collect::<Vec<_>>();
171
172        let dirty_graph_count = analyses
173            .iter()
174            .filter(|entry| entry.analysis.incremental_plan.dirty_node_count > 0)
175            .count();
176        let reused_graph_count = analyses
177            .iter()
178            .filter(|entry| entry.analysis.reused_previous_analysis)
179            .count();
180
181        OmenaQueryExpressionDomainIncrementalFlowAnalysisV0 {
182            schema_version: OMENA_QUERY_CURRENT_SCHEMA_VERSION,
183            product: "omena-query.expression-domain-incremental-flow-analysis",
184            input_version: input.version.clone(),
185            revision,
186            graph_count: analyses.len(),
187            dirty_graph_count,
188            reused_graph_count,
189            analyses,
190        }
191    }
192}
193
194pub fn summarize_omena_query_core_abstract_value_domain() -> AbstractValueDomainSummaryV0 {
195    summarize_omena_abstract_value_domain()
196}
197
198pub fn summarize_omena_query_fragment_bundle(input: &EngineInputV2) -> OmenaQueryFragmentBundleV0 {
199    OmenaQueryFragmentBundleV0 {
200        schema_version: OMENA_QUERY_CURRENT_SCHEMA_VERSION,
201        product: "omena-query.fragment-bundle",
202        input_version: input.version.clone(),
203        expression_semantics: summarize_omena_query_expression_semantics_query_fragments(input),
204        source_resolution: summarize_omena_query_source_resolution_query_fragments(input),
205        selector_usage: summarize_omena_query_selector_usage_query_fragments(input),
206    }
207}
208
209pub fn summarize_omena_query_expression_semantics_query_fragments(
210    input: &EngineInputV2,
211) -> ExpressionSemanticsQueryFragmentsV0 {
212    summarize_expression_semantics_query_fragments_input(input)
213}
214
215pub fn summarize_omena_query_expression_domain_flow_analysis(
216    input: &EngineInputV2,
217) -> ExpressionDomainFlowAnalysisV0 {
218    summarize_expression_domain_flow_analysis_input(input)
219}
220
221pub fn summarize_omena_query_expression_domain_control_flow_analysis(
222    input: &EngineInputV2,
223) -> ExpressionDomainControlFlowAnalysisV0 {
224    summarize_expression_domain_control_flow_analysis_input(input)
225}
226
227pub fn summarize_omena_query_expression_domain_call_site_flow_analysis(
228    input: &EngineInputV2,
229) -> ExpressionDomainCallSiteFlowAnalysisV0 {
230    summarize_expression_domain_call_site_flow_analysis_input(input)
231}
232
233pub fn summarize_omena_query_expression_domain_provenance_explanations(
234    input: &EngineInputV2,
235) -> ExpressionDomainProvenanceExplanationsV0 {
236    summarize_expression_domain_provenance_explanations_input(input)
237}
238
239pub fn summarize_omena_query_expression_domain_reduced_product_iteration(
240    input: &EngineInputV2,
241) -> ExpressionDomainReducedProductIterationV0 {
242    summarize_expression_domain_reduced_product_iteration_input(input)
243}
244
245pub fn summarize_omena_query_expression_domain_incremental_flow_analysis(
246    input: &EngineInputV2,
247    runtime: &mut OmenaQueryExpressionDomainFlowRuntimeV0,
248) -> OmenaQueryExpressionDomainIncrementalFlowAnalysisV0 {
249    runtime.analyze_input(input)
250}
251
252pub fn summarize_omena_query_expression_domain_selector_projection(
253    input: &EngineInputV2,
254) -> OmenaQueryExpressionDomainSelectorProjectionV0 {
255    let style_selectors_by_path = style_selector_universe_by_path(input);
256    let expression_targets = expression_target_style_paths(input);
257    let flow_analysis = summarize_omena_query_expression_domain_flow_analysis(input);
258    let mut projections = Vec::new();
259
260    for graph in flow_analysis.analyses {
261        for node in graph.analysis.nodes {
262            let target_style_paths = target_style_paths_for_flow_node(
263                node.id.as_str(),
264                node.predecessor_ids.as_slice(),
265                &expression_targets,
266            );
267            let selector_universe = selector_universe_for_targets(
268                target_style_paths.as_slice(),
269                &style_selectors_by_path,
270            );
271            let projection = project_abstract_value_selectors(&node.value, &selector_universe);
272            projections.push(OmenaQueryExpressionDomainSelectorProjectionEntryV0 {
273                graph_id: graph.graph_id.clone(),
274                file_path: graph.file_path.clone(),
275                node_id: node.id,
276                target_style_paths,
277                value_kind: node.value_kind,
278                reduced_product: summarize_reduced_class_value_product(&node.value),
279                selector_names: projection.selector_names,
280                certainty: projection.certainty,
281            });
282        }
283    }
284
285    OmenaQueryExpressionDomainSelectorProjectionV0 {
286        schema_version: OMENA_QUERY_CURRENT_SCHEMA_VERSION,
287        product: "omena-query.expression-domain-selector-projection",
288        input_version: input.version.clone(),
289        projection_count: projections.len(),
290        projections,
291    }
292}
293
294fn expression_target_style_paths(input: &EngineInputV2) -> BTreeMap<String, String> {
295    input
296        .sources
297        .iter()
298        .flat_map(|source| source.document.class_expressions.iter())
299        .map(|expression| (expression.id.clone(), expression.scss_module_path.clone()))
300        .collect()
301}
302
303fn style_selector_universe_by_path(input: &EngineInputV2) -> BTreeMap<String, Vec<String>> {
304    input
305        .styles
306        .iter()
307        .map(|style| {
308            let selector_names = style
309                .document
310                .selectors
311                .iter()
312                .map(|selector| {
313                    selector
314                        .canonical_name
315                        .clone()
316                        .unwrap_or_else(|| selector.name.clone())
317                })
318                .collect::<BTreeSet<_>>()
319                .into_iter()
320                .collect::<Vec<_>>();
321            (style.file_path.clone(), selector_names)
322        })
323        .collect()
324}
325
326fn target_style_paths_for_flow_node(
327    node_id: &str,
328    predecessor_ids: &[String],
329    expression_targets: &BTreeMap<String, String>,
330) -> Vec<String> {
331    let mut targets = BTreeSet::new();
332    if let Some(target) = expression_targets.get(node_id) {
333        targets.insert(target.clone());
334    }
335    for predecessor_id in predecessor_ids {
336        if let Some(target) = expression_targets.get(predecessor_id) {
337            targets.insert(target.clone());
338        }
339    }
340    targets.into_iter().collect()
341}
342
343fn selector_universe_for_targets(
344    target_style_paths: &[String],
345    style_selectors_by_path: &BTreeMap<String, Vec<String>>,
346) -> Vec<String> {
347    let mut selectors = BTreeSet::new();
348    if target_style_paths.is_empty() {
349        for selector_names in style_selectors_by_path.values() {
350            selectors.extend(selector_names.iter().cloned());
351        }
352    } else {
353        for target_style_path in target_style_paths {
354            if let Some(selector_names) = style_selectors_by_path.get(target_style_path) {
355                selectors.extend(selector_names.iter().cloned());
356            }
357        }
358    }
359    selectors.into_iter().collect()
360}
361
362pub fn summarize_omena_query_source_resolution_query_fragments(
363    input: &EngineInputV2,
364) -> SourceResolutionQueryFragmentsV0 {
365    summarize_omena_resolver_query_fragments(input)
366}
367
368pub fn summarize_omena_query_selector_usage_query_fragments(
369    input: &EngineInputV2,
370) -> SelectorUsageQueryFragmentsV0 {
371    summarize_selector_usage_query_fragments_input(input)
372}
373
374pub fn summarize_omena_query_source_resolution_canonical_producer_signal(
375    input: &EngineInputV2,
376) -> SourceResolutionCanonicalProducerSignalV0 {
377    summarize_omena_resolver_canonical_producer_signal(input)
378}
379
380pub fn summarize_omena_query_source_resolution_runtime(
381    input: &EngineInputV2,
382) -> OmenaResolverSourceResolutionRuntimeIndexV0 {
383    summarize_omena_resolver_source_resolution_runtime(input)
384}
385
386pub fn summarize_omena_query_expression_semantics_canonical_producer_signal(
387    input: &EngineInputV2,
388) -> ExpressionSemanticsCanonicalProducerSignalV0 {
389    summarize_expression_semantics_canonical_producer_signal_input(input)
390}
391
392pub fn summarize_omena_query_selector_usage_canonical_producer_signal(
393    input: &EngineInputV2,
394) -> SelectorUsageCanonicalProducerSignalV0 {
395    summarize_selector_usage_canonical_producer_signal_input(input)
396}
397
398#[cfg(test)]
399mod tests {
400    use super::*;
401
402    #[test]
403    fn expression_domain_runtime_reuses_graph_databases_across_revisions() {
404        let input = EngineInputV2 {
405            version: "core-runtime".to_string(),
406            sources: Vec::new(),
407            styles: Vec::new(),
408            type_facts: Vec::new(),
409        };
410        let mut runtime = OmenaQueryExpressionDomainFlowRuntimeV0::default();
411
412        let first =
413            summarize_omena_query_expression_domain_incremental_flow_analysis(&input, &mut runtime);
414        let second =
415            summarize_omena_query_expression_domain_incremental_flow_analysis(&input, &mut runtime);
416
417        assert_eq!(first.revision, 1);
418        assert_eq!(second.revision, 2);
419        assert_eq!(runtime.revision(), 2);
420    }
421}