Skip to content
Merged
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 @@ -9,16 +9,61 @@
*/
package net.sf.jsqlparser.statement.alter;

import net.sf.jsqlparser.statement.create.table.TableOption;

/**
* Internal subclass for table-level option operations within ALTER TABLE. Handles ENGINE,
* ALGORITHM, LOCK, KEY_BLOCK_SIZE, COMMENT, ENCRYPTION, AUTO_INCREMENT (SET_TABLE_OPTION),
* DISCARD/IMPORT TABLESPACE, DISABLE/ENABLE KEYS.
*/
public class AlterExpressionTableOption extends AlterExpression {

private TableOption structuredTableOption;

public TableOption getStructuredTableOption() {
return structuredTableOption;
}

/** Uses the same option model as CREATE TABLE, with legacy getters as live projections. */
public void setStructuredTableOption(TableOption option) {
structuredTableOption = option;
setOperation(AlterOperation.SET_TABLE_OPTION);
super.setTableOption(null);
super.setUseEqual(false);
}

@Override
public String getTableOption() {
return structuredTableOption == null ? super.getTableOption()
: structuredTableOption.toString();
}

@Override
public void setTableOption(String option) {
structuredTableOption = null;
super.setTableOption(option);
}

@Override
public boolean getUseEqual() {
return structuredTableOption == null ? super.getUseEqual()
: structuredTableOption.isUseEquals();
}

@Override
public void setUseEqual(boolean useEqual) {
if (structuredTableOption != null) {
structuredTableOption.setUseEquals(useEqual);
}
super.setUseEqual(useEqual);
}

@Override
protected void appendBody(StringBuilder b) {
switch (getOperation()) {
case SET_TABLE_OPTION:
b.append(getTableOption());
break;
case COMMENT:
b.append("COMMENT ");
b.append(getCommentText());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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, OTHER
ENGINE, CHARACTER_SET, COLLATE, COMMENT, AUTO_INCREMENT, STATS_AUTO_RECALC, STATS_PERSISTENT, STATS_SAMPLE_PAGES, UNION, ENCRYPTION, PASSWORD, DATA_DIRECTORY, INDEX_DIRECTORY, OTHER
}

private Kind kind = Kind.OTHER;
Expand Down
75 changes: 45 additions & 30 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -1542,10 +1542,16 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
|| "STATS_SAMPLE_PAGES".equalsIgnoreCase(name);
}

private boolean isMySqlScalarTableOptionAhead() {
int kind = getToken(1).kind;
return kind == K_ENCRYPTION || kind == K_PASSWORD || kind == K_AUTO_INCREMENT
|| (kind == K_DATA || kind == K_INDEX) && getToken(2).kind == K_DIRECTORY;
}

private boolean isMySqlTableOptionAhead() {
int kind = getToken(1).kind;
if (kind == K_ENGINE || kind == K_COLLATE || kind == K_COMMENT
|| kind == K_AUTO_INCREMENT || kind == K_UNION
|| isMySqlScalarTableOptionAhead() || kind == K_UNION
|| isMySqlStatisticsOptionAhead()) {
return true;
}
Expand Down Expand Up @@ -15163,12 +15169,8 @@ TableOption MySqlTableOption(): {
[ "=" { useEquals = true; } ]
value=MySqlTableOptionValue()
|
tk=<K_AUTO_INCREMENT> {
name = tk.image;
kind = TableOption.Kind.AUTO_INCREMENT;
}
[ "=" { useEquals = true; } ]
value=MySqlTableOptionValue()
LOOKAHEAD({ isMySqlScalarTableOptionAhead() }) option=MySqlScalarTableOption()
{ return option; }
)
{
option = new TableOption(kind, name, value, useEquals);
Expand All @@ -15177,6 +15179,41 @@ TableOption MySqlTableOption(): {
}
}

/** Common CREATE/ALTER options with a quoted string or unsigned integer value. */
TableOption MySqlScalarTableOption():
{
Token token;
String name;
String value;
TableOption.Kind kind;
boolean useEquals = false;
}
{
(
<K_AUTO_INCREMENT> { name = "AUTO_INCREMENT"; kind = TableOption.Kind.AUTO_INCREMENT; }
[ "=" { useEquals = true; } ] token=<S_LONG> { value = token.image; }
|
(
<K_ENCRYPTION> { name = "ENCRYPTION"; kind = TableOption.Kind.ENCRYPTION; }
| <K_PASSWORD> { name = "PASSWORD"; kind = TableOption.Kind.PASSWORD; }
| <K_DATA> <K_DIRECTORY> { name = "DATA DIRECTORY"; kind = TableOption.Kind.DATA_DIRECTORY; }
| <K_INDEX> <K_DIRECTORY> { name = "INDEX DIRECTORY"; kind = TableOption.Kind.INDEX_DIRECTORY; }
)
[ "=" { useEquals = true; } ] token=<S_CHAR_LITERAL> { value = token.image; }
)
{ return new TableOption(kind, name, value, useEquals); }
}

AlterExpressionTableOption AlterScalarTableOption():
{
TableOption option;
AlterExpressionTableOption expression = new AlterExpressionTableOption();
}
{
option=MySqlScalarTableOption()
{ expression.setStructuredTableOption(option); return expression; }
}

String MySqlTableOptionValue(): {
Token token;
String value = null;
Expand Down Expand Up @@ -17677,17 +17714,7 @@ AlterExpression AlterExpression():
tk=<S_CHAR_LITERAL> { alterExp.setCommentText(tk.image); }
)
|
({ alterExp = new AlterExpressionTableOption(); }
<K_ENCRYPTION> {alterExp.setOperation(AlterOperation.SET_TABLE_OPTION);}
["=" { alterExp.setUseEqual(true);} ]
tk=<S_CHAR_LITERAL> {
if (alterExp.getUseEqual()) {
alterExp.setTableOption("ENCRYPTION = " + tk.image);
} else {
alterExp.setTableOption("ENCRYPTION " + tk.image);
}
}
)
LOOKAHEAD({ isMySqlScalarTableOptionAhead() }) alterExp=AlterScalarTableOption()
|
alterExp = AlterExpressionDiscardOrImport()
|
Expand Down Expand Up @@ -17720,18 +17747,6 @@ AlterExpression AlterExpression():
alterExp.setOperation(AlterOperation.ENABLE_KEYS);
}
)
|
({ alterExp = new AlterExpressionTableOption(); }
<K_AUTO_INCREMENT> {alterExp.setOperation(AlterOperation.SET_TABLE_OPTION);}
["=" { alterExp.setUseEqual(true);} ]
tk=<S_LONG> {
if (alterExp.getUseEqual()) {
alterExp.setTableOption("AUTO_INCREMENT = " + tk.image);
} else {
alterExp.setTableOption("AUTO_INCREMENT " + tk.image);
}
}
)
|
alterExp = AlterExpressionPartitionOp()
|
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2019 JSQLParser
* %%
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
* #L%
*/
package net.sf.jsqlparser.statement.create;

import static org.junit.jupiter.api.Assertions.*;
import java.util.List;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.alter.Alter;
import net.sf.jsqlparser.statement.alter.AlterExpressionTableOption;
import net.sf.jsqlparser.statement.create.table.CreateTable;
import net.sf.jsqlparser.statement.create.table.TableOption;
import net.sf.jsqlparser.util.deparser.StatementDeParser;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;

class MySqlSharedTableOptionTest {
@ParameterizedTest
@CsvSource(value = {"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 = '|',
quoteCharacter = '"')
void sharesCreateAndAlterModels(String name, String value, String kind)
throws JSQLParserException {
for (String equals : new String[] {" ", " = "}) {
String optionSql = name + equals + value;
CreateTable create =
(CreateTable) CCJSqlParserUtil.parse("CREATE TABLE t (id BIGINT) " + optionSql);
Alter alter = (Alter) CCJSqlParserUtil
.parse("ALTER TABLE t " + optionSql + ", ADD COLUMN extra INT");
AlterExpressionTableOption action = assertInstanceOf(AlterExpressionTableOption.class,
alter.getAlterExpressions().get(0));
TableOption option = action.getStructuredTableOption();
assertEquals(TableOption.Kind.valueOf(kind), option.getKind());
assertEquals(create.getTableOptions().get(0).toString(), option.toString());
assertEquals(value, option.getValue());
assertEquals(equals.contains("="), action.getUseEqual());
assertEquals(optionSql, action.getTableOption());
assertEquals(2, alter.getAlterExpressions().size());
roundTrip(create);
roundTrip(alter);
option.setUseEquals(!option.isUseEquals());
assertEquals(option.isUseEquals(), action.getUseEqual());
roundTrip(alter);
action.setUseEqual(true);
assertTrue(option.isUseEquals());
action.setTableOption("ENCRYPTION 'N'");
assertNull(action.getStructuredTableOption());
assertEquals("ENCRYPTION 'N'", action.toString());
}
}

@Test
void commasAndQuerySourceStayOutsideOptions() throws JSQLParserException {
CreateTable table = (CreateTable) CCJSqlParserUtil.parse(
"CREATE TABLE t (id INT) ENGINE=InnoDB, ENCRYPTION='N', PASSWORD='a,b' AS SELECT 1 AS id");
assertEquals(3, table.getTableOptions().size());
assertNotNull(table.getSelect());
table.getTableOptions().get(1).setValue("'Y'");
assertTrue(table.getTableOptionsStrings().contains("'Y'"));
roundTrip(table);
table.setTableOptionsStrings(List.of("ENCRYPTION", "=", "'N'"));
assertNull(table.getTableOptions());
roundTrip(table);
}

@ParameterizedTest
@ValueSource(strings = {"ENCRYPTION=", "PASSWORD=1", "DATA DIRECTORY='/tmp' INDEX DIRECTORY=",
"AUTO_INCREMENT=-1", "AUTO_INCREMENT='5'", "ENCRYPTION='N',"})
void rejectsMissingOrWrongCreateValues(String option) {
assertThrows(JSQLParserException.class,
() -> CCJSqlParserUtil.parse("CREATE TABLE t (id INT) " + option));
}

@ParameterizedTest
@ValueSource(strings = {"VECTOR", "VECTOR(0)", "VECTOR(1)", "VECTOR(2048)", "VECTOR(16383)"})
void preservesMySql9VectorColumnsBeforeTableOptions(String type) throws JSQLParserException {
CreateTable table = (CreateTable) CCJSqlParserUtil.parse(
"CREATE TABLE t (v " + type + ") ENCRYPTION='N'");
assertEquals(type, table.getColumnDefinitions().get(0).getColDataType().toString()
.replace(" ", ""));
assertEquals(TableOption.Kind.ENCRYPTION, table.getTableOptions().get(0).getKind());
roundTrip(table);
}

private static void roundTrip(Statement statement) throws JSQLParserException {
StringBuilder out = new StringBuilder();
statement.accept(new StatementDeParser(out));
assertEquals(statement.toString(), out.toString());
assertEquals(out.toString(), CCJSqlParserUtil.parse(out.toString()).toString());
}
}
Loading