Skip to content
Open
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
13 changes: 13 additions & 0 deletions 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.update.UpdateConflictAlgorithm;
import net.sf.jsqlparser.statement.upsert.UpsertType;

import java.util.ArrayList;
Expand Down Expand Up @@ -284,6 +285,18 @@ protected UpsertType insertOrType(String algorithm) throws ParseException {
}
}

protected UpdateConflictAlgorithm updateOrAlgorithm(String algorithm) throws ParseException {
if (!Dialect.SQLITE.name().equals(getAsString(Feature.dialect))) {
throw new ParseException("UPDATE OR " + algorithm + " needs Dialect.SQLITE");
}
for (UpdateConflictAlgorithm conflictAlgorithm : UpdateConflictAlgorithm.values()) {
if (conflictAlgorithm.name().equalsIgnoreCase(algorithm)) {
return conflictAlgorithm;
}
}
throw new ParseException("Expected ABORT, FAIL, IGNORE, REPLACE or ROLLBACK");
}

public String getAsString(Feature f) {
return getConfiguration().getAsString(f);
}
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/net/sf/jsqlparser/statement/update/Update.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public class Update extends ASTNodeAccessImpl implements Statement {
private Limit limit;
private OptionClause option;
private ReturningClause returningClause;
private UpdateConflictAlgorithm conflictAlgorithm;
private UpdateModifierPriority modifierPriority;
private boolean modifierIgnore;

Expand Down Expand Up @@ -344,6 +345,14 @@ public Update setReturningClause(ReturningClause returningClause) {
return this;
}

public UpdateConflictAlgorithm getConflictAlgorithm() {
return conflictAlgorithm;
}

public void setConflictAlgorithm(UpdateConflictAlgorithm conflictAlgorithm) {
this.conflictAlgorithm = conflictAlgorithm;
}

public UpdateModifierPriority getModifierPriority() {
return modifierPriority;
}
Expand Down Expand Up @@ -381,6 +390,9 @@ public String toString() {
if (oracleHint != null) {
b.append(oracleHint).append(" ");
}
if (conflictAlgorithm != null) {
b.append("OR ").append(conflictAlgorithm.name()).append(" ");
}
if (modifierPriority != null) {
b.append(modifierPriority.name()).append(" ");
}
Expand Down Expand Up @@ -505,6 +517,11 @@ public Update withExpressions(List<Expression> expressions) {
return this;
}

public Update withConflictAlgorithm(UpdateConflictAlgorithm conflictAlgorithm) {
this.setConflictAlgorithm(conflictAlgorithm);
return this;
}

public Update withModifierPriority(UpdateModifierPriority modifierPriority) {
this.setModifierPriority(modifierPriority);
return this;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2026 JSQLParser
* %%
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
* #L%
*/
package net.sf.jsqlparser.statement.update;

import java.util.Locale;

/**
* The conflict resolution algorithm of SQLite's {@code UPDATE OR <algorithm>}.
*/
public enum UpdateConflictAlgorithm {
ABORT, FAIL, IGNORE, REPLACE, ROLLBACK;

public static UpdateConflictAlgorithm from(String algorithm) {
return Enum.valueOf(UpdateConflictAlgorithm.class, algorithm.toUpperCase(Locale.ROOT));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ public void deParse(Update update) {
if (update.getOracleHint() != null) {
builder.append(update.getOracleHint()).append(" ");
}
if (update.getConflictAlgorithm() != null) {
builder.append("OR ").append(update.getConflictAlgorithm()).append(" ");
}
if (update.getModifierPriority() != null) {
builder.append(update.getModifierPriority()).append(" ");
}
Expand Down
4 changes: 4 additions & 0 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -7703,11 +7703,15 @@ Update Update():
Token tk = null;
UpdateModifierPriority modifierPriority = null;
boolean modifierIgnore = false;
String algorithm;

OutputClause outputClause = null;
}
{
<K_UPDATE> { update.setOracleHint(getOracleHint()); }
// SQLite: UPDATE OR <algorithm>; ABORT and FAIL are not keywords of the parser
[ <K_OR> algorithm=RelObjectName()
{ update.setConflictAlgorithm(updateOrAlgorithm(algorithm)); } ]
[ LOOKAHEAD(2) <K_LOW_PRIORITY> { modifierPriority = UpdateModifierPriority.LOW_PRIORITY; }]
[ LOOKAHEAD(2) <K_IGNORE> { modifierIgnore = true; }]
table=TableWithAliasAndMysqlIndexHint()
Expand Down
3 changes: 2 additions & 1 deletion src/site/sphinx/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1612,7 +1612,8 @@ One grammar covers every supported RDBMS, but a few pieces of syntax mean differ
* - ``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
- ``INSERT OR ABORT``, ``INSERT OR FAIL``, ``INSERT OR IGNORE`` and ``INSERT OR ROLLBACK`` conflict clauses;
``UPDATE OR`` with any of ``ABORT``, ``FAIL``, ``IGNORE``, ``REPLACE`` and ``ROLLBACK``

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

Expand Down
42 changes: 42 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 @@ -12,13 +12,17 @@
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.statement.update.UpdateConflictAlgorithm;
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.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class SQLiteTest {
Expand Down Expand Up @@ -52,4 +56,42 @@ void testInsertOrUnknownAlgorithm() {
() -> CCJSqlParserUtil.parse("INSERT OR NOTHING INTO t VALUES (1)",
parser -> parser.withDialect(Dialect.SQLITE)));
}

@ParameterizedTest
@ValueSource(strings = {"ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK"})
void testUpdateOrConflictAlgorithm(String algorithm) throws JSQLParserException {
Update update = (Update) TestUtils.assertSqlCanBeParsedAndDeparsed(
"UPDATE OR " + algorithm + " t SET a = 1, b = 2 WHERE c = 3", true,
parser -> parser.withDialect(Dialect.SQLITE));
assertEquals(UpdateConflictAlgorithm.valueOf(algorithm), update.getConflictAlgorithm());

TestUtils.assertSqlCanBeParsedAndDeparsed(
"update or " + algorithm.toLowerCase(Locale.ROOT) + " t set a = 1", true,
parser -> parser.withDialect(Dialect.SQLITE));
}

// A parser without any UPDATE OR support throws here too: these cases pin the SQLITE
// dialect guard, they do not show that UPDATE OR parses.
@ParameterizedTest
@ValueSource(strings = {"ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK"})
void testUpdateOrConflictAlgorithmNeedsSqliteDialect(String algorithm) {
assertThrows(JSQLParserException.class,
() -> CCJSqlParserUtil.parse("UPDATE OR " + algorithm + " t SET a = 1"));
}

// A parser without any UPDATE OR support throws here too: this case pins the check of the
// word after OR, it does not show that UPDATE OR parses.
@Test
void testUpdateOrUnknownAlgorithm() {
assertThrows(JSQLParserException.class,
() -> CCJSqlParserUtil.parse("UPDATE OR NOTHING t SET a = 1",
parser -> parser.withDialect(Dialect.SQLITE)));
}

@Test
void testUpdateWithoutConflictAlgorithm() throws JSQLParserException {
Update update = (Update) TestUtils.assertSqlCanBeParsedAndDeparsed(
"UPDATE t SET a = 1", true, parser -> parser.withDialect(Dialect.SQLITE));
assertNull(update.getConflictAlgorithm());
}
}