From 6ed4f2ef380921ae2382f03321b15efc04002a04 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Thu, 4 Dec 2025 22:19:59 -0500 Subject: [PATCH 1/2] parser: fix parsing insert select on conflict We were parsing the on conflict as a join. rel: https://github.com/sbdchd/squawk/issues/718 --- crates/squawk_parser/src/grammar.rs | 2 +- crates/squawk_parser/tests/data/ok/insert.sql | 9 ++++ .../tests/snapshots/tests__insert_ok.snap | 51 +++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/crates/squawk_parser/src/grammar.rs b/crates/squawk_parser/src/grammar.rs index 1e0b739f..2e6ed586 100644 --- a/crates/squawk_parser/src/grammar.rs +++ b/crates/squawk_parser/src/grammar.rs @@ -3456,7 +3456,7 @@ fn join(p: &mut Parser<'_>) { if !opt_from_item(p) { p.error("expected from_item"); } - if p.at(ON_KW) { + if p.at(ON_KW) && !p.nth_at(1, CONFLICT_KW) { on_clause(p); } else if p.at(USING_KW) { join_using_clause(p); diff --git a/crates/squawk_parser/tests/data/ok/insert.sql b/crates/squawk_parser/tests/data/ok/insert.sql index b4e2f8c2..d805d2dc 100644 --- a/crates/squawk_parser/tests/data/ok/insert.sql +++ b/crates/squawk_parser/tests/data/ok/insert.sql @@ -91,3 +91,12 @@ INSERT INTO distributors (did, dname) VALUES (10, 'Conrad International') -- with schema insert into s.t (c) values (1); + + +-- regression check for issue #718 +-- we were parsing the `on conflict` as the start of a `join on` +INSERT INTO t +SELECT * +FROM t +CROSS JOIN f +ON CONFLICT DO NOTHING; diff --git a/crates/squawk_parser/tests/snapshots/tests__insert_ok.snap b/crates/squawk_parser/tests/snapshots/tests__insert_ok.snap index 14c8e826..d337aeea 100644 --- a/crates/squawk_parser/tests/snapshots/tests__insert_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__insert_ok.snap @@ -1463,4 +1463,55 @@ SOURCE_FILE INT_NUMBER "1" R_PAREN ")" SEMICOLON ";" + WHITESPACE "\n\n\n" + COMMENT "-- regression check for issue #718" + WHITESPACE "\n" + COMMENT "-- we were parsing the `on conflict` as the start of a `join on`" + WHITESPACE "\n" + INSERT + INSERT_KW "INSERT" + WHITESPACE " " + INTO_KW "INTO" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME_REF + IDENT "t" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + STAR "*" + WHITESPACE "\n" + FROM_CLAUSE + FROM_KW "FROM" + WHITESPACE " " + JOIN_EXPR + FROM_ITEM + NAME_REF + IDENT "t" + WHITESPACE "\n" + JOIN + JOIN_CROSS + CROSS_KW "CROSS" + WHITESPACE " " + JOIN_KW "JOIN" + WHITESPACE " " + FROM_ITEM + NAME_REF + IDENT "f" + WHITESPACE "\n" + ON_CONFLICT_CLAUSE + ON_KW "ON" + WHITESPACE " " + CONFLICT_KW "CONFLICT" + WHITESPACE " " + CONFLICT_DO_NOTHING + DO_KW "DO" + WHITESPACE " " + NOTHING_KW "NOTHING" + SEMICOLON ";" WHITESPACE "\n" From 36d130aafd88327483922a34ee4bca75eb871cad Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Thu, 4 Dec 2025 23:10:52 -0500 Subject: [PATCH 2/2] more changes --- crates/squawk_parser/src/grammar.rs | 4 +- crates/squawk_parser/tests/data/ok/insert.sql | 21 +- .../tests/snapshots/tests__insert_ok.snap | 189 +++++++++++++++++- 3 files changed, 211 insertions(+), 3 deletions(-) diff --git a/crates/squawk_parser/src/grammar.rs b/crates/squawk_parser/src/grammar.rs index 2e6ed586..f54d87d2 100644 --- a/crates/squawk_parser/src/grammar.rs +++ b/crates/squawk_parser/src/grammar.rs @@ -3456,7 +3456,9 @@ fn join(p: &mut Parser<'_>) { if !opt_from_item(p) { p.error("expected from_item"); } - if p.at(ON_KW) && !p.nth_at(1, CONFLICT_KW) { + // need to check that we're not actually at the on_conflict clause in an + // insert/update statement + if p.at(ON_KW) && !(p.nth_at(1, CONFLICT_KW) && matches!(p.nth(2), DO_KW | ON_KW | L_PAREN)) { on_clause(p); } else if p.at(USING_KW) { join_using_clause(p); diff --git a/crates/squawk_parser/tests/data/ok/insert.sql b/crates/squawk_parser/tests/data/ok/insert.sql index d805d2dc..94169cae 100644 --- a/crates/squawk_parser/tests/data/ok/insert.sql +++ b/crates/squawk_parser/tests/data/ok/insert.sql @@ -99,4 +99,23 @@ INSERT INTO t SELECT * FROM t CROSS JOIN f -ON CONFLICT DO NOTHING; +ON CONFLICT + DO NOTHING; + +insert into t +select * from u join i +on conflict = conflict +on conflict + do nothing; + +insert into t +select * from u join i +on conflict = conflict +on conflict (foo) + do nothing; + +insert into t +select * from u join i +on conflict = conflict +on conflict + on constraint temporal_rng_pk do nothing; diff --git a/crates/squawk_parser/tests/snapshots/tests__insert_ok.snap b/crates/squawk_parser/tests/snapshots/tests__insert_ok.snap index d337aeea..c6374af1 100644 --- a/crates/squawk_parser/tests/snapshots/tests__insert_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__insert_ok.snap @@ -1508,10 +1508,197 @@ SOURCE_FILE ON_KW "ON" WHITESPACE " " CONFLICT_KW "CONFLICT" - WHITESPACE " " + WHITESPACE "\n " CONFLICT_DO_NOTHING DO_KW "DO" WHITESPACE " " NOTHING_KW "NOTHING" SEMICOLON ";" + WHITESPACE "\n\n" + INSERT + INSERT_KW "insert" + WHITESPACE " " + INTO_KW "into" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME_REF + IDENT "t" + WHITESPACE " \n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + STAR "*" + WHITESPACE " " + FROM_CLAUSE + FROM_KW "from" + WHITESPACE " " + JOIN_EXPR + FROM_ITEM + NAME_REF + IDENT "u" + WHITESPACE " " + JOIN + JOIN_INNER + JOIN_KW "join" + WHITESPACE " " + FROM_ITEM + NAME_REF + IDENT "i" + WHITESPACE " \n" + ON_CLAUSE + ON_KW "on" + WHITESPACE " " + BIN_EXPR + NAME_REF + CONFLICT_KW "conflict" + WHITESPACE " " + EQ "=" + WHITESPACE " " + NAME_REF + CONFLICT_KW "conflict" + WHITESPACE "\n" + ON_CONFLICT_CLAUSE + ON_KW "on" + WHITESPACE " " + CONFLICT_KW "conflict" + WHITESPACE "\n " + CONFLICT_DO_NOTHING + DO_KW "do" + WHITESPACE " " + NOTHING_KW "nothing" + SEMICOLON ";" + WHITESPACE "\n\n" + INSERT + INSERT_KW "insert" + WHITESPACE " " + INTO_KW "into" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME_REF + IDENT "t" + WHITESPACE " \n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + STAR "*" + WHITESPACE " " + FROM_CLAUSE + FROM_KW "from" + WHITESPACE " " + JOIN_EXPR + FROM_ITEM + NAME_REF + IDENT "u" + WHITESPACE " " + JOIN + JOIN_INNER + JOIN_KW "join" + WHITESPACE " " + FROM_ITEM + NAME_REF + IDENT "i" + WHITESPACE " \n" + ON_CLAUSE + ON_KW "on" + WHITESPACE " " + BIN_EXPR + NAME_REF + CONFLICT_KW "conflict" + WHITESPACE " " + EQ "=" + WHITESPACE " " + NAME_REF + CONFLICT_KW "conflict" + WHITESPACE "\n" + ON_CONFLICT_CLAUSE + ON_KW "on" + WHITESPACE " " + CONFLICT_KW "conflict" + WHITESPACE " " + CONFLICT_ON_INDEX + CONFLICT_INDEX_ITEM_LIST + L_PAREN "(" + CONFLICT_INDEX_ITEM + NAME_REF + IDENT "foo" + R_PAREN ")" + WHITESPACE "\n " + CONFLICT_DO_NOTHING + DO_KW "do" + WHITESPACE " " + NOTHING_KW "nothing" + SEMICOLON ";" + WHITESPACE "\n\n" + INSERT + INSERT_KW "insert" + WHITESPACE " " + INTO_KW "into" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME_REF + IDENT "t" + WHITESPACE " \n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + STAR "*" + WHITESPACE " " + FROM_CLAUSE + FROM_KW "from" + WHITESPACE " " + JOIN_EXPR + FROM_ITEM + NAME_REF + IDENT "u" + WHITESPACE " " + JOIN + JOIN_INNER + JOIN_KW "join" + WHITESPACE " " + FROM_ITEM + NAME_REF + IDENT "i" + WHITESPACE " \n" + ON_CLAUSE + ON_KW "on" + WHITESPACE " " + BIN_EXPR + NAME_REF + CONFLICT_KW "conflict" + WHITESPACE " " + EQ "=" + WHITESPACE " " + NAME_REF + CONFLICT_KW "conflict" + WHITESPACE "\n" + ON_CONFLICT_CLAUSE + ON_KW "on" + WHITESPACE " " + CONFLICT_KW "conflict" + WHITESPACE "\n " + CONFLICT_ON_CONSTRAINT + ON_KW "on" + WHITESPACE " " + CONSTRAINT_KW "constraint" + WHITESPACE " " + NAME_REF + IDENT "temporal_rng_pk" + WHITESPACE " " + CONFLICT_DO_NOTHING + DO_KW "do" + WHITESPACE " " + NOTHING_KW "nothing" + SEMICOLON ";" WHITESPACE "\n"