Skip to content

Commit d208e5e

Browse files
Handle nested parentheses in INSERT subquery detection
When parsing `INSERT INTO t <source>`, 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 <noreply@anthropic.com>
1 parent e79119c commit d208e5e

2 files changed

Lines changed: 20 additions & 17 deletions

File tree

src/parser/mod.rs

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18476,24 +18476,19 @@ impl<'a> Parser<'a> {
1847618476
}
1847718477

1847818478
/// Returns true if the immediate tokens look like the
18479-
/// beginning of a subquery. `(SELECT ...`
18479+
/// beginning of a subquery. `(SELECT ...` or `((SELECT ...` etc.
1848018480
fn peek_subquery_start(&mut self) -> bool {
18481-
matches!(
18482-
self.peek_tokens_ref(),
18483-
[
18484-
TokenWithSpan {
18485-
token: Token::LParen,
18486-
..
18487-
},
18488-
TokenWithSpan {
18489-
token: Token::Word(Word {
18490-
keyword: Keyword::SELECT,
18491-
..
18492-
}),
18493-
..
18494-
},
18495-
]
18496-
)
18481+
// Handle (SELECT, ((SELECT, (((SELECT, etc.
18482+
// This makes INSERT consistent with other contexts where nested
18483+
// parentheses around subqueries are handled by recursive descent.
18484+
let mut i = 0;
18485+
loop {
18486+
match &self.peek_nth_token_ref(i).token {
18487+
Token::LParen => i += 1,
18488+
Token::Word(w) if w.keyword == Keyword::SELECT => return i > 0,
18489+
_ => return false,
18490+
}
18491+
}
1849718492
}
1849818493

1849918494
/// Returns true if the immediate tokens look like the

tests/sqlparser_common.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,14 @@ fn parse_insert_select_from_returning() {
351351
}
352352
}
353353

354+
#[test]
355+
fn parse_insert_nested_parenthesized_subquery() {
356+
// The source query may be wrapped in one or more layers of parentheses;
357+
// the leading parens must not be mistaken for a column list.
358+
verified_stmt("INSERT INTO t ((SELECT 1))");
359+
verified_stmt("INSERT INTO t (((SELECT 1)))");
360+
}
361+
354362
#[test]
355363
fn parse_returning_as_column_alias() {
356364
verified_stmt("SELECT 1 AS RETURNING");

0 commit comments

Comments
 (0)