Skip to main content

uni_locy/
result.rs

1use std::collections::HashMap;
2use std::time::Duration;
3
4use uni_common::Value;
5
6use crate::types::{RuntimeWarning, RuntimeWarningCode};
7
8/// A single row of bindings from a query result.
9pub type Row = HashMap<String, Value>;
10
11/// Opaque savepoint identifier for transactional rollback.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub struct SavepointId(pub u64);
14
15/// The result of evaluating a compiled Locy program.
16#[derive(Debug, Clone)]
17pub struct LocyResult {
18    /// Derived facts per rule name.
19    pub derived: HashMap<String, Vec<Row>>,
20    /// Execution statistics.
21    pub stats: LocyStats,
22    /// Results from Phase 4 commands.
23    pub command_results: Vec<CommandResult>,
24    /// Runtime warnings collected during evaluation.
25    pub warnings: Vec<RuntimeWarning>,
26    /// Groups where BDD computation fell back to independence mode.
27    /// Maps rule name → list of human-readable key group descriptions.
28    pub approximate_groups: HashMap<String, Vec<String>>,
29}
30
31/// Result of executing a single Phase 4 command.
32#[derive(Debug, Clone)]
33pub enum CommandResult {
34    Query(Vec<Row>),
35    Assume(Vec<Row>),
36    Explain(DerivationNode),
37    Abduce(AbductionResult),
38    Derive { affected: usize },
39    Cypher(Vec<Row>),
40}
41
42/// A node in a derivation tree, produced by EXPLAIN RULE.
43#[derive(Debug, Clone)]
44pub struct DerivationNode {
45    pub rule: String,
46    pub clause_index: usize,
47    pub priority: Option<i64>,
48    pub bindings: HashMap<String, Value>,
49    pub along_values: HashMap<String, Value>,
50    pub children: Vec<DerivationNode>,
51    pub graph_fact: Option<String>,
52    /// True when this node's probability was computed via BDD fallback
53    /// (independence mode) because the group exceeded `max_bdd_variables`.
54    pub approximate: bool,
55    /// Probability of this specific proof path, populated when top-k proof
56    /// filtering is active (Scallop, Huang et al. 2021).
57    pub proof_probability: Option<f64>,
58}
59
60/// Result of an ABDUCE query.
61#[derive(Debug, Clone)]
62pub struct AbductionResult {
63    pub modifications: Vec<ValidatedModification>,
64}
65
66/// A modification with validation status and cost.
67#[derive(Debug, Clone)]
68pub struct ValidatedModification {
69    pub modification: Modification,
70    pub validated: bool,
71    pub cost: f64,
72}
73
74/// A proposed graph modification from ABDUCE.
75#[derive(Debug, Clone)]
76pub enum Modification {
77    RemoveEdge {
78        source_var: String,
79        target_var: String,
80        edge_var: String,
81        edge_type: String,
82        match_properties: HashMap<String, Value>,
83    },
84    ChangeProperty {
85        element_var: String,
86        property: String,
87        old_value: Box<Value>,
88        new_value: Box<Value>,
89    },
90    AddEdge {
91        source_var: String,
92        target_var: String,
93        edge_type: String,
94        properties: HashMap<String, Value>,
95    },
96}
97
98/// Statistics collected during Locy program evaluation.
99#[derive(Debug, Clone, Default)]
100pub struct LocyStats {
101    pub strata_evaluated: usize,
102    pub total_iterations: usize,
103    pub derived_nodes: usize,
104    pub derived_edges: usize,
105    pub evaluation_time: Duration,
106    pub queries_executed: usize,
107    pub mutations_executed: usize,
108    /// Peak memory used by derived relations (in bytes).
109    pub peak_memory_bytes: usize,
110}
111
112impl LocyResult {
113    /// Get derived facts for a specific rule.
114    pub fn derived_facts(&self, rule: &str) -> Option<&Vec<Row>> {
115        self.derived.get(rule)
116    }
117
118    /// Get rows from the first Query command result.
119    pub fn rows(&self) -> Option<&Vec<Row>> {
120        self.command_results.iter().find_map(|cr| cr.as_query())
121    }
122
123    /// Get column names from the first Query command result's first row.
124    pub fn columns(&self) -> Option<Vec<String>> {
125        self.rows()
126            .and_then(|rows| rows.first().map(|row| row.keys().cloned().collect()))
127    }
128
129    /// Get execution statistics.
130    pub fn stats(&self) -> &LocyStats {
131        &self.stats
132    }
133
134    /// Get the total number of fixpoint iterations.
135    pub fn iterations(&self) -> usize {
136        self.stats.total_iterations
137    }
138
139    /// Get runtime warnings collected during evaluation.
140    pub fn warnings(&self) -> &[RuntimeWarning] {
141        &self.warnings
142    }
143
144    /// Check whether a specific warning code was emitted.
145    pub fn has_warning(&self, code: &RuntimeWarningCode) -> bool {
146        self.warnings.iter().any(|w| w.code == *code)
147    }
148}
149
150impl CommandResult {
151    /// If this is an Explain result, return the derivation node.
152    pub fn as_explain(&self) -> Option<&DerivationNode> {
153        match self {
154            CommandResult::Explain(node) => Some(node),
155            _ => None,
156        }
157    }
158
159    /// If this is a Query result, return the rows.
160    pub fn as_query(&self) -> Option<&Vec<Row>> {
161        match self {
162            CommandResult::Query(rows) => Some(rows),
163            _ => None,
164        }
165    }
166
167    /// If this is an Abduce result, return it.
168    pub fn as_abduce(&self) -> Option<&AbductionResult> {
169        match self {
170            CommandResult::Abduce(result) => Some(result),
171            _ => None,
172        }
173    }
174}