Skip to content

Commit 9663289

Browse files
feat: parse INSERT OR ABORT, FAIL, IGNORE and ROLLBACK (SQLite) (#2853)
* 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 * 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. * fix: keep CCJSqlParser under the class size limit for JaCoCo The extra INSERT OR alternatives pushed the constant pool of CCJSqlParser past 65535 entries once JaCoCo instrumented it, so the class was silently left without coverage data and the coverage check failed. Parse the algorithm with RelObjectName and decide in AbstractJSqlParser instead.
1 parent aa599ba commit 9663289

4 files changed

Lines changed: 66 additions & 2 deletions

File tree

‎src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java‎

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import net.sf.jsqlparser.parser.feature.Feature;
1313
import net.sf.jsqlparser.parser.feature.FeatureConfiguration;
14+
import net.sf.jsqlparser.statement.upsert.UpsertType;
1415

1516
import java.util.ArrayList;
1617
import java.util.Collections;
@@ -48,7 +49,7 @@ public enum Dialect {
4849
AdjacentStringLiterals.WHITESPACE,
4950
Feature.allowDoubleQuotedStrings,
5051
Feature.allowBackslashEscapeCharacter), SNOWFLAKE(
51-
Feature.allowBackslashEscapeCharacter), INFORMIX, SPANNER, DORIS, COCKROACHDB, TERADATA;
52+
Feature.allowBackslashEscapeCharacter), INFORMIX, SPANNER, DORIS, COCKROACHDB, TERADATA, SQLITE;
5253

5354
private final Set<Feature> lexerFeatures;
5455
private final AdjacentStringLiterals adjacentStringLiterals;
@@ -259,6 +260,30 @@ public Integer getAsInteger(Feature f) {
259260
return getConfiguration().getAsInteger(f);
260261
}
261262

263+
/**
264+
* The INSERT OR conflict algorithms other than REPLACE are SQLite syntax.
265+
*/
266+
protected UpsertType insertOrType(String algorithm) throws ParseException {
267+
if ("REPLACE".equalsIgnoreCase(algorithm)) {
268+
return UpsertType.INSERT_OR_REPLACE;
269+
}
270+
if (!Dialect.SQLITE.name().equals(getAsString(Feature.dialect))) {
271+
throw new ParseException("INSERT OR " + algorithm + " needs Dialect.SQLITE");
272+
}
273+
switch (algorithm.toUpperCase()) {
274+
case "IGNORE":
275+
return UpsertType.INSERT_OR_IGNORE;
276+
case "ROLLBACK":
277+
return UpsertType.INSERT_OR_ROLLBACK;
278+
case "ABORT":
279+
return UpsertType.INSERT_OR_ABORT;
280+
case "FAIL":
281+
return UpsertType.INSERT_OR_FAIL;
282+
default:
283+
throw new ParseException("Expected ABORT, FAIL, IGNORE, REPLACE or ROLLBACK");
284+
}
285+
}
286+
262287
public String getAsString(Feature f) {
263288
return getConfiguration().getAsString(f);
264289
}

‎src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8098,6 +8098,7 @@ OutputClause OutputClause():
80988098
Upsert Upsert():
80998099
{
81008100
Upsert upsert = new Upsert();
8101+
String algorithm;
81018102
Table table = null;
81028103
ExpressionList<Column> columns;
81038104
List<UpdateSet> updateSets;
@@ -8114,7 +8115,10 @@ Upsert Upsert():
81148115
<K_REPLACE> { upsert.setUpsertType(UpsertType.REPLACE); }
81158116
|
81168117
(
8117-
<K_INSERT> <K_OR> <K_REPLACE> { upsert.setUpsertType(UpsertType.INSERT_OR_REPLACE); }
8118+
<K_INSERT> <K_OR>
8119+
// ABORT and FAIL are not keywords of the parser
8120+
algorithm=RelObjectName()
8121+
{ upsert.setUpsertType(insertOrType(algorithm)); }
81188122
)
81198123
)
81208124
[ LOOKAHEAD(2) <K_INTO> { upsert.setUsingInto(true); } ]

‎src/site/sphinx/usage.rst‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1604,6 +1604,8 @@ One grammar covers every supported RDBMS, but a few pieces of syntax mean differ
16041604
- ``ALTER TABLE ... ALTER PRIMARY KEY USING COLUMNS (...)`` with optional hash sharding and storage parameters
16051605
* - ``TERADATA``
16061606
- ``UPDATE target FROM sources SET ...`` with the FROM clause before SET
1607+
* - ``SQLITE``
1608+
- ``INSERT OR ABORT``, ``INSERT OR FAIL``, ``INSERT OR IGNORE`` and ``INSERT OR ROLLBACK`` conflict clauses
16071609

16081610
Features set explicitly *after* the preset win over it.
16091611

‎src/test/java/net/sf/jsqlparser/statement/select/SQLiteTest.java‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,46 @@
1010
package net.sf.jsqlparser.statement.select;
1111

1212
import net.sf.jsqlparser.JSQLParserException;
13+
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
14+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
1315
import net.sf.jsqlparser.test.TestUtils;
1416
import org.junit.jupiter.api.Test;
17+
import org.junit.jupiter.params.ParameterizedTest;
18+
import org.junit.jupiter.params.provider.ValueSource;
19+
20+
import java.util.Locale;
21+
22+
import static org.junit.jupiter.api.Assertions.assertThrows;
1523

1624
public class SQLiteTest {
1725
@Test
1826
void testInsertOrReplaceUpsert() throws JSQLParserException {
1927
String sqlString = "INSERT OR REPLACE INTO kjobLocks VALUES (?, ?, ?)";
2028
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlString, true);
2129
}
30+
31+
@ParameterizedTest
32+
@ValueSource(strings = {"ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK"})
33+
void testInsertOrConflictAlgorithm(String algorithm) throws JSQLParserException {
34+
TestUtils.assertSqlCanBeParsedAndDeparsed(
35+
"INSERT OR " + algorithm + " INTO t (a, b) VALUES (1, 2)", true,
36+
parser -> parser.withDialect(Dialect.SQLITE));
37+
TestUtils.assertSqlCanBeParsedAndDeparsed(
38+
"insert or " + algorithm.toLowerCase(Locale.ROOT) + " into t select * from s", true,
39+
parser -> parser.withDialect(Dialect.SQLITE));
40+
}
41+
42+
@ParameterizedTest
43+
@ValueSource(strings = {"ABORT", "FAIL", "IGNORE", "ROLLBACK"})
44+
void testInsertOrConflictAlgorithmNeedsSqliteDialect(String algorithm) {
45+
assertThrows(JSQLParserException.class,
46+
() -> CCJSqlParserUtil.parse("INSERT OR " + algorithm + " INTO t VALUES (1)"));
47+
}
48+
49+
@Test
50+
void testInsertOrUnknownAlgorithm() {
51+
assertThrows(JSQLParserException.class,
52+
() -> CCJSqlParserUtil.parse("INSERT OR NOTHING INTO t VALUES (1)",
53+
parser -> parser.withDialect(Dialect.SQLITE)));
54+
}
2255
}

0 commit comments

Comments
 (0)