Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import net.sf.jsqlparser.parser.feature.Feature;
import net.sf.jsqlparser.parser.feature.FeatureConfiguration;
import net.sf.jsqlparser.statement.upsert.UpsertType;

import java.util.ArrayList;
import java.util.Collections;
Expand Down Expand Up @@ -48,7 +49,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<Feature> lexerFeatures;
private final AdjacentStringLiterals adjacentStringLiterals;
Expand Down Expand Up @@ -259,6 +260,30 @@ public Integer getAsInteger(Feature f) {
return getConfiguration().getAsInteger(f);
}

/**
* The INSERT OR conflict algorithms other than REPLACE are SQLite syntax.
*/
protected UpsertType insertOrType(String algorithm) throws ParseException {
if ("REPLACE".equalsIgnoreCase(algorithm)) {
return UpsertType.INSERT_OR_REPLACE;
}
if (!Dialect.SQLITE.name().equals(getAsString(Feature.dialect))) {
throw new ParseException("INSERT OR " + algorithm + " needs Dialect.SQLITE");
}
switch (algorithm.toUpperCase()) {
case "IGNORE":
return UpsertType.INSERT_OR_IGNORE;
case "ROLLBACK":
return UpsertType.INSERT_OR_ROLLBACK;
case "ABORT":
return UpsertType.INSERT_OR_ABORT;
case "FAIL":
return UpsertType.INSERT_OR_FAIL;
default:
throw new ParseException("Expected ABORT, FAIL, IGNORE, REPLACE or ROLLBACK");
}
}

public String getAsString(Feature f) {
return getConfiguration().getAsString(f);
}
Expand Down
6 changes: 5 additions & 1 deletion src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -8099,6 +8099,7 @@ OutputClause OutputClause():
Upsert Upsert():
{
Upsert upsert = new Upsert();
String algorithm;
Table table = null;
ExpressionList<Column> columns;
List<UpdateSet> updateSets;
Expand All @@ -8115,7 +8116,10 @@ Upsert Upsert():
<K_REPLACE> { upsert.setUpsertType(UpsertType.REPLACE); }
|
(
<K_INSERT> <K_OR> <K_REPLACE> { upsert.setUpsertType(UpsertType.INSERT_OR_REPLACE); }
<K_INSERT> <K_OR>
// ABORT and FAIL are not keywords of the parser
algorithm=RelObjectName()
{ upsert.setUpsertType(insertOrType(algorithm)); }
)
)
[ LOOKAHEAD(2) <K_INTO> { upsert.setUsingInto(true); } ]
Expand Down
2 changes: 2 additions & 0 deletions src/site/sphinx/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
33 changes: 33 additions & 0 deletions src/test/java/net/sf/jsqlparser/statement/select/SQLiteTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,46 @@
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
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)));
}
}
Loading