Skip to main content

fukurow_core/
query.rs

1//! Query interface for graph operations
2
3use crate::store::GraphStore;
4use std::collections::HashMap;
5
6/// Simple query builder for graph patterns
7#[derive(Debug, Clone)]
8pub struct GraphQuery {
9    patterns: Vec<QueryPattern>,
10}
11
12#[derive(Debug, Clone)]
13pub struct QueryPattern {
14    pub subject: PatternValue,
15    pub predicate: PatternValue,
16    pub object: PatternValue,
17}
18
19#[derive(Debug, Clone)]
20pub enum PatternValue {
21    Variable(String),
22    Constant(String),
23}
24
25impl GraphQuery {
26    pub fn new() -> Self {
27        Self {
28            patterns: Vec::new(),
29        }
30    }
31
32    /// Get the number of patterns in this query
33    pub fn pattern_count(&self) -> usize {
34        self.patterns.len()
35    }
36
37    pub fn where_clause(mut self, subject: PatternValue, predicate: PatternValue, object: PatternValue) -> Self {
38        self.patterns.push(QueryPattern {
39            subject,
40            predicate,
41            object,
42        });
43        self
44    }
45
46    /// Execute query against the graph store
47    pub fn execute(&self, store: &GraphStore) -> Vec<HashMap<String, String>> {
48        let mut results = Vec::new();
49        self.execute_recursive(store, 0, &mut HashMap::new(), &mut results);
50        results
51    }
52
53    fn execute_recursive(
54        &self,
55        store: &GraphStore,
56        pattern_index: usize,
57        bindings: &mut HashMap<String, String>,
58        results: &mut Vec<HashMap<String, String>>,
59    ) {
60        if pattern_index >= self.patterns.len() {
61            // All patterns matched, add result
62            results.push(bindings.clone());
63            return;
64        }
65
66        let pattern = &self.patterns[pattern_index];
67        let matches = self.find_matches(store, pattern, bindings);
68
69        for binding_update in matches {
70            // Apply new bindings
71            let mut new_bindings = bindings.clone();
72            for (var, value) in binding_update {
73                new_bindings.insert(var, value);
74            }
75
76            // Continue with next pattern
77            self.execute_recursive(store, pattern_index + 1, &mut new_bindings, results);
78        }
79    }
80
81    fn find_matches(
82        &self,
83        store: &GraphStore,
84        pattern: &QueryPattern,
85        current_bindings: &HashMap<String, String>,
86    ) -> Vec<HashMap<String, String>> {
87        let subject = self.resolve_pattern_value(&pattern.subject, current_bindings);
88        let predicate = self.resolve_pattern_value(&pattern.predicate, current_bindings);
89        let object = self.resolve_pattern_value(&pattern.object, current_bindings);
90
91        let matches = store.find_triples(subject.as_deref(), predicate.as_deref(), object.as_deref());
92
93        let mut results = Vec::new();
94
95        for triple in matches {
96            let mut binding = HashMap::new();
97
98            // Bind variables
99            if let PatternValue::Variable(var) = &pattern.subject {
100                if !current_bindings.contains_key(var) {
101                    binding.insert(var.clone(), triple.subject.clone());
102                }
103            }
104            if let PatternValue::Variable(var) = &pattern.predicate {
105                if !current_bindings.contains_key(var) {
106                    binding.insert(var.clone(), triple.predicate.clone());
107                }
108            }
109            if let PatternValue::Variable(var) = &pattern.object {
110                if !current_bindings.contains_key(var) {
111                    binding.insert(var.clone(), triple.object.clone());
112                }
113            }
114
115            // Check if binding is consistent with current bindings
116            let mut consistent = true;
117            for (var, value) in &binding {
118                if let Some(existing) = current_bindings.get(var) {
119                    if existing != value {
120                        consistent = false;
121                        break;
122                    }
123                }
124            }
125
126            if consistent {
127                results.push(binding);
128            }
129        }
130
131        results
132    }
133
134    fn resolve_pattern_value(&self, value: &PatternValue, bindings: &HashMap<String, String>) -> Option<String> {
135        match value {
136            PatternValue::Constant(s) => Some(s.clone()),
137            PatternValue::Variable(var) => bindings.get(var).cloned(),
138        }
139    }
140}
141
142/// Helper functions for creating patterns
143pub fn var(name: &str) -> PatternValue {
144    PatternValue::Variable(name.to_string())
145}
146
147pub fn const_val(value: &str) -> PatternValue {
148    PatternValue::Constant(value.to_string())
149}