Skip to content

Commit 8b9fb01

Browse files
committed
feat: share MySQL scalar table options across CREATE and ALTER
Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent 0f58f7f commit 8b9fb01

4 files changed

Lines changed: 183 additions & 31 deletions

File tree

‎src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionTableOption.java‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,61 @@
99
*/
1010
package net.sf.jsqlparser.statement.alter;
1111

12+
import net.sf.jsqlparser.statement.create.table.TableOption;
13+
1214
/**
1315
* Internal subclass for table-level option operations within ALTER TABLE. Handles ENGINE,
1416
* ALGORITHM, LOCK, KEY_BLOCK_SIZE, COMMENT, ENCRYPTION, AUTO_INCREMENT (SET_TABLE_OPTION),
1517
* DISCARD/IMPORT TABLESPACE, DISABLE/ENABLE KEYS.
1618
*/
1719
public class AlterExpressionTableOption extends AlterExpression {
1820

21+
private TableOption structuredTableOption;
22+
23+
public TableOption getStructuredTableOption() {
24+
return structuredTableOption;
25+
}
26+
27+
/** Uses the same option model as CREATE TABLE, with legacy getters as live projections. */
28+
public void setStructuredTableOption(TableOption option) {
29+
structuredTableOption = option;
30+
setOperation(AlterOperation.SET_TABLE_OPTION);
31+
super.setTableOption(null);
32+
super.setUseEqual(false);
33+
}
34+
35+
@Override
36+
public String getTableOption() {
37+
return structuredTableOption == null ? super.getTableOption()
38+
: structuredTableOption.toString();
39+
}
40+
41+
@Override
42+
public void setTableOption(String option) {
43+
structuredTableOption = null;
44+
super.setTableOption(option);
45+
}
46+
47+
@Override
48+
public boolean getUseEqual() {
49+
return structuredTableOption == null ? super.getUseEqual()
50+
: structuredTableOption.isUseEquals();
51+
}
52+
53+
@Override
54+
public void setUseEqual(boolean useEqual) {
55+
if (structuredTableOption != null) {
56+
structuredTableOption.setUseEquals(useEqual);
57+
}
58+
super.setUseEqual(useEqual);
59+
}
60+
1961
@Override
2062
protected void appendBody(StringBuilder b) {
2163
switch (getOperation()) {
64+
case SET_TABLE_OPTION:
65+
b.append(getTableOption());
66+
break;
2267
case COMMENT:
2368
b.append("COMMENT ");
2469
b.append(getCommentText());

‎src/main/java/net/sf/jsqlparser/statement/create/table/TableOption.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
public class TableOption implements Serializable {
2222

2323
public enum Kind {
24-
ENGINE, CHARACTER_SET, COLLATE, COMMENT, AUTO_INCREMENT, STATS_AUTO_RECALC, STATS_PERSISTENT, STATS_SAMPLE_PAGES, UNION, OTHER
24+
ENGINE, CHARACTER_SET, COLLATE, COMMENT, AUTO_INCREMENT, STATS_AUTO_RECALC, STATS_PERSISTENT, STATS_SAMPLE_PAGES, UNION, ENCRYPTION, PASSWORD, DATA_DIRECTORY, INDEX_DIRECTORY, OTHER
2525
}
2626

2727
private Kind kind = Kind.OTHER;

‎src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt‎

Lines changed: 45 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1542,10 +1542,16 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
15421542
|| "STATS_SAMPLE_PAGES".equalsIgnoreCase(name);
15431543
}
15441544

1545+
private boolean isMySqlScalarTableOptionAhead() {
1546+
int kind = getToken(1).kind;
1547+
return kind == K_ENCRYPTION || kind == K_PASSWORD || kind == K_AUTO_INCREMENT
1548+
|| (kind == K_DATA || kind == K_INDEX) && getToken(2).kind == K_DIRECTORY;
1549+
}
1550+
15451551
private boolean isMySqlTableOptionAhead() {
15461552
int kind = getToken(1).kind;
15471553
if (kind == K_ENGINE || kind == K_COLLATE || kind == K_COMMENT
1548-
|| kind == K_AUTO_INCREMENT || kind == K_UNION
1554+
|| isMySqlScalarTableOptionAhead() || kind == K_UNION
15491555
|| isMySqlStatisticsOptionAhead()) {
15501556
return true;
15511557
}
@@ -15163,12 +15169,8 @@ TableOption MySqlTableOption(): {
1516315169
[ "=" { useEquals = true; } ]
1516415170
value=MySqlTableOptionValue()
1516515171
|
15166-
tk=<K_AUTO_INCREMENT> {
15167-
name = tk.image;
15168-
kind = TableOption.Kind.AUTO_INCREMENT;
15169-
}
15170-
[ "=" { useEquals = true; } ]
15171-
value=MySqlTableOptionValue()
15172+
LOOKAHEAD({ isMySqlScalarTableOptionAhead() }) option=MySqlScalarTableOption()
15173+
{ return option; }
1517215174
)
1517315175
{
1517415176
option = new TableOption(kind, name, value, useEquals);
@@ -15177,6 +15179,41 @@ TableOption MySqlTableOption(): {
1517715179
}
1517815180
}
1517915181

15182+
/** Common CREATE/ALTER options with a quoted string or unsigned integer value. */
15183+
TableOption MySqlScalarTableOption():
15184+
{
15185+
Token token;
15186+
String name;
15187+
String value;
15188+
TableOption.Kind kind;
15189+
boolean useEquals = false;
15190+
}
15191+
{
15192+
(
15193+
<K_AUTO_INCREMENT> { name = "AUTO_INCREMENT"; kind = TableOption.Kind.AUTO_INCREMENT; }
15194+
[ "=" { useEquals = true; } ] token=<S_LONG> { value = token.image; }
15195+
|
15196+
(
15197+
<K_ENCRYPTION> { name = "ENCRYPTION"; kind = TableOption.Kind.ENCRYPTION; }
15198+
| <K_PASSWORD> { name = "PASSWORD"; kind = TableOption.Kind.PASSWORD; }
15199+
| <K_DATA> <K_DIRECTORY> { name = "DATA DIRECTORY"; kind = TableOption.Kind.DATA_DIRECTORY; }
15200+
| <K_INDEX> <K_DIRECTORY> { name = "INDEX DIRECTORY"; kind = TableOption.Kind.INDEX_DIRECTORY; }
15201+
)
15202+
[ "=" { useEquals = true; } ] token=<S_CHAR_LITERAL> { value = token.image; }
15203+
)
15204+
{ return new TableOption(kind, name, value, useEquals); }
15205+
}
15206+
15207+
AlterExpressionTableOption AlterScalarTableOption():
15208+
{
15209+
TableOption option;
15210+
AlterExpressionTableOption expression = new AlterExpressionTableOption();
15211+
}
15212+
{
15213+
option=MySqlScalarTableOption()
15214+
{ expression.setStructuredTableOption(option); return expression; }
15215+
}
15216+
1518015217
String MySqlTableOptionValue(): {
1518115218
Token token;
1518215219
String value = null;
@@ -17677,17 +17714,7 @@ AlterExpression AlterExpression():
1767717714
tk=<S_CHAR_LITERAL> { alterExp.setCommentText(tk.image); }
1767817715
)
1767917716
|
17680-
({ alterExp = new AlterExpressionTableOption(); }
17681-
<K_ENCRYPTION> {alterExp.setOperation(AlterOperation.SET_TABLE_OPTION);}
17682-
["=" { alterExp.setUseEqual(true);} ]
17683-
tk=<S_CHAR_LITERAL> {
17684-
if (alterExp.getUseEqual()) {
17685-
alterExp.setTableOption("ENCRYPTION = " + tk.image);
17686-
} else {
17687-
alterExp.setTableOption("ENCRYPTION " + tk.image);
17688-
}
17689-
}
17690-
)
17717+
LOOKAHEAD({ isMySqlScalarTableOptionAhead() }) alterExp=AlterScalarTableOption()
1769117718
|
1769217719
alterExp = AlterExpressionDiscardOrImport()
1769317720
|
@@ -17720,18 +17747,6 @@ AlterExpression AlterExpression():
1772017747
alterExp.setOperation(AlterOperation.ENABLE_KEYS);
1772117748
}
1772217749
)
17723-
|
17724-
({ alterExp = new AlterExpressionTableOption(); }
17725-
<K_AUTO_INCREMENT> {alterExp.setOperation(AlterOperation.SET_TABLE_OPTION);}
17726-
["=" { alterExp.setUseEqual(true);} ]
17727-
tk=<S_LONG> {
17728-
if (alterExp.getUseEqual()) {
17729-
alterExp.setTableOption("AUTO_INCREMENT = " + tk.image);
17730-
} else {
17731-
alterExp.setTableOption("AUTO_INCREMENT " + tk.image);
17732-
}
17733-
}
17734-
)
1773517750
|
1773617751
alterExp = AlterExpressionPartitionOp()
1773717752
|
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2019 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.statement.create;
11+
12+
import static org.junit.jupiter.api.Assertions.*;
13+
import java.util.List;
14+
import net.sf.jsqlparser.JSQLParserException;
15+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
16+
import net.sf.jsqlparser.statement.Statement;
17+
import net.sf.jsqlparser.statement.alter.Alter;
18+
import net.sf.jsqlparser.statement.alter.AlterExpressionTableOption;
19+
import net.sf.jsqlparser.statement.create.table.CreateTable;
20+
import net.sf.jsqlparser.statement.create.table.TableOption;
21+
import net.sf.jsqlparser.util.deparser.StatementDeParser;
22+
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.params.ParameterizedTest;
24+
import org.junit.jupiter.params.provider.CsvSource;
25+
import org.junit.jupiter.params.provider.ValueSource;
26+
27+
class MySqlSharedTableOptionTest {
28+
@ParameterizedTest
29+
@CsvSource(value = {"ENCRYPTION|'N'|ENCRYPTION", "PASSWORD|'ignored'|PASSWORD",
30+
"DATA DIRECTORY|'/tmp/data dir'|DATA_DIRECTORY",
31+
"INDEX DIRECTORY|'/tmp/index dir'|INDEX_DIRECTORY",
32+
"AUTO_INCREMENT|18446744073709551614|AUTO_INCREMENT"}, delimiter = '|',
33+
quoteCharacter = '"')
34+
void sharesCreateAndAlterModels(String name, String value, String kind)
35+
throws JSQLParserException {
36+
for (String equals : new String[] {" ", " = "}) {
37+
String optionSql = name + equals + value;
38+
CreateTable create =
39+
(CreateTable) CCJSqlParserUtil.parse("CREATE TABLE t (id BIGINT) " + optionSql);
40+
Alter alter = (Alter) CCJSqlParserUtil
41+
.parse("ALTER TABLE t " + optionSql + ", ADD COLUMN extra INT");
42+
AlterExpressionTableOption action = assertInstanceOf(AlterExpressionTableOption.class,
43+
alter.getAlterExpressions().get(0));
44+
TableOption option = action.getStructuredTableOption();
45+
assertEquals(TableOption.Kind.valueOf(kind), option.getKind());
46+
assertEquals(create.getTableOptions().get(0).toString(), option.toString());
47+
assertEquals(value, option.getValue());
48+
assertEquals(equals.contains("="), action.getUseEqual());
49+
assertEquals(optionSql, action.getTableOption());
50+
assertEquals(2, alter.getAlterExpressions().size());
51+
roundTrip(create);
52+
roundTrip(alter);
53+
option.setUseEquals(!option.isUseEquals());
54+
assertEquals(option.isUseEquals(), action.getUseEqual());
55+
roundTrip(alter);
56+
action.setUseEqual(true);
57+
assertTrue(option.isUseEquals());
58+
action.setTableOption("ENCRYPTION 'N'");
59+
assertNull(action.getStructuredTableOption());
60+
assertEquals("ENCRYPTION 'N'", action.toString());
61+
}
62+
}
63+
64+
@Test
65+
void commasAndQuerySourceStayOutsideOptions() throws JSQLParserException {
66+
CreateTable table = (CreateTable) CCJSqlParserUtil.parse(
67+
"CREATE TABLE t (id INT) ENGINE=InnoDB, ENCRYPTION='N', PASSWORD='a,b' AS SELECT 1 AS id");
68+
assertEquals(3, table.getTableOptions().size());
69+
assertNotNull(table.getSelect());
70+
table.getTableOptions().get(1).setValue("'Y'");
71+
assertTrue(table.getTableOptionsStrings().contains("'Y'"));
72+
roundTrip(table);
73+
table.setTableOptionsStrings(List.of("ENCRYPTION", "=", "'N'"));
74+
assertNull(table.getTableOptions());
75+
roundTrip(table);
76+
}
77+
78+
@ParameterizedTest
79+
@ValueSource(strings = {"ENCRYPTION=", "PASSWORD=1", "DATA DIRECTORY='/tmp' INDEX DIRECTORY=",
80+
"AUTO_INCREMENT=-1", "AUTO_INCREMENT='5'", "ENCRYPTION='N',"})
81+
void rejectsMissingOrWrongCreateValues(String option) {
82+
assertThrows(JSQLParserException.class,
83+
() -> CCJSqlParserUtil.parse("CREATE TABLE t (id INT) " + option));
84+
}
85+
86+
private static void roundTrip(Statement statement) throws JSQLParserException {
87+
StringBuilder out = new StringBuilder();
88+
statement.accept(new StatementDeParser(out));
89+
assertEquals(statement.toString(), out.toString());
90+
assertEquals(out.toString(), CCJSqlParserUtil.parse(out.toString()).toString());
91+
}
92+
}

0 commit comments

Comments
 (0)