use super::goal::Goal;
use crate::types::Value;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct QueryResult {
pub provable: bool,
pub bindings: HashMap<String, Value>,
pub proof_trace: ProofTrace,
pub missing_facts: Vec<String>,
pub stats: QueryStats,
}
#[derive(Debug, Clone)]
pub struct ProofTrace {
pub goal: String,
pub steps: Vec<ProofStep>,
}
#[derive(Debug, Clone)]
pub struct ProofStep {
pub rule_name: String,
pub goal: String,
pub sub_steps: Vec<ProofStep>,
pub depth: usize,
}
#[derive(Debug, Clone, Default)]
pub struct QueryStats {
pub goals_explored: usize,
pub rules_evaluated: usize,
pub max_depth: usize,
pub duration_ms: Option<u64>,
}
impl QueryResult {
pub fn success(bindings: HashMap<String, Value>, proof: ProofTrace, stats: QueryStats) -> Self {
Self {
provable: true,
bindings,
proof_trace: proof,
missing_facts: Vec::new(),
stats,
}
}
pub fn failure(missing: Vec<String>, stats: QueryStats) -> Self {
Self {
provable: false,
bindings: HashMap::new(),
proof_trace: ProofTrace::empty(),
missing_facts: missing,
stats,
}
}
}
impl ProofTrace {
pub fn empty() -> Self {
Self {
goal: String::new(),
steps: Vec::new(),
}
}
pub fn new(goal: String) -> Self {
Self {
goal,
steps: Vec::new(),
}
}
pub fn add_step(&mut self, step: ProofStep) {
self.steps.push(step);
}
pub fn from_goal(goal: &Goal) -> Self {
let mut trace = Self::new(goal.pattern.clone());
for (i, rule_name) in goal.candidate_rules.iter().enumerate() {
let step = ProofStep {
rule_name: rule_name.clone(),
goal: goal.pattern.clone(),
sub_steps: goal.sub_goals.iter()
.map(|sg| ProofStep::from_goal(sg, i + 1))
.collect(),
depth: goal.depth,
};
trace.add_step(step);
}
trace
}
pub fn print(&self) {
println!("Proof for goal: {}", self.goal);
for step in &self.steps {
step.print(0);
}
}
}
impl ProofStep {
fn from_goal(goal: &Goal, depth: usize) -> Self {
Self {
rule_name: goal.candidate_rules.first()
.cloned()
.unwrap_or_else(|| "unknown".to_string()),
goal: goal.pattern.clone(),
sub_steps: goal.sub_goals.iter()
.map(|sg| Self::from_goal(sg, depth + 1))
.collect(),
depth,
}
}
fn print(&self, indent: usize) {
let prefix = " ".repeat(indent);
println!("{}→ [{}] {}", prefix, self.rule_name, self.goal);
for sub in &self.sub_steps {
sub.print(indent + 1);
}
}
}
pub struct QueryParser;
impl QueryParser {
pub fn parse(query: &str) -> Result<Goal, String> {
use super::expression::ExpressionParser;
if query.is_empty() {
return Err("Empty query".to_string());
}
match ExpressionParser::parse(query) {
Ok(expr) => {
Ok(Goal::with_expression(query.to_string(), expr))
}
Err(e) => {
Err(format!("Failed to parse query: {}", e))
}
}
}
pub fn validate(query: &str) -> Result<(), String> {
if query.is_empty() {
return Err("Query cannot be empty".to_string());
}
Self::parse(query).map(|_| ())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_query_result_creation() {
let stats = QueryStats::default();
let success = QueryResult::success(
HashMap::new(),
ProofTrace::empty(),
stats.clone(),
);
assert!(success.provable);
let failure = QueryResult::failure(vec!["fact".to_string()], stats);
assert!(!failure.provable);
assert_eq!(failure.missing_facts.len(), 1);
}
#[test]
fn test_proof_trace() {
let mut trace = ProofTrace::new("User.IsVIP == true".to_string());
assert_eq!(trace.goal, "User.IsVIP == true");
assert!(trace.steps.is_empty());
let step = ProofStep {
rule_name: "VIPRule".to_string(),
goal: "User.IsVIP == true".to_string(),
sub_steps: Vec::new(),
depth: 0,
};
trace.add_step(step);
assert_eq!(trace.steps.len(), 1);
}
#[test]
fn test_proof_step() {
let step = ProofStep {
rule_name: "TestRule".to_string(),
goal: "test".to_string(),
sub_steps: Vec::new(),
depth: 0,
};
assert_eq!(step.rule_name, "TestRule");
assert_eq!(step.depth, 0);
}
#[test]
fn test_query_parser() {
let result = QueryParser::parse("User.IsVIP == true");
assert!(result.is_ok());
let empty = QueryParser::parse("");
assert!(empty.is_err());
}
#[test]
fn test_query_validation() {
assert!(QueryParser::validate("User.Age > 18").is_ok());
assert!(QueryParser::validate("User.IsVIP == true").is_ok());
assert!(QueryParser::validate("").is_err());
assert!(QueryParser::validate("(unclosed").is_err());
}
#[test]
fn test_query_stats() {
let stats = QueryStats {
goals_explored: 5,
rules_evaluated: 3,
max_depth: 2,
duration_ms: Some(100),
};
assert_eq!(stats.goals_explored, 5);
assert_eq!(stats.duration_ms, Some(100));
}
}