1use crate::sparql_ast::*;
6
7#[repr(C)]
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum PhysicalOperatorType {
11 SubjectScan { subject: u64 },
13 PredicateScan { predicate: u64 },
15 ObjectScan { object: u64 },
17 TripleScan {
19 subject: u64,
20 predicate: u64,
21 object: u64,
22 },
23 HashJoin {
25 left: OperatorId,
26 right: OperatorId,
27 join_var: VariableId,
28 },
29 NestedLoopJoin {
31 left: OperatorId,
32 right: OperatorId,
33 join_var: VariableId,
34 },
35 Filter {
37 input: OperatorId,
38 expression: ExpressionId,
39 },
40 Bind {
44 input: OperatorId,
45 var: VariableId,
46 expression: ExpressionId,
47 },
48 Project {
50 input: OperatorId,
51 vars: [VariableId; MAX_VARIABLES],
52 var_count: u8,
53 },
54 Limit {
56 input: OperatorId,
57 limit: u64,
58 offset: u64,
59 },
60 Sort {
62 input: OperatorId,
63 order_by: [ExpressionId; MAX_ORDER_CONDITIONS],
64 order_count: u8,
65 ascending: [bool; MAX_ORDER_CONDITIONS],
66 },
67 Union { left: OperatorId, right: OperatorId },
69 Optional { left: OperatorId, right: OperatorId },
71 AntiJoin { left: OperatorId, right: OperatorId },
74 Distinct { input: OperatorId },
76 SubSelect { query_id: u16 },
79 GroupBy {
81 input: OperatorId,
82 group_vars: [VariableId; MAX_VARIABLES],
83 group_var_count: u8,
84 aggregates: [AggregateSpec; 16],
85 aggregate_count: u8,
86 },
87 Having {
89 input: OperatorId,
90 expression: ExpressionId,
91 },
92 PropertyPath {
94 subject: u64,
95 path_id: PathId,
96 object: u64,
97 },
98 Graph {
100 graph_var_or_id: u64,
101 inner: OperatorId,
102 },
103 Service {
105 endpoint_did_id: u64,
106 inner_pattern: OperatorId,
107 },
108 AsOf {
110 input: OperatorId,
111 timestamp_ms: u64,
112 mode: TemporalMode,
113 },
114 StarTripleScan {
116 inner_subject: u64,
117 inner_predicate: u64,
118 inner_object: u64,
119 outer_predicate: u64,
120 outer_object: u64,
121 },
122}
123
124pub type OperatorId = u16;
125
126#[repr(C)]
128#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129pub struct AggregateSpec {
130 pub func: u8, pub input_var: VariableId,
132 pub output_var: VariableId,
133}
134
135#[repr(C)]
137#[derive(Debug, Clone, Copy)]
138pub struct PlanOperator {
139 pub operator_type: PhysicalOperatorType,
140 pub estimated_cardinality: u64,
141}
142
143#[repr(C)]
145pub struct ExecutionPlan {
146 pub operators: [PlanOperator; 64], pub operator_count: u8,
148 pub root_operator: OperatorId,
149}
150
151impl ExecutionPlan {
152 pub fn new() -> Self {
153 Self {
154 operators: [PlanOperator {
155 operator_type: PhysicalOperatorType::SubjectScan { subject: 0 },
156 estimated_cardinality: 0,
157 }; 64],
158 operator_count: 0,
159 root_operator: 0,
160 }
161 }
162
163 pub fn add_operator(
164 &mut self,
165 op: PhysicalOperatorType,
166 cardinality: u64,
167 ) -> Result<OperatorId, String> {
168 if self.operator_count >= 64 {
169 return Err("Operator overflow".to_string());
170 }
171 let id = self.operator_count as OperatorId;
172 self.operators[self.operator_count as usize] = PlanOperator {
173 operator_type: op,
174 estimated_cardinality: cardinality,
175 };
176 self.operator_count += 1;
177 Ok(id)
178 }
179}
180
181impl Default for ExecutionPlan {
182 fn default() -> Self {
183 Self::new()
184 }
185}
186
187pub struct QueryPlanner;
189
190impl QueryPlanner {
191 pub fn plan(query: &SparqlQuery, ctx: &SparqlQueryContext) -> Result<ExecutionPlan, String> {
193 match query {
194 SparqlQuery::Select(select) => Self::plan_select(select, ctx),
195 SparqlQuery::Ask(ask) => Self::plan_ask(ask, ctx),
196 SparqlQuery::Construct(construct) => Self::plan_construct(construct, ctx),
197 SparqlQuery::Describe(describe) => Self::plan_describe(describe, ctx),
198 }
199 }
200
201 fn plan_select(
202 select: &SelectQuery,
203 ctx: &SparqlQueryContext,
204 ) -> Result<ExecutionPlan, String> {
205 let mut plan = ExecutionPlan::new();
206
207 let root_op = Self::plan_pattern(select.root_pattern, ctx, &mut plan)?;
209
210 let group_op = if select.group_by_count > 0 || select.aggregate_count > 0 {
212 let mut group_vars = [0u8; MAX_VARIABLES];
213 if select.group_by_count > 0 {
214 group_vars[..select.group_by_count as usize]
215 .copy_from_slice(&select.group_by[..select.group_by_count as usize]);
216 }
217 let mut aggregates = [crate::sparql_planner::AggregateSpec {
218 func: 0,
219 input_var: 0,
220 output_var: 0,
221 }; 16];
222 if select.aggregate_count > 0 {
223 aggregates[..select.aggregate_count as usize]
224 .copy_from_slice(&select.aggregates[..select.aggregate_count as usize]);
225 }
226 plan.add_operator(
227 PhysicalOperatorType::GroupBy {
228 input: root_op,
229 group_vars,
230 group_var_count: select.group_by_count,
231 aggregates,
232 aggregate_count: select.aggregate_count,
233 },
234 0,
235 )?
236 } else {
237 root_op
238 };
239
240 let project_op = if select.var_count > 0 {
242 let mut vars = [0u8; MAX_VARIABLES];
243 vars[..select.var_count as usize]
244 .copy_from_slice(&select.variables[..select.var_count as usize]);
245 plan.add_operator(
246 PhysicalOperatorType::Project {
247 input: group_op,
248 vars,
249 var_count: select.var_count,
250 },
251 0, )?
253 } else {
254 root_op
255 };
256
257 let distinct_op = if select.distinct || select.reduced {
262 plan.add_operator(PhysicalOperatorType::Distinct { input: project_op }, 0)?
263 } else {
264 project_op
265 };
266
267 let sort_op = if select.order_by_count > 0 {
269 let mut order_by = [0u16; MAX_ORDER_CONDITIONS];
270 let mut ascending = [true; MAX_ORDER_CONDITIONS];
271 for i in 0..select.order_by_count as usize {
272 order_by[i] = select.order_by[i].expr;
273 ascending[i] = select.order_by[i].ascending;
274 }
275 plan.add_operator(
276 PhysicalOperatorType::Sort {
277 input: distinct_op,
278 order_by,
279 order_count: select.order_by_count,
280 ascending,
281 },
282 0,
283 )?
284 } else {
285 distinct_op
286 };
287
288 let final_op = if select.limit.is_some() || select.offset > 0 {
290 plan.add_operator(
291 PhysicalOperatorType::Limit {
292 input: sort_op,
293 limit: select.limit.unwrap_or(u64::MAX),
294 offset: select.offset,
295 },
296 0,
297 )?
298 } else {
299 sort_op
300 };
301
302 plan.root_operator = final_op;
303 Ok(plan)
304 }
305
306 fn plan_ask(ask: &AskQuery, ctx: &SparqlQueryContext) -> Result<ExecutionPlan, String> {
307 let mut plan = ExecutionPlan::new();
308 let root_op = Self::plan_pattern(ask.root_pattern, ctx, &mut plan)?;
309 plan.root_operator = root_op;
310 Ok(plan)
311 }
312
313 fn plan_construct(
314 construct: &ConstructQuery,
315 ctx: &SparqlQueryContext,
316 ) -> Result<ExecutionPlan, String> {
317 let mut plan = ExecutionPlan::new();
318 let root_op = Self::plan_pattern(construct.root_pattern, ctx, &mut plan)?;
319 plan.root_operator = root_op;
320 Ok(plan)
321 }
322
323 fn plan_describe(
324 describe: &DescribeQuery,
325 ctx: &SparqlQueryContext,
326 ) -> Result<ExecutionPlan, String> {
327 let mut plan = ExecutionPlan::new();
328 if let Some(pattern_id) = describe.root_pattern {
329 let root_op = Self::plan_pattern(pattern_id, ctx, &mut plan)?;
330 plan.root_operator = root_op;
331 }
332 Ok(plan)
333 }
334
335 pub(crate) fn plan_pattern(
336 pattern_id: PatternId,
337 ctx: &SparqlQueryContext,
338 plan: &mut ExecutionPlan,
339 ) -> Result<OperatorId, String> {
340 let pattern = ctx
341 .patterns
342 .get(pattern_id as usize)
343 .ok_or("Pattern ID out of bounds")?;
344
345 match pattern {
346 Pattern::Triple {
347 subject,
348 predicate,
349 object,
350 } => plan.add_operator(
351 PhysicalOperatorType::TripleScan {
352 subject: *subject,
353 predicate: *predicate,
354 object: *object,
355 },
356 0,
357 ),
358 Pattern::StarTriple {
359 inner_subject,
360 inner_predicate,
361 inner_object,
362 outer_predicate,
363 outer_object,
364 } => plan.add_operator(
365 PhysicalOperatorType::StarTripleScan {
366 inner_subject: *inner_subject,
367 inner_predicate: *inner_predicate,
368 inner_object: *inner_object,
369 outer_predicate: *outer_predicate,
370 outer_object: *outer_object,
371 },
372 0,
373 ),
374 Pattern::Optional { inner } => {
375 let inner_op = Self::plan_pattern(*inner, ctx, plan)?;
376 Ok(inner_op)
378 }
379 Pattern::Union { left, right } => {
380 let left_op = Self::plan_pattern(*left, ctx, plan)?;
381 let right_op = Self::plan_pattern(*right, ctx, plan)?;
382 plan.add_operator(
383 PhysicalOperatorType::Union {
384 left: left_op,
385 right: right_op,
386 },
387 0,
388 )
389 }
390
391 Pattern::Filter {
392 pattern: inner_pattern,
393 expression,
394 } => {
395 let inner_op = Self::plan_pattern(*inner_pattern, ctx, plan)?;
396 plan.add_operator(
397 PhysicalOperatorType::Filter {
398 input: inner_op,
399 expression: *expression,
400 },
401 0,
402 )
403 }
404 Pattern::Bind {
405 pattern: inner_pattern,
406 var,
407 expression,
408 } => {
409 let inner_op = Self::plan_pattern(*inner_pattern, ctx, plan)?;
410 plan.add_operator(
411 PhysicalOperatorType::Bind {
412 input: inner_op,
413 var: *var,
414 expression: *expression,
415 },
416 0,
417 )
418 }
419 Pattern::Minus { inner } => Self::plan_pattern(*inner, ctx, plan),
420 Pattern::Group { start_idx, len } => {
421 let mut current_op: Option<OperatorId> = None;
429 for i in *start_idx..(*start_idx + *len) {
430 let child = ctx
431 .patterns
432 .get(i as usize)
433 .ok_or("Pattern ID out of bounds")?;
434 match child {
435 Pattern::Optional { inner } => {
436 let right = Self::plan_pattern(*inner, ctx, plan)?;
437 current_op = Some(match current_op {
438 Some(left) => plan.add_operator(
439 PhysicalOperatorType::Optional { left, right },
440 0,
441 )?,
442 None => right,
445 });
446 }
447 Pattern::Minus { inner } => {
448 let right = Self::plan_pattern(*inner, ctx, plan)?;
449 current_op = Some(match current_op {
450 Some(left) => plan.add_operator(
451 PhysicalOperatorType::AntiJoin { left, right },
452 0,
453 )?,
454 None => right,
458 });
459 }
460 _ => {
461 let pattern_op = Self::plan_pattern(i, ctx, plan)?;
462 current_op = Some(match current_op {
463 Some(curr) => plan.add_operator(
464 PhysicalOperatorType::NestedLoopJoin {
465 left: curr,
466 right: pattern_op,
467 join_var: 0,
468 },
469 0,
470 )?,
471 None => pattern_op,
472 });
473 }
474 }
475 }
476 current_op.ok_or("Empty group pattern".to_string())
477 }
478 Pattern::PropertyPath {
479 subject,
480 path,
481 object,
482 } => plan.add_operator(
483 PhysicalOperatorType::PropertyPath {
484 subject: *subject,
485 path_id: *path,
486 object: *object,
487 },
488 0,
489 ),
490 Pattern::SubSelect { query_id } => plan.add_operator(
491 PhysicalOperatorType::SubSelect {
492 query_id: *query_id,
493 },
494 0,
495 ),
496 Pattern::Graph {
497 graph_var_or_id,
498 inner,
499 } => {
500 let inner_op = Self::plan_pattern(*inner, ctx, plan)?;
501 plan.add_operator(
502 PhysicalOperatorType::Graph {
503 graph_var_or_id: *graph_var_or_id,
504 inner: inner_op,
505 },
506 0,
507 )
508 }
509 Pattern::Service {
510 endpoint_did_id,
511 inner_pattern,
512 } => {
513 let inner_op = Self::plan_pattern(*inner_pattern, ctx, plan)?;
514 plan.add_operator(
515 PhysicalOperatorType::Service {
516 endpoint_did_id: *endpoint_did_id,
517 inner_pattern: inner_op,
518 },
519 0,
520 )
521 }
522 Pattern::AsOf {
523 inner,
524 timestamp_ms,
525 mode,
526 } => {
527 let inner_op = Self::plan_pattern(*inner, ctx, plan)?;
528 plan.add_operator(
529 PhysicalOperatorType::AsOf {
530 input: inner_op,
531 timestamp_ms: *timestamp_ms,
532 mode: *mode,
533 },
534 0,
535 )
536 }
537 }
538 }
539}
540
541#[cfg(test)]
542mod tests {
543 use super::*;
544
545 #[test]
546 fn test_execution_plan_creation() {
547 let plan = ExecutionPlan::new();
548 assert_eq!(plan.operator_count, 0);
549 }
550
551 #[test]
552 fn test_add_operator() {
553 let mut plan = ExecutionPlan::new();
554 let op = PhysicalOperatorType::SubjectScan { subject: 42 };
555 let id = plan.add_operator(op, 0).unwrap();
556 assert_eq!(id, 0);
557 assert_eq!(plan.operator_count, 1);
558 }
559
560 #[test]
561 fn test_plan_triple_pattern() {
562 let mut ctx = SparqlQueryContext::new();
563 let pattern = Pattern::Triple {
564 subject: 1,
565 predicate: 2,
566 object: 3,
567 };
568 let pattern_id = ctx.alloc_pattern(pattern).unwrap();
569
570 let mut plan = ExecutionPlan::new();
571 let op_id = QueryPlanner::plan_pattern(pattern_id, &ctx, &mut plan).unwrap();
572 assert_eq!(op_id, 0);
573 assert_eq!(plan.operator_count, 1);
574 }
575}