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
Original file line number Diff line number Diff line change
Expand Up @@ -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<Feature> lexerFeatures;
private final AdjacentStringLiterals adjacentStringLiterals;
Expand Down
30 changes: 29 additions & 1 deletion src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -3144,6 +3144,11 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
}
}

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);
Expand Down Expand Up @@ -8073,7 +8078,30 @@ Upsert Upsert():
<K_REPLACE> { upsert.setUpsertType(UpsertType.REPLACE); }
|
(
<K_INSERT> <K_OR> <K_REPLACE> { upsert.setUpsertType(UpsertType.INSERT_OR_REPLACE); }
<K_INSERT> <K_OR>
(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do understand that this is SQLite specific syntax. If my understanding is correct, then please guard this block by the SQLite Dialect.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, done in 8805d37. There was no SQLite value in Dialect, so I added Dialect.SQLITE and listed it in the dialect table in usage.rst.

INSERT OR IGNORE, ROLLBACK, ABORT and FAIL now parse only with Dialect.SQLITE; other dialects get a ParseException saying so. INSERT OR REPLACE still parses everywhere, because it did before this PR. The tests cover both cases, and the checks are green.

<K_REPLACE> { upsert.setUpsertType(UpsertType.INSERT_OR_REPLACE); }
|
// the other conflict resolution algorithms are SQLite syntax
<K_IGNORE> {
requireSqliteSyntax("INSERT OR IGNORE");
upsert.setUpsertType(UpsertType.INSERT_OR_IGNORE);
}
|
<K_ROLLBACK> {
requireSqliteSyntax("INSERT OR ROLLBACK");
upsert.setUpsertType(UpsertType.INSERT_OR_ROLLBACK);
}
|
// ABORT and FAIL are not keywords of the parser
tk=<S_IDENTIFIER> {
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) <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