gluesql_core/query_builder/select/
project.rs1use {
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 let actual = table("Good").select().project("id");
224 let expected = "SELECT id FROM Good";
225 test_query_builder(actual, expected);
226
227 let actual = table("Group").select().project("*, Group.*, name");
229 let expected = "SELECT *, Group.*, name FROM Group";
230 test_query_builder(actual, expected);
231
232 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 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 let actual = table("Foo").select().project("*");
260 let expected = "SELECT * FROM Foo";
261 test_query_builder(actual, expected);
262
263 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 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 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 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 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 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 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}