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