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 => {
277                    // Check if this starts "WHERE"
278                    if i + 5 < chars.len() {
279                        let substr: String = chars[i..i + 5].iter().collect();
280                        if substr == "WHERE" {
281                            return true;
282                        }
283                    }
284                }
285                _ => {}
286            }
287        }
288
289        false
290    }
291}
292
293/// Evaluator for nested queries
294pub struct NestedQueryEvaluator;
295
296impl NestedQueryEvaluator {
297    /// Evaluate a nested query
298    ///
299    /// Algorithm:
300    /// 1. Evaluate the subquery to get intermediate results
301    /// 2. For each subquery result, bind variables in outer goal
302    /// 3. Evaluate outer goal with those bindings
303    /// 4. Collect and merge all solutions
304    pub fn evaluate(_nested: &NestedQuery, _initial_bindings: &Bindings) -> NestedQueryResult {
305        // This is a placeholder - actual implementation would recursively
306        // call the backward chaining engine
307
308        // For now, return empty result
309        NestedQueryResult::new()
310    }
311
312    /// Merge bindings from subquery into outer goal
313    #[allow(dead_code)]
314    fn merge_bindings(
315        outer_bindings: &Bindings,
316        subquery_bindings: &Bindings,
317        shared_vars: &[String],
318    ) -> Bindings {
319        let mut merged = outer_bindings.clone();
320
321        // Add bindings from subquery for shared variables
322        for var in shared_vars {
323            if let Some(value) = subquery_bindings.get(var) {
324                // Use bind method (which returns Result)
325                let _ = merged.bind(var.clone(), value.clone());
326            }
327        }
328
329        merged
330    }
331}
332
333/// Statistics for nested query evaluation
334#[derive(Debug, Clone, Default)]
335pub struct NestedQueryStats {
336    /// Total number of nested queries evaluated
337    pub total_nested: usize,
338
339    /// Total number of subquery evaluations
340    pub total_subquery_evals: usize,
341
342    /// Maximum nesting depth encountered
343    pub max_depth: usize,
344
345    /// Average number of solutions per subquery
346    pub avg_subquery_solutions: f64,
347}
348
349impl NestedQueryStats {
350    /// Create new stats
351    pub fn new() -> Self {
352        Self::default()
353    }
354
355    /// Record a nested query evaluation
356    pub fn record_evaluation(&mut self, depth: usize, subquery_solutions: usize) {
357        self.total_nested += 1;
358        self.total_subquery_evals += subquery_solutions;
359        self.max_depth = self.max_depth.max(depth);
360
361        // Update average
362        self.avg_subquery_solutions = self.total_subquery_evals as f64 / self.total_nested as f64;
363    }
364
365    /// Get a summary string
366    pub fn summary(&self) -> String {
367        format!(
368            "Nested queries: {} | Subquery evals: {} | Max depth: {} | Avg solutions: {:.2}",
369            self.total_nested,
370            self.total_subquery_evals,
371            self.max_depth,
372            self.avg_subquery_solutions
373        )
374    }
375}
376
377#[cfg(test)]
378mod tests {
379    use super::*;
380
381    #[test]
382    fn test_query_creation() {
383        let query = Query::new("test(?x)".to_string());
384        assert_eq!(query.pattern, "test(?x)");
385        assert!(query.goals.is_empty());
386        assert!(!query.has_nested());
387    }
388
389    #[test]
390    fn test_query_add_goal() {
391        let mut query = Query::new("test".to_string());
392        let goal = Goal::new("parent(?x, ?y)".to_string());
393        query.add_goal(goal);
394
395        assert_eq!(query.goals.len(), 1);
396    }
397
398    #[test]
399    fn test_query_variables() {
400        let mut query = Query::new("test".to_string());
401
402        let goal1 = Goal::new("parent(?x, ?y)".to_string());
403        let goal2 = Goal::new("age(?x, ?age)".to_string());
404
405        query.add_goal(goal1);
406        query.add_goal(goal2);
407
408        let vars = query.variables();
409        assert!(vars.contains(&"?x".to_string()));
410        assert!(vars.contains(&"?y".to_string()));
411        assert!(vars.contains(&"?age".to_string()));
412    }
413
414    #[test]
415    fn test_nested_query_creation() {
416        let outer_goal = Goal::new("grandparent(?x, ?z)".to_string());
417        let mut subquery = Query::new("parent(?y, ?z)".to_string());
418
419        let sub_goal = Goal::new("parent(?y, ?z)".to_string());
420        subquery.add_goal(sub_goal);
421
422        let nested = NestedQuery::new(outer_goal, subquery);
423
424        assert!(!nested.subquery.goals.is_empty());
425    }
426
427    #[test]
428    fn test_nested_query_shared_variables() {
429        let outer_goal = Goal::new("grandparent(?x, ?z)".to_string());
430
431        let mut subquery = Query::new("parent(?y, ?z)".to_string());
432        let sub_goal = Goal::new("parent(?y, ?z)".to_string());
433        subquery.add_goal(sub_goal);
434
435        let nested = NestedQuery::new(outer_goal, subquery);
436
437        // ?z is shared between outer and subquery
438        assert!(nested.has_shared_variables());
439        assert!(nested.shared_variables.contains(&"?z".to_string()));
440    }
441
442    #[test]
443    fn test_nested_query_result() {
444        let result = NestedQueryResult::new();
445        assert!(!result.success);
446        assert_eq!(result.subquery_count, 0);
447
448        let success = NestedQueryResult::success(vec![], vec![]);
449        assert!(!success.success); // Empty solutions = not successful
450    }
451
452    #[test]
453    fn test_nested_query_parser_has_nested() {
454        assert!(NestedQueryParser::has_nested(
455            "grandparent(?x, ?z) WHERE parent(?x, ?y) AND (parent(?y, ?z) WHERE child(?z, ?y))"
456        ));
457
458        assert!(!NestedQueryParser::has_nested(
459            "parent(?x, ?y) WHERE person(?x) AND person(?y)"
460        ));
461    }
462
463    #[test]
464    fn test_nested_query_stats() {
465        let mut stats = NestedQueryStats::new();
466
467        stats.record_evaluation(1, 5);
468        stats.record_evaluation(2, 10);
469
470        assert_eq!(stats.total_nested, 2);
471        assert_eq!(stats.total_subquery_evals, 15);
472        assert_eq!(stats.max_depth, 2);
473        assert_eq!(stats.avg_subquery_solutions, 7.5);
474    }
475
476    #[test]
477    fn test_parser_simple_query() {
478        let query = NestedQueryParser::parse("parent(?x, ?y) WHERE person(?x) AND person(?y)");
479        assert!(!query.goals.is_empty());
480    }
481}