rust_rule_engine/backward/
query.rs1use super::goal::Goal;
4use super::search::Solution;
5use crate::types::Value;
6use std::collections::HashMap;
7
8#[derive(Debug, Clone)]
10pub struct QueryResult {
11 pub provable: bool,
13
14 pub bindings: HashMap<String, Value>,
16
17 pub proof_trace: ProofTrace,
19
20 pub missing_facts: Vec<String>,
22
23 pub stats: QueryStats,
25
26 pub solutions: Vec<Solution>,
28}
29
30#[derive(Debug, Clone)]
32pub struct ProofTrace {
33 pub goal: String,
35
36 pub steps: Vec<ProofStep>,
38}
39
40#[derive(Debug, Clone)]
42pub struct ProofStep {
43 pub rule_name: String,
45
46 pub goal: String,
48
49 pub sub_steps: Vec<ProofStep>,
51
52 pub depth: usize,
54}
55
56#[derive(Debug, Clone, Default)]
58pub struct QueryStats {
59 pub goals_explored: usize,
61
62 pub rules_evaluated: usize,
64
65 pub max_depth: usize,
67
68 pub duration_ms: Option<u64>,
70}
71
72impl QueryResult {
73 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 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 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 pub fn empty() -> Self {
118 Self {
119 goal: String::new(),
120 steps: Vec::new(),
121 }
122 }
123
124 pub fn new(goal: String) -> Self {
126 Self {
127 goal,
128 steps: Vec::new(),
129 }
130 }
131
132 pub fn add_step(&mut self, step: ProofStep) {
134 self.steps.push(step);
135 }
136
137 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 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 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 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
190pub struct QueryParser;
192
193impl QueryParser {
194 pub fn parse(query: &str) -> Result<Goal, String> {
202 use super::expression::ExpressionParser;
203
204 if query.is_empty() {
206 return Err("Empty query".to_string());
207 }
208
209 let trimmed = query.trim();
211 let (is_negated, actual_query) = if trimmed.starts_with("NOT ") {
212 (true, &trimmed[4..]) } else {
214 (false, trimmed)
215 };
216
217 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 pub fn validate(query: &str) -> Result<(), String> {
234 if query.is_empty() {
235 return Err("Query cannot be empty".to_string());
236 }
237
238 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 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 let and_result = QueryParser::parse("User.IsVIP == true && Order.Total > 1000");
409 assert!(and_result.is_ok());
410
411 let or_result = QueryParser::parse("User.Points > 500 || User.IsVIP == true");
413 assert!(or_result.is_ok());
414
415 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 assert!(QueryParser::parse("").is_err());
424
425 assert!(QueryParser::parse("(User.IsVIP == true").is_err());
427
428 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 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 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 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}