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 c54a38e30..6f60a4f76 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 @@ -21,7 +21,7 @@ public class TableOption implements Serializable { 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, STORAGE_PARAMETERS, WITHOUT_OIDS, ENGINE_ATTRIBUTE, SECONDARY_ENGINE_ATTRIBUTE, ROW_FORMAT, 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, ENGINE_ATTRIBUTE, SECONDARY_ENGINE_ATTRIBUTE, ROW_FORMAT, OTHER } private Kind kind = Kind.OTHER; diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 4ecee0e89..582eb5511 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -1560,9 +1560,15 @@ public class CCJSqlParser extends AbstractJSqlParser { return isKeywordAhead("ENGINE_ATTRIBUTE") || isKeywordAhead("SECONDARY_ENGINE_ATTRIBUTE"); } + private boolean isMySqlAdditionalTableOptionAhead() { + String name = getToken(1).image.toUpperCase(Locale.ROOT); + return Arrays.asList("SECONDARY_ENGINE", "AUTOEXTEND_SIZE", "INSERT_METHOD", "PACK_KEYS", + "DELAY_KEY_WRITE", "CHECKSUM", "CONNECTION", "COMPRESSION").contains(name); + } + private boolean isMySqlScalarTableOptionAhead() { int kind = getToken(1).kind; - return isMySqlEngineAttributeAhead() || kind == K_ENCRYPTION || kind == K_PASSWORD || kind == K_AUTO_INCREMENT + return isMySqlAdditionalTableOptionAhead() || isMySqlEngineAttributeAhead() || kind == K_ENCRYPTION || kind == K_PASSWORD || kind == K_AUTO_INCREMENT || (kind == K_DATA || kind == K_INDEX) && getToken(2).kind == K_DIRECTORY; } @@ -15534,7 +15540,7 @@ TableOption MySqlTableOption(): { } } -/** Common CREATE/ALTER options with a quoted string or unsigned integer value. */ +/** Shared CREATE/ALTER table options with option-specific value syntax. */ TableOption MySqlScalarTableOption(): { Token token; @@ -15542,9 +15548,14 @@ TableOption MySqlScalarTableOption(): String value; TableOption.Kind kind; boolean useEquals = false; + TableOption additional; } { ( + LOOKAHEAD({ isMySqlAdditionalTableOptionAhead() }) additional=MySqlAdditionalTableOption() + { name = additional.getName(); kind = additional.getKind(); + value = additional.getValue(); useEquals = additional.isUseEquals(); } + | { name = "AUTO_INCREMENT"; kind = TableOption.Kind.AUTO_INCREMENT; } [ "=" { useEquals = true; } ] token= { value = token.image; } | @@ -15563,6 +15574,65 @@ TableOption MySqlScalarTableOption(): { return new TableOption(kind, name, value, useEquals); } } +TableOption MySqlAdditionalTableOption(): +{ + Token token; + String name; + String value; + boolean useEquals = false; +} +{ + ( + LOOKAHEAD({ isKeywordAhead("SECONDARY_ENGINE") }) + token= { name = token.image.toUpperCase(Locale.ROOT); } + [ "=" { useEquals = true; } ] + ( token= { value = token.image; } | token= { value = token.image; } | value=RelObjectName() ) + | + LOOKAHEAD({ isKeywordAhead("INSERT_METHOD") }) + token= { name = token.image.toUpperCase(Locale.ROOT); } + [ "=" { useEquals = true; } ] + ( token= | token= | token= ) { value = token.image; } + | + LOOKAHEAD({ isKeywordAhead("PACK_KEYS") }) + token= { name = token.image.toUpperCase(Locale.ROOT); } + [ "=" { useEquals = true; } ] + ( token= | token= ) { + value = token.image; + if (token.kind == S_LONG && !"0".equals(value) && !"1".equals(value)) { + throw new ParseException("PACK_KEYS requires 0, 1 or DEFAULT"); + } + } + | + LOOKAHEAD({ isKeywordAhead("AUTOEXTEND_SIZE") }) + token= { name = token.image.toUpperCase(Locale.ROOT); } + [ "=" { useEquals = true; } ] + value=MySqlSizeNumber() + | + LOOKAHEAD({ isKeywordAhead("CHECKSUM") || isKeywordAhead("DELAY_KEY_WRITE") }) + token= { name = token.image.toUpperCase(Locale.ROOT); } + [ "=" { useEquals = true; } ] + token= { value = token.image; } + | + token= { name = token.image.toUpperCase(Locale.ROOT); } + [ "=" { useEquals = true; } ] + token= { value = token.image; } + ) + { if (true) { return new TableOption(TableOption.Kind.valueOf(name), name, value, useEquals); } } +} + +String MySqlSizeNumber(): +{ + Token number; +} +{ + ( number= | number= ) + { + requireDdlSyntax(number.kind == S_LONG || number.image.matches("[0-9]+[kKmMgG]"), + "Expected an unsigned size with an optional K, M or G suffix"); + return number.image; + } +} + /** Statistics and row format share CREATE's validation and option nodes. */ AlterExpressionTableOption AlterSharedTableOption(): { TableOption option; AlterExpressionTableOption action = new AlterExpressionTableOption(); } diff --git a/src/test/java/net/sf/jsqlparser/statement/create/MySqlSharedTableOptionTest.java b/src/test/java/net/sf/jsqlparser/statement/create/MySqlSharedTableOptionTest.java index 012645ca8..c8e456aad 100644 --- a/src/test/java/net/sf/jsqlparser/statement/create/MySqlSharedTableOptionTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/create/MySqlSharedTableOptionTest.java @@ -26,7 +26,15 @@ class MySqlSharedTableOptionTest { @ParameterizedTest - @CsvSource(value = {"ENCRYPTION|'N'|ENCRYPTION", "PASSWORD|'ignored'|PASSWORD", + @CsvSource(value = {"SECONDARY_ENGINE|NULL|SECONDARY_ENGINE", + "SECONDARY_ENGINE|'engine'|SECONDARY_ENGINE", + "SECONDARY_ENGINE|engine_name|SECONDARY_ENGINE", "AUTOEXTEND_SIZE|4M|AUTOEXTEND_SIZE", + "AUTOEXTEND_SIZE|0|AUTOEXTEND_SIZE", "INSERT_METHOD|NO|INSERT_METHOD", + "INSERT_METHOD|FIRST|INSERT_METHOD", "INSERT_METHOD|LAST|INSERT_METHOD", + "PACK_KEYS|DEFAULT|PACK_KEYS", "PACK_KEYS|1|PACK_KEYS", "PACK_KEYS|0|PACK_KEYS", + "DELAY_KEY_WRITE|2|DELAY_KEY_WRITE", "CHECKSUM|0|CHECKSUM", + "CONNECTION|'mysql://server/db/t'|CONNECTION", "COMPRESSION|'zlib'|COMPRESSION", + "ENCRYPTION|'N'|ENCRYPTION", "PASSWORD|'ignored'|PASSWORD", "DATA DIRECTORY|'/tmp/data dir'|DATA_DIRECTORY", "INDEX DIRECTORY|'/tmp/index dir'|INDEX_DIRECTORY", "AUTO_INCREMENT|18446744073709551614|AUTO_INCREMENT"}, delimiter = '|', @@ -77,7 +85,8 @@ void commasAndQuerySourceStayOutsideOptions() throws JSQLParserException { @ParameterizedTest @ValueSource(strings = {"ENCRYPTION=", "PASSWORD=1", "DATA DIRECTORY='/tmp' INDEX DIRECTORY=", - "AUTO_INCREMENT=-1", "AUTO_INCREMENT='5'", "ENCRYPTION='N',"}) + "PACK_KEYS=2", "AUTOEXTEND_SIZE=-1", "INSERT_METHOD=UNKNOWN", "COMPRESSION=1", + "SECONDARY_ENGINE=", "AUTO_INCREMENT=-1", "AUTO_INCREMENT='5'", "ENCRYPTION='N',"}) void rejectsMissingOrWrongCreateValues(String option) { assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse("CREATE TABLE t (id INT) " + option));