1mod aggregation;
2mod distinct;
3mod filter;
4mod having;
5mod join;
6mod limit;
7mod offset;
8mod order_by_expr;
9mod project;
10mod select_order_by;
11mod source;
12mod values;
13
14pub use {
15 aggregation::{AggregationInputPlan, AggregationPlan},
16 distinct::{DistinctInputPlan, DistinctPlan},
17 filter::{FilterInputPlan, FilterPlan},
18 having::HavingPlan,
19 join::{
20 HashJoinInputPlan, HashJoinPlan, InnerJoinInputPlan, InnerJoinPlan, JoinConditionInputPlan,
21 JoinConditionPlan, LeftOuterJoinInputPlan, LeftOuterJoinPlan, NestedLoopJoinInputPlan,
22 NestedLoopJoinPlan,
23 },
24 limit::{LimitInputPlan, LimitPlan},
25 offset::{OffsetInputPlan, OffsetPlan},
26 order_by_expr::OrderByExprPlan,
27 project::{ProjectInputPlan, ProjectPlan},
28 select_order_by::SelectOrderByPlan,
29 source::{
30 DerivedSourcePlan, DictionarySourcePlan, IndexPredicatePlan, SeriesSourcePlan, SourcePlan,
31 TableAccessPlan, TableAliasPlan, TableSourcePlan,
32 },
33 values::{ValuesOrderByPlan, ValuesPlan},
34};
35
36use {
37 crate::ast,
38 serde::{Deserialize, Serialize},
39};
40
41#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
42pub enum QueryPlan {
43 Project(ProjectPlan),
44 Values(ValuesPlan),
45 SelectOrderBy(SelectOrderByPlan),
46 ValuesOrderBy(ValuesOrderByPlan),
47 Distinct(DistinctPlan),
48 Offset(OffsetPlan),
49 Limit(LimitPlan),
50}
51
52impl QueryPlan {
53 pub fn project(&self) -> Option<&ProjectPlan> {
54 match self {
55 Self::Project(project) => Some(project),
56 Self::Values(_) | Self::ValuesOrderBy(_) => None,
57 Self::SelectOrderBy(order_by) => Some(&order_by.input),
58 Self::Distinct(distinct) => Some(distinct.project()),
59 Self::Offset(offset) => offset.project(),
60 Self::Limit(limit) => limit.project(),
61 }
62 }
63}
64
65impl From<ast::Query> for QueryPlan {
66 fn from(query: ast::Query) -> Self {
67 let ast::Query {
68 body,
69 order_by,
70 limit: limit_expr,
71 offset: offset_expr,
72 } = query;
73
74 let order_by = order_by.into_iter().map(Into::into).collect::<Vec<_>>();
75
76 let input = match body {
77 ast::SetExpr::Select(select) => {
78 let ast::Select {
79 distinct: is_distinct,
80 projection,
81 from,
82 selection,
83 group_by,
84 having,
85 } = *select;
86 let input = joins(from);
87 let input = filter(input, selection);
88 let input = group_by_having(input, group_by, having);
89 let input = ProjectPlan {
90 input,
91 projection: projection.into(),
92 };
93
94 let input = if order_by.is_empty() {
95 DistinctInputPlan::Project(input)
96 } else {
97 DistinctInputPlan::SelectOrderBy(SelectOrderByPlan {
98 input,
99 exprs: order_by,
100 })
101 };
102 distinct(input, is_distinct)
103 }
104 ast::SetExpr::Values(values) => {
105 let input = values.into();
106 if order_by.is_empty() {
107 OffsetInputPlan::Values(input)
108 } else {
109 OffsetInputPlan::ValuesOrderBy(ValuesOrderByPlan {
110 input,
111 exprs: order_by,
112 })
113 }
114 }
115 };
116 let input = offset(input, offset_expr);
117
118 limit(input, limit_expr)
119 }
120}
121
122fn joins(from: ast::TableWithJoins) -> NestedLoopJoinInputPlan {
123 let ast::TableWithJoins { relation, joins } = from;
124
125 joins.into_iter().fold(
126 NestedLoopJoinInputPlan::Source(relation.into()),
127 |input, join| {
128 let ast::Join {
129 relation,
130 join_operator,
131 } = join;
132 let nested_loop = NestedLoopJoinPlan {
133 input,
134 right: relation.into(),
135 };
136
137 match join_operator {
138 ast::JoinOperator::Inner(ast::JoinConstraint::None) => {
139 NestedLoopJoinInputPlan::InnerJoin(Box::new(InnerJoinPlan {
140 input: InnerJoinInputPlan::NestedLoop(nested_loop),
141 }))
142 }
143 ast::JoinOperator::Inner(ast::JoinConstraint::On(expr)) => {
144 NestedLoopJoinInputPlan::InnerJoin(Box::new(InnerJoinPlan {
145 input: InnerJoinInputPlan::Condition(JoinConditionPlan {
146 input: JoinConditionInputPlan::NestedLoop(nested_loop),
147 expr: expr.into(),
148 }),
149 }))
150 }
151 ast::JoinOperator::LeftOuter(ast::JoinConstraint::None) => {
152 NestedLoopJoinInputPlan::LeftOuterJoin(Box::new(LeftOuterJoinPlan {
153 input: LeftOuterJoinInputPlan::NestedLoop(nested_loop),
154 }))
155 }
156 ast::JoinOperator::LeftOuter(ast::JoinConstraint::On(expr)) => {
157 NestedLoopJoinInputPlan::LeftOuterJoin(Box::new(LeftOuterJoinPlan {
158 input: LeftOuterJoinInputPlan::Condition(JoinConditionPlan {
159 input: JoinConditionInputPlan::NestedLoop(nested_loop),
160 expr: expr.into(),
161 }),
162 }))
163 }
164 }
165 },
166 )
167}
168
169fn filter(input: NestedLoopJoinInputPlan, selection: Option<ast::Expr>) -> AggregationInputPlan {
170 match selection {
171 Some(expr) => AggregationInputPlan::Filter(FilterPlan {
172 input: match input {
173 NestedLoopJoinInputPlan::Source(source) => FilterInputPlan::Source(source),
174 NestedLoopJoinInputPlan::InnerJoin(join) => FilterInputPlan::InnerJoin(join),
175 NestedLoopJoinInputPlan::LeftOuterJoin(join) => {
176 FilterInputPlan::LeftOuterJoin(join)
177 }
178 },
179 expr: expr.into(),
180 }),
181 None => match input {
182 NestedLoopJoinInputPlan::Source(source) => AggregationInputPlan::Source(source),
183 NestedLoopJoinInputPlan::InnerJoin(join) => AggregationInputPlan::InnerJoin(join),
184 NestedLoopJoinInputPlan::LeftOuterJoin(join) => {
185 AggregationInputPlan::LeftOuterJoin(join)
186 }
187 },
188 }
189}
190
191fn group_by_having(
192 input: AggregationInputPlan,
193 group_by: Vec<ast::Expr>,
194 having: Option<ast::Expr>,
195) -> ProjectInputPlan {
196 let group_by = group_by.into_iter().map(Into::into).collect::<Vec<_>>();
197
198 match having {
199 Some(having) => ProjectInputPlan::Having(HavingPlan {
200 input: AggregationPlan {
201 input,
202 group_by,
203 aggregate_slots: Vec::new(),
204 },
205 expr: having.into(),
206 }),
207 None if group_by.is_empty() => match input {
208 AggregationInputPlan::Source(source) => ProjectInputPlan::Source(source),
209 AggregationInputPlan::InnerJoin(join) => ProjectInputPlan::InnerJoin(join),
210 AggregationInputPlan::LeftOuterJoin(join) => ProjectInputPlan::LeftOuterJoin(join),
211 AggregationInputPlan::Filter(filter) => ProjectInputPlan::Filter(filter),
212 },
213 None => ProjectInputPlan::Aggregation(AggregationPlan {
214 input,
215 group_by,
216 aggregate_slots: Vec::new(),
217 }),
218 }
219}
220
221fn distinct(input: DistinctInputPlan, is_distinct: bool) -> OffsetInputPlan {
222 if is_distinct {
223 OffsetInputPlan::Distinct(DistinctPlan { input })
224 } else {
225 match input {
226 DistinctInputPlan::Project(project) => OffsetInputPlan::Project(project),
227 DistinctInputPlan::SelectOrderBy(order_by) => OffsetInputPlan::SelectOrderBy(order_by),
228 }
229 }
230}
231
232fn offset(input: OffsetInputPlan, expr: Option<ast::Expr>) -> LimitInputPlan {
233 match expr {
234 Some(expr) => LimitInputPlan::Offset(OffsetPlan {
235 input,
236 count: expr.into(),
237 }),
238 None => match input {
239 OffsetInputPlan::Project(project) => LimitInputPlan::Project(project),
240 OffsetInputPlan::Values(values) => LimitInputPlan::Values(values),
241 OffsetInputPlan::SelectOrderBy(order_by) => LimitInputPlan::SelectOrderBy(order_by),
242 OffsetInputPlan::ValuesOrderBy(order_by) => LimitInputPlan::ValuesOrderBy(order_by),
243 OffsetInputPlan::Distinct(distinct) => LimitInputPlan::Distinct(distinct),
244 },
245 }
246}
247
248fn limit(input: LimitInputPlan, expr: Option<ast::Expr>) -> QueryPlan {
249 match expr {
250 Some(expr) => QueryPlan::Limit(LimitPlan {
251 input,
252 count: expr.into(),
253 }),
254 None => match input {
255 LimitInputPlan::Project(project) => QueryPlan::Project(project),
256 LimitInputPlan::Values(values) => QueryPlan::Values(values),
257 LimitInputPlan::SelectOrderBy(order_by) => QueryPlan::SelectOrderBy(order_by),
258 LimitInputPlan::ValuesOrderBy(order_by) => QueryPlan::ValuesOrderBy(order_by),
259 LimitInputPlan::Distinct(distinct) => QueryPlan::Distinct(distinct),
260 LimitInputPlan::Offset(offset) => QueryPlan::Offset(offset),
261 },
262 }
263}
264
265#[cfg(test)]
266mod tests {
267 use {
268 super::{
269 AggregationInputPlan, AggregationPlan, DistinctInputPlan, DistinctPlan, FilterPlan,
270 HavingPlan, LimitInputPlan, LimitPlan, OffsetInputPlan, OffsetPlan, OrderByExprPlan,
271 ProjectInputPlan, ProjectPlan, QueryPlan,
272 },
273 crate::{
274 ast::{BinaryOperator, Literal},
275 data::Value,
276 parse_sql::parse,
277 plan::{
278 ExprPlan, FilterInputPlan, InnerJoinInputPlan, InnerJoinPlan,
279 JoinConditionInputPlan, JoinConditionPlan, LeftOuterJoinInputPlan,
280 LeftOuterJoinPlan, NestedLoopJoinInputPlan, NestedLoopJoinPlan, ProjectionPlan,
281 SelectItemPlan, SelectOrderByPlan, SourcePlan, StatementPlan, TableAccessPlan,
282 TableSourcePlan, ValuesOrderByPlan,
283 },
284 translate::translate,
285 },
286 pretty_assertions::assert_eq,
287 };
288
289 fn statement_plan(sql: &str) -> StatementPlan {
290 parse(sql)
291 .and_then(|mut statements| translate(&statements.remove(0)))
292 .map(StatementPlan::from)
293 .unwrap()
294 }
295
296 fn relation_plan() -> SourcePlan {
297 SourcePlan::Table(TableSourcePlan {
298 name: "Item".to_owned(),
299 alias: None,
300 access: TableAccessPlan::FullScan,
301 })
302 }
303
304 fn filter_plan() -> FilterPlan {
305 FilterPlan {
306 input: FilterInputPlan::Source(relation_plan()),
307 expr: ExprPlan::Identifier("active".to_owned()),
308 }
309 }
310
311 fn project_statement(input: ProjectInputPlan) -> StatementPlan {
312 StatementPlan::Query(QueryPlan::Project(ProjectPlan {
313 input,
314 projection: ProjectionPlan::SelectItems(vec![SelectItemPlan::Wildcard]),
315 }))
316 }
317
318 #[test]
319 fn query_plan_preserves_typed_select_aggregation_having_relations() {
320 assert_eq!(
321 statement_plan("SELECT * FROM Item"),
322 project_statement(ProjectInputPlan::Source(relation_plan()))
323 );
324 assert_eq!(
325 statement_plan("SELECT * FROM Item WHERE active"),
326 project_statement(ProjectInputPlan::Filter(filter_plan()))
327 );
328 assert_eq!(
329 statement_plan("SELECT * FROM Item GROUP BY category"),
330 project_statement(ProjectInputPlan::Aggregation(AggregationPlan {
331 input: AggregationInputPlan::Source(relation_plan()),
332 group_by: vec![ExprPlan::Identifier("category".to_owned())],
333 aggregate_slots: Vec::new(),
334 }))
335 );
336 assert_eq!(
337 statement_plan("SELECT * FROM Item WHERE active GROUP BY category"),
338 project_statement(ProjectInputPlan::Aggregation(AggregationPlan {
339 input: AggregationInputPlan::Filter(filter_plan()),
340 group_by: vec![ExprPlan::Identifier("category".to_owned())],
341 aggregate_slots: Vec::new(),
342 }))
343 );
344 assert_eq!(
345 statement_plan("SELECT * FROM Item GROUP BY category HAVING TRUE"),
346 project_statement(ProjectInputPlan::Having(HavingPlan {
347 input: AggregationPlan {
348 input: AggregationInputPlan::Source(relation_plan()),
349 group_by: vec![ExprPlan::Identifier("category".to_owned())],
350 aggregate_slots: Vec::new(),
351 },
352 expr: ExprPlan::Value(Value::Bool(true)),
353 }))
354 );
355 assert_eq!(
356 statement_plan("SELECT * FROM Item WHERE active GROUP BY category HAVING TRUE"),
357 project_statement(ProjectInputPlan::Having(HavingPlan {
358 input: AggregationPlan {
359 input: AggregationInputPlan::Filter(filter_plan()),
360 group_by: vec![ExprPlan::Identifier("category".to_owned())],
361 aggregate_slots: Vec::new(),
362 },
363 expr: ExprPlan::Value(Value::Bool(true)),
364 }))
365 );
366 assert_eq!(
367 statement_plan("SELECT * FROM Item HAVING TRUE"),
368 project_statement(ProjectInputPlan::Having(HavingPlan {
369 input: AggregationPlan {
370 input: AggregationInputPlan::Source(relation_plan()),
371 group_by: Vec::new(),
372 aggregate_slots: Vec::new(),
373 },
374 expr: ExprPlan::Value(Value::Bool(true)),
375 }))
376 );
377 assert_eq!(
378 statement_plan("SELECT * FROM Item WHERE active HAVING TRUE"),
379 project_statement(ProjectInputPlan::Having(HavingPlan {
380 input: AggregationPlan {
381 input: AggregationInputPlan::Filter(filter_plan()),
382 group_by: Vec::new(),
383 aggregate_slots: Vec::new(),
384 },
385 expr: ExprPlan::Value(Value::Bool(true)),
386 }))
387 );
388 }
389
390 #[test]
391 fn query_plan_preserves_filter_through_terminal_stages() {
392 let actual =
393 statement_plan("SELECT DISTINCT * FROM Item WHERE active ORDER BY id LIMIT 3 OFFSET 2");
394 let project = ProjectPlan {
395 input: ProjectInputPlan::Filter(filter_plan()),
396 projection: ProjectionPlan::SelectItems(vec![SelectItemPlan::Wildcard]),
397 };
398 let order_by = SelectOrderByPlan {
399 input: project,
400 exprs: vec![OrderByExprPlan {
401 expr: ExprPlan::Identifier("id".to_owned()),
402 asc: None,
403 }],
404 };
405 let distinct = DistinctPlan {
406 input: DistinctInputPlan::SelectOrderBy(order_by),
407 };
408 let offset = OffsetPlan {
409 input: OffsetInputPlan::Distinct(distinct),
410 count: ExprPlan::Literal(Literal::Number(2.into())),
411 };
412 let expected = StatementPlan::Query(QueryPlan::Limit(LimitPlan {
413 input: LimitInputPlan::Offset(offset),
414 count: ExprPlan::Literal(Literal::Number(3.into())),
415 }));
416
417 assert_eq!(actual, expected);
418 }
419
420 #[test]
421 fn query_plan_builds_left_deep_join_pipeline() {
422 let actual = statement_plan("SELECT * FROM A JOIN B ON A.id = B.a_id LEFT JOIN C");
423 let first_join = InnerJoinPlan {
424 input: InnerJoinInputPlan::Condition(JoinConditionPlan {
425 input: JoinConditionInputPlan::NestedLoop(NestedLoopJoinPlan {
426 input: NestedLoopJoinInputPlan::Source(SourcePlan::Table(TableSourcePlan {
427 name: "A".to_owned(),
428 alias: None,
429 access: TableAccessPlan::FullScan,
430 })),
431 right: SourcePlan::Table(TableSourcePlan {
432 name: "B".to_owned(),
433 alias: None,
434 access: TableAccessPlan::FullScan,
435 }),
436 }),
437 expr: ExprPlan::BinaryOp {
438 left: Box::new(ExprPlan::CompoundIdentifier {
439 alias: "A".to_owned(),
440 ident: "id".to_owned(),
441 }),
442 op: BinaryOperator::Eq,
443 right: Box::new(ExprPlan::CompoundIdentifier {
444 alias: "B".to_owned(),
445 ident: "a_id".to_owned(),
446 }),
447 },
448 }),
449 };
450 let expected = project_statement(ProjectInputPlan::LeftOuterJoin(Box::new(
451 LeftOuterJoinPlan {
452 input: LeftOuterJoinInputPlan::NestedLoop(NestedLoopJoinPlan {
453 input: NestedLoopJoinInputPlan::InnerJoin(Box::new(first_join)),
454 right: SourcePlan::Table(TableSourcePlan {
455 name: "C".to_owned(),
456 alias: None,
457 access: TableAccessPlan::FullScan,
458 }),
459 }),
460 },
461 )));
462
463 assert_eq!(actual, expected);
464 }
465
466 #[test]
467 fn query_plan_finds_project_through_terminal_stages() {
468 let expected = ProjectPlan {
469 input: ProjectInputPlan::Source(relation_plan()),
470 projection: ProjectionPlan::SelectItems(vec![SelectItemPlan::Wildcard]),
471 };
472
473 for sql in [
474 "SELECT * FROM Item",
475 "SELECT * FROM Item ORDER BY id",
476 "SELECT DISTINCT * FROM Item",
477 "SELECT DISTINCT * FROM Item ORDER BY id",
478 "SELECT * FROM Item OFFSET 2",
479 "SELECT * FROM Item ORDER BY id OFFSET 2",
480 "SELECT DISTINCT * FROM Item OFFSET 2",
481 "SELECT * FROM Item LIMIT 3",
482 "SELECT * FROM Item ORDER BY id LIMIT 3",
483 "SELECT DISTINCT * FROM Item LIMIT 3",
484 "SELECT * FROM Item LIMIT 3 OFFSET 2",
485 "SELECT DISTINCT * FROM Item ORDER BY id LIMIT 3 OFFSET 2",
486 ] {
487 let statement = statement_plan(sql);
488 let actual = match &statement {
489 StatementPlan::Query(query) => query.project(),
490 _ => None,
491 };
492
493 assert_eq!(actual, Some(&expected), "{sql}");
494 }
495
496 for sql in [
497 "VALUES (1)",
498 "VALUES (1) ORDER BY column1",
499 "VALUES (1) OFFSET 2",
500 "VALUES (1) ORDER BY column1 OFFSET 2",
501 "VALUES (1) LIMIT 3",
502 "VALUES (1) ORDER BY column1 LIMIT 3",
503 "VALUES (1) LIMIT 3 OFFSET 2",
504 "VALUES (1) ORDER BY column1 LIMIT 3 OFFSET 2",
505 ] {
506 let statement = statement_plan(sql);
507 let actual = match &statement {
508 StatementPlan::Query(query) => query.project(),
509 _ => None,
510 };
511
512 assert_eq!(actual, None, "{sql}");
513 }
514 }
515
516 #[test]
517 fn query_plan_wraps_only_present_terminal_stages() {
518 assert!(matches!(
519 statement_plan("SELECT * FROM Item"),
520 StatementPlan::Query(QueryPlan::Project(_))
521 ));
522 assert!(matches!(
523 statement_plan("SELECT * FROM Item ORDER BY id"),
524 StatementPlan::Query(QueryPlan::SelectOrderBy(_))
525 ));
526 assert!(matches!(
527 statement_plan("SELECT * FROM Item LIMIT 3"),
528 StatementPlan::Query(QueryPlan::Limit(LimitPlan {
529 input: LimitInputPlan::Project(_),
530 ..
531 }))
532 ));
533 assert!(matches!(
534 statement_plan("SELECT * FROM Item OFFSET 2"),
535 StatementPlan::Query(QueryPlan::Offset(OffsetPlan {
536 input: OffsetInputPlan::Project(_),
537 ..
538 }))
539 ));
540 assert!(matches!(
541 statement_plan("SELECT * FROM Item ORDER BY id OFFSET 2"),
542 StatementPlan::Query(QueryPlan::Offset(OffsetPlan {
543 input: OffsetInputPlan::SelectOrderBy(_),
544 ..
545 }))
546 ));
547 assert!(matches!(
548 statement_plan("SELECT * FROM Item ORDER BY id LIMIT 3"),
549 StatementPlan::Query(QueryPlan::Limit(LimitPlan {
550 input: LimitInputPlan::SelectOrderBy(_),
551 ..
552 }))
553 ));
554 assert!(matches!(
555 statement_plan("SELECT * FROM Item LIMIT 3 OFFSET 2"),
556 StatementPlan::Query(QueryPlan::Limit(LimitPlan {
557 count: ExprPlan::Literal(Literal::Number(limit)),
558 input: LimitInputPlan::Offset(OffsetPlan {
559 count: ExprPlan::Literal(Literal::Number(offset)),
560 input: OffsetInputPlan::Project(_),
561 }),
562 })) if limit == 3 && offset == 2
563 ));
564 assert!(matches!(
565 statement_plan("SELECT * FROM Item ORDER BY id LIMIT 3 OFFSET 2"),
566 StatementPlan::Query(QueryPlan::Limit(LimitPlan {
567 input: LimitInputPlan::Offset(OffsetPlan {
568 input: OffsetInputPlan::SelectOrderBy(SelectOrderByPlan { .. }),
569 ..
570 }),
571 ..
572 }))
573 ));
574 }
575
576 #[test]
577 fn query_plan_places_select_distinct_after_order_by() {
578 assert!(matches!(
579 statement_plan("SELECT DISTINCT * FROM Item"),
580 StatementPlan::Query(QueryPlan::Distinct(DistinctPlan {
581 input: DistinctInputPlan::Project(_),
582 }))
583 ));
584 assert!(matches!(
585 statement_plan("SELECT DISTINCT * FROM Item ORDER BY id"),
586 StatementPlan::Query(QueryPlan::Distinct(DistinctPlan {
587 input: DistinctInputPlan::SelectOrderBy(_),
588 }))
589 ));
590 assert!(matches!(
591 statement_plan("SELECT DISTINCT * FROM Item OFFSET 2"),
592 StatementPlan::Query(QueryPlan::Offset(OffsetPlan {
593 input: OffsetInputPlan::Distinct(DistinctPlan {
594 input: DistinctInputPlan::Project(_),
595 }),
596 ..
597 }))
598 ));
599 assert!(matches!(
600 statement_plan("SELECT DISTINCT * FROM Item ORDER BY id OFFSET 2"),
601 StatementPlan::Query(QueryPlan::Offset(OffsetPlan {
602 input: OffsetInputPlan::Distinct(DistinctPlan {
603 input: DistinctInputPlan::SelectOrderBy(_),
604 }),
605 ..
606 }))
607 ));
608 assert!(matches!(
609 statement_plan("SELECT DISTINCT * FROM Item LIMIT 3"),
610 StatementPlan::Query(QueryPlan::Limit(LimitPlan {
611 input: LimitInputPlan::Distinct(DistinctPlan {
612 input: DistinctInputPlan::Project(_),
613 }),
614 ..
615 }))
616 ));
617 assert!(matches!(
618 statement_plan("SELECT DISTINCT * FROM Item ORDER BY id LIMIT 3"),
619 StatementPlan::Query(QueryPlan::Limit(LimitPlan {
620 input: LimitInputPlan::Distinct(DistinctPlan {
621 input: DistinctInputPlan::SelectOrderBy(_),
622 }),
623 ..
624 }))
625 ));
626 assert!(matches!(
627 statement_plan("SELECT DISTINCT * FROM Item LIMIT 3 OFFSET 2"),
628 StatementPlan::Query(QueryPlan::Limit(LimitPlan {
629 input: LimitInputPlan::Offset(OffsetPlan {
630 input: OffsetInputPlan::Distinct(DistinctPlan {
631 input: DistinctInputPlan::Project(_),
632 }),
633 ..
634 }),
635 ..
636 }))
637 ));
638 assert!(matches!(
639 statement_plan("SELECT DISTINCT * FROM Item ORDER BY id LIMIT 3 OFFSET 2"),
640 StatementPlan::Query(QueryPlan::Limit(LimitPlan {
641 input: LimitInputPlan::Offset(OffsetPlan {
642 input: OffsetInputPlan::Distinct(DistinctPlan {
643 input: DistinctInputPlan::SelectOrderBy(_),
644 }),
645 ..
646 }),
647 ..
648 }))
649 ));
650 }
651
652 #[test]
653 fn query_plan_preserves_values_terminal_stage_relations() {
654 assert!(matches!(
655 statement_plan("VALUES (1)"),
656 StatementPlan::Query(QueryPlan::Values(_))
657 ));
658 assert!(matches!(
659 statement_plan("VALUES (1) ORDER BY column1"),
660 StatementPlan::Query(QueryPlan::ValuesOrderBy(_))
661 ));
662 assert!(matches!(
663 statement_plan("VALUES (1) OFFSET 2"),
664 StatementPlan::Query(QueryPlan::Offset(OffsetPlan {
665 input: OffsetInputPlan::Values(_),
666 ..
667 }))
668 ));
669 assert!(matches!(
670 statement_plan("VALUES (1) ORDER BY column1 OFFSET 2"),
671 StatementPlan::Query(QueryPlan::Offset(OffsetPlan {
672 input: OffsetInputPlan::ValuesOrderBy(_),
673 ..
674 }))
675 ));
676 assert!(matches!(
677 statement_plan("VALUES (1) LIMIT 3"),
678 StatementPlan::Query(QueryPlan::Limit(LimitPlan {
679 input: LimitInputPlan::Values(_),
680 ..
681 }))
682 ));
683 assert!(matches!(
684 statement_plan("VALUES (1) ORDER BY column1 LIMIT 3"),
685 StatementPlan::Query(QueryPlan::Limit(LimitPlan {
686 input: LimitInputPlan::ValuesOrderBy(_),
687 ..
688 }))
689 ));
690 assert!(matches!(
691 statement_plan("VALUES (1) LIMIT 3 OFFSET 2"),
692 StatementPlan::Query(QueryPlan::Limit(LimitPlan {
693 input: LimitInputPlan::Offset(OffsetPlan {
694 input: OffsetInputPlan::Values(_),
695 ..
696 }),
697 ..
698 }))
699 ));
700 assert!(matches!(
701 statement_plan("VALUES (1) ORDER BY column1 LIMIT 3 OFFSET 2"),
702 StatementPlan::Query(QueryPlan::Limit(LimitPlan {
703 input: LimitInputPlan::Offset(OffsetPlan {
704 input: OffsetInputPlan::ValuesOrderBy(ValuesOrderByPlan { .. }),
705 ..
706 }),
707 ..
708 }))
709 ));
710 }
711}