From fbf36c55749f15d2c32d402560c0da5098b9477d Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Fri, 3 Jul 2026 18:53:52 -0400 Subject: [PATCH] parser: fix parsing insert/create view/declare with parens --- crates/squawk_parser/src/grammar.rs | 11 +- .../tests/data/ok/create_view.sql | 10 + .../squawk_parser/tests/data/ok/declare.sql | 5 + crates/squawk_parser/tests/data/ok/insert.sql | 8 + .../snapshots/tests__create_view_ok.snap | 276 ++++++++++++++++++ .../tests/snapshots/tests__declare_ok.snap | 88 ++++++ .../tests/snapshots/tests__insert_ok.snap | 187 ++++++++++++ 7 files changed, 580 insertions(+), 5 deletions(-) diff --git a/crates/squawk_parser/src/grammar.rs b/crates/squawk_parser/src/grammar.rs index 5e7a0710d..c6808c327 100644 --- a/crates/squawk_parser/src/grammar.rs +++ b/crates/squawk_parser/src/grammar.rs @@ -12318,7 +12318,7 @@ fn create_view(p: &mut Parser<'_>) -> CompletedMarker { p.expect(AS_KW); match stmt(p, &StmtRestrictions::default()) { Some(statement) => match statement.kind() { - SELECT | COMPOUND_SELECT | SELECT_INTO | VALUES | TABLE => (), + SELECT | COMPOUND_SELECT | SELECT_INTO | VALUES | TABLE | PAREN_SELECT => (), kind => p.error(format!("expected SELECT, got {kind:?}")), }, None => p.error("expected SELECT"), @@ -12548,7 +12548,7 @@ fn declare(p: &mut Parser<'_>) -> CompletedMarker { // select stmt let statement = stmt(p, &StmtRestrictions::default()); match statement.map(|x| x.kind()) { - Some(SELECT | SELECT_INTO | COMPOUND_SELECT | TABLE | VALUES) => (), + Some(SELECT | SELECT_INTO | COMPOUND_SELECT | TABLE | VALUES | PAREN_SELECT) => (), Some(kind) => { p.error(format!( "expected SELECT, TABLE, or VALUES statement, got {kind:?}", @@ -13461,7 +13461,10 @@ fn insert(p: &mut Parser<'_>, m: Option, semi_allowed: bool) -> Complete path_name_ref(p); opt_as_alias_with_as(p); // [ ( column_name [, ...] ) ] - opt_column_ref_list(p); + // a leading `(` may open a parenthesized query instead of a column list + if !(p.at(L_PAREN) && p.nth_at_ts(1, SELECT_FIRST)) { + opt_column_ref_list(p); + } // [ OVERRIDING { SYSTEM | USER } VALUE ] if p.eat(OVERRIDING_KW) { let _ = p.eat(SYSTEM_KW) || p.expect(USER_KW); @@ -13470,8 +13473,6 @@ fn insert(p: &mut Parser<'_>, m: Option, semi_allowed: bool) -> Complete // { DEFAULT VALUES | VALUES ( { expression | DEFAULT } [, ...] ) [, ...] | query } if p.eat(DEFAULT_KW) { p.expect(VALUES_KW); - } else if p.at(VALUES_KW) { - values(p, None, false); } else { query(p); } diff --git a/crates/squawk_parser/tests/data/ok/create_view.sql b/crates/squawk_parser/tests/data/ok/create_view.sql index 21b72c662..0cc4a33ed 100644 --- a/crates/squawk_parser/tests/data/ok/create_view.sql +++ b/crates/squawk_parser/tests/data/ok/create_view.sql @@ -46,3 +46,13 @@ with local check option; -- regression test create or replace view my_view as select x from foo; + +-- parenthesized / compound queries +create view v as (select 1); +create view v as (values (1)); +create view v as (select 1 union select 2); +create view v as (values (1, 2) union values (3, 4)); +create view v as (table t); +create view v as (select 1 order by 1); +create view v as ((select 1)); +create view v as (with x as (select 1) select * from x); diff --git a/crates/squawk_parser/tests/data/ok/declare.sql b/crates/squawk_parser/tests/data/ok/declare.sql index 6a468d176..3d7d9e9d3 100644 --- a/crates/squawk_parser/tests/data/ok/declare.sql +++ b/crates/squawk_parser/tests/data/ok/declare.sql @@ -14,3 +14,8 @@ cursor declare c cursor for select 1; +-- parenthesized / compound queries +declare c cursor for (select 1); +declare c cursor for (values (1) union values (2)); +declare c cursor for ((select 1)); + diff --git a/crates/squawk_parser/tests/data/ok/insert.sql b/crates/squawk_parser/tests/data/ok/insert.sql index 505f3a0ad..d9ddc09e5 100644 --- a/crates/squawk_parser/tests/data/ok/insert.sql +++ b/crates/squawk_parser/tests/data/ok/insert.sql @@ -129,3 +129,11 @@ on conflict -- on conflict do select INSERT INTO t (a) VALUES (1) ON CONFLICT (a) DO SELECT FOR UPDATE WHERE a > 0 RETURNING *; + +-- parenthesized / compound query sources +insert into t (values (1)); +insert into t (values (1) union values (2)); +insert into t values (1) union values (2); +insert into t (select 1); +insert into t (select 1 union select 2); +insert into t select 1 union select 2; diff --git a/crates/squawk_parser/tests/snapshots/tests__create_view_ok.snap b/crates/squawk_parser/tests/snapshots/tests__create_view_ok.snap index 98fd51216..c0d1a7a36 100644 --- a/crates/squawk_parser/tests/snapshots/tests__create_view_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__create_view_ok.snap @@ -571,4 +571,280 @@ SOURCE_FILE NAME_REF IDENT "foo" SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- parenthesized / compound queries" + WHITESPACE "\n" + CREATE_VIEW + CREATE_KW "create" + WHITESPACE " " + VIEW_KW "view" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME + IDENT "v" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + PAREN_SELECT + L_PAREN "(" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + CREATE_VIEW + CREATE_KW "create" + WHITESPACE " " + VIEW_KW "view" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME + IDENT "v" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + PAREN_SELECT + L_PAREN "(" + VALUES + VALUES_KW "values" + WHITESPACE " " + ROW_LIST + ROW + L_PAREN "(" + LITERAL + INT_NUMBER "1" + R_PAREN ")" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + CREATE_VIEW + CREATE_KW "create" + WHITESPACE " " + VIEW_KW "view" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME + IDENT "v" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + PAREN_SELECT + L_PAREN "(" + COMPOUND_SELECT + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1" + WHITESPACE " " + UNION_KW "union" + WHITESPACE " " + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "2" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + CREATE_VIEW + CREATE_KW "create" + WHITESPACE " " + VIEW_KW "view" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME + IDENT "v" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + PAREN_SELECT + L_PAREN "(" + COMPOUND_SELECT + VALUES + VALUES_KW "values" + WHITESPACE " " + ROW_LIST + ROW + L_PAREN "(" + LITERAL + INT_NUMBER "1" + COMMA "," + WHITESPACE " " + LITERAL + INT_NUMBER "2" + R_PAREN ")" + WHITESPACE " " + UNION_KW "union" + WHITESPACE " " + VALUES + VALUES_KW "values" + WHITESPACE " " + ROW_LIST + ROW + L_PAREN "(" + LITERAL + INT_NUMBER "3" + COMMA "," + WHITESPACE " " + LITERAL + INT_NUMBER "4" + R_PAREN ")" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + CREATE_VIEW + CREATE_KW "create" + WHITESPACE " " + VIEW_KW "view" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME + IDENT "v" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + PAREN_SELECT + L_PAREN "(" + TABLE + TABLE_KW "table" + WHITESPACE " " + RELATION_NAME + PATH + PATH_SEGMENT + NAME_REF + IDENT "t" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + CREATE_VIEW + CREATE_KW "create" + WHITESPACE " " + VIEW_KW "view" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME + IDENT "v" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + PAREN_SELECT + L_PAREN "(" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1" + WHITESPACE " " + ORDER_BY_CLAUSE + ORDER_KW "order" + WHITESPACE " " + BY_KW "by" + WHITESPACE " " + SORT_BY_LIST + SORT_BY + LITERAL + INT_NUMBER "1" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + CREATE_VIEW + CREATE_KW "create" + WHITESPACE " " + VIEW_KW "view" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME + IDENT "v" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + PAREN_SELECT + L_PAREN "(" + PAREN_SELECT + L_PAREN "(" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1" + R_PAREN ")" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + CREATE_VIEW + CREATE_KW "create" + WHITESPACE " " + VIEW_KW "view" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME + IDENT "v" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + PAREN_SELECT + L_PAREN "(" + SELECT + WITH_CLAUSE + WITH_KW "with" + WHITESPACE " " + WITH_TABLE + NAME + IDENT "x" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + L_PAREN "(" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1" + R_PAREN ")" + WHITESPACE " " + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + STAR "*" + WHITESPACE " " + FROM_CLAUSE + FROM_KW "from" + WHITESPACE " " + FROM_ITEM + NAME_REF + IDENT "x" + R_PAREN ")" + SEMICOLON ";" WHITESPACE "\n" diff --git a/crates/squawk_parser/tests/snapshots/tests__declare_ok.snap b/crates/squawk_parser/tests/snapshots/tests__declare_ok.snap index 3a9670385..abf5ef9a0 100644 --- a/crates/squawk_parser/tests/snapshots/tests__declare_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__declare_ok.snap @@ -115,3 +115,91 @@ SOURCE_FILE INT_NUMBER "1" SEMICOLON ";" WHITESPACE "\n\n" + COMMENT "-- parenthesized / compound queries" + WHITESPACE "\n" + DECLARE + DECLARE_KW "declare" + WHITESPACE " " + NAME + IDENT "c" + WHITESPACE " " + CURSOR_KW "cursor" + WHITESPACE " " + FOR_KW "for" + WHITESPACE " " + PAREN_SELECT + L_PAREN "(" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + DECLARE + DECLARE_KW "declare" + WHITESPACE " " + NAME + IDENT "c" + WHITESPACE " " + CURSOR_KW "cursor" + WHITESPACE " " + FOR_KW "for" + WHITESPACE " " + PAREN_SELECT + L_PAREN "(" + COMPOUND_SELECT + VALUES + VALUES_KW "values" + WHITESPACE " " + ROW_LIST + ROW + L_PAREN "(" + LITERAL + INT_NUMBER "1" + R_PAREN ")" + WHITESPACE " " + UNION_KW "union" + WHITESPACE " " + VALUES + VALUES_KW "values" + WHITESPACE " " + ROW_LIST + ROW + L_PAREN "(" + LITERAL + INT_NUMBER "2" + R_PAREN ")" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + DECLARE + DECLARE_KW "declare" + WHITESPACE " " + NAME + IDENT "c" + WHITESPACE " " + CURSOR_KW "cursor" + WHITESPACE " " + FOR_KW "for" + WHITESPACE " " + PAREN_SELECT + L_PAREN "(" + PAREN_SELECT + L_PAREN "(" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1" + R_PAREN ")" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n\n" diff --git a/crates/squawk_parser/tests/snapshots/tests__insert_ok.snap b/crates/squawk_parser/tests/snapshots/tests__insert_ok.snap index 40cb7ed75..4432bd902 100644 --- a/crates/squawk_parser/tests/snapshots/tests__insert_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__insert_ok.snap @@ -1881,4 +1881,191 @@ SOURCE_FILE TARGET STAR "*" SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- parenthesized / compound query sources" + WHITESPACE "\n" + INSERT + INSERT_KW "insert" + WHITESPACE " " + INTO_KW "into" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME_REF + IDENT "t" + WHITESPACE " " + PAREN_SELECT + L_PAREN "(" + VALUES + VALUES_KW "values" + WHITESPACE " " + ROW_LIST + ROW + L_PAREN "(" + LITERAL + INT_NUMBER "1" + R_PAREN ")" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + INSERT + INSERT_KW "insert" + WHITESPACE " " + INTO_KW "into" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME_REF + IDENT "t" + WHITESPACE " " + PAREN_SELECT + L_PAREN "(" + COMPOUND_SELECT + VALUES + VALUES_KW "values" + WHITESPACE " " + ROW_LIST + ROW + L_PAREN "(" + LITERAL + INT_NUMBER "1" + R_PAREN ")" + WHITESPACE " " + UNION_KW "union" + WHITESPACE " " + VALUES + VALUES_KW "values" + WHITESPACE " " + ROW_LIST + ROW + L_PAREN "(" + LITERAL + INT_NUMBER "2" + R_PAREN ")" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + INSERT + INSERT_KW "insert" + WHITESPACE " " + INTO_KW "into" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME_REF + IDENT "t" + WHITESPACE " " + COMPOUND_SELECT + VALUES + VALUES_KW "values" + WHITESPACE " " + ROW_LIST + ROW + L_PAREN "(" + LITERAL + INT_NUMBER "1" + R_PAREN ")" + WHITESPACE " " + UNION_KW "union" + WHITESPACE " " + VALUES + VALUES_KW "values" + WHITESPACE " " + ROW_LIST + ROW + L_PAREN "(" + LITERAL + INT_NUMBER "2" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + INSERT + INSERT_KW "insert" + WHITESPACE " " + INTO_KW "into" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME_REF + IDENT "t" + WHITESPACE " " + PAREN_SELECT + L_PAREN "(" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + INSERT + INSERT_KW "insert" + WHITESPACE " " + INTO_KW "into" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME_REF + IDENT "t" + WHITESPACE " " + PAREN_SELECT + L_PAREN "(" + COMPOUND_SELECT + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1" + WHITESPACE " " + UNION_KW "union" + WHITESPACE " " + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "2" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + INSERT + INSERT_KW "insert" + WHITESPACE " " + INTO_KW "into" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME_REF + IDENT "t" + WHITESPACE " " + COMPOUND_SELECT + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1" + WHITESPACE " " + UNION_KW "union" + WHITESPACE " " + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "2" + SEMICOLON ";" WHITESPACE "\n"