use std::collections::HashMap;
use std::time::Duration;
use uni_common::{Properties, Value};
use crate::types::{RuntimeWarning, RuntimeWarningCode};
pub type FactRow = HashMap<String, Value>;
#[derive(Debug, Clone)]
pub struct LocyResult {
pub derived: HashMap<String, Vec<FactRow>>,
pub stats: LocyStats,
pub command_results: Vec<CommandResult>,
pub warnings: Vec<RuntimeWarning>,
pub approximate_groups: HashMap<String, Vec<String>>,
pub derived_fact_set: Option<DerivedFactSet>,
pub timed_out: bool,
}
#[derive(Debug, Clone)]
pub enum CommandResult {
Query(Vec<FactRow>),
Assume(Vec<FactRow>),
Explain(DerivationNode),
Abduce(AbductionResult),
Derive { affected: usize },
Cypher(Vec<FactRow>),
}
#[derive(Debug, Clone)]
pub struct DerivationNode {
pub rule: String,
pub clause_index: usize,
pub priority: Option<i64>,
pub bindings: HashMap<String, Value>,
pub along_values: HashMap<String, Value>,
pub children: Vec<DerivationNode>,
pub graph_fact: Option<String>,
pub approximate: bool,
pub proof_probability: Option<f64>,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct AbductionResult {
pub modifications: Vec<ValidatedModification>,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct ValidatedModification {
pub modification: Modification,
pub validated: bool,
pub cost: f64,
}
#[derive(Debug, Clone, serde::Serialize)]
pub enum Modification {
RemoveEdge {
source_var: String,
target_var: String,
edge_var: String,
edge_type: String,
match_properties: HashMap<String, Value>,
},
ChangeProperty {
element_var: String,
property: String,
old_value: Box<Value>,
new_value: Box<Value>,
},
AddEdge {
source_var: String,
target_var: String,
edge_type: String,
properties: HashMap<String, Value>,
},
}
#[derive(Debug, Clone)]
pub struct DerivedEdge {
pub edge_type: String,
pub source_label: String,
pub source_properties: Properties,
pub target_label: String,
pub target_properties: Properties,
pub edge_properties: Properties,
}
#[derive(Debug, Clone)]
pub struct DerivedFactSet {
pub vertices: HashMap<String, Vec<Properties>>,
pub edges: Vec<DerivedEdge>,
pub stats: LocyStats,
pub evaluated_at_version: u64,
#[doc(hidden)]
pub mutation_queries: Vec<uni_cypher::ast::Query>,
}
impl DerivedFactSet {
pub fn fact_count(&self) -> usize {
self.vertices.values().map(|v| v.len()).sum::<usize>() + self.edges.len()
}
pub fn is_empty(&self) -> bool {
self.vertices.is_empty() && self.edges.is_empty()
}
}
#[derive(Debug, Clone, Default)]
pub struct LocyStats {
pub strata_evaluated: usize,
pub total_iterations: usize,
pub derived_nodes: usize,
pub derived_edges: usize,
pub evaluation_time: Duration,
pub queries_executed: usize,
pub mutations_executed: usize,
pub peak_memory_bytes: usize,
}
impl LocyResult {
pub fn derived_facts(&self, rule: &str) -> Option<&Vec<FactRow>> {
self.derived.get(rule)
}
pub fn rows(&self) -> Option<&Vec<FactRow>> {
self.command_results.iter().find_map(|cr| cr.as_query())
}
pub fn columns(&self) -> Option<Vec<String>> {
self.rows()
.and_then(|rows| rows.first().map(|row| row.keys().cloned().collect()))
}
pub fn stats(&self) -> &LocyStats {
&self.stats
}
pub fn iterations(&self) -> usize {
self.stats.total_iterations
}
pub fn warnings(&self) -> &[RuntimeWarning] {
&self.warnings
}
pub fn has_warning(&self, code: &RuntimeWarningCode) -> bool {
self.warnings.iter().any(|w| w.code == *code)
}
}
impl CommandResult {
pub fn as_explain(&self) -> Option<&DerivationNode> {
match self {
CommandResult::Explain(node) => Some(node),
_ => None,
}
}
pub fn as_query(&self) -> Option<&Vec<FactRow>> {
match self {
CommandResult::Query(rows) => Some(rows),
_ => None,
}
}
pub fn as_abduce(&self) -> Option<&AbductionResult> {
match self {
CommandResult::Abduce(result) => Some(result),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn abduce_result_serializes_to_json() {
let result = AbductionResult {
modifications: vec![
ValidatedModification {
modification: Modification::ChangeProperty {
element_var: "a".into(),
property: "flagged".into(),
old_value: Box::new(Value::String("false".into())),
new_value: Box::new(Value::String("true".into())),
},
validated: true,
cost: 0.5,
},
ValidatedModification {
modification: Modification::RemoveEdge {
source_var: "a".into(),
target_var: "b".into(),
edge_var: "e".into(),
edge_type: "TRANSFERS_TO".into(),
match_properties: HashMap::from([("amount".into(), Value::Float(1000.0))]),
},
validated: false,
cost: 1.0,
},
ValidatedModification {
modification: Modification::AddEdge {
source_var: "a".into(),
target_var: "b".into(),
edge_type: "FLAGGED_BY".into(),
properties: HashMap::new(),
},
validated: true,
cost: 1.5,
},
],
};
let json = serde_json::to_value(&result).expect("serialization failed");
let mods = json["modifications"].as_array().unwrap();
assert_eq!(mods.len(), 3);
assert_eq!(mods[0]["validated"], true);
assert_eq!(mods[0]["cost"], 0.5);
assert!(mods[0]["modification"]["ChangeProperty"].is_object());
assert!(mods[1]["modification"]["RemoveEdge"].is_object());
assert!(mods[2]["modification"]["AddEdge"].is_object());
}
}