Skip to main content

rust_rule_engine/backward/
nested.rs

1//! Nested query support for backward chaining
2//!
3//! This module implements nested/subqueries in backward chaining queries.
4//! Subqueries allow using the results of one query as input to another query.
5//!
6//! # Examples
7//!
8//! ```rust,ignore
9//! // Find grandparents: people who are parents of parents
10//! let results = engine.query(
11//!     "grandparent(?x, ?z) WHERE
12//!         parent(?x, ?y) AND
13//!         (parent(?y, ?z) WHERE child(?z, ?y))",
14//!     &mut facts
15//! )?;
16//!
17//! // Find high-value customers: VIPs or those with large purchases
18//! let results = engine.query(
19//!     "high_value(?customer) WHERE
20//!         (vip(?customer) OR
21//!          (purchase(?customer, ?item, ?amount) WHERE ?amount > 1000))",
22//!     &mut facts
23//! )?;
24//! ```
25
26use super::goal::Goal;
27use super::unification::Bindings;
28
29/// Represents a nested query (subquery) within a larger query
30#[derive(Debug, Clone)]
31pub struct NestedQuery {
32    /// The outer goal that depends on the subquery
33    pub outer_goal: Goal,
34
35    /// The inner subquery to evaluate first
36    pub subquery: Box<Query>,
37
38    /// Variables shared between outer and inner queries
39    pub shared_variables: Vec<String>,
40}
41
42/// Represents a query that may contain nested subqueries
43#[derive(Debug, Clone)]
44pub struct Query {
45    /// Main goals to evaluate
46    pub goals: Vec<Goal>,
47
48    /// Any nested subqueries
49    pub nested: Vec<NestedQuery>,
50
51    /// Original query string
52    pub pattern: String,
53}
54
55impl Query {
56    /// Create a new query
57    pub fn new(pattern: String) -> Self {
58        Self {
59            goals: Vec::new(),
60            nested: Vec::new(),
61            pattern,
62        }
63    }
64
65    /// Add a goal to this query
66    pub fn add_goal(&mut self, goal: Goal) {
67        self.goals.push(goal);
68    }
69
70    /// Add a nested subquery
71    pub fn add_nested(&mut self, nested: NestedQuery) {
72        self.nested.push(nested);
73    }
74
75    /// Check if this query has nested subqueries
76    pub fn has_nested(&self) -> bool {
77        !self.nested.is_empty()
78    }
79
80    /// Get all variables used in this query
81    pub fn variables(&self) -> Vec<String> {
82        let mut vars = Vec::new();
83
84        for goal in &self.goals {
85            // Extract variables from pattern (simple heuristic: look for ?var)
86            let pattern_vars = Self::extract_variables(&goal.pattern);
87            for var in pattern_vars {
88                if !vars.contains(&var) {
89                    vars.push(var);
90                }
91            }
92        }
93
94        vars
95    }
96
97    /// Extract variables from a pattern string (simple regex-like extraction)
98    fn extract_variables(pattern: &str) -> Vec<String> {
99        let mut vars = Vec::new();
100        let chars: Vec<char> = pattern.chars().collect();
101        let mut i = 0;
102
103        while i < chars.len() {
104            if chars[i] == '?' {
105                // Found a variable, extract until non-alphanumeric
106                let mut var = String::from("?");
107                i += 1;
108                while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
109                    var.push(chars[i]);
110                    i += 1;
111                }
112                if !vars.contains(&var) {
113                    vars.push(var);
114                }
115            } else {
116                i += 1;
117            }
118        }
119
120        vars
121    }
122}
123
124impl NestedQuery {
125    /// Create a new nested query
126    pub fn new(outer_goal: Goal, subquery: Query) -> Self {
127        // Find shared variables between outer goal and subquery
128        let outer_vars = Query::extract_variables(&outer_goal.pattern);
129        let subquery_vars = subquery.variables();
130
131        let shared_variables: Vec<String> = outer_vars
132            .iter()
133            .filter(|v| subquery_vars.contains(v))
134            .cloned()
135            .collect();
136
137        Self {
138            outer_goal,
139            subquery: Box::new(subquery),
140            shared_variables,
141        }
142    }
143
144    /// Check if this nested query shares variables with its parent
145    pub fn has_shared_variables(&self) -> bool {
146        !self.shared_variables.is_empty()
147    }
148}
149
150/// Result of evaluating a nested query
151#[derive(Debug, Clone)]
152pub struct NestedQueryResult {
153    /// Solutions from the subquery
154    pub subquery_solutions: Vec<Bindings>,
155
156    /// Final solutions after applying to outer goal
157    pub final_solutions: Vec<Bindings>,
158
159    /// Whether the nested query succeeded
160    pub success: bool,
161
162    /// Number of subquery evaluations
163    pub subquery_count: usize,
164}
165
166impl NestedQueryResult {
167    /// Create a new result
168    pub fn new() -> Self {
169        Self {
170            subquery_solutions: Vec::new(),
171            final_solutions: Vec::new(),
172            success: false,
173            subquery_count: 0,
174        }
175    }
176
177    /// Create a successful result
178    pub fn success(subquery_solutions: Vec<Bindings>, final_solutions: Vec<Bindings>) -> Self {
179        Self {
180            success: !final_solutions.is_empty(),
181            subquery_count: subquery_solutions.len(),
182            subquery_solutions,
183            final_solutions,
184        }
185    }
186
187    /// Create a failed result
188    pub fn failure() -> Self {
189        Self {
190            subquery_solutions: Vec::new(),
191            final_solutions: Vec::new(),
192            success: false,
193            subquery_count: 0,
194        }
195    }
196}
197
198impl Default for NestedQueryResult {
199    fn default() -> Self {
200        Self::new()
201    }
202}
203
204/// Parser for nested queries
205pub struct NestedQueryParser;
206
207impl NestedQueryParser {
208    /// Parse a query that might contain nested subqueries
209    ///
210    /// Syntax: "outer_goal WHERE (subquery WHERE conditions)"
211    ///
212    /// Examples:
213    /// - "grandparent(?x, ?z) WHERE parent(?x, ?y) AND (parent(?y, ?z) WHERE child(?z, ?y))"
214    /// - "eligible(?p) WHERE (manager(?p) WHERE senior(?p))"
215    pub fn parse(query_str: &str) -> Query {
216        let mut query = Query::new(query_str.to_string());
217
218        // Simple parsing - look for WHERE clauses with nested parentheses
219        // This is a simplified parser - production would use a proper parser
220
221        if let Some(where_idx) = query_str.find(" WHERE ") {
222            let conditions = &query_str[where_idx + 7..].trim();
223
224            // Parse conditions (simplified - doesn't handle complex nesting)
225            let goals = Self::parse_conditions(conditions);
226            for goal in goals {
227                query.add_goal(goal);
228            }
229        }
230
231        query
232    }
233
234    /// Parse conditions into goals
235    fn parse_conditions(conditions: &str) -> Vec<Goal> {
236        let mut goals = Vec::new();
237
238        // Split by AND (simplified - doesn't handle parentheses properly)
239        for condition in conditions.split(" AND ") {
240            let condition = condition.trim();
241
242            // Check if this is a nested query (contains WHERE)
243            if condition.contains(" WHERE ") {
244                // Skip nested queries for now - would need recursive parsing
245                continue;
246            }
247
248            // Create a simple goal
249            if !condition.is_empty() && !condition.starts_with('(') {
250                goals.push(Goal::new(condition.to_string()));
251            }
252        }
253
254        goals
255    }
256
257    /// Check if a query contains nested subqueries
258    pub fn has_nested(query_str: &str) -> bool {
259        // Look for WHERE inside parentheses - indicates nested query
260        let mut paren_depth = 0;
261        let mut in_parens = false;
262
263        let chars: Vec<char> = query_str.chars().collect();
264        for i in 0..chars.len() {
265            match chars[i] {
266                '(' => {
267                    paren_depth += 1;
268                    in_parens = true;
269                }
270                ')' => {
271                    paren_depth -= 1;
272                    if paren_depth == 0 {
273                        in_parens = false;
274                    }
275                }
276                'W' if in_parens && paren_depth > 0 && i + 5 < chars.len() => {
277                    // Check if this starts "WHERE"
278                    let substr: String = chars[i..i + 5].iter().collect();
279                    if substr == "WHERE" {
280                        return true;
281                    }
282                }
283                _ => {}
284            }
285        }
286
287        false
288    }
289}
290
291/// Evaluator for nested queries
292pub struct NestedQueryEvaluator;
293
294impl NestedQueryEvaluator {
295    /// Evaluate a nested query
296    ///
297    /// Algorithm:
298    /// 1. Evaluate the subquery to get intermediate results
299    /// 2. For each subquery result, bind variables in outer goal
300    /// 3. Evaluate outer goal with those bindings
301    /// 4. Collect and merge all solutions
302    pub fn evaluate(_nested: &NestedQuery, _initial_bindings: &Bindings) -> NestedQueryResult {
303        // This is a placeholder - actual implementation would recursively
304        // call the backward chaining engine
305
306        // For now, return empty result
307        NestedQueryResult::new()
308    }
309
310    /// Merge bindings from subquery into outer goal
311    #[allow(dead_code)]
312    fn merge_bindings(
313        outer_bindings: &Bindings,
314        subquery_bindings: &Bindings,
315        shared_vars: &[String],
316    ) -> Bindings {
317        let mut merged = outer_bindings.clone();
318
319        // Add bindings from subquery for shared variables
320        for var in shared_vars {
321            if let Some(value) = subquery_bindings.get(var) {
322                // Use bind method (which returns Result)
323                let _ = merged.bind(var.clone(), value.clone());
324            }
325        }
326
327        merged
328    }
329}
330
331/// Statistics for nested query evaluation
332#[derive(Debug, Clone, Default)]
333pub struct NestedQueryStats {
334    /// Total number of nested queries evaluated
335    pub total_nested: usize,
336
337    /// Total number of subquery evaluations
338    pub total_subquery_evals: usize,
339
340    /// Maximum nesting depth encountered
341    pub max_depth: usize,
342
343    /// Average number of solutions per subquery
344    pub avg_subquery_solutions: f64,
345}
346
347impl NestedQueryStats {
348    /// Create new stats
349    pub fn new() -> Self {
350        Self::default()
351    }
352
353    /// Record a nested query evaluation
354    pub fn record_evaluation(&mut self, depth: usize, subquery_solutions: usize) {
355        self.total_nested += 1;
356        self.total_subquery_evals += subquery_solutions;
357        self.max_depth = self.max_depth.max(depth);
358
359        // Update average
360        self.avg_subquery_solutions = self.total_subquery_evals as f64 / self.total_nested as f64;
361    }
362
363    /// Get a summary string
364    pub fn summary(&self) -> String {
365        format!(
366            "Nested queries: {} | Subquery evals: {} | Max depth: {} | Avg solutions: {:.2}",
367            self.total_nested,
368            self.total_subquery_evals,
369            self.max_depth,
370            self.avg_subquery_solutions
371        )
372    }
373}
374
375#[cfg(test)]
376mod tests {
377    use super::*;
378
379    #[test]
380    fn test_query_creation() {
381        let query = Query::new("test(?x)".to_string());
382        assert_eq!(query.pattern, "test(?x)");
383        assert!(query.goals.is_empty());
384        assert!(!query.has_nested());
385    }
386
387    #[test]
388    fn test_query_add_goal() {
389        let mut query = Query::new("test".to_string());
390        let goal = Goal::new("parent(?x, ?y)".to_string());
391        query.add_goal(goal);
392
393        assert_eq!(query.goals.len(), 1);
394    }
395
396    #[test]
397    fn test_query_variables() {
398        let mut query = Query::new("test".to_string());
399
400        let goal1 = Goal::new("parent(?x, ?y)".to_string());
401        let goal2 = Goal::new("age(?x, ?age)".to_string());
402
403        query.add_goal(goal1);
404        query.add_goal(goal2);
405
406        let vars = query.variables();
407        assert!(vars.contains(&"?x".to_string()));
408        assert!(vars.contains(&"?y".to_string()));
409        assert!(vars.contains(&"?age".to_string()));
410    }
411
412    #[test]
413    fn test_nested_query_creation() {
414        let outer_goal = Goal::new("grandparent(?x, ?z)".to_string());
415        let mut subquery = Query::new("parent(?y, ?z)".to_string());
416
417        let sub_goal = Goal::new("parent(?y, ?z)".to_string());
418        subquery.add_goal(sub_goal);
419
420        let nested = NestedQuery::new(outer_goal, subquery);
421
422        assert!(!nested.subquery.goals.is_empty());
423    }
424
425    #[test]
426    fn test_nested_query_shared_variables() {
427        let outer_goal = Goal::new("grandparent(?x, ?z)".to_string());
428
429        let mut subquery = Query::new("parent(?y, ?z)".to_string());
430        let sub_goal = Goal::new("parent(?y, ?z)".to_string());
431        subquery.add_goal(sub_goal);
432
433        let nested = NestedQuery::new(outer_goal, subquery);
434
435        // ?z is shared between outer and subquery
436        assert!(nested.has_shared_variables());
437        assert!(nested.shared_variables.contains(&"?z".to_string()));
438    }
439
440    #[test]
441    fn test_nested_query_result() {
442        let result = NestedQueryResult::new();
443        assert!(!result.success);
444        assert_eq!(result.subquery_count, 0);
445
446        let success = NestedQueryResult::success(vec![], vec![]);
447        assert!(!success.success); // Empty solutions = not successful
448    }
449
450    #[test]
451    fn test_nested_query_parser_has_nested() {
452        assert!(NestedQueryParser::has_nested(
453            "grandparent(?x, ?z) WHERE parent(?x, ?y) AND (parent(?y, ?z) WHERE child(?z, ?y))"
454        ));
455
456        assert!(!NestedQueryParser::has_nested(
457            "parent(?x, ?y) WHERE person(?x) AND person(?y)"
458        ));
459    }
460
461    #[test]
462    fn test_nested_query_stats() {
463        let mut stats = NestedQueryStats::new();
464
465        stats.record_evaluation(1, 5);
466        stats.record_evaluation(2, 10);
467
468        assert_eq!(stats.total_nested, 2);
469        assert_eq!(stats.total_subquery_evals, 15);
470        assert_eq!(stats.max_depth, 2);
471        assert_eq!(stats.avg_subquery_solutions, 7.5);
472    }
473
474    #[test]
475    fn test_parser_simple_query() {
476        let query = NestedQueryParser::parse("parent(?x, ?y) WHERE person(?x) AND person(?y)");
477        assert!(!query.goals.is_empty());
478    }
479}