Skip to main content

gluesql_core/plan/statement/
query.rs

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}