Skip to main content

gluesql_core/query_builder/select/
group_by.rs

1use {
2    super::{
3        BuildAggregationInputPlan, BuildAggregationPlan, BuildProjectInputPlan, BuildSelect,
4        DistinctNode,
5    },
6    crate::{
7        ast::Select,
8        plan::{AggregationInputPlan, AggregationPlan, ProjectInputPlan},
9        query_builder::{
10            ExprList, ExprNode, FilterNode, HavingNode, InnerHashJoinNode, InnerJoinConditionNode,
11            InnerNestedLoopJoinNode, LeftOuterHashJoinNode, LeftOuterJoinConditionNode,
12            LeftOuterNestedLoopJoinNode, LimitNode, OffsetNode, OrderByExprList, ProjectNode,
13            QueryNode, SelectItemList, SelectNode, SelectOrderByNode, SourceNode,
14        },
15        result::Result,
16    },
17};
18
19#[derive(Clone, Debug)]
20pub(super) enum PrevNode<'a> {
21    Select(SelectNode<'a>),
22    InnerNestedLoop(Box<InnerNestedLoopJoinNode<'a>>),
23    LeftOuterNestedLoop(Box<LeftOuterNestedLoopJoinNode<'a>>),
24    InnerHash(Box<InnerHashJoinNode<'a>>),
25    LeftOuterHash(Box<LeftOuterHashJoinNode<'a>>),
26    InnerCondition(Box<InnerJoinConditionNode<'a>>),
27    LeftOuterCondition(Box<LeftOuterJoinConditionNode<'a>>),
28    Filter(FilterNode<'a>),
29}
30
31impl BuildAggregationInputPlan for PrevNode<'_> {
32    fn build_aggregation_input_plan(self) -> Result<AggregationInputPlan> {
33        match self {
34            Self::Select(node) => node.build_aggregation_input_plan(),
35            Self::InnerNestedLoop(node) => node.build_aggregation_input_plan(),
36            Self::LeftOuterNestedLoop(node) => node.build_aggregation_input_plan(),
37            Self::InnerHash(node) => node.build_aggregation_input_plan(),
38            Self::LeftOuterHash(node) => node.build_aggregation_input_plan(),
39            Self::InnerCondition(node) => node.build_aggregation_input_plan(),
40            Self::LeftOuterCondition(node) => node.build_aggregation_input_plan(),
41            Self::Filter(node) => node.build_aggregation_input_plan(),
42        }
43    }
44}
45
46impl BuildSelect for PrevNode<'_> {
47    fn build_select(self) -> Result<Select> {
48        match self {
49            Self::Select(node) => node.build_select(),
50            Self::InnerNestedLoop(node) => node.build_select(),
51            Self::LeftOuterNestedLoop(node) => node.build_select(),
52            Self::InnerHash(node) => node.build_select(),
53            Self::LeftOuterHash(node) => node.build_select(),
54            Self::InnerCondition(node) => node.build_select(),
55            Self::LeftOuterCondition(node) => node.build_select(),
56            Self::Filter(node) => node.build_select(),
57        }
58    }
59}
60
61impl<'a> From<SelectNode<'a>> for PrevNode<'a> {
62    fn from(node: SelectNode<'a>) -> Self {
63        PrevNode::Select(node)
64    }
65}
66
67impl<'a> From<InnerNestedLoopJoinNode<'a>> for PrevNode<'a> {
68    fn from(node: InnerNestedLoopJoinNode<'a>) -> Self {
69        Self::InnerNestedLoop(Box::new(node))
70    }
71}
72
73impl<'a> From<LeftOuterNestedLoopJoinNode<'a>> for PrevNode<'a> {
74    fn from(node: LeftOuterNestedLoopJoinNode<'a>) -> Self {
75        Self::LeftOuterNestedLoop(Box::new(node))
76    }
77}
78
79impl<'a> From<InnerHashJoinNode<'a>> for PrevNode<'a> {
80    fn from(node: InnerHashJoinNode<'a>) -> Self {
81        Self::InnerHash(Box::new(node))
82    }
83}
84
85impl<'a> From<LeftOuterHashJoinNode<'a>> for PrevNode<'a> {
86    fn from(node: LeftOuterHashJoinNode<'a>) -> Self {
87        Self::LeftOuterHash(Box::new(node))
88    }
89}
90
91impl<'a> From<InnerJoinConditionNode<'a>> for PrevNode<'a> {
92    fn from(node: InnerJoinConditionNode<'a>) -> Self {
93        Self::InnerCondition(Box::new(node))
94    }
95}
96
97impl<'a> From<LeftOuterJoinConditionNode<'a>> for PrevNode<'a> {
98    fn from(node: LeftOuterJoinConditionNode<'a>) -> Self {
99        Self::LeftOuterCondition(Box::new(node))
100    }
101}
102
103impl<'a> From<FilterNode<'a>> for PrevNode<'a> {
104    fn from(node: FilterNode<'a>) -> Self {
105        PrevNode::Filter(node)
106    }
107}
108
109#[derive(Clone, Debug)]
110pub struct GroupByNode<'a> {
111    prev_node: PrevNode<'a>,
112    expr_list: ExprList<'a>,
113}
114
115impl<'a> GroupByNode<'a> {
116    pub(super) fn new<N: Into<PrevNode<'a>>, T: Into<ExprList<'a>>>(
117        prev_node: N,
118        expr_list: T,
119    ) -> Self {
120        Self {
121            prev_node: prev_node.into(),
122            expr_list: expr_list.into(),
123        }
124    }
125
126    pub fn having<T: Into<ExprNode<'a>>>(self, expr: T) -> HavingNode<'a> {
127        HavingNode::new(self, expr)
128    }
129
130    pub fn offset<T: Into<ExprNode<'a>>>(self, expr: T) -> OffsetNode<'a> {
131        OffsetNode::new(self, expr)
132    }
133
134    pub fn limit<T: Into<ExprNode<'a>>>(self, expr: T) -> LimitNode<'a> {
135        LimitNode::new(self, expr)
136    }
137
138    pub fn project<T: Into<SelectItemList<'a>>>(self, select_items: T) -> ProjectNode<'a> {
139        ProjectNode::new(self, select_items)
140    }
141
142    pub fn order_by<T: Into<OrderByExprList<'a>>>(self, expr_list: T) -> SelectOrderByNode<'a> {
143        SelectOrderByNode::new(self, expr_list)
144    }
145
146    pub fn distinct(self) -> DistinctNode<'a> {
147        DistinctNode::new(self)
148    }
149
150    pub fn alias_as(self, table_alias: &'a str) -> SourceNode<'a> {
151        QueryNode::GroupByNode(self).alias_as(table_alias)
152    }
153}
154
155impl BuildAggregationPlan for GroupByNode<'_> {
156    fn build_aggregation_plan(self) -> Result<AggregationPlan> {
157        Ok(AggregationPlan {
158            input: self.prev_node.build_aggregation_input_plan()?,
159            group_by: self.expr_list.build_exprs_plan()?,
160            aggregate_slots: Vec::new(),
161        })
162    }
163}
164
165impl BuildProjectInputPlan for GroupByNode<'_> {
166    fn build_project_input_plan(self) -> Result<ProjectInputPlan> {
167        self.build_aggregation_plan()
168            .map(ProjectInputPlan::Aggregation)
169    }
170}
171
172impl BuildSelect for GroupByNode<'_> {
173    fn build_select(self) -> Result<Select> {
174        let mut select = self.prev_node.build_select()?;
175        select.group_by = self.expr_list.build_exprs()?;
176
177        Ok(select)
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use {
184        crate::{
185            plan::{
186                AggregationInputPlan, AggregationPlan, HashJoinInputPlan, HashJoinPlan,
187                InnerJoinInputPlan, InnerJoinPlan, ProjectInputPlan, ProjectPlan, ProjectionPlan,
188                QueryPlan, SourcePlan, StatementPlan, TableAccessPlan, TableSourcePlan,
189            },
190            query_builder::{Build, SelectItemList, col, table, test_query_builder},
191        },
192        pretty_assertions::assert_eq,
193    };
194
195    #[test]
196    fn group_by() {
197        // select node -> group by node -> build
198        let actual = table("Foo").select().group_by("a");
199        let expected = "SELECT * FROM Foo GROUP BY a";
200        test_query_builder(actual, expected);
201
202        // inner nested loop join node -> group by node -> build
203        let actual = table("Foo").select().join("Bar").group_by("b");
204        let expected = "SELECT * FROM Foo JOIN Bar GROUP BY b";
205        test_query_builder(actual, expected);
206
207        // inner nested loop join node -> group by node -> build
208        let actual = table("Foo").select().join_as("Bar", "B").group_by("b");
209        let expected = "SELECT * FROM Foo JOIN Bar AS B GROUP BY b";
210        test_query_builder(actual, expected);
211
212        // left outer nested loop join node -> group by node -> build
213        let actual = table("Foo").select().left_join("Bar").group_by("b");
214        let expected = "SELECT * FROM Foo LEFT JOIN Bar GROUP BY b";
215        test_query_builder(actual, expected);
216
217        // left outer nested loop join node -> group by node -> build
218        let actual = table("Foo").select().left_join_as("Bar", "B").group_by("b");
219        let expected = "SELECT * FROM Foo LEFT JOIN Bar AS B GROUP BY b";
220        test_query_builder(actual, expected);
221
222        // inner join condition node -> group by node -> build
223        let actual = table("Foo")
224            .select()
225            .join("Bar")
226            .on("Foo.id = Bar.id")
227            .group_by("b");
228        let expected = "SELECT * FROM Foo JOIN Bar ON Foo.id = Bar.id GROUP BY b";
229        test_query_builder(actual, expected);
230
231        // filter node -> group by node -> build
232        let actual = table("Bar")
233            .select()
234            .filter(col("id").is_null())
235            .group_by("id, (a + name)");
236        let expected = "
237                SELECT * FROM Bar
238                WHERE id IS NULL
239                GROUP BY id, (a + name)
240            ";
241        test_query_builder(actual, expected);
242
243        // inner hash join node -> group by node -> build
244        let actual = table("Player")
245            .select()
246            .join("PlayerItem")
247            .hash_executor("PlayerItem.user_id", "Player.id")
248            .group_by("PlayerItem.category")
249            .build();
250        let expected = {
251            let join = InnerJoinPlan {
252                input: InnerJoinInputPlan::Hash(HashJoinPlan {
253                    input: HashJoinInputPlan::Source(SourcePlan::Table(TableSourcePlan {
254                        name: "Player".to_owned(),
255                        alias: None,
256                        access: TableAccessPlan::FullScan,
257                    })),
258                    right: SourcePlan::Table(TableSourcePlan {
259                        name: "PlayerItem".to_owned(),
260                        alias: None,
261                        access: TableAccessPlan::FullScan,
262                    }),
263                    input_key: col("Player.id").build_expr_plan().unwrap(),
264                    right_key: col("PlayerItem.user_id").build_expr_plan().unwrap(),
265                    right_filter: None,
266                }),
267            };
268            let project = ProjectPlan {
269                input: ProjectInputPlan::Aggregation(AggregationPlan {
270                    input: AggregationInputPlan::InnerJoin(Box::new(join)),
271                    group_by: vec![col("PlayerItem.category").build_expr_plan().unwrap()],
272                    aggregate_slots: Vec::new(),
273                }),
274                projection: ProjectionPlan::SelectItems(
275                    SelectItemList::from("*").build_select_items_plan().unwrap(),
276                ),
277            };
278
279            Ok(StatementPlan::Query(QueryPlan::Project(project)))
280        };
281        assert_eq!(actual, expected);
282
283        // select -> group by -> derived subquery
284        let actual = table("Foo").select().group_by("a").alias_as("Sub").select();
285        let expected = "SELECT * FROM (SELECT * FROM Foo GROUP BY a) Sub";
286        test_query_builder(actual, expected);
287    }
288}