gluesql_core/query_builder/
select_item.rs1use {
2 super::ExprNode,
3 crate::{
4 ast::{SelectItem, ToSqlUnquoted},
5 parse_sql::parse_select_item,
6 plan::SelectItemPlan,
7 query_builder::{ExprWithAliasNode, QueryBuilderError},
8 result::{Error, Result},
9 translate::{NO_PARAMS, translate_select_item},
10 },
11};
12
13#[derive(Clone, Debug)]
14pub enum SelectItemNode<'a> {
15 SelectItem(SelectItem),
16 Expr(ExprNode<'a>),
17 Text(String),
18 ExprWithAliasNode(ExprWithAliasNode<'a>),
19}
20
21impl From<SelectItem> for SelectItemNode<'_> {
22 fn from(select_item: SelectItem) -> Self {
23 Self::SelectItem(select_item)
24 }
25}
26
27impl<'a> From<ExprNode<'a>> for SelectItemNode<'a> {
28 fn from(expr_node: ExprNode<'a>) -> Self {
29 Self::Expr(expr_node)
30 }
31}
32
33impl From<&str> for SelectItemNode<'_> {
34 fn from(select_item: &str) -> Self {
35 Self::Text(select_item.to_owned())
36 }
37}
38
39impl<'a> From<ExprWithAliasNode<'a>> for SelectItemNode<'a> {
40 fn from(expr_node: ExprWithAliasNode<'a>) -> Self {
41 Self::ExprWithAliasNode(expr_node)
42 }
43}
44
45impl SelectItemNode<'_> {
46 pub(super) fn build_select_item_plan(self) -> Result<SelectItemPlan> {
47 match self {
48 SelectItemNode::SelectItem(select_item) => Ok(select_item.into()),
49 SelectItemNode::Text(select_item) => parse_select_item(select_item)
50 .and_then(|item| translate_select_item(&item, NO_PARAMS).map(Into::into)),
51 SelectItemNode::Expr(expr_node) => {
52 let expr = expr_node
53 .clone()
54 .build_expr()
55 .map_err(|error| match error {
56 Error::QueryBuilder(
57 QueryBuilderError::HashJoinExecutorRequiresPlan
58 | QueryBuilderError::IndexByRequiresPlan,
59 ) => QueryBuilderError::ProjectionLabelRequiresAlias.into(),
60 error => error,
61 })?;
62 let label = expr.to_sql_unquoted();
63 let expr = expr_node.build_expr_plan()?;
64
65 Ok(SelectItemPlan::Expr { expr, label })
66 }
67 SelectItemNode::ExprWithAliasNode(alias_node) => {
68 let (expr, label) = alias_node.build_expr_with_alias_plan()?;
69
70 Ok(SelectItemPlan::Expr { expr, label })
71 }
72 }
73 }
74
75 pub(super) fn build_select_item(self) -> Result<SelectItem> {
76 match self {
77 SelectItemNode::SelectItem(select_item) => Ok(select_item),
78 SelectItemNode::Text(select_item) => parse_select_item(select_item)
79 .and_then(|item| translate_select_item(&item, NO_PARAMS)),
80 SelectItemNode::Expr(expr_node) => {
81 let expr = expr_node.build_expr()?;
82 let label = expr.to_sql_unquoted();
83
84 Ok(SelectItem::Expr { expr, label })
85 }
86 SelectItemNode::ExprWithAliasNode(alias_node) => {
87 let (expr, label) = alias_node.build_expr_with_alias()?;
88
89 Ok(SelectItem::Expr { expr, label })
90 }
91 }
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use {
98 crate::{
99 ast::SelectItem,
100 parse_sql::parse_select_item,
101 plan::SelectItemPlan,
102 query_builder::{
103 QueryBuilderError, SelectItemNode, col, expr, primary_key, subquery, table,
104 },
105 result::Error,
106 translate::{NO_PARAMS, translate_select_item},
107 },
108 pretty_assertions::assert_eq,
109 };
110
111 fn test(actual: SelectItemNode, expected: &str) {
112 let parsed = &parse_select_item(expected).expect(expected);
113 let expected = translate_select_item(parsed, NO_PARAMS);
114
115 assert_eq!(actual.clone().build_select_item(), expected);
116 assert_eq!(
117 actual.build_select_item_plan(),
118 expected.map(SelectItemPlan::from)
119 );
120 }
121
122 #[test]
123 fn select_item() {
124 let actual = SelectItem::Wildcard.into();
125 let expected = "*";
126 test(actual, expected);
127
128 let actual = "Foo.*".into();
129 let expected = "Foo.*";
130 test(actual, expected);
131
132 let actual = "id as hello".into();
133 let expected = "id as hello";
134 test(actual, expected);
135
136 let actual = col("id").into();
137 let expected = "id";
138 test(actual, expected);
139
140 let actual = col("id").alias_as("hello").into();
141 let expected = "id as hello";
142 test(actual, expected);
143 }
144
145 #[test]
146 fn plan_only_projection_expr_requires_alias_for_label() {
147 let actual: SelectItemNode = subquery(
148 table("Player")
149 .select()
150 .join("PlayerItem")
151 .hash_executor("PlayerItem.user_id", "Player.id"),
152 )
153 .into();
154
155 assert_eq!(
156 actual.build_select_item_plan(),
157 Err(Error::QueryBuilder(
158 QueryBuilderError::ProjectionLabelRequiresAlias
159 ))
160 );
161
162 let actual: SelectItemNode = subquery(
163 table("Player")
164 .select()
165 .join("PlayerItem")
166 .hash_executor("PlayerItem.user_id", "Player.id"),
167 )
168 .alias_as("matched")
169 .into();
170
171 assert!(matches!(
172 actual.build_select_item_plan(),
173 Ok(SelectItemPlan::Expr { label, .. }) if label == "matched"
174 ));
175
176 let actual: SelectItemNode =
177 subquery(table("Player").index_by(primary_key().eq("1")).select()).into();
178
179 assert_eq!(
180 actual.build_select_item_plan(),
181 Err(Error::QueryBuilder(
182 QueryBuilderError::ProjectionLabelRequiresAlias
183 ))
184 );
185
186 let actual: SelectItemNode = subquery(
187 table("Player")
188 .index_by(primary_key().eq("1"))
189 .select()
190 .project("id"),
191 )
192 .alias_as("indexed")
193 .into();
194
195 assert!(matches!(
196 actual.build_select_item_plan(),
197 Ok(SelectItemPlan::Expr { label, .. }) if label == "indexed"
198 ));
199
200 let actual: SelectItemNode = expr(")").into();
201 let actual = actual.build_select_item_plan().map(|_| ());
202 assert!(matches!(actual, Err(Error::Parser(_))));
203 }
204}