rust_rule_engine/backward/
query.rs

1//! Query interface for backward chaining
2
3use super::goal::Goal;
4use crate::types::Value;
5use std::collections::HashMap;
6
7/// Result of a query operation
8#[derive(Debug, Clone)]
9pub struct QueryResult {
10    /// Whether the query goal is provable
11    pub provable: bool,
12    
13    /// Variable bindings that satisfy the query
14    pub bindings: HashMap<String, Value>,
15    
16    /// Trace of how the goal was proven
17    pub proof_trace: ProofTrace,
18    
19    /// Facts that are missing to prove the goal
20    pub missing_facts: Vec<String>,
21    
22    /// Execution statistics
23    pub stats: QueryStats,
24}
25
26/// Trace showing how a goal was proven
27#[derive(Debug, Clone)]
28pub struct ProofTrace {
29    /// Root goal that was proven
30    pub goal: String,
31    
32    /// Steps taken to prove the goal
33    pub steps: Vec<ProofStep>,
34}
35
36/// Single step in a proof
37#[derive(Debug, Clone)]
38pub struct ProofStep {
39    /// Rule that was applied
40    pub rule_name: String,
41    
42    /// Goal this step proved
43    pub goal: String,
44    
45    /// Sub-steps (for nested proofs)
46    pub sub_steps: Vec<ProofStep>,
47    
48    /// Depth in the proof tree
49    pub depth: usize,
50}
51
52/// Statistics about query execution
53#[derive(Debug, Clone, Default)]
54pub struct QueryStats {
55    /// Number of goals explored
56    pub goals_explored: usize,
57    
58    /// Number of rules evaluated
59    pub rules_evaluated: usize,
60    
61    /// Maximum depth reached
62    pub max_depth: usize,
63    
64    /// Time taken (if measured)
65    pub duration_ms: Option<u64>,
66}
67
68impl QueryResult {
69    /// Create a successful query result
70    pub fn success(bindings: HashMap<String, Value>, proof: ProofTrace, stats: QueryStats) -> Self {
71        Self {
72            provable: true,
73            bindings,
74            proof_trace: proof,
75            missing_facts: Vec::new(),
76            stats,
77        }
78    }
79    
80    /// Create a failed query result
81    pub fn failure(missing: Vec<String>, stats: QueryStats) -> Self {
82        Self {
83            provable: false,
84            bindings: HashMap::new(),
85            proof_trace: ProofTrace::empty(),
86            missing_facts: missing,
87            stats,
88        }
89    }
90}
91
92impl ProofTrace {
93    /// Create an empty proof trace
94    pub fn empty() -> Self {
95        Self {
96            goal: String::new(),
97            steps: Vec::new(),
98        }
99    }
100    
101    /// Create a new proof trace
102    pub fn new(goal: String) -> Self {
103        Self {
104            goal,
105            steps: Vec::new(),
106        }
107    }
108    
109    /// Add a step to the proof
110    pub fn add_step(&mut self, step: ProofStep) {
111        self.steps.push(step);
112    }
113    
114    /// Build trace from a goal tree
115    pub fn from_goal(goal: &Goal) -> Self {
116        let mut trace = Self::new(goal.pattern.clone());
117        
118        for (i, rule_name) in goal.candidate_rules.iter().enumerate() {
119            let step = ProofStep {
120                rule_name: rule_name.clone(),
121                goal: goal.pattern.clone(),
122                sub_steps: goal.sub_goals.iter()
123                    .map(|sg| ProofStep::from_goal(sg, i + 1))
124                    .collect(),
125                depth: goal.depth,
126            };
127            trace.add_step(step);
128        }
129        
130        trace
131    }
132    
133    /// Print the proof trace in a readable format
134    pub fn print(&self) {
135        println!("Proof for goal: {}", self.goal);
136        for step in &self.steps {
137            step.print(0);
138        }
139    }
140}
141
142impl ProofStep {
143    /// Create from a goal
144    fn from_goal(goal: &Goal, depth: usize) -> Self {
145        Self {
146            rule_name: goal.candidate_rules.first()
147                .cloned()
148                .unwrap_or_else(|| "unknown".to_string()),
149            goal: goal.pattern.clone(),
150            sub_steps: goal.sub_goals.iter()
151                .map(|sg| Self::from_goal(sg, depth + 1))
152                .collect(),
153            depth,
154        }
155    }
156    
157    /// Print this step with indentation
158    fn print(&self, indent: usize) {
159        let prefix = "  ".repeat(indent);
160        println!("{}→ [{}] {}", prefix, self.rule_name, self.goal);
161        for sub in &self.sub_steps {
162            sub.print(indent + 1);
163        }
164    }
165}
166
167/// Query parser for converting strings to goals
168pub struct QueryParser;
169
170impl QueryParser {
171    /// Parse a query string into a Goal
172    ///
173    /// Examples:
174    /// - "User.IsVIP == true"
175    /// - "Order.Total > 1000"
176    /// - "User.IsVIP == true && Order.Amount > 1000"
177    pub fn parse(query: &str) -> Result<Goal, String> {
178        use super::expression::ExpressionParser;
179
180        // Simple parsing for now
181        if query.is_empty() {
182            return Err("Empty query".to_string());
183        }
184
185        // Parse expression using ExpressionParser
186        match ExpressionParser::parse(query) {
187            Ok(expr) => {
188                Ok(Goal::with_expression(query.to_string(), expr))
189            }
190            Err(e) => {
191                Err(format!("Failed to parse query: {}", e))
192            }
193        }
194    }
195
196    /// Validate query syntax
197    pub fn validate(query: &str) -> Result<(), String> {
198        if query.is_empty() {
199            return Err("Query cannot be empty".to_string());
200        }
201
202        // Try to parse to validate
203        Self::parse(query).map(|_| ())
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210    
211    #[test]
212    fn test_query_result_creation() {
213        let stats = QueryStats::default();
214        
215        let success = QueryResult::success(
216            HashMap::new(),
217            ProofTrace::empty(),
218            stats.clone(),
219        );
220        assert!(success.provable);
221        
222        let failure = QueryResult::failure(vec!["fact".to_string()], stats);
223        assert!(!failure.provable);
224        assert_eq!(failure.missing_facts.len(), 1);
225    }
226    
227    #[test]
228    fn test_proof_trace() {
229        let mut trace = ProofTrace::new("User.IsVIP == true".to_string());
230        assert_eq!(trace.goal, "User.IsVIP == true");
231        assert!(trace.steps.is_empty());
232        
233        let step = ProofStep {
234            rule_name: "VIPRule".to_string(),
235            goal: "User.IsVIP == true".to_string(),
236            sub_steps: Vec::new(),
237            depth: 0,
238        };
239        
240        trace.add_step(step);
241        assert_eq!(trace.steps.len(), 1);
242    }
243    
244    #[test]
245    fn test_proof_step() {
246        let step = ProofStep {
247            rule_name: "TestRule".to_string(),
248            goal: "test".to_string(),
249            sub_steps: Vec::new(),
250            depth: 0,
251        };
252        
253        assert_eq!(step.rule_name, "TestRule");
254        assert_eq!(step.depth, 0);
255    }
256    
257    #[test]
258    fn test_query_parser() {
259        let result = QueryParser::parse("User.IsVIP == true");
260        assert!(result.is_ok());
261        
262        let empty = QueryParser::parse("");
263        assert!(empty.is_err());
264    }
265    
266    #[test]
267    fn test_query_validation() {
268        assert!(QueryParser::validate("User.Age > 18").is_ok());
269        assert!(QueryParser::validate("User.IsVIP == true").is_ok());
270        assert!(QueryParser::validate("").is_err());
271        // Note: "invalid" is now valid as a field name
272        // Use empty string or malformed syntax for error cases
273        assert!(QueryParser::validate("(unclosed").is_err());
274    }
275    
276    #[test]
277    fn test_query_stats() {
278        let stats = QueryStats {
279            goals_explored: 5,
280            rules_evaluated: 3,
281            max_depth: 2,
282            duration_ms: Some(100),
283        };
284        
285        assert_eq!(stats.goals_explored, 5);
286        assert_eq!(stats.duration_ms, Some(100));
287    }
288}