diff --git a/src/main/java/net/sf/jsqlparser/statement/create/table/CreateTable.java b/src/main/java/net/sf/jsqlparser/statement/create/table/CreateTable.java index ed0d93009..d9e9a6029 100644 --- a/src/main/java/net/sf/jsqlparser/statement/create/table/CreateTable.java +++ b/src/main/java/net/sf/jsqlparser/statement/create/table/CreateTable.java @@ -570,9 +570,14 @@ private void appendColumnDefinitions(StringBuilder b) { public void appendTableOptionsTo(StringBuilder builder, java.util.function.Consumer expressionPrinter) { if (tableOptions != null) { + TableOption previous = null; for (TableOption option : tableOptions) { - builder.append(' '); + // SQLite requires a comma between WITHOUT ROWID and STRICT + builder.append(isSqliteTableOption(previous) && isSqliteTableOption(option) + ? ", " + : " "); option.appendTo(builder, expressionPrinter); + previous = option; } } else { String options = PlainSelect.getStringList(tableOptionsStrings, false, false); @@ -582,6 +587,11 @@ public void appendTableOptionsTo(StringBuilder builder, } } + private static boolean isSqliteTableOption(TableOption option) { + return option != null && (option.getKind() == TableOption.Kind.WITHOUT_ROWID + || option.getKind() == TableOption.Kind.STRICT); + } + private void appendTableOptions(StringBuilder b) { if (partitioning != null && tableOptionsAfterPartition) { b.append(" ").append(partitioning); diff --git a/src/main/java/net/sf/jsqlparser/statement/create/table/TableOption.java b/src/main/java/net/sf/jsqlparser/statement/create/table/TableOption.java index d895f1f04..0ce2c4f40 100644 --- a/src/main/java/net/sf/jsqlparser/statement/create/table/TableOption.java +++ b/src/main/java/net/sf/jsqlparser/statement/create/table/TableOption.java @@ -39,7 +39,7 @@ public class TableOption implements Serializable { private StringValue comment; public enum Kind { - ENGINE, CHARACTER_SET, COLLATE, COMMENT, AUTO_INCREMENT, STATS_AUTO_RECALC, STATS_PERSISTENT, STATS_SAMPLE_PAGES, UNION, ENCRYPTION, PASSWORD, DATA_DIRECTORY, INDEX_DIRECTORY, SECONDARY_ENGINE, AUTOEXTEND_SIZE, INSERT_METHOD, PACK_KEYS, DELAY_KEY_WRITE, CHECKSUM, CONNECTION, COMPRESSION, STORAGE_PARAMETERS, WITHOUT_OIDS, ENGINE_ATTRIBUTE, SECONDARY_ENGINE_ATTRIBUTE, ROW_FORMAT, AVG_ROW_LENGTH, MAX_ROWS, MIN_ROWS, KEY_BLOCK_SIZE, TABLESPACE, FOREIGN_SERVER, ACCESS_METHOD, ON_COMMIT, FILE_BLOCK_SIZE, OTHER + ENGINE, CHARACTER_SET, COLLATE, COMMENT, AUTO_INCREMENT, STATS_AUTO_RECALC, STATS_PERSISTENT, STATS_SAMPLE_PAGES, UNION, ENCRYPTION, PASSWORD, DATA_DIRECTORY, INDEX_DIRECTORY, SECONDARY_ENGINE, AUTOEXTEND_SIZE, INSERT_METHOD, PACK_KEYS, DELAY_KEY_WRITE, CHECKSUM, CONNECTION, COMPRESSION, STORAGE_PARAMETERS, WITHOUT_OIDS, WITHOUT_ROWID, STRICT, ENGINE_ATTRIBUTE, SECONDARY_ENGINE_ATTRIBUTE, ROW_FORMAT, AVG_ROW_LENGTH, MAX_ROWS, MIN_ROWS, KEY_BLOCK_SIZE, TABLESPACE, FOREIGN_SERVER, ACCESS_METHOD, ON_COMMIT, FILE_BLOCK_SIZE, OTHER } /** diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 4cf410188..efd2fa612 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -19156,10 +19156,17 @@ TableOption CreateTableOption(): option = new TableOption(); option.setStorageParameters(storageParameters); } | + LOOKAHEAD({ getToken(1).kind == K_WITHOUT && "ROWID".equalsIgnoreCase(getToken(2).image) }) + ContextualKeyword("ROWID") { + option = new TableOption(TableOption.Kind.WITHOUT_ROWID, "WITHOUT ROWID", null, false); + } + | LOOKAHEAD( ) ContextualKeyword("OIDS") { option = new TableOption(TableOption.Kind.WITHOUT_OIDS, "WITHOUT OIDS", null, false); } | + { option = new TableOption(TableOption.Kind.STRICT, "STRICT", null, false); } + | LOOKAHEAD( ) option=OnCommitTableOption() | // USING INDEX TABLESPACE stays a raw parameter 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 6ef751ac1..e4377f9d5 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SQLiteTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SQLiteTest.java @@ -12,13 +12,18 @@ import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.create.table.CreateTable; +import net.sf.jsqlparser.statement.create.table.TableOption; 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.List; import java.util.Locale; +import java.util.stream.Collectors; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; public class SQLiteTest { @@ -52,4 +57,25 @@ void testInsertOrUnknownAlgorithm() { () -> CCJSqlParserUtil.parse("INSERT OR NOTHING INTO t VALUES (1)", parser -> parser.withDialect(Dialect.SQLITE))); } + + @ParameterizedTest + @ValueSource(strings = { + "CREATE TABLE t (a INTEGER PRIMARY KEY, b TEXT) WITHOUT ROWID", + "CREATE TABLE t (a INTEGER PRIMARY KEY, b TEXT) STRICT", + "CREATE TABLE t (a INTEGER PRIMARY KEY, b BLOB) STRICT, WITHOUT ROWID", + "CREATE TABLE t (a INTEGER PRIMARY KEY, b TEXT) WITHOUT ROWID, STRICT", + "create table t (a integer primary key) without rowid"}) + void testCreateTableWithoutRowidAndStrict(String sql) throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true); + } + + @Test + void testCreateTableOptionKinds() throws JSQLParserException { + CreateTable createTable = (CreateTable) CCJSqlParserUtil.parse( + "CREATE TABLE t (a INTEGER PRIMARY KEY) STRICT, WITHOUT ROWID"); + assertEquals(List.of(TableOption.Kind.STRICT, TableOption.Kind.WITHOUT_ROWID), + createTable.getTableOptions().stream().map(TableOption::getKind) + .collect(Collectors.toList())); + } } +