rust_rule_engine/backward/
query.rs

1//! Query interface for backward chaining
2
3use super::goal::Goal;
4use super::search::Solution;
5use crate::types::Value;
6use std::collections::HashMap;
7
8/// Result of a query operation
9#[derive(Debug, Clone)]
10pub struct QueryResult {
11    /// Whether the query goal is provable
12    pub provable: bool,
13
14    /// Variable bindings that satisfy the query
15    pub bindings: HashMap<String, Value>,
16
17    /// Trace of how the goal was proven
18    pub proof_trace: ProofTrace,
19
20    /// Facts that are missing to prove the goal
21    pub missing_facts: Vec<String>,
22
23    /// Execution statistics
24    pub stats: QueryStats,
25
26    /// All solutions found (when max_solutions > 1)
27    pub solutions: Vec<Solution>,
28}
29
30/// Trace showing how a goal was proven
31#[derive(Debug, Clone)]
32pub struct ProofTrace {
33    /// Root goal that was proven
34    pub goal: String,
35    
36    /// Steps taken to prove the goal
37    pub steps: Vec<ProofStep>,
38}
39
40/// Single step in a proof
41#[derive(Debug, Clone)]
42pub struct ProofStep {
43    /// Rule that was applied
44    pub rule_name: String,
45    
46    /// Goal this step proved
47    pub goal: String,
48    
49    /// Sub-steps (for nested proofs)
50    pub sub_steps: Vec<ProofStep>,
51    
52    /// Depth in the proof tree
53    pub depth: usize,
54}
55
56/// Statistics about query execution
57#[derive(Debug, Clone, Default)]
58pub struct QueryStats {
59    /// Number of goals explored
60    pub goals_explored: usize,
61    
62    /// Number of rules evaluated
63    pub rules_evaluated: usize,
64    
65    /// Maximum depth reached
66    pub max_depth: usize,
67    
68    /// Time taken (if measured)
69    pub duration_ms: Option<u64>,
70}
71
72impl QueryResult {
73    /// Create a successful query result
74    pub fn success(bindings: HashMap<String, Value>, proof: ProofTrace, stats: QueryStats) -> Self {
75        Self {
76            provable: true,
77            bindings,
78            proof_trace: proof,
79            missing_facts: Vec::new(),
80            stats,
81            solutions: Vec::new(),
82        }
83    }
84
85    /// Create a successful query result with multiple solutions
86    pub fn success_with_solutions(
87        bindings: HashMap<String, Value>,
88        proof: ProofTrace,
89        stats: QueryStats,
90        solutions: Vec<Solution>,
91    ) -> Self {
92        Self {
93            provable: true,
94            bindings,
95            proof_trace: proof,
96            missing_facts: Vec::new(),
97            stats,
98            solutions,
99        }
100    }
101
102    /// Create a failed query result
103    pub fn failure(missing: Vec<String>, stats: QueryStats) -> Self {
104        Self {
105            provable: false,
106            bindings: HashMap::new(),
107            proof_trace: ProofTrace::empty(),
108            missing_facts: missing,
109            stats,
110            solutions: Vec::new(),
111        }
112    }
113}
114
115impl ProofTrace {
116    /// Create an empty proof trace
117    pub fn empty() -> Self {
118        Self {
119            goal: String::new(),
120            steps: Vec::new(),
121        }
122    }
123    
124    /// Create a new proof trace
125    pub fn new(goal: String) -> Self {
126        Self {
127            goal,
128            steps: Vec::new(),
129        }
130    }
131    
132    /// Add a step to the proof
133    pub fn add_step(&mut self, step: ProofStep) {
134        self.steps.push(step);
135    }
136    
137    /// Build trace from a goal tree
138    pub fn from_goal(goal: &Goal) -> Self {
139        let mut trace = Self::new(goal.pattern.clone());
140        
141        for (i, rule_name) in goal.candidate_rules.iter().enumerate() {
142            let step = ProofStep {
143                rule_name: rule_name.clone(),
144                goal: goal.pattern.clone(),
145                sub_steps: goal.sub_goals.iter()
146                    .map(|sg| ProofStep::from_goal(sg, i + 1))
147                    .collect(),
148                depth: goal.depth,
149            };
150            trace.add_step(step);
151        }
152        
153        trace
154    }
155    
156    /// Print the proof trace in a readable format
157    pub fn print(&self) {
158        println!("Proof for goal: {}", self.goal);
159        for step in &self.steps {
160            step.print(0);
161        }
162    }
163}
164
165impl ProofStep {
166    /// Create from a goal
167    fn from_goal(goal: &Goal, depth: usize) -> Self {
168        Self {
169            rule_name: goal.candidate_rules.first()
170                .cloned()
171                .unwrap_or_else(|| "unknown".to_string()),
172            goal: goal.pattern.clone(),
173            sub_steps: goal.sub_goals.iter()
174                .map(|sg| Self::from_goal(sg, depth + 1))
175                .collect(),
176            depth,
177        }
178    }
179    
180    /// Print this step with indentation
181    fn print(&self, indent: usize) {
182        let prefix = "  ".repeat(indent);
183        println!("{}→ [{}] {}", prefix, self.rule_name, self.goal);
184        for sub in &self.sub_steps {
185            sub.print(indent + 1);
186        }
187    }
188}
189
190/// Query parser for converting strings to goals
191pub struct QueryParser;
192
193impl QueryParser {
194    /// Parse a query string into a Goal
195    ///
196    /// Examples:
197    /// - "User.IsVIP == true"
198    /// - "Order.Total > 1000"
199    /// - "User.IsVIP == true && Order.Amount > 1000"
200    /// - "NOT User.IsBanned == true"  (negated goal)
201    pub fn parse(query: &str) -> Result<Goal, String> {
202        use super::expression::ExpressionParser;
203
204        // Simple parsing for now
205        if query.is_empty() {
206            return Err("Empty query".to_string());
207        }
208
209        // Check for NOT keyword at the beginning
210        let trimmed = query.trim();
211        let (is_negated, actual_query) = if trimmed.starts_with("NOT ") {
212            (true, &trimmed[4..]) // Skip "NOT "
213        } else {
214            (false, trimmed)
215        };
216
217        // Parse expression using ExpressionParser
218        match ExpressionParser::parse(actual_query) {
219            Ok(expr) => {
220                if is_negated {
221                    Ok(Goal::negated_with_expression(query.to_string(), expr))
222                } else {
223                    Ok(Goal::with_expression(query.to_string(), expr))
224                }
225            }
226            Err(e) => {
227                Err(format!("Failed to parse query: {}", e))
228            }
229        }
230    }
231
232    /// Validate query syntax
233    pub fn validate(query: &str) -> Result<(), String> {
234        if query.is_empty() {
235            return Err("Query cannot be empty".to_string());
236        }
237
238        // Try to parse to validate
239        Self::parse(query).map(|_| ())
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246    
247    #[test]
248    fn test_query_result_creation() {
249        let stats = QueryStats::default();
250        
251        let success = QueryResult::success(
252            HashMap::new(),
253            ProofTrace::empty(),
254            stats.clone(),
255        );
256        assert!(success.provable);
257        
258        let failure = QueryResult::failure(vec!["fact".to_string()], stats);
259        assert!(!failure.provable);
260        assert_eq!(failure.missing_facts.len(), 1);
261    }
262    
263    #[test]
264    fn test_proof_trace() {
265        let mut trace = ProofTrace::new("User.IsVIP == true".to_string());
266        assert_eq!(trace.goal, "User.IsVIP == true");
267        assert!(trace.steps.is_empty());
268        
269        let step = ProofStep {
270            rule_name: "VIPRule".to_string(),
271            goal: "User.IsVIP == true".to_string(),
272            sub_steps: Vec::new(),
273            depth: 0,
274        };
275        
276        trace.add_step(step);
277        assert_eq!(trace.steps.len(), 1);
278    }
279    
280    #[test]
281    fn test_proof_step() {
282        let step = ProofStep {
283            rule_name: "TestRule".to_string(),
284            goal: "test".to_string(),
285            sub_steps: Vec::new(),
286            depth: 0,
287        };
288        
289        assert_eq!(step.rule_name, "TestRule");
290        assert_eq!(step.depth, 0);
291    }
292    
293    #[test]
294    fn test_query_parser() {
295        let result = QueryParser::parse("User.IsVIP == true");
296        assert!(result.is_ok());
297        
298        let empty = QueryParser::parse("");
299        assert!(empty.is_err());
300    }
301    
302    #[test]
303    fn test_query_validation() {
304        assert!(QueryParser::validate("User.Age > 18").is_ok());
305        assert!(QueryParser::validate("User.IsVIP == true").is_ok());
306        assert!(QueryParser::validate("").is_err());
307        // Note: "invalid" is now valid as a field name
308        // Use empty string or malformed syntax for error cases
309        assert!(QueryParser::validate("(unclosed").is_err());
310    }
311    
312    #[test]
313    fn test_query_stats() {
314        let stats = QueryStats {
315            goals_explored: 5,
316            rules_evaluated: 3,
317            max_depth: 2,
318            duration_ms: Some(100),
319        };
320
321        assert_eq!(stats.goals_explored, 5);
322        assert_eq!(stats.duration_ms, Some(100));
323    }
324
325    #[test]
326    fn test_query_stats_default() {
327        let stats = QueryStats::default();
328        assert_eq!(stats.goals_explored, 0);
329        assert_eq!(stats.rules_evaluated, 0);
330        assert_eq!(stats.max_depth, 0);
331        assert_eq!(stats.duration_ms, None);
332    }
333
334    #[test]
335    fn test_query_result_with_bindings() {
336        let mut bindings = HashMap::new();
337        bindings.insert("X".to_string(), Value::String("VIP".to_string()));
338        bindings.insert("Y".to_string(), Value::Number(1000.0));
339
340        let stats = QueryStats::default();
341        let result = QueryResult::success(bindings, ProofTrace::empty(), stats);
342
343        assert!(result.provable);
344        assert_eq!(result.bindings.len(), 2);
345        assert_eq!(result.bindings.get("X"), Some(&Value::String("VIP".to_string())));
346        assert_eq!(result.bindings.get("Y"), Some(&Value::Number(1000.0)));
347    }
348
349    #[test]
350    fn test_query_result_failure_with_missing_facts() {
351        let missing = vec![
352            "User.IsVIP".to_string(),
353            "Order.Total".to_string(),
354        ];
355
356        let stats = QueryStats::default();
357        let result = QueryResult::failure(missing, stats);
358
359        assert!(!result.provable);
360        assert_eq!(result.missing_facts.len(), 2);
361        assert!(result.bindings.is_empty());
362    }
363
364    #[test]
365    fn test_proof_trace_from_goal() {
366        let mut goal = Goal::new("User.IsVIP == true".to_string());
367        goal.depth = 1;
368        goal.add_candidate_rule("VIPRule".to_string());
369
370        let mut subgoal = Goal::new("User.Points > 1000".to_string());
371        subgoal.depth = 2;
372        subgoal.add_candidate_rule("PointsRule".to_string());
373
374        goal.add_subgoal(subgoal);
375
376        let trace = ProofTrace::from_goal(&goal);
377
378        assert_eq!(trace.goal, "User.IsVIP == true");
379        assert_eq!(trace.steps.len(), 1);
380        assert_eq!(trace.steps[0].rule_name, "VIPRule");
381        assert_eq!(trace.steps[0].sub_steps.len(), 1);
382    }
383
384    #[test]
385    fn test_proof_step_nested() {
386        let sub_step = ProofStep {
387            rule_name: "SubRule".to_string(),
388            goal: "subgoal".to_string(),
389            sub_steps: Vec::new(),
390            depth: 2,
391        };
392
393        let step = ProofStep {
394            rule_name: "MainRule".to_string(),
395            goal: "main".to_string(),
396            sub_steps: vec![sub_step],
397            depth: 1,
398        };
399
400        assert_eq!(step.sub_steps.len(), 1);
401        assert_eq!(step.sub_steps[0].rule_name, "SubRule");
402        assert_eq!(step.sub_steps[0].depth, 2);
403    }
404
405    #[test]
406    fn test_query_parser_complex_expressions() {
407        // Test AND expression
408        let and_result = QueryParser::parse("User.IsVIP == true && Order.Total > 1000");
409        assert!(and_result.is_ok());
410
411        // Test OR expression
412        let or_result = QueryParser::parse("User.Points > 500 || User.IsVIP == true");
413        assert!(or_result.is_ok());
414
415        // Test NOT expression
416        let not_result = QueryParser::parse("!(User.IsBanned == true)");
417        assert!(not_result.is_ok());
418    }
419
420    #[test]
421    fn test_query_parser_invalid_syntax() {
422        // Empty query
423        assert!(QueryParser::parse("").is_err());
424
425        // Unclosed parenthesis
426        assert!(QueryParser::parse("(User.IsVIP == true").is_err());
427
428        // Invalid operator sequence
429        assert!(QueryParser::parse("User.IsVIP == == true").is_err());
430    }
431
432    #[test]
433    fn test_proof_trace_empty() {
434        let trace = ProofTrace::empty();
435        assert!(trace.goal.is_empty());
436        assert!(trace.steps.is_empty());
437    }
438
439    #[test]
440    fn test_query_parser_not_keyword() {
441        // Test NOT keyword parsing
442        let result = QueryParser::parse("NOT User.IsBanned == true");
443        assert!(result.is_ok());
444        let goal = result.unwrap();
445        assert!(goal.is_negated);
446        assert_eq!(goal.pattern, "NOT User.IsBanned == true");
447    }
448
449    #[test]
450    fn test_query_parser_not_keyword_with_spaces() {
451        // Test NOT with extra spaces
452        let result = QueryParser::parse("  NOT   User.IsBanned == true  ");
453        assert!(result.is_ok());
454        let goal = result.unwrap();
455        assert!(goal.is_negated);
456    }
457
458    #[test]
459    fn test_query_parser_normal_query_not_negated() {
460        let result = QueryParser::parse("User.IsVIP == true");
461        assert!(result.is_ok());
462        let goal = result.unwrap();
463        assert!(!goal.is_negated);
464    }
465
466    #[test]
467    fn test_query_parser_not_complex_expression() {
468        // Test NOT with complex expressions
469        let result = QueryParser::parse("NOT User.Points > 100 && User.Level < 5");
470        assert!(result.is_ok());
471        let goal = result.unwrap();
472        assert!(goal.is_negated);
473    }
474}