Skip to content

Commit 7708a40

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 ea9b413 commit 7708a40

File tree

7 files changed

+65
-0
lines changed

7 files changed

+65
-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
@@ -99,4 +99,8 @@ impl Dialect for DatabricksDialect {
9999
fn supports_bang_not_operator(&self) -> bool {
100100
true
101101
}
102+
103+
fn supports_select_item_multi_column_alias(&self) -> bool {
104+
true
105+
}
102106
}

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
@@ -1670,6 +1670,17 @@ pub trait Dialect: Debug + Any {
16701670
fn supports_comma_separated_trim(&self) -> bool {
16711671
false
16721672
}
1673+
1674+
/// Returns true if the dialect supports parenthesized multi-column
1675+
/// aliases in SELECT items. For example:
1676+
/// ```sql
1677+
/// SELECT stack(2, 'a', 'b') AS (col1, col2)
1678+
/// ```
1679+
///
1680+
/// [Spark SQL](https://spark.apache.org/docs/latest/sql-ref-syntax-qry-select.html)
1681+
fn supports_select_item_multi_column_alias(&self) -> bool {
1682+
false
1683+
}
16731684
}
16741685

16751686
/// 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_common.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18754,3 +18754,18 @@ fn test_wildcard_func_arg() {
1875418754
dialects.verified_expr("HASH(* EXCLUDE (col1))");
1875518755
dialects.verified_expr("HASH(* EXCLUDE (col1, col2))");
1875618756
}
18757+
18758+
#[test]
18759+
fn parse_select_item_multi_column_alias() {
18760+
all_dialects_where(|d| d.supports_select_item_multi_column_alias())
18761+
.verified_stmt("SELECT stack(2, 'a', 'b', 'c', 'd') AS (col1, col2)");
18762+
18763+
all_dialects_where(|d| d.supports_select_item_multi_column_alias())
18764+
.verified_stmt("SELECT stack(2, 'a', 'b', 'c', 'd') AS (col1, col2) FROM t");
18765+
18766+
assert!(
18767+
all_dialects_where(|d| !d.supports_select_item_multi_column_alias())
18768+
.parse_sql_statements("SELECT stack(2, 'a', 'b') AS (col1, col2)")
18769+
.is_err()
18770+
);
18771+
}

0 commit comments

Comments
 (0)