From d208e5e3a6d769726d165189b8476a2f43bdccae Mon Sep 17 00:00:00 2001 From: sabir Date: Wed, 25 Mar 2026 00:35:23 +0100 Subject: [PATCH] Handle nested parentheses in INSERT subquery detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When parsing `INSERT INTO t `, the parser must decide whether the tokens following the table name begin an explicit column list `(a, b, ...)` or the source query `(SELECT ...)`. `peek_subquery_start` made this decision by matching exactly `( SELECT`, so a source query wrapped in more than one layer of parentheses — e.g. `INSERT INTO t ((SELECT 1))` — was mistaken for a column list and failed to parse. Skip any number of leading `(` and treat the tokens as a subquery start as long as at least one paren precedes the `SELECT`. This matches how nested parentheses around subqueries are already handled elsewhere via recursive descent. Co-Authored-By: Claude Opus 4.8 --- src/parser/mod.rs | 29 ++++++++++++----------------- tests/sqlparser_common.rs | 8 ++++++++ 2 files changed, 20 insertions(+), 17 deletions(-) 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");