Skip to content

Commit e8ac6b1

Browse files
authored
Merge branch 'main' into push-vuqvpqzkltln
2 parents df75250 + 6f0e803 commit e8ac6b1

11 files changed

Lines changed: 290 additions & 10 deletions

File tree

src/ast/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7625,6 +7625,10 @@ pub enum FunctionArgExpr {
76257625
QualifiedWildcard(ObjectName),
76267626
/// An unqualified `*` wildcard.
76277627
Wildcard,
7628+
/// An unqualified `*` wildcard with additional options, e.g. `* EXCLUDE(col)`.
7629+
///
7630+
/// Used in Snowflake to support expressions like `HASH(* EXCLUDE(col))`.
7631+
WildcardWithOptions(WildcardAdditionalOptions),
76287632
}
76297633

76307634
impl From<Expr> for FunctionArgExpr {
@@ -7643,6 +7647,7 @@ impl fmt::Display for FunctionArgExpr {
76437647
FunctionArgExpr::Expr(expr) => write!(f, "{expr}"),
76447648
FunctionArgExpr::QualifiedWildcard(prefix) => write!(f, "{prefix}.*"),
76457649
FunctionArgExpr::Wildcard => f.write_str("*"),
7650+
FunctionArgExpr::WildcardWithOptions(opts) => write!(f, "*{opts}"),
76467651
}
76477652
}
76487653
}

src/ast/query.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,9 @@ pub struct WildcardAdditionalOptions {
934934
pub opt_replace: Option<ReplaceSelectItem>,
935935
/// `[RENAME ...]`.
936936
pub opt_rename: Option<RenameSelectItem>,
937+
/// `[AS <alias>]`.
938+
/// Redshift syntax: <https://docs.aws.amazon.com/redshift/latest/dg/r_SELECT_list.html>
939+
pub opt_alias: Option<Ident>,
937940
}
938941

939942
impl Default for WildcardAdditionalOptions {
@@ -945,6 +948,7 @@ impl Default for WildcardAdditionalOptions {
945948
opt_except: None,
946949
opt_replace: None,
947950
opt_rename: None,
951+
opt_alias: None,
948952
}
949953
}
950954
}
@@ -966,6 +970,9 @@ impl fmt::Display for WildcardAdditionalOptions {
966970
if let Some(rename) = &self.opt_rename {
967971
write!(f, " {rename}")?;
968972
}
973+
if let Some(alias) = &self.opt_alias {
974+
write!(f, " AS {alias}")?;
975+
}
969976
Ok(())
970977
}
971978
}

src/ast/spans.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1824,6 +1824,7 @@ impl Spanned for WildcardAdditionalOptions {
18241824
opt_except,
18251825
opt_replace,
18261826
opt_rename,
1827+
opt_alias,
18271828
} = self;
18281829

18291830
union_spans(
@@ -1832,7 +1833,8 @@ impl Spanned for WildcardAdditionalOptions {
18321833
.chain(opt_exclude.as_ref().map(|i| i.span()))
18331834
.chain(opt_rename.as_ref().map(|i| i.span()))
18341835
.chain(opt_replace.as_ref().map(|i| i.span()))
1835-
.chain(opt_except.as_ref().map(|i| i.span())),
1836+
.chain(opt_except.as_ref().map(|i| i.span()))
1837+
.chain(opt_alias.as_ref().map(|i| i.span)),
18361838
)
18371839
}
18381840
}
@@ -2128,6 +2130,7 @@ impl Spanned for FunctionArg {
21282130
///
21292131
/// Missing spans:
21302132
/// - [FunctionArgExpr::Wildcard]
2133+
/// - [FunctionArgExpr::WildcardWithOptions]
21312134
impl Spanned for FunctionArgExpr {
21322135
fn span(&self) -> Span {
21332136
match self {
@@ -2136,6 +2139,7 @@ impl Spanned for FunctionArgExpr {
21362139
union_spans(object_name.0.iter().map(|i| i.span()))
21372140
}
21382141
FunctionArgExpr::Wildcard => Span::empty(),
2142+
FunctionArgExpr::WildcardWithOptions(_) => Span::empty(),
21392143
}
21402144
}
21412145
}

src/dialect/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1529,6 +1529,18 @@ pub trait Dialect: Debug + Any {
15291529
false
15301530
}
15311531

1532+
/// Returns true if this dialect supports aliasing a wildcard select item.
1533+
///
1534+
/// Example:
1535+
/// ```sql
1536+
/// SELECT t.* AS alias FROM t
1537+
/// ```
1538+
///
1539+
/// [Redshift](https://docs.aws.amazon.com/redshift/latest/dg/r_SELECT_list.html)
1540+
fn supports_select_wildcard_with_alias(&self) -> bool {
1541+
false
1542+
}
1543+
15321544
/// Returns true if this dialect supports the `OPTIMIZE TABLE` statement.
15331545
///
15341546
/// Example:

src/dialect/mssql.rs

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,18 +129,64 @@ impl Dialect for MsSqlDialect {
129129

130130
fn is_select_item_alias(&self, explicit: bool, kw: &Keyword, parser: &mut Parser) -> bool {
131131
match kw {
132-
// List of keywords that cannot be used as select item aliases in MSSQL
133-
// regardless of whether the alias is explicit or implicit
134-
Keyword::IF | Keyword::ELSE => false,
132+
// List of keywords that cannot be used as select item (column) aliases in MSSQL
133+
// regardless of whether the alias is explicit or implicit.
134+
//
135+
// These are T-SQL statement-starting keywords; allowing them as implicit aliases
136+
// causes the parser to consume the keyword as an alias for the previous expression,
137+
// then fail on the token that follows (e.g. `TABLE`, `@var`, `sp_name`, …).
138+
Keyword::IF
139+
| Keyword::ELSE
140+
| Keyword::DECLARE
141+
| Keyword::EXEC
142+
| Keyword::EXECUTE
143+
| Keyword::INSERT
144+
| Keyword::UPDATE
145+
| Keyword::DELETE
146+
| Keyword::DROP
147+
| Keyword::CREATE
148+
| Keyword::ALTER
149+
| Keyword::TRUNCATE
150+
| Keyword::PRINT
151+
| Keyword::WHILE
152+
| Keyword::RETURN
153+
| Keyword::THROW
154+
| Keyword::RAISERROR
155+
| Keyword::MERGE => false,
135156
_ => explicit || self.is_column_alias(kw, parser),
136157
}
137158
}
138159

139160
fn is_table_factor_alias(&self, explicit: bool, kw: &Keyword, parser: &mut Parser) -> bool {
140161
match kw {
141162
// List of keywords that cannot be used as table aliases in MSSQL
142-
// regardless of whether the alias is explicit or implicit
143-
Keyword::IF | Keyword::ELSE => false,
163+
// regardless of whether the alias is explicit or implicit.
164+
//
165+
// These are T-SQL statement-starting keywords. Without blocking them here,
166+
// a bare `SELECT * FROM t` followed by a newline and one of these keywords
167+
// would cause the parser to consume the keyword as a table alias for `t`,
168+
// then fail on the token that follows (e.g. `@var`, `sp_name`, `TABLE`, …).
169+
//
170+
// `SET` is already covered by the global `RESERVED_FOR_TABLE_ALIAS` list;
171+
// the keywords below are MSSQL-specific additions.
172+
Keyword::IF
173+
| Keyword::ELSE
174+
| Keyword::DECLARE
175+
| Keyword::EXEC
176+
| Keyword::EXECUTE
177+
| Keyword::INSERT
178+
| Keyword::UPDATE
179+
| Keyword::DELETE
180+
| Keyword::DROP
181+
| Keyword::CREATE
182+
| Keyword::ALTER
183+
| Keyword::TRUNCATE
184+
| Keyword::PRINT
185+
| Keyword::WHILE
186+
| Keyword::RETURN
187+
| Keyword::THROW
188+
| Keyword::RAISERROR
189+
| Keyword::MERGE => false,
144190
_ => explicit || self.is_table_alias(kw, parser),
145191
}
146192
}

src/dialect/postgresql.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,4 +302,8 @@ impl Dialect for PostgreSqlDialect {
302302
fn supports_insert_table_alias(&self) -> bool {
303303
true
304304
}
305+
306+
fn supports_create_table_like_parenthesized(&self) -> bool {
307+
true
308+
}
305309
}

src/dialect/redshift.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,10 @@ impl Dialect for RedshiftSqlDialect {
141141
true
142142
}
143143

144+
fn supports_select_wildcard_with_alias(&self) -> bool {
145+
true
146+
}
147+
144148
fn supports_select_exclude(&self) -> bool {
145149
true
146150
}

src/parser/mod.rs

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17597,7 +17597,26 @@ impl<'a> Parser<'a> {
1759717597
if let Some(arg) = arg {
1759817598
return Ok(arg);
1759917599
}
17600-
Ok(FunctionArg::Unnamed(self.parse_wildcard_expr()?.into()))
17600+
let wildcard_expr = self.parse_wildcard_expr()?;
17601+
let arg_expr: FunctionArgExpr = match wildcard_expr {
17602+
Expr::Wildcard(ref token) if self.dialect.supports_select_wildcard_exclude() => {
17603+
// Support `* EXCLUDE(col1, col2, ...)` inside function calls (e.g. Snowflake's
17604+
// `HASH(* EXCLUDE(col))`). Parse the options the same way SELECT items do.
17605+
let opts = self.parse_wildcard_additional_options(token.0.clone())?;
17606+
if opts.opt_exclude.is_some()
17607+
|| opts.opt_except.is_some()
17608+
|| opts.opt_replace.is_some()
17609+
|| opts.opt_rename.is_some()
17610+
|| opts.opt_ilike.is_some()
17611+
{
17612+
FunctionArgExpr::WildcardWithOptions(opts)
17613+
} else {
17614+
wildcard_expr.into()
17615+
}
17616+
}
17617+
other => other.into(),
17618+
};
17619+
Ok(FunctionArg::Unnamed(arg_expr))
1760117620
}
1760217621

1760317622
fn parse_function_named_arg_operator(&mut self) -> Result<FunctionArgOperator, ParserError> {
@@ -17885,13 +17904,24 @@ impl<'a> Parser<'a> {
1788517904
None
1788617905
};
1788717906

17907+
let opt_alias = if self.dialect.supports_select_wildcard_with_alias() {
17908+
if self.parse_keyword(Keyword::AS) {
17909+
Some(self.parse_identifier()?)
17910+
} else {
17911+
None
17912+
}
17913+
} else {
17914+
None
17915+
};
17916+
1788817917
Ok(WildcardAdditionalOptions {
1788917918
wildcard_token: wildcard_token.into(),
1789017919
opt_ilike,
1789117920
opt_exclude,
1789217921
opt_except,
1789317922
opt_rename,
1789417923
opt_replace,
17924+
opt_alias,
1789517925
})
1789617926
}
1789717927

@@ -18574,6 +18604,9 @@ impl<'a> Parser<'a> {
1857418604

1857518605
/// Parse a SQL `EXECUTE` statement
1857618606
pub fn parse_execute(&mut self) -> Result<Statement, ParserError> {
18607+
// Track whether the procedure/expression name itself was wrapped in parens,
18608+
// i.e. `EXEC (@sql)` (dynamic string execution) vs `EXEC sp_name`.
18609+
// When the name has parens there are no additional parameters.
1857718610
let name = if self.dialect.supports_execute_immediate()
1857818611
&& self.parse_keyword(Keyword::IMMEDIATE)
1857918612
{
@@ -18584,10 +18617,18 @@ impl<'a> Parser<'a> {
1858418617
if has_parentheses {
1858518618
self.expect_token(&Token::RParen)?;
1858618619
}
18587-
Some(name)
18620+
Some((name, has_parentheses))
1858818621
};
1858918622

18590-
let has_parentheses = self.consume_token(&Token::LParen);
18623+
let name_had_parentheses = name.as_ref().map(|(_, p)| *p).unwrap_or(false);
18624+
18625+
// Only look for a parameter list when the name was NOT wrapped in parens.
18626+
// `EXEC (@sql)` is dynamic SQL execution and takes no parameters here.
18627+
let has_parentheses = if name_had_parentheses {
18628+
false
18629+
} else {
18630+
self.consume_token(&Token::LParen)
18631+
};
1859118632

1859218633
let end_kws = &[Keyword::USING, Keyword::OUTPUT, Keyword::DEFAULT];
1859318634
let end_token = match (has_parentheses, self.peek_token().token) {
@@ -18597,12 +18638,18 @@ impl<'a> Parser<'a> {
1859718638
(false, _) => Token::SemiColon,
1859818639
};
1859918640

18600-
let parameters = self.parse_comma_separated0(Parser::parse_expr, end_token)?;
18641+
let parameters = if name_had_parentheses {
18642+
vec![]
18643+
} else {
18644+
self.parse_comma_separated0(Parser::parse_expr, end_token)?
18645+
};
1860118646

1860218647
if has_parentheses {
1860318648
self.expect_token(&Token::RParen)?;
1860418649
}
1860518650

18651+
let name = name.map(|(n, _)| n);
18652+
1860618653
let into = if self.parse_keyword(Keyword::INTO) {
1860718654
self.parse_comma_separated(Self::parse_identifier)?
1860818655
} else {

tests/sqlparser_common.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1280,6 +1280,26 @@ fn parse_select_expr_star() {
12801280
dialects.verified_only_select("SELECT myfunc().* EXCEPT (foo) FROM T");
12811281
}
12821282

1283+
#[test]
1284+
fn parse_select_wildcard_with_alias() {
1285+
let dialects = all_dialects_where(|d| d.supports_select_wildcard_with_alias());
1286+
1287+
// qualified wildcard with alias
1288+
dialects
1289+
.parse_sql_statements("SELECT t.* AS all_cols FROM t")
1290+
.unwrap();
1291+
1292+
// unqualified wildcard with alias
1293+
dialects
1294+
.parse_sql_statements("SELECT * AS all_cols FROM t")
1295+
.unwrap();
1296+
1297+
// mixed: regular column + qualified wildcard with alias
1298+
dialects
1299+
.parse_sql_statements("SELECT a.id, b.* AS b_cols FROM a JOIN b ON (a.id = b.a_id)")
1300+
.unwrap();
1301+
}
1302+
12831303
#[test]
12841304
fn test_eof_after_as() {
12851305
let res = parse_sql_statements("SELECT foo AS");
@@ -18563,3 +18583,19 @@ fn parse_array_subscript() {
1856318583
dialects.verified_stmt("SELECT arr[1][2]");
1856418584
dialects.verified_stmt("SELECT arr[:][:]");
1856518585
}
18586+
18587+
#[test]
18588+
fn test_wildcard_func_arg() {
18589+
// Wildcard (*) and wildcard with EXCLUDE as a function argument.
18590+
// Documented for Snowflake's HASH function but parsed for any dialect that
18591+
// supports the wildcard-EXCLUDE select syntax.
18592+
let dialects = all_dialects_where(|d| d.supports_select_wildcard_exclude());
18593+
18594+
// Wildcard with EXCLUDE — canonical form has a space before the parenthesised column list.
18595+
dialects.one_statement_parses_to(
18596+
"SELECT HASH(* EXCLUDE(col1)) FROM t",
18597+
"SELECT HASH(* EXCLUDE (col1)) FROM t",
18598+
);
18599+
dialects.verified_expr("HASH(* EXCLUDE (col1))");
18600+
dialects.verified_expr("HASH(* EXCLUDE (col1, col2))");
18601+
}

0 commit comments

Comments
 (0)