gluesql_core/ast_builder/select/
having.rs

1use {
2    super::Prebuild,
3    crate::{
4        ast::Select,
5        ast_builder::{
6            ExprNode, GroupByNode, LimitNode, OffsetNode, OrderByExprList, OrderByNode,
7            ProjectNode, QueryNode, SelectItemList, TableFactorNode,
8        },
9        result::Result,
10    },
11};
12
13#[derive(Clone, Debug)]
14pub enum PrevNode<'a> {
15    GroupBy(GroupByNode<'a>),
16}
17
18impl<'a> Prebuild<Select> for PrevNode<'a> {
19    fn prebuild(self) -> Result<Select> {
20        match self {
21            Self::GroupBy(node) => node.prebuild(),
22        }
23    }
24}
25
26impl<'a> From<GroupByNode<'a>> for PrevNode<'a> {
27    fn from(node: GroupByNode<'a>) -> Self {
28        PrevNode::GroupBy(node)
29    }
30}
31
32#[derive(Clone, Debug)]
33pub struct HavingNode<'a> {
34    prev_node: PrevNode<'a>,
35    expr: ExprNode<'a>,
36}
37
38impl<'a> HavingNode<'a> {
39    pub fn new<N: Into<PrevNode<'a>>, T: Into<ExprNode<'a>>>(prev_node: N, expr: T) -> Self {
40        Self {
41            prev_node: prev_node.into(),
42            expr: expr.into(),
43        }
44    }
45
46    pub fn offset<T: Into<ExprNode<'a>>>(self, expr: T) -> OffsetNode<'a> {
47        OffsetNode::new(self, expr)
48    }
49
50    pub fn limit<T: Into<ExprNode<'a>>>(self, expr: T) -> LimitNode<'a> {
51        LimitNode::new(self, expr)
52    }
53
54    pub fn project<T: Into<SelectItemList<'a>>>(self, select_items: T) -> ProjectNode<'a> {
55        ProjectNode::new(self, select_items)
56    }
57
58    pub fn order_by<T: Into<OrderByExprList<'a>>>(self, expr_list: T) -> OrderByNode<'a> {
59        OrderByNode::new(self, expr_list)
60    }
61
62    pub fn alias_as(self, table_alias: &'a str) -> TableFactorNode<'a> {
63        QueryNode::HavingNode(self).alias_as(table_alias)
64    }
65}
66
67impl<'a> Prebuild<Select> for HavingNode<'a> {
68    fn prebuild(self) -> Result<Select> {
69        let mut select: Select = self.prev_node.prebuild()?;
70        select.having = Some(self.expr.try_into()?);
71
72        Ok(select)
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use crate::ast_builder::{Build, table, test};
79
80    #[test]
81    fn having() {
82        // group by node -> having node -> offset node
83        let actual = table("Bar")
84            .select()
85            .filter("id IS NULL")
86            .group_by("id, (a + name)")
87            .having("COUNT(id) > 10")
88            .offset(10)
89            .build();
90        let expected = "
91            SELECT * FROM Bar
92            WHERE id IS NULL
93            GROUP BY id, (a + name)
94            HAVING COUNT(id) > 10
95            OFFSET 10
96        ";
97        test(actual, expected);
98
99        // group by node -> having node -> limit node
100        let actual = table("Bar")
101            .select()
102            .filter("id IS NULL")
103            .group_by("id, (a + name)")
104            .having("COUNT(id) > 10")
105            .limit(10)
106            .build();
107        let expected = "
108            SELECT * FROM Bar
109            WHERE id IS NULL
110            GROUP BY id, (a + name)
111            HAVING COUNT(id) > 10
112            LIMIT 10
113            ";
114        test(actual, expected);
115
116        // group by node -> having node -> project node
117        let actual = table("Bar")
118            .select()
119            .filter("id IS NULL")
120            .group_by("id, (a + name)")
121            .having("COUNT(id) > 10")
122            .project(vec!["id", "(a + name) AS b", "COUNT(id) AS c"])
123            .build();
124        let expected = "
125            SELECT id, (a + name) AS b, COUNT(id) AS c
126            FROM Bar
127            WHERE id IS NULL
128            GROUP BY id, (a + name)
129            HAVING COUNT(id) > 10
130        ";
131        test(actual, expected);
132
133        // group by node -> having node -> build
134        let actual = table("Bar")
135            .select()
136            .filter("id IS NULL")
137            .group_by("id, (a + name)")
138            .having("COUNT(id) > 10")
139            .build();
140        let expected = "
141                SELECT * FROM Bar
142                WHERE id IS NULL
143                GROUP BY id, (a + name)
144                HAVING COUNT(id) > 10
145            ";
146        test(actual, expected);
147
148        // select -> group by -> having -> derived subquery
149        let actual = table("Foo")
150            .select()
151            .group_by("a")
152            .having("a > 1")
153            .alias_as("Sub")
154            .select()
155            .build();
156        let expected = "SELECT * FROM (SELECT * FROM Foo GROUP BY a HAVING a > 1) Sub";
157        test(actual, expected);
158    }
159}