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 a7203c03b5..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); @@ -8073,7 +8078,30 @@ Upsert Upsert(): { upsert.setUpsertType(UpsertType.REPLACE); } | ( - { upsert.setUpsertType(UpsertType.INSERT_OR_REPLACE); } + + ( + { upsert.setUpsertType(UpsertType.INSERT_OR_REPLACE); } + | + // the other conflict resolution algorithms are SQLite syntax + { + requireSqliteSyntax("INSERT OR IGNORE"); + upsert.setUpsertType(UpsertType.INSERT_OR_IGNORE); + } + | + { + requireSqliteSyntax("INSERT OR ROLLBACK"); + upsert.setUpsertType(UpsertType.INSERT_OR_ROLLBACK); + } + | + // 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) + ? UpsertType.INSERT_OR_ABORT : UpsertType.INSERT_OR_FAIL); + } + ) ) ) [ LOOKAHEAD(2) { upsert.setUsingInto(true); } ] 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 ecb1204956..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,8 +10,16 @@ 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; +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 +27,29 @@ 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, + parser -> parser.withDialect(Dialect.SQLITE)); + TestUtils.assertSqlCanBeParsedAndDeparsed( + "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)", + parser -> parser.withDialect(Dialect.SQLITE))); + } }