Skip to main content

gluesql_core/query_builder/select/
project.rs

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