diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 6378d4c4d..bd6334e41 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -18476,24 +18476,19 @@ impl<'a> Parser<'a> { } /// Returns true if the immediate tokens look like the - /// beginning of a subquery. `(SELECT ...` + /// beginning of a subquery. `(SELECT ...` or `((SELECT ...` etc. fn peek_subquery_start(&mut self) -> bool { - matches!( - self.peek_tokens_ref(), - [ - TokenWithSpan { - token: Token::LParen, - .. - }, - TokenWithSpan { - token: Token::Word(Word { - keyword: Keyword::SELECT, - .. - }), - .. - }, - ] - ) + // Handle (SELECT, ((SELECT, (((SELECT, etc. + // This makes INSERT consistent with other contexts where nested + // parentheses around subqueries are handled by recursive descent. + let mut i = 0; + loop { + match &self.peek_nth_token_ref(i).token { + Token::LParen => i += 1, + Token::Word(w) if w.keyword == Keyword::SELECT => return i > 0, + _ => return false, + } + } } /// Returns true if the immediate tokens look like the diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index e629b71a8..f6f5fcddb 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -351,6 +351,14 @@ fn parse_insert_select_from_returning() { } } +#[test] +fn parse_insert_nested_parenthesized_subquery() { + // The source query may be wrapped in one or more layers of parentheses; + // the leading parens must not be mistaken for a column list. + verified_stmt("INSERT INTO t ((SELECT 1))"); + verified_stmt("INSERT INTO t (((SELECT 1)))"); +} + #[test] fn parse_returning_as_column_alias() { verified_stmt("SELECT 1 AS RETURNING");