Skip to main content

oxirs_core/query/
plan.rs

1//! Query execution planning
2//!
3//! This module is responsible for converting SPARQL algebra into
4//! optimized execution plans.
5
6use crate::model::pattern::{ObjectPattern, PredicatePattern, SubjectPattern, TriplePattern};
7use crate::model::*;
8use crate::query::algebra::{
9    AlgebraTriplePattern, Expression, GraphPattern, OrderExpression, Query, QueryForm,
10    SelectVariables, TermPattern,
11};
12use crate::OxirsError;
13
14/// Convert algebra TriplePattern to model TriplePattern
15/// This function bridges the gap between algebra and model pattern representations
16pub fn convert_triple_pattern(pattern: &AlgebraTriplePattern) -> TriplePattern {
17    // Convert AlgebraTriplePattern to model TriplePattern
18    convert_algebra_triple_pattern(pattern)
19}
20
21/// Convert AlgebraTriplePattern to model TriplePattern
22/// This function converts AlgebraTriplePattern with TermPattern fields to model TriplePattern
23pub fn convert_algebra_triple_pattern(pattern: &AlgebraTriplePattern) -> TriplePattern {
24    let subject = match &pattern.subject {
25        TermPattern::NamedNode(nn) => Some(SubjectPattern::NamedNode(nn.clone())),
26        TermPattern::BlankNode(bn) => Some(SubjectPattern::BlankNode(bn.clone())),
27        TermPattern::Variable(v) => Some(SubjectPattern::Variable(v.clone())),
28        TermPattern::Literal(_) => None, // Literals can't be subjects in RDF
29        TermPattern::QuotedTriple(qt) => Some(SubjectPattern::QuotedTriple(qt.clone())),
30    };
31
32    let predicate = match &pattern.predicate {
33        TermPattern::NamedNode(nn) => Some(PredicatePattern::NamedNode(nn.clone())),
34        TermPattern::Variable(v) => Some(PredicatePattern::Variable(v.clone())),
35        _ => None, // Only named nodes and variables can be predicates in RDF
36    };
37
38    let object = match &pattern.object {
39        TermPattern::NamedNode(nn) => Some(ObjectPattern::NamedNode(nn.clone())),
40        TermPattern::BlankNode(bn) => Some(ObjectPattern::BlankNode(bn.clone())),
41        TermPattern::Literal(lit) => Some(ObjectPattern::Literal(lit.clone())),
42        TermPattern::Variable(v) => Some(ObjectPattern::Variable(v.clone())),
43        TermPattern::QuotedTriple(qt) => Some(ObjectPattern::QuotedTriple(qt.clone())),
44    };
45
46    TriplePattern::new(subject, predicate, object)
47}
48
49/// Convert model TriplePattern to algebra TriplePattern
50/// This function provides the reverse conversion for compatibility
51pub fn convert_to_algebra_pattern(
52    pattern: &TriplePattern,
53) -> Result<AlgebraTriplePattern, OxirsError> {
54    let subject = match pattern.subject() {
55        Some(SubjectPattern::NamedNode(nn)) => TermPattern::NamedNode(nn.clone()),
56        Some(SubjectPattern::BlankNode(bn)) => TermPattern::BlankNode(bn.clone()),
57        Some(SubjectPattern::Variable(v)) => TermPattern::Variable(v.clone()),
58        Some(SubjectPattern::QuotedTriple(qt)) => TermPattern::QuotedTriple(qt.clone()),
59        None => {
60            return Err(OxirsError::Query(
61                "Subject pattern is required in algebra representation".to_string(),
62            ))
63        }
64    };
65
66    let predicate = match pattern.predicate() {
67        Some(PredicatePattern::NamedNode(nn)) => TermPattern::NamedNode(nn.clone()),
68        Some(PredicatePattern::Variable(v)) => TermPattern::Variable(v.clone()),
69        None => {
70            return Err(OxirsError::Query(
71                "Predicate pattern is required in algebra representation".to_string(),
72            ))
73        }
74    };
75
76    let object = match pattern.object() {
77        Some(ObjectPattern::NamedNode(nn)) => TermPattern::NamedNode(nn.clone()),
78        Some(ObjectPattern::BlankNode(bn)) => TermPattern::BlankNode(bn.clone()),
79        Some(ObjectPattern::Literal(lit)) => TermPattern::Literal(lit.clone()),
80        Some(ObjectPattern::Variable(v)) => TermPattern::Variable(v.clone()),
81        Some(ObjectPattern::QuotedTriple(qt)) => TermPattern::QuotedTriple(qt.clone()),
82        None => {
83            return Err(OxirsError::Query(
84                "Object pattern is required in algebra representation".to_string(),
85            ))
86        }
87    };
88
89    Ok(AlgebraTriplePattern::new(subject, predicate, object))
90}
91
92/// A query execution plan
93#[derive(Debug, Clone)]
94pub enum ExecutionPlan {
95    /// Scan all triples matching a pattern
96    TripleScan {
97        pattern: crate::model::pattern::TriplePattern,
98    },
99    /// Join two sub-plans
100    HashJoin {
101        left: Box<ExecutionPlan>,
102        right: Box<ExecutionPlan>,
103        join_vars: Vec<Variable>,
104    },
105    /// Filter results
106    Filter {
107        input: Box<ExecutionPlan>,
108        condition: Expression,
109    },
110    /// Project specific variables
111    Project {
112        input: Box<ExecutionPlan>,
113        vars: Vec<Variable>,
114    },
115    /// Sort results
116    Sort {
117        input: Box<ExecutionPlan>,
118        order_by: Vec<OrderExpression>,
119    },
120    /// Limit results
121    Limit {
122        input: Box<ExecutionPlan>,
123        limit: usize,
124        offset: usize,
125    },
126    /// Union of two plans
127    Union {
128        left: Box<ExecutionPlan>,
129        right: Box<ExecutionPlan>,
130    },
131    /// Distinct results
132    Distinct { input: Box<ExecutionPlan> },
133}
134
135/// Query planner that converts algebra to execution plans
136pub struct QueryPlanner;
137
138impl QueryPlanner {
139    /// Creates a new query planner
140    pub fn new() -> Self {
141        QueryPlanner
142    }
143
144    /// Plans a query for execution
145    pub fn plan_query(&self, query: &Query) -> Result<ExecutionPlan, OxirsError> {
146        match &query.form {
147            QueryForm::Select {
148                where_clause,
149                variables,
150                distinct,
151                order_by,
152                limit,
153                offset,
154                ..
155            } => {
156                let mut plan = self.plan_graph_pattern(where_clause)?;
157
158                // Add projection if needed
159                if let SelectVariables::Specific(vars) = variables {
160                    plan = ExecutionPlan::Project {
161                        input: Box::new(plan),
162                        vars: vars.clone(),
163                    };
164                }
165
166                // Add distinct if needed
167                if *distinct {
168                    plan = ExecutionPlan::Distinct {
169                        input: Box::new(plan),
170                    };
171                }
172
173                // Add ordering if needed
174                if !order_by.is_empty() {
175                    plan = ExecutionPlan::Sort {
176                        input: Box::new(plan),
177                        order_by: order_by.clone(),
178                    };
179                }
180
181                // Add limit/offset if needed
182                if let Some(limit_val) = limit {
183                    plan = ExecutionPlan::Limit {
184                        input: Box::new(plan),
185                        limit: *limit_val,
186                        offset: *offset,
187                    };
188                } else if *offset > 0 {
189                    plan = ExecutionPlan::Limit {
190                        input: Box::new(plan),
191                        limit: usize::MAX,
192                        offset: *offset,
193                    };
194                }
195
196                Ok(plan)
197            }
198            _ => Err(OxirsError::Query(
199                "Only SELECT queries are currently supported".to_string(),
200            )),
201        }
202    }
203
204    /// Plans a graph pattern
205    fn plan_graph_pattern(&self, pattern: &GraphPattern) -> Result<ExecutionPlan, OxirsError> {
206        match pattern {
207            GraphPattern::Bgp(patterns) => {
208                if patterns.is_empty() {
209                    return Err(OxirsError::Query("Empty basic graph pattern".to_string()));
210                }
211
212                // Start with the first pattern
213                let mut plan = ExecutionPlan::TripleScan {
214                    pattern: convert_triple_pattern(&patterns[0]),
215                };
216
217                // Join with remaining patterns
218                for pattern in &patterns[1..] {
219                    let right_plan = ExecutionPlan::TripleScan {
220                        pattern: convert_triple_pattern(pattern),
221                    };
222
223                    // Find join variables
224                    let join_vars = self.find_join_variables(&plan, &right_plan);
225
226                    plan = ExecutionPlan::HashJoin {
227                        left: Box::new(plan),
228                        right: Box::new(right_plan),
229                        join_vars,
230                    };
231                }
232
233                Ok(plan)
234            }
235            GraphPattern::Filter { expr, inner } => {
236                let inner_plan = self.plan_graph_pattern(inner)?;
237                Ok(ExecutionPlan::Filter {
238                    input: Box::new(inner_plan),
239                    condition: expr.clone(),
240                })
241            }
242            GraphPattern::Union(left, right) => {
243                let left_plan = self.plan_graph_pattern(left)?;
244                let right_plan = self.plan_graph_pattern(right)?;
245                Ok(ExecutionPlan::Union {
246                    left: Box::new(left_plan),
247                    right: Box::new(right_plan),
248                })
249            }
250            _ => Err(OxirsError::Query(
251                "Graph pattern not yet supported".to_string(),
252            )),
253        }
254    }
255
256    /// Find variables that appear in both plans (for joins)
257    fn find_join_variables(&self, _left: &ExecutionPlan, _right: &ExecutionPlan) -> Vec<Variable> {
258        // Placeholder - would analyze both plans to find common variables
259        Vec::new()
260    }
261}
262
263impl Default for QueryPlanner {
264    fn default() -> Self {
265        Self::new()
266    }
267}