From 3280c3db78ea8d6f12bfc62fbac621a1dfd7ff2c Mon Sep 17 00:00:00 2001 From: Andriy Romanov Date: Wed, 3 Dec 2025 14:46:19 -0800 Subject: [PATCH 1/3] fix: support parsing parenthesized wildcard `(*)` --- src/parser/mod.rs | 9 +++++++++ tests/sqlparser_common.rs | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 74b06ec8d..14ddeb3e9 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1268,6 +1268,15 @@ impl<'a> Parser<'a> { Token::Mul => { return Ok(Expr::Wildcard(AttachedToken(next_token))); } + // Handle parenthesized wildcard: (*) + Token::LParen => { + let inner_token = self.next_token(); + if inner_token.token == Token::Mul && self.peek_token().token == Token::RParen { + self.next_token(); // consume RParen + return Ok(Expr::Wildcard(AttachedToken(inner_token))); + } + // Not a (*), reset and fall through to parse_expr + } _ => (), }; diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index da8e7b495..9a8ce226c 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -17953,3 +17953,22 @@ fn test_parse_set_session_authorization() { })) ); } + +#[test] +fn parse_select_distinct_parenthesized_wildcard() { + // Test SELECT DISTINCT(*) which uses a parenthesized wildcard + // The parentheses are syntactic sugar and get normalized to just * + let sql = "SELECT DISTINCT (*) FROM table1"; + let canonical = "SELECT DISTINCT * FROM table1"; + let select = all_dialects().verified_only_select_with_canonical(sql, canonical); + assert_eq!(select.distinct, Some(Distinct::Distinct)); + assert_eq!(select.projection.len(), 1); + assert!(matches!(select.projection[0], SelectItem::Wildcard(_))); + + // Also test without spaces: SELECT DISTINCT(*) + let sql_no_spaces = "SELECT DISTINCT(*) FROM table1"; + let select2 = all_dialects().verified_only_select_with_canonical(sql_no_spaces, canonical); + assert_eq!(select2.distinct, Some(Distinct::Distinct)); + assert_eq!(select2.projection.len(), 1); + assert!(matches!(select2.projection[0], SelectItem::Wildcard(_))); +} From 3ec4eefa36af31c88991a4a0481ca91814244c27 Mon Sep 17 00:00:00 2001 From: Andriy Romanov Date: Thu, 4 Dec 2025 16:51:36 -0800 Subject: [PATCH 2/3] Refactoring based on comments --- src/parser/mod.rs | 10 ++++++---- tests/sqlparser_common.rs | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 14ddeb3e9..e59e08378 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1270,12 +1270,14 @@ impl<'a> Parser<'a> { } // Handle parenthesized wildcard: (*) Token::LParen => { - let inner_token = self.next_token(); - if inner_token.token == Token::Mul && self.peek_token().token == Token::RParen { + let [maybe_mul, maybe_rparen] = self.peek_tokens_ref(); + if maybe_mul.token == Token::Mul && maybe_rparen.token == Token::RParen { + let mul_token = self.next_token(); // consume Mul self.next_token(); // consume RParen - return Ok(Expr::Wildcard(AttachedToken(inner_token))); + return Ok(Expr::Wildcard(AttachedToken(mul_token))); } - // Not a (*), reset and fall through to parse_expr + // Not a (*), fall through to reset index and call parse_expr + self.prev_token(); } _ => (), }; diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 9a8ce226c..9f549e4d0 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -17955,7 +17955,7 @@ fn test_parse_set_session_authorization() { } #[test] -fn parse_select_distinct_parenthesized_wildcard() { +fn parse_select_parenthesized_wildcard() { // Test SELECT DISTINCT(*) which uses a parenthesized wildcard // The parentheses are syntactic sugar and get normalized to just * let sql = "SELECT DISTINCT (*) FROM table1"; From 3732a4a3f4566619ed9a58a86a4f4e43f82350a8 Mon Sep 17 00:00:00 2001 From: Andriy Romanov Date: Thu, 18 Dec 2025 17:26:07 -0800 Subject: [PATCH 3/3] Refactoring based on comments --- src/parser/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index e59e08378..d1c4fe05b 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1276,8 +1276,6 @@ impl<'a> Parser<'a> { self.next_token(); // consume RParen return Ok(Expr::Wildcard(AttachedToken(mul_token))); } - // Not a (*), fall through to reset index and call parse_expr - self.prev_token(); } _ => (), };