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(_) => None, // RDF-star not yet fully implemented
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(_) => None, // RDF-star not yet fully implemented
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        None => {
59            return Err(OxirsError::Query(
60                "Subject pattern is required in algebra representation".to_string(),
61            ))
62        }
63    };
64
65    let predicate = match pattern.predicate() {
66        Some(PredicatePattern::NamedNode(nn)) => TermPattern::NamedNode(nn.clone()),
67        Some(PredicatePattern::Variable(v)) => TermPattern::Variable(v.clone()),
68        None => {
69            return Err(OxirsError::Query(
70                "Predicate pattern is required in algebra representation".to_string(),
71            ))
72        }
73    };
74
75    let object = match pattern.object() {
76        Some(ObjectPattern::NamedNode(nn)) => TermPattern::NamedNode(nn.clone()),
77        Some(ObjectPattern::BlankNode(bn)) => TermPattern::BlankNode(bn.clone()),
78        Some(ObjectPattern::Literal(lit)) => TermPattern::Literal(lit.clone()),
79        Some(ObjectPattern::Variable(v)) => TermPattern::Variable(v.clone()),
80        None => {
81            return Err(OxirsError::Query(
82                "Object pattern is required in algebra representation".to_string(),
83            ))
84        }
85    };
86
87    Ok(AlgebraTriplePattern::new(subject, predicate, object))
88}
89
90/// A query execution plan
91#[derive(Debug, Clone)]
92pub enum ExecutionPlan {
93    /// Scan all triples matching a pattern
94    TripleScan {
95        pattern: crate::model::pattern::TriplePattern,
96    },
97    /// Join two sub-plans
98    HashJoin {
99        left: Box<ExecutionPlan>,
100        right: Box<ExecutionPlan>,
101        join_vars: Vec<Variable>,
102    },
103    /// Filter results
104    Filter {
105        input: Box<ExecutionPlan>,
106        condition: Expression,
107    },
108    /// Project specific variables
109    Project {
110        input: Box<ExecutionPlan>,
111        vars: Vec<Variable>,
112    },
113    /// Sort results
114    Sort {
115        input: Box<ExecutionPlan>,
116        order_by: Vec<OrderExpression>,
117    },
118    /// Limit results
119    Limit {
120        input: Box<ExecutionPlan>,
121        limit: usize,
122        offset: usize,
123    },
124    /// Union of two plans
125    Union {
126        left: Box<ExecutionPlan>,
127        right: Box<ExecutionPlan>,
128    },
129    /// Distinct results
130    Distinct { input: Box<ExecutionPlan> },
131}
132
133/// Query planner that converts algebra to execution plans
134pub struct QueryPlanner;
135
136impl QueryPlanner {
137    /// Creates a new query planner
138    pub fn new() -> Self {
139        QueryPlanner
140    }
141
142    /// Plans a query for execution
143    pub fn plan_query(&self, query: &Query) -> Result<ExecutionPlan, OxirsError> {
144        match &query.form {
145            QueryForm::Select {
146                where_clause,
147                variables,
148                distinct,
149                order_by,
150                limit,
151                offset,
152                ..
153            } => {
154                let mut plan = self.plan_graph_pattern(where_clause)?;
155
156                // Add projection if needed
157                if let SelectVariables::Specific(vars) = variables {
158                    plan = ExecutionPlan::Project {
159                        input: Box::new(plan),
160                        vars: vars.clone(),
161                    };
162                }
163
164                // Add distinct if needed
165                if *distinct {
166                    plan = ExecutionPlan::Distinct {
167                        input: Box::new(plan),
168                    };
169                }
170
171                // Add ordering if needed
172                if !order_by.is_empty() {
173                    plan = ExecutionPlan::Sort {
174                        input: Box::new(plan),
175                        order_by: order_by.clone(),
176                    };
177                }
178
179                // Add limit/offset if needed
180                if let Some(limit_val) = limit {
181                    plan = ExecutionPlan::Limit {
182                        input: Box::new(plan),
183                        limit: *limit_val,
184                        offset: *offset,
185                    };
186                } else if *offset > 0 {
187                    plan = ExecutionPlan::Limit {
188                        input: Box::new(plan),
189                        limit: usize::MAX,
190                        offset: *offset,
191                    };
192                }
193
194                Ok(plan)
195            }
196            _ => Err(OxirsError::Query(
197                "Only SELECT queries are currently supported".to_string(),
198            )),
199        }
200    }
201
202    /// Plans a graph pattern
203    fn plan_graph_pattern(&self, pattern: &GraphPattern) -> Result<ExecutionPlan, OxirsError> {
204        match pattern {
205            GraphPattern::Bgp(patterns) => {
206                if patterns.is_empty() {
207                    return Err(OxirsError::Query("Empty basic graph pattern".to_string()));
208                }
209
210                // Start with the first pattern
211                let mut plan = ExecutionPlan::TripleScan {
212                    pattern: convert_triple_pattern(&patterns[0]),
213                };
214
215                // Join with remaining patterns
216                for pattern in &patterns[1..] {
217                    let right_plan = ExecutionPlan::TripleScan {
218                        pattern: convert_triple_pattern(pattern),
219                    };
220
221                    // Find join variables
222                    let join_vars = self.find_join_variables(&plan, &right_plan);
223
224                    plan = ExecutionPlan::HashJoin {
225                        left: Box::new(plan),
226                        right: Box::new(right_plan),
227                        join_vars,
228                    };
229                }
230
231                Ok(plan)
232            }
233            GraphPattern::Filter { expr, inner } => {
234                let inner_plan = self.plan_graph_pattern(inner)?;
235                Ok(ExecutionPlan::Filter {
236                    input: Box::new(inner_plan),
237                    condition: expr.clone(),
238                })
239            }
240            GraphPattern::Union(left, right) => {
241                let left_plan = self.plan_graph_pattern(left)?;
242                let right_plan = self.plan_graph_pattern(right)?;
243                Ok(ExecutionPlan::Union {
244                    left: Box::new(left_plan),
245                    right: Box::new(right_plan),
246                })
247            }
248            _ => Err(OxirsError::Query(
249                "Graph pattern not yet supported".to_string(),
250            )),
251        }
252    }
253
254    /// Find variables that appear in both plans (for joins)
255    fn find_join_variables(&self, _left: &ExecutionPlan, _right: &ExecutionPlan) -> Vec<Variable> {
256        // Placeholder - would analyze both plans to find common variables
257        Vec::new()
258    }
259}
260
261impl Default for QueryPlanner {
262    fn default() -> Self {
263        Self::new()
264    }
265}