1use std::collections::HashMap;
2use std::time::Duration;
3
4use uni_common::{Properties, Value};
5
6use crate::types::{RuntimeWarning, RuntimeWarningCode};
7
8pub type FactRow = HashMap<String, Value>;
10
11#[derive(Debug, Clone)]
13pub struct LocyResult {
14 pub derived: HashMap<String, Vec<FactRow>>,
16 pub stats: LocyStats,
18 pub command_results: Vec<CommandResult>,
20 pub warnings: Vec<RuntimeWarning>,
22 pub approximate_groups: HashMap<String, Vec<String>>,
25 pub derived_fact_set: Option<DerivedFactSet>,
28 pub timed_out: bool,
32}
33
34#[derive(Debug, Clone)]
36pub enum CommandResult {
37 Query(Vec<FactRow>),
38 Assume(Vec<FactRow>),
39 Explain(DerivationNode),
40 Abduce(AbductionResult),
41 Derive { affected: usize },
42 Cypher(Vec<FactRow>),
43}
44
45#[derive(Debug, Clone)]
47pub struct DerivationNode {
48 pub rule: String,
49 pub clause_index: usize,
50 pub priority: Option<i64>,
51 pub bindings: HashMap<String, Value>,
52 pub along_values: HashMap<String, Value>,
53 pub children: Vec<DerivationNode>,
54 pub graph_fact: Option<String>,
55 pub approximate: bool,
58 pub proof_probability: Option<f64>,
61}
62
63#[derive(Debug, Clone, serde::Serialize)]
65pub struct AbductionResult {
66 pub modifications: Vec<ValidatedModification>,
67}
68
69#[derive(Debug, Clone, serde::Serialize)]
71pub struct ValidatedModification {
72 pub modification: Modification,
73 pub validated: bool,
75 pub cost: f64,
77}
78
79#[derive(Debug, Clone, serde::Serialize)]
81pub enum Modification {
82 RemoveEdge {
83 source_var: String,
84 target_var: String,
85 edge_var: String,
86 edge_type: String,
87 match_properties: HashMap<String, Value>,
89 },
90 ChangeProperty {
91 element_var: String,
92 property: String,
93 old_value: Box<Value>,
94 new_value: Box<Value>,
95 },
96 AddEdge {
97 source_var: String,
98 target_var: String,
99 edge_type: String,
100 properties: HashMap<String, Value>,
101 },
102}
103
104#[derive(Debug, Clone)]
106pub struct DerivedEdge {
107 pub edge_type: String,
108 pub source_label: String,
109 pub source_properties: Properties,
110 pub target_label: String,
111 pub target_properties: Properties,
112 pub edge_properties: Properties,
113}
114
115#[derive(Debug, Clone)]
119pub struct DerivedFactSet {
120 pub vertices: HashMap<String, Vec<Properties>>,
122 pub edges: Vec<DerivedEdge>,
124 pub stats: LocyStats,
126 pub evaluated_at_version: u64,
128 #[doc(hidden)]
130 pub mutation_queries: Vec<uni_cypher::ast::Query>,
131}
132
133impl DerivedFactSet {
134 pub fn fact_count(&self) -> usize {
136 self.vertices.values().map(|v| v.len()).sum::<usize>() + self.edges.len()
137 }
138
139 pub fn is_empty(&self) -> bool {
141 self.vertices.is_empty() && self.edges.is_empty()
142 }
143}
144
145#[derive(Debug, Clone, Default)]
147pub struct LocyStats {
148 pub strata_evaluated: usize,
149 pub total_iterations: usize,
150 pub derived_nodes: usize,
151 pub derived_edges: usize,
152 pub evaluation_time: Duration,
153 pub queries_executed: usize,
154 pub mutations_executed: usize,
155 pub peak_memory_bytes: usize,
157}
158
159impl LocyResult {
160 pub fn derived_facts(&self, rule: &str) -> Option<&Vec<FactRow>> {
162 self.derived.get(rule)
163 }
164
165 pub fn rows(&self) -> Option<&Vec<FactRow>> {
167 self.command_results.iter().find_map(|cr| cr.as_query())
168 }
169
170 pub fn columns(&self) -> Option<Vec<String>> {
172 self.rows()
173 .and_then(|rows| rows.first().map(|row| row.keys().cloned().collect()))
174 }
175
176 pub fn stats(&self) -> &LocyStats {
178 &self.stats
179 }
180
181 pub fn iterations(&self) -> usize {
183 self.stats.total_iterations
184 }
185
186 pub fn warnings(&self) -> &[RuntimeWarning] {
188 &self.warnings
189 }
190
191 pub fn has_warning(&self, code: &RuntimeWarningCode) -> bool {
193 self.warnings.iter().any(|w| w.code == *code)
194 }
195}
196
197impl CommandResult {
198 pub fn as_explain(&self) -> Option<&DerivationNode> {
200 match self {
201 CommandResult::Explain(node) => Some(node),
202 _ => None,
203 }
204 }
205
206 pub fn as_query(&self) -> Option<&Vec<FactRow>> {
208 match self {
209 CommandResult::Query(rows) => Some(rows),
210 _ => None,
211 }
212 }
213
214 pub fn as_abduce(&self) -> Option<&AbductionResult> {
216 match self {
217 CommandResult::Abduce(result) => Some(result),
218 _ => None,
219 }
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 #[test]
228 fn abduce_result_serializes_to_json() {
229 let result = AbductionResult {
230 modifications: vec![
231 ValidatedModification {
232 modification: Modification::ChangeProperty {
233 element_var: "a".into(),
234 property: "flagged".into(),
235 old_value: Box::new(Value::String("false".into())),
236 new_value: Box::new(Value::String("true".into())),
237 },
238 validated: true,
239 cost: 0.5,
240 },
241 ValidatedModification {
242 modification: Modification::RemoveEdge {
243 source_var: "a".into(),
244 target_var: "b".into(),
245 edge_var: "e".into(),
246 edge_type: "TRANSFERS_TO".into(),
247 match_properties: HashMap::from([("amount".into(), Value::Float(1000.0))]),
248 },
249 validated: false,
250 cost: 1.0,
251 },
252 ValidatedModification {
253 modification: Modification::AddEdge {
254 source_var: "a".into(),
255 target_var: "b".into(),
256 edge_type: "FLAGGED_BY".into(),
257 properties: HashMap::new(),
258 },
259 validated: true,
260 cost: 1.5,
261 },
262 ],
263 };
264
265 let json = serde_json::to_value(&result).expect("serialization failed");
266 let mods = json["modifications"].as_array().unwrap();
267 assert_eq!(mods.len(), 3);
268 assert_eq!(mods[0]["validated"], true);
269 assert_eq!(mods[0]["cost"], 0.5);
270 assert!(mods[0]["modification"]["ChangeProperty"].is_object());
271 assert!(mods[1]["modification"]["RemoveEdge"].is_object());
272 assert!(mods[2]["modification"]["AddEdge"].is_object());
273 }
274}