From acaf9683743ed917bb578b917c3fcc1b62125bb8 Mon Sep 17 00:00:00 2001 From: Yoa Bot Date: Sun, 22 Feb 2026 12:27:24 +0100 Subject: [PATCH 1/2] fix(redshift): support wildcard select items with alias (e.g. t.* AS alias) Redshift allows aliasing wildcard expressions in SELECT, e.g.: SELECT t.* AS alias FROM t Previously the parser would fail with: ParserError("Expected: end of statement, found: AS") Fix: add opt_alias field to WildcardAdditionalOptions and parse the optional AS clause when the dialect supports it. Adds supports_select_wildcard_with_alias() dialect method, enabled for RedshiftSqlDialect. --- src/ast/query.rs | 7 +++++++ src/ast/spans.rs | 4 +++- src/dialect/mod.rs | 12 ++++++++++++ src/dialect/redshift.rs | 4 ++++ src/parser/mod.rs | 11 +++++++++++ tests/sqlparser_redshift.rs | 19 +++++++++++++++++++ 6 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/ast/query.rs b/src/ast/query.rs index ff617a38e4..c54b379140 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -933,6 +933,9 @@ pub struct WildcardAdditionalOptions { pub opt_replace: Option, /// `[RENAME ...]`. pub opt_rename: Option, + /// `[AS ]`. + /// Redshift syntax: + pub opt_alias: Option, } impl Default for WildcardAdditionalOptions { @@ -944,6 +947,7 @@ impl Default for WildcardAdditionalOptions { opt_except: None, opt_replace: None, opt_rename: None, + opt_alias: None, } } } @@ -965,6 +969,9 @@ impl fmt::Display for WildcardAdditionalOptions { if let Some(rename) = &self.opt_rename { write!(f, " {rename}")?; } + if let Some(alias) = &self.opt_alias { + write!(f, " AS {alias}")?; + } Ok(()) } } diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 128fe01bee..e00cee3dce 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -1824,6 +1824,7 @@ impl Spanned for WildcardAdditionalOptions { opt_except, opt_replace, opt_rename, + opt_alias, } = self; union_spans( @@ -1832,7 +1833,8 @@ impl Spanned for WildcardAdditionalOptions { .chain(opt_exclude.as_ref().map(|i| i.span())) .chain(opt_rename.as_ref().map(|i| i.span())) .chain(opt_replace.as_ref().map(|i| i.span())) - .chain(opt_except.as_ref().map(|i| i.span())), + .chain(opt_except.as_ref().map(|i| i.span())) + .chain(opt_alias.as_ref().map(|i| i.span)), ) } } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index b1be1590de..a3bd366465 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -1512,6 +1512,18 @@ pub trait Dialect: Debug + Any { false } + /// Returns true if this dialect supports aliasing a wildcard select item. + /// + /// Example: + /// ```sql + /// SELECT t.* AS alias FROM t + /// ``` + /// + /// [Redshift](https://docs.aws.amazon.com/redshift/latest/dg/r_SELECT_list.html) + fn supports_select_wildcard_with_alias(&self) -> bool { + false + } + /// Returns true if this dialect supports the `OPTIMIZE TABLE` statement. /// /// Example: diff --git a/src/dialect/redshift.rs b/src/dialect/redshift.rs index 21958e3829..5969ee55e6 100644 --- a/src/dialect/redshift.rs +++ b/src/dialect/redshift.rs @@ -141,6 +141,10 @@ impl Dialect for RedshiftSqlDialect { true } + fn supports_select_wildcard_with_alias(&self) -> bool { + true + } + fn supports_select_exclude(&self) -> bool { true } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 16eb7a8b19..91bdb1449c 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -17847,6 +17847,16 @@ impl<'a> Parser<'a> { None }; + let opt_alias = if self.dialect.supports_select_wildcard_with_alias() { + if self.parse_keyword(Keyword::AS) { + Some(self.parse_identifier()?) + } else { + None + } + } else { + None + }; + Ok(WildcardAdditionalOptions { wildcard_token: wildcard_token.into(), opt_ilike, @@ -17854,6 +17864,7 @@ impl<'a> Parser<'a> { opt_except, opt_rename, opt_replace, + opt_alias, }) } diff --git a/tests/sqlparser_redshift.rs b/tests/sqlparser_redshift.rs index 90652ff48f..09e12b61ce 100644 --- a/tests/sqlparser_redshift.rs +++ b/tests/sqlparser_redshift.rs @@ -452,3 +452,22 @@ fn parse_vacuum() { _ => unreachable!(), } } + + +#[test] +fn test_redshift_select_wildcard_with_alias() { + // qualified wildcard with alias: t.* AS alias + redshift() + .parse_sql_statements(r#"SELECT t.* AS all_cols FROM t"#) + .unwrap(); + + // unqualified wildcard with alias + redshift() + .parse_sql_statements(r#"SELECT * AS all_cols FROM t"#) + .unwrap(); + + // mixed: regular column + qualified wildcard with alias in a multi-join query + redshift() + .parse_sql_statements(r#"SELECT a.id, b.* AS b_cols FROM a JOIN b ON (a.id = b.a_id)"#) + .unwrap(); +} From 4dfb07a50130b95d40ee17c50b78a50cc362ab5d Mon Sep 17 00:00:00 2001 From: Yoa Bot Date: Sun, 22 Feb 2026 16:38:09 +0100 Subject: [PATCH 2/2] refactor: move wildcard alias tests to sqlparser_common.rs Use all_dialects_where(|d| d.supports_select_wildcard_with_alias()) so tests apply to any dialect that enables the feature, per project conventions. --- tests/sqlparser_common.rs | 20 ++++++++++++++++++++ tests/sqlparser_redshift.rs | 19 ------------------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index a3b5404d30..3f0ca96a03 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -1280,6 +1280,26 @@ fn parse_select_expr_star() { dialects.verified_only_select("SELECT myfunc().* EXCEPT (foo) FROM T"); } +#[test] +fn parse_select_wildcard_with_alias() { + let dialects = all_dialects_where(|d| d.supports_select_wildcard_with_alias()); + + // qualified wildcard with alias + dialects + .parse_sql_statements("SELECT t.* AS all_cols FROM t") + .unwrap(); + + // unqualified wildcard with alias + dialects + .parse_sql_statements("SELECT * AS all_cols FROM t") + .unwrap(); + + // mixed: regular column + qualified wildcard with alias + dialects + .parse_sql_statements("SELECT a.id, b.* AS b_cols FROM a JOIN b ON (a.id = b.a_id)") + .unwrap(); +} + #[test] fn test_eof_after_as() { let res = parse_sql_statements("SELECT foo AS"); diff --git a/tests/sqlparser_redshift.rs b/tests/sqlparser_redshift.rs index 09e12b61ce..90652ff48f 100644 --- a/tests/sqlparser_redshift.rs +++ b/tests/sqlparser_redshift.rs @@ -452,22 +452,3 @@ fn parse_vacuum() { _ => unreachable!(), } } - - -#[test] -fn test_redshift_select_wildcard_with_alias() { - // qualified wildcard with alias: t.* AS alias - redshift() - .parse_sql_statements(r#"SELECT t.* AS all_cols FROM t"#) - .unwrap(); - - // unqualified wildcard with alias - redshift() - .parse_sql_statements(r#"SELECT * AS all_cols FROM t"#) - .unwrap(); - - // mixed: regular column + qualified wildcard with alias in a multi-join query - redshift() - .parse_sql_statements(r#"SELECT a.id, b.* AS b_cols FROM a JOIN b ON (a.id = b.a_id)"#) - .unwrap(); -}