Skip to main content

gluesql_core/query_builder/select/
having.rs

1use {
2    super::{
3        BuildAggregationInputPlan, BuildAggregationPlan, BuildHavingPlan, BuildProjectInputPlan,
4        BuildSelect, DistinctNode,
5    },
6    crate::{
7        ast::Select,
8        plan::{AggregationPlan, HavingPlan, ProjectInputPlan},
9        query_builder::{
10            ExprNode, FilterNode, GroupByNode, 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    GroupBy(GroupByNode<'a>),
30}
31
32impl PrevNode<'_> {
33    fn build_aggregation_plan(self) -> Result<AggregationPlan> {
34        match self {
35            Self::Select(node) => node.build_aggregation_input_plan(),
36            Self::InnerNestedLoop(node) => node.build_aggregation_input_plan(),
37            Self::LeftOuterNestedLoop(node) => node.build_aggregation_input_plan(),
38            Self::InnerHash(node) => node.build_aggregation_input_plan(),
39            Self::LeftOuterHash(node) => node.build_aggregation_input_plan(),
40            Self::InnerCondition(node) => node.build_aggregation_input_plan(),
41            Self::LeftOuterCondition(node) => node.build_aggregation_input_plan(),
42            Self::Filter(node) => node.build_aggregation_input_plan(),
43            Self::GroupBy(node) => return node.build_aggregation_plan(),
44        }
45        .map(|input| AggregationPlan {
46            input,
47            group_by: Vec::new(),
48            aggregate_slots: Vec::new(),
49        })
50    }
51}
52
53impl BuildSelect for PrevNode<'_> {
54    fn build_select(self) -> Result<Select> {
55        match self {
56            Self::Select(node) => node.build_select(),
57            Self::InnerNestedLoop(node) => node.build_select(),
58            Self::LeftOuterNestedLoop(node) => node.build_select(),
59            Self::InnerHash(node) => node.build_select(),
60            Self::LeftOuterHash(node) => node.build_select(),
61            Self::InnerCondition(node) => node.build_select(),
62            Self::LeftOuterCondition(node) => node.build_select(),
63            Self::Filter(node) => node.build_select(),
64            Self::GroupBy(node) => node.build_select(),
65        }
66    }
67}
68
69impl<'a> From<SelectNode<'a>> for PrevNode<'a> {
70    fn from(node: SelectNode<'a>) -> Self {
71        Self::Select(node)
72    }
73}
74
75impl<'a> From<InnerNestedLoopJoinNode<'a>> for PrevNode<'a> {
76    fn from(node: InnerNestedLoopJoinNode<'a>) -> Self {
77        Self::InnerNestedLoop(Box::new(node))
78    }
79}
80
81impl<'a> From<LeftOuterNestedLoopJoinNode<'a>> for PrevNode<'a> {
82    fn from(node: LeftOuterNestedLoopJoinNode<'a>) -> Self {
83        Self::LeftOuterNestedLoop(Box::new(node))
84    }
85}
86
87impl<'a> From<InnerHashJoinNode<'a>> for PrevNode<'a> {
88    fn from(node: InnerHashJoinNode<'a>) -> Self {
89        Self::InnerHash(Box::new(node))
90    }
91}
92
93impl<'a> From<LeftOuterHashJoinNode<'a>> for PrevNode<'a> {
94    fn from(node: LeftOuterHashJoinNode<'a>) -> Self {
95        Self::LeftOuterHash(Box::new(node))
96    }
97}
98
99impl<'a> From<InnerJoinConditionNode<'a>> for PrevNode<'a> {
100    fn from(node: InnerJoinConditionNode<'a>) -> Self {
101        Self::InnerCondition(Box::new(node))
102    }
103}
104
105impl<'a> From<LeftOuterJoinConditionNode<'a>> for PrevNode<'a> {
106    fn from(node: LeftOuterJoinConditionNode<'a>) -> Self {
107        Self::LeftOuterCondition(Box::new(node))
108    }
109}
110
111impl<'a> From<FilterNode<'a>> for PrevNode<'a> {
112    fn from(node: FilterNode<'a>) -> Self {
113        Self::Filter(node)
114    }
115}
116
117impl<'a> From<GroupByNode<'a>> for PrevNode<'a> {
118    fn from(node: GroupByNode<'a>) -> Self {
119        Self::GroupBy(node)
120    }
121}
122
123#[derive(Clone, Debug)]
124pub struct HavingNode<'a> {
125    prev_node: PrevNode<'a>,
126    expr: ExprNode<'a>,
127}
128
129impl<'a> HavingNode<'a> {
130    pub(super) fn new<N: Into<PrevNode<'a>>, T: Into<ExprNode<'a>>>(prev_node: N, expr: T) -> Self {
131        Self {
132            prev_node: prev_node.into(),
133            expr: expr.into(),
134        }
135    }
136
137    pub fn offset<T: Into<ExprNode<'a>>>(self, expr: T) -> OffsetNode<'a> {
138        OffsetNode::new(self, expr)
139    }
140
141    pub fn limit<T: Into<ExprNode<'a>>>(self, expr: T) -> LimitNode<'a> {
142        LimitNode::new(self, expr)
143    }
144
145    pub fn project<T: Into<SelectItemList<'a>>>(self, select_items: T) -> ProjectNode<'a> {
146        ProjectNode::new(self, select_items)
147    }
148
149    pub fn order_by<T: Into<OrderByExprList<'a>>>(self, expr_list: T) -> SelectOrderByNode<'a> {
150        SelectOrderByNode::new(self, expr_list)
151    }
152
153    pub fn distinct(self) -> DistinctNode<'a> {
154        DistinctNode::new(self)
155    }
156
157    pub fn alias_as(self, table_alias: &'a str) -> SourceNode<'a> {
158        QueryNode::HavingNode(self).alias_as(table_alias)
159    }
160}
161
162impl BuildHavingPlan for HavingNode<'_> {
163    fn build_having_plan(self) -> Result<HavingPlan> {
164        Ok(HavingPlan {
165            input: self.prev_node.build_aggregation_plan()?,
166            expr: self.expr.build_expr_plan()?,
167        })
168    }
169}
170
171impl BuildProjectInputPlan for HavingNode<'_> {
172    fn build_project_input_plan(self) -> Result<ProjectInputPlan> {
173        self.build_having_plan().map(ProjectInputPlan::Having)
174    }
175}
176
177impl BuildSelect for HavingNode<'_> {
178    fn build_select(self) -> Result<Select> {
179        let mut select = self.prev_node.build_select()?;
180        select.having = Some(self.expr.build_expr()?);
181
182        Ok(select)
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use {
189        crate::{
190            data::Value,
191            plan::{
192                AggregationInputPlan, AggregationPlan, ExprPlan, HashJoinInputPlan, HashJoinPlan,
193                HavingPlan, InnerJoinInputPlan, InnerJoinPlan, ProjectInputPlan, ProjectPlan,
194                ProjectionPlan, QueryPlan, SelectItemPlan, SourcePlan, StatementPlan,
195                TableAccessPlan, TableSourcePlan,
196            },
197            query_builder::{
198                Build, QueryBuilderError, select::BuildQuery, table, test_query_builder,
199            },
200            result::Error,
201        },
202        pretty_assertions::assert_eq,
203    };
204
205    #[test]
206    fn prev_nodes() {
207        let actual = table("Foo").select().having("TRUE");
208        let expected = "SELECT * FROM Foo HAVING TRUE";
209        test_query_builder(actual, expected);
210
211        let actual = table("Foo").select().join("Bar").having("TRUE");
212        let expected = "SELECT * FROM Foo JOIN Bar HAVING TRUE";
213        test_query_builder(actual, expected);
214
215        let actual = table("Foo")
216            .select()
217            .join("Bar")
218            .on("Foo.id = Bar.id")
219            .having("TRUE");
220        let expected = "SELECT * FROM Foo JOIN Bar ON Foo.id = Bar.id HAVING TRUE";
221        test_query_builder(actual, expected);
222
223        let actual = table("Foo").select().filter("id > 1").having("TRUE");
224        let expected = "SELECT * FROM Foo WHERE id > 1 HAVING TRUE";
225        test_query_builder(actual, expected);
226    }
227
228    #[test]
229    fn plan_only_hash_join() {
230        let actual = table("Foo")
231            .select()
232            .join("Bar")
233            .hash_executor("Foo.id", "Bar.id")
234            .having("TRUE")
235            .build_query()
236            .map(|_| ());
237        let expected = Err(Error::QueryBuilder(
238            QueryBuilderError::HashJoinExecutorRequiresPlan,
239        ));
240        assert_eq!(actual, expected);
241
242        let actual = table("Foo")
243            .select()
244            .join("Bar")
245            .hash_executor("Foo.id", "Bar.id")
246            .having("TRUE")
247            .build();
248        let expected = Ok(StatementPlan::Query(QueryPlan::Project(ProjectPlan {
249            input: ProjectInputPlan::Having(HavingPlan {
250                input: AggregationPlan {
251                    input: AggregationInputPlan::InnerJoin(Box::new(InnerJoinPlan {
252                        input: InnerJoinInputPlan::Hash(HashJoinPlan {
253                            input: HashJoinInputPlan::Source(SourcePlan::Table(TableSourcePlan {
254                                name: "Foo".to_owned(),
255                                alias: None,
256                                access: TableAccessPlan::FullScan,
257                            })),
258                            right: SourcePlan::Table(TableSourcePlan {
259                                name: "Bar".to_owned(),
260                                alias: None,
261                                access: TableAccessPlan::FullScan,
262                            }),
263                            input_key: ExprPlan::CompoundIdentifier {
264                                alias: "Bar".to_owned(),
265                                ident: "id".to_owned(),
266                            },
267                            right_key: ExprPlan::CompoundIdentifier {
268                                alias: "Foo".to_owned(),
269                                ident: "id".to_owned(),
270                            },
271                            right_filter: None,
272                        }),
273                    })),
274                    group_by: Vec::new(),
275                    aggregate_slots: Vec::new(),
276                },
277                expr: ExprPlan::Value(Value::Bool(true)),
278            }),
279            projection: ProjectionPlan::SelectItems(vec![SelectItemPlan::Wildcard]),
280        })));
281
282        assert_eq!(actual, expected);
283    }
284
285    #[test]
286    fn having() {
287        // group by node -> having node -> offset node
288        let actual = table("Bar")
289            .select()
290            .filter("id IS NULL")
291            .group_by("id, (a + name)")
292            .having("COUNT(id) > 10")
293            .offset(10);
294        let expected = "
295            SELECT * FROM Bar
296            WHERE id IS NULL
297            GROUP BY id, (a + name)
298            HAVING COUNT(id) > 10
299            OFFSET 10
300        ";
301        test_query_builder(actual, expected);
302
303        // group by node -> having node -> limit node
304        let actual = table("Bar")
305            .select()
306            .filter("id IS NULL")
307            .group_by("id, (a + name)")
308            .having("COUNT(id) > 10")
309            .limit(10);
310        let expected = "
311            SELECT * FROM Bar
312            WHERE id IS NULL
313            GROUP BY id, (a + name)
314            HAVING COUNT(id) > 10
315            LIMIT 10
316            ";
317        test_query_builder(actual, expected);
318
319        // group by node -> having node -> project node
320        let actual = table("Bar")
321            .select()
322            .filter("id IS NULL")
323            .group_by("id, (a + name)")
324            .having("COUNT(id) > 10")
325            .project(vec!["id", "(a + name) AS b", "COUNT(id) AS c"]);
326        let expected = "
327            SELECT id, (a + name) AS b, COUNT(id) AS c
328            FROM Bar
329            WHERE id IS NULL
330            GROUP BY id, (a + name)
331            HAVING COUNT(id) > 10
332        ";
333        test_query_builder(actual, expected);
334
335        // group by node -> having node -> build
336        let actual = table("Bar")
337            .select()
338            .filter("id IS NULL")
339            .group_by("id, (a + name)")
340            .having("COUNT(id) > 10");
341        let expected = "
342                SELECT * FROM Bar
343                WHERE id IS NULL
344                GROUP BY id, (a + name)
345                HAVING COUNT(id) > 10
346            ";
347        test_query_builder(actual, expected);
348
349        // select -> group by -> having -> derived subquery
350        let actual = table("Foo")
351            .select()
352            .group_by("a")
353            .having("a > 1")
354            .alias_as("Sub")
355            .select();
356        let expected = "SELECT * FROM (SELECT * FROM Foo GROUP BY a HAVING a > 1) Sub";
357        test_query_builder(actual, expected);
358    }
359}