From 25fd3dd5865ba530492ed254de0cc2da4426b870 Mon Sep 17 00:00:00 2001 From: ItsDeadlyProgrammer Date: Fri, 23 Oct 2026 11:22:15 +0530 Subject: [PATCH 1/2] feat: parse INSERT OR ABORT, FAIL, IGNORE and ROLLBACK (SQLite) UpsertType and the deparsers already had these values, but the grammar only accepted INSERT OR REPLACE. Part of #2852 --- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 17 +++++++++++++- .../statement/select/SQLiteTest.java | 22 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index a7203c03b5..8bedabdb15 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -8073,7 +8073,22 @@ Upsert Upsert(): { upsert.setUpsertType(UpsertType.REPLACE); } | ( - { upsert.setUpsertType(UpsertType.INSERT_OR_REPLACE); } + + ( + { upsert.setUpsertType(UpsertType.INSERT_OR_REPLACE); } + | + { upsert.setUpsertType(UpsertType.INSERT_OR_IGNORE); } + | + { upsert.setUpsertType(UpsertType.INSERT_OR_ROLLBACK); } + | + // SQLite conflict resolution algorithms that are not keywords of the parser + tk= { + requireDdlSyntax("ABORT".equalsIgnoreCase(tk.image) || "FAIL".equalsIgnoreCase(tk.image), + "Expected ABORT, FAIL, IGNORE, REPLACE or ROLLBACK"); + upsert.setUpsertType("ABORT".equalsIgnoreCase(tk.image) + ? UpsertType.INSERT_OR_ABORT : UpsertType.INSERT_OR_FAIL); + } + ) ) ) [ LOOKAHEAD(2) { upsert.setUsingInto(true); } ] diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SQLiteTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SQLiteTest.java index ecb1204956..53f04b5829 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SQLiteTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SQLiteTest.java @@ -10,8 +10,15 @@ package net.sf.jsqlparser.statement.select; import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.test.TestUtils; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.Locale; + +import static org.junit.jupiter.api.Assertions.assertThrows; public class SQLiteTest { @Test @@ -19,4 +26,19 @@ void testInsertOrReplaceUpsert() throws JSQLParserException { String sqlString = "INSERT OR REPLACE INTO kjobLocks VALUES (?, ?, ?)"; TestUtils.assertSqlCanBeParsedAndDeparsed(sqlString, true); } + + @ParameterizedTest + @ValueSource(strings = {"ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK"}) + void testInsertOrConflictAlgorithm(String algorithm) throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed( + "INSERT OR " + algorithm + " INTO t (a, b) VALUES (1, 2)", true); + TestUtils.assertSqlCanBeParsedAndDeparsed( + "insert or " + algorithm.toLowerCase(Locale.ROOT) + " into t select * from s", true); + } + + @Test + void testInsertOrUnknownAlgorithm() { + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse("INSERT OR NOTHING INTO t VALUES (1)")); + } } From fe5d183f75a2a4f87e63ddd707590a41f9f4d10c Mon Sep 17 00:00:00 2001 From: ItsDeadlyProgrammer Date: Fri, 23 Oct 2026 16:26:59 +0530 Subject: [PATCH 2/2] feat: guard INSERT OR conflict clauses by the SQLITE dialect Add Dialect.SQLITE. INSERT OR ABORT, FAIL, IGNORE and ROLLBACK need it. INSERT OR REPLACE keeps parsing in every dialect. --- .../jsqlparser/parser/AbstractJSqlParser.java | 2 +- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 19 ++++++++++++++++--- src/site/sphinx/usage.rst | 2 ++ .../statement/select/SQLiteTest.java | 17 ++++++++++++++--- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java b/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java index f38264f5e6..ffc924693c 100644 --- a/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java +++ b/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java @@ -48,7 +48,7 @@ public enum Dialect { AdjacentStringLiterals.WHITESPACE, Feature.allowDoubleQuotedStrings, Feature.allowBackslashEscapeCharacter), SNOWFLAKE( - Feature.allowBackslashEscapeCharacter), INFORMIX, SPANNER, DORIS, COCKROACHDB, TERADATA; + Feature.allowBackslashEscapeCharacter), INFORMIX, SPANNER, DORIS, COCKROACHDB, TERADATA, SQLITE; private final Set lexerFeatures; private final AdjacentStringLiterals adjacentStringLiterals; diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 8bedabdb15..c5d499e4ef 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -3144,6 +3144,11 @@ public class CCJSqlParser extends AbstractJSqlParser { } } + private void requireSqliteSyntax(String syntax) throws ParseException { + requireDdlSyntax(Dialect.SQLITE.name().equals(getAsString(Feature.dialect)), + syntax + " needs Dialect.SQLITE"); + } + private void requireDdlSyntax(boolean valid, String message) throws ParseException { if (!valid) { throw new ParseException(message); @@ -8077,12 +8082,20 @@ Upsert Upsert(): ( { upsert.setUpsertType(UpsertType.INSERT_OR_REPLACE); } | - { upsert.setUpsertType(UpsertType.INSERT_OR_IGNORE); } + // the other conflict resolution algorithms are SQLite syntax + { + requireSqliteSyntax("INSERT OR IGNORE"); + upsert.setUpsertType(UpsertType.INSERT_OR_IGNORE); + } | - { upsert.setUpsertType(UpsertType.INSERT_OR_ROLLBACK); } + { + requireSqliteSyntax("INSERT OR ROLLBACK"); + upsert.setUpsertType(UpsertType.INSERT_OR_ROLLBACK); + } | - // SQLite conflict resolution algorithms that are not keywords of the parser + // ABORT and FAIL are not keywords of the parser tk= { + requireSqliteSyntax("INSERT OR " + tk.image); requireDdlSyntax("ABORT".equalsIgnoreCase(tk.image) || "FAIL".equalsIgnoreCase(tk.image), "Expected ABORT, FAIL, IGNORE, REPLACE or ROLLBACK"); upsert.setUpsertType("ABORT".equalsIgnoreCase(tk.image) diff --git a/src/site/sphinx/usage.rst b/src/site/sphinx/usage.rst index a07b72f088..c784f81d3a 100644 --- a/src/site/sphinx/usage.rst +++ b/src/site/sphinx/usage.rst @@ -1604,6 +1604,8 @@ One grammar covers every supported RDBMS, but a few pieces of syntax mean differ - ``ALTER TABLE ... ALTER PRIMARY KEY USING COLUMNS (...)`` with optional hash sharding and storage parameters * - ``TERADATA`` - ``UPDATE target FROM sources SET ...`` with the FROM clause before SET + * - ``SQLITE`` + - ``INSERT OR ABORT``, ``INSERT OR FAIL``, ``INSERT OR IGNORE`` and ``INSERT OR ROLLBACK`` conflict clauses Features set explicitly *after* the preset win over it. diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SQLiteTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SQLiteTest.java index 53f04b5829..6ef751ac10 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SQLiteTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SQLiteTest.java @@ -10,6 +10,7 @@ package net.sf.jsqlparser.statement.select; import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.test.TestUtils; import org.junit.jupiter.api.Test; @@ -31,14 +32,24 @@ void testInsertOrReplaceUpsert() throws JSQLParserException { @ValueSource(strings = {"ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK"}) void testInsertOrConflictAlgorithm(String algorithm) throws JSQLParserException { TestUtils.assertSqlCanBeParsedAndDeparsed( - "INSERT OR " + algorithm + " INTO t (a, b) VALUES (1, 2)", true); + "INSERT OR " + algorithm + " INTO t (a, b) VALUES (1, 2)", true, + parser -> parser.withDialect(Dialect.SQLITE)); TestUtils.assertSqlCanBeParsedAndDeparsed( - "insert or " + algorithm.toLowerCase(Locale.ROOT) + " into t select * from s", true); + "insert or " + algorithm.toLowerCase(Locale.ROOT) + " into t select * from s", true, + parser -> parser.withDialect(Dialect.SQLITE)); + } + + @ParameterizedTest + @ValueSource(strings = {"ABORT", "FAIL", "IGNORE", "ROLLBACK"}) + void testInsertOrConflictAlgorithmNeedsSqliteDialect(String algorithm) { + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse("INSERT OR " + algorithm + " INTO t VALUES (1)")); } @Test void testInsertOrUnknownAlgorithm() { assertThrows(JSQLParserException.class, - () -> CCJSqlParserUtil.parse("INSERT OR NOTHING INTO t VALUES (1)")); + () -> CCJSqlParserUtil.parse("INSERT OR NOTHING INTO t VALUES (1)", + parser -> parser.withDialect(Dialect.SQLITE))); } }