Skip to content

Commit 156b6ff

Browse files
committed
Support multi-column aliases in SELECT items for Databricks
Spark SQL grammar allows parenthesized identifier lists as SELECT item aliases: namedExpression: expression (AS? (identifier | identifierList))? identifierList: '(' identifier (',' identifier)* ')' This enables syntax like: SELECT stack(2, 'a', 'b', 'c', 'd') AS (col1, col2)
1 parent 6f8e7b8 commit 156b6ff

File tree

7 files changed

+64
-0
lines changed

7 files changed

+64
-0
lines changed

src/ast/query.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,15 @@ pub enum SelectItem {
872872
/// The alias for the expression.
873873
alias: Ident,
874874
},
875+
/// An expression, followed by `[ AS ] (alias1, alias2, ...)`
876+
///
877+
/// [Spark SQL](https://spark.apache.org/docs/latest/sql-ref-syntax-qry-select.html)
878+
ExprWithAliases {
879+
/// The expression being projected.
880+
expr: Expr,
881+
/// The list of aliases for the expression.
882+
aliases: Vec<Ident>,
883+
},
875884
/// An expression, followed by a wildcard expansion.
876885
/// e.g. `alias.*`, `STRUCT<STRING>('foo').*`
877886
QualifiedWildcard(SelectItemQualifiedWildcardKind, WildcardAdditionalOptions),
@@ -1175,6 +1184,12 @@ impl fmt::Display for SelectItem {
11751184
f.write_str(" AS ")?;
11761185
alias.fmt(f)
11771186
}
1187+
SelectItem::ExprWithAliases { expr, aliases } => {
1188+
expr.fmt(f)?;
1189+
f.write_str(" AS (")?;
1190+
display_comma_separated(aliases).fmt(f)?;
1191+
f.write_str(")")
1192+
}
11781193
SelectItem::QualifiedWildcard(kind, additional_options) => {
11791194
kind.fmt(f)?;
11801195
additional_options.fmt(f)

src/ast/spans.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1821,6 +1821,9 @@ impl Spanned for SelectItem {
18211821
match self {
18221822
SelectItem::UnnamedExpr(expr) => expr.span(),
18231823
SelectItem::ExprWithAlias { expr, alias } => expr.span().union(&alias.span),
1824+
SelectItem::ExprWithAliases { expr, aliases } => {
1825+
union_spans(iter::once(expr.span()).chain(aliases.iter().map(|i| i.span)))
1826+
}
18241827
SelectItem::QualifiedWildcard(kind, wildcard_additional_options) => union_spans(
18251828
[kind.span()]
18261829
.into_iter()

src/dialect/databricks.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,8 @@ impl Dialect for DatabricksDialect {
9090
fn supports_optimize_table(&self) -> bool {
9191
true
9292
}
93+
94+
fn supports_select_item_multi_column_alias(&self) -> bool {
95+
true
96+
}
9397
}

src/dialect/generic.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,4 +288,8 @@ impl Dialect for GenericDialect {
288288
fn supports_comma_separated_trim(&self) -> bool {
289289
true
290290
}
291+
292+
fn supports_select_item_multi_column_alias(&self) -> bool {
293+
true
294+
}
291295
}

src/dialect/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1664,6 +1664,17 @@ pub trait Dialect: Debug + Any {
16641664
fn supports_comma_separated_trim(&self) -> bool {
16651665
false
16661666
}
1667+
1668+
/// Returns true if the dialect supports parenthesized multi-column
1669+
/// aliases in SELECT items. For example:
1670+
/// ```sql
1671+
/// SELECT stack(2, 'a', 'b') AS (col1, col2)
1672+
/// ```
1673+
///
1674+
/// [Spark SQL](https://spark.apache.org/docs/latest/sql-ref-syntax-qry-select.html)
1675+
fn supports_select_item_multi_column_alias(&self) -> bool {
1676+
false
1677+
}
16671678
}
16681679

16691680
/// Operators for which precedence must be defined.

src/parser/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18074,6 +18074,19 @@ impl<'a> Parser<'a> {
1807418074
self.parse_wildcard_additional_options(wildcard_token)?,
1807518075
))
1807618076
}
18077+
expr if self.dialect.supports_select_item_multi_column_alias()
18078+
&& self.peek_keyword(Keyword::AS)
18079+
&& self.peek_nth_token(1).token == Token::LParen =>
18080+
{
18081+
self.expect_keyword(Keyword::AS)?;
18082+
self.expect_token(&Token::LParen)?;
18083+
let aliases = self.parse_comma_separated(|p| p.parse_identifier())?;
18084+
self.expect_token(&Token::RParen)?;
18085+
Ok(SelectItem::ExprWithAliases {
18086+
expr: maybe_prefixed_expr(expr, prefix),
18087+
aliases,
18088+
})
18089+
}
1807718090
expr => self
1807818091
.maybe_parse_select_item_alias()
1807918092
.map(|alias| match alias {

tests/sqlparser_databricks.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,3 +644,17 @@ fn parse_databricks_json_accessor() {
644644
"SELECT raw:store.bicycle.price::DOUBLE FROM store_data",
645645
);
646646
}
647+
648+
#[test]
649+
fn parse_select_item_multi_column_alias() {
650+
databricks_and_generic().verified_stmt("SELECT stack(2, 'a', 'b', 'c', 'd') AS (col1, col2)");
651+
652+
databricks_and_generic()
653+
.verified_stmt("SELECT stack(2, 'a', 'b', 'c', 'd') AS (col1, col2) FROM t");
654+
655+
assert!(
656+
all_dialects_where(|d| !d.supports_select_item_multi_column_alias())
657+
.parse_sql_statements("SELECT stack(2, 'a', 'b') AS (col1, col2)")
658+
.is_err()
659+
);
660+
}

0 commit comments

Comments
 (0)