Skip to content

Commit 28e132c

Browse files
committed
Complete MySQL index algorithm and lock options and share CREATE INDEX rendering
1 parent bc18fdf commit 28e132c

4 files changed

Lines changed: 145 additions & 83 deletions

File tree

src/main/java/net/sf/jsqlparser/statement/create/index/CreateIndex.java

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,11 @@ public void setTailParameters(List<String> tailParameters) {
137137

138138
@Override
139139
public String toString() {
140-
StringBuilder buffer = new StringBuilder();
140+
return appendTo(new StringBuilder()).toString();
141+
}
141142

143+
/** Shared rendering for the statement model and CreateIndexDeParser. */
144+
public StringBuilder appendTo(StringBuilder buffer) {
142145
buffer.append("CREATE ");
143146

144147
if (index.getType() != null) {
@@ -182,16 +185,14 @@ public String toString() {
182185

183186
buffer.append(")");
184187

185-
appendPostgreSqlTail(buffer);
186-
187-
if (tailParameters != null) {
188-
for (String param : tailParameters) {
189-
buffer.append(" ").append(param);
190-
}
188+
}
189+
appendPostgreSqlTail(buffer);
190+
if (tailParameters != null) {
191+
for (String param : tailParameters) {
192+
buffer.append(" ").append(param);
191193
}
192194
}
193-
194-
return buffer.toString();
195+
return buffer;
195196
}
196197

197198
private void appendPostgreSqlTail(StringBuilder buffer) {

src/main/java/net/sf/jsqlparser/util/deparser/CreateIndexDeParser.java

Lines changed: 1 addition & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,7 @@
99
*/
1010
package net.sf.jsqlparser.util.deparser;
1111

12-
import static java.util.stream.Collectors.joining;
13-
1412
import net.sf.jsqlparser.statement.create.index.CreateIndex;
15-
import net.sf.jsqlparser.statement.create.table.Index;
16-
import net.sf.jsqlparser.statement.select.PlainSelect;
1713

1814
public class CreateIndexDeParser extends AbstractDeParser<CreateIndex> {
1915

@@ -23,74 +19,6 @@ public CreateIndexDeParser(StringBuilder buffer) {
2319

2420
@Override
2521
public void deParse(CreateIndex createIndex) {
26-
Index index = createIndex.getIndex();
27-
28-
builder.append("CREATE ");
29-
30-
if (index.getType() != null) {
31-
builder.append(index.getType());
32-
builder.append(" ");
33-
}
34-
35-
builder.append("INDEX ");
36-
if (createIndex.isConcurrently()) {
37-
builder.append("CONCURRENTLY ");
38-
}
39-
if (createIndex.isUsingIfNotExists()) {
40-
builder.append("IF NOT EXISTS ");
41-
}
42-
if (index.getName() != null) {
43-
builder.append(index.getName()).append(" ");
44-
}
45-
46-
String using = index.getUsing();
47-
if (using != null && createIndex.isIndexTypeBeforeOn()) {
48-
builder.append("USING ").append(using).append(" ");
49-
}
50-
51-
builder.append("ON ");
52-
if (createIndex.isOnly()) {
53-
builder.append("ONLY ");
54-
}
55-
builder.append(createIndex.getTable().getFullyQualifiedName());
56-
57-
if (using != null && !createIndex.isIndexTypeBeforeOn()) {
58-
builder.append(" USING ");
59-
builder.append(using);
60-
}
61-
62-
if (index.getColumnsNames() != null) {
63-
builder.append(" (");
64-
builder.append(index.getColumnWithParams().stream()
65-
.map(Index.ColumnParams::toString)
66-
.collect(joining(", ")));
67-
builder.append(")");
68-
}
69-
70-
if (createIndex.getIncludeColumns() != null) {
71-
builder.append(" INCLUDE (")
72-
.append(String.join(", ", createIndex.getIncludeColumns())).append(")");
73-
}
74-
if (createIndex.getNullsDistinct() != null) {
75-
builder.append(" NULLS ")
76-
.append(createIndex.getNullsDistinct() ? "DISTINCT" : "NOT DISTINCT");
77-
}
78-
if (createIndex.getStorageParameters() != null) {
79-
builder.append(" WITH ").append(PlainSelect.getStringList(
80-
createIndex.getStorageParameters(), true, true));
81-
}
82-
if (createIndex.getTableSpace() != null) {
83-
builder.append(" TABLESPACE ").append(createIndex.getTableSpace());
84-
}
85-
if (createIndex.getWhere() != null) {
86-
builder.append(" WHERE ").append(createIndex.getWhere());
87-
}
88-
89-
if (createIndex.getTailParameters() != null) {
90-
for (String param : createIndex.getTailParameters()) {
91-
builder.append(" ").append(param);
92-
}
93-
}
22+
createIndex.appendTo(builder);
9423
}
95-
9624
}

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

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11725,7 +11725,8 @@ CreateIndex CreateIndex():
1172511725
[ LOOKAHEAD(2) <K_TABLESPACE> tableSpace=RelObjectName()
1172611726
{ createIndex.setTableSpace(tableSpace); } ]
1172711727
[ LOOKAHEAD(2) <K_WHERE> where=Expression() { createIndex.setWhere(where); } ]
11728-
( LOOKAHEAD(2) parameter=CreateParameter() { tailParameters.addAll(parameter); } )*
11728+
( LOOKAHEAD(2, { getToken(1).kind != K_LOCK || getToken(2).kind != K_TABLE })
11729+
parameter=CreateIndexTailOption() { tailParameters.addAll(parameter); } )*
1172911730
{
1173011731
index.setColumns(colNames);
1173111732
createIndex.setIndex(index);
@@ -11735,6 +11736,35 @@ CreateIndex CreateIndex():
1173511736
}
1173611737
}
1173711738

11739+
List<String> CreateIndexTailOption():
11740+
{
11741+
List<String> option;
11742+
}
11743+
{
11744+
(
11745+
LOOKAHEAD({ getToken(1).kind == K_ALGORITHM || getToken(1).kind == K_LOCK })
11746+
option=IndexAlgorithmOrLockOption()
11747+
|
11748+
option=CreateParameter()
11749+
)
11750+
{ return option; }
11751+
}
11752+
11753+
// CREATE INDEX and DROP INDEX share MySQL's algorithm_option / lock_option syntax.
11754+
// Keep the legacy token list, including optional equals signs and the original order.
11755+
List<String> IndexAlgorithmOrLockOption():
11756+
{
11757+
List<String> option = new ArrayList<String>();
11758+
Token keyword;
11759+
Token value;
11760+
}
11761+
{
11762+
( keyword=<K_ALGORITHM> | keyword=<K_LOCK> ) { option.add(keyword.image); }
11763+
[ "=" { option.add("="); } ]
11764+
( value=<K_DEFAULT> | value=<K_NONE> | value=<K_EXCLUSIVE> | value=<S_IDENTIFIER> )
11765+
{ option.add(value.image); return option; }
11766+
}
11767+
1173811768
List<String> ColumnDefinitionParameter(): {
1173911769
Token tk = null;
1174011770
List<String> parameter = new ArrayList<String>();
@@ -13533,6 +13563,7 @@ Drop Drop():
1353313563
Table name;
1353413564
List<String> dropArgs = new ArrayList<String>();
1353513565
List<String> funcArgs = null;
13566+
List<String> indexOption;
1353613567
boolean useTemporary = false;
1353713568
}
1353813569
{
@@ -13569,6 +13600,10 @@ Drop Drop():
1356913600
( "," name = Table() { drop.addNames(name); } )*
1357013601
[ LOOKAHEAD(2) funcArgs = FuncArgsList() ]
1357113602
(
13603+
LOOKAHEAD({ getToken(1).kind == K_ALGORITHM
13604+
|| (getToken(1).kind == K_LOCK && getToken(2).kind != K_TABLE) })
13605+
indexOption=IndexAlgorithmOrLockOption() { dropArgs.addAll(indexOption); }
13606+
|
1357213607
(
1357313608
tk=<S_IDENTIFIER> | tk=<K_CASCADE> | tk=<K_RESTRICT>
1357413609
// MySQL DROP INDEX accepts a trailing algorithm_option / lock_option,
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2026 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 net.sf.jsqlparser.JSQLParserException;
13+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
14+
import net.sf.jsqlparser.statement.Statements;
15+
import net.sf.jsqlparser.statement.create.index.CreateIndex;
16+
import net.sf.jsqlparser.statement.drop.Drop;
17+
import net.sf.jsqlparser.statement.lock.LockStatement;
18+
import net.sf.jsqlparser.test.TestUtils;
19+
import net.sf.jsqlparser.util.deparser.CreateIndexDeParser;
20+
import org.junit.jupiter.api.Test;
21+
import org.junit.jupiter.params.ParameterizedTest;
22+
import org.junit.jupiter.params.provider.ValueSource;
23+
24+
import java.util.List;
25+
26+
import static org.junit.jupiter.api.Assertions.*;
27+
28+
class MySQLIndexOptionsTest {
29+
@Test
30+
void parsesAlgorithmAndLockOptionsInEitherOrder() throws Exception {
31+
for (String algorithm : List.of("DEFAULT", "INPLACE", "COPY")) {
32+
for (String lock : List.of("DEFAULT", "NONE", "SHARED", "EXCLUSIVE")) {
33+
for (String equals : List.of(" ", " = ")) {
34+
String algorithmOption = "ALGORITHM" + equals + algorithm;
35+
String lockOption = "LOCK" + equals + lock;
36+
for (String options : List.of(algorithmOption + " " + lockOption,
37+
lockOption + " " + algorithmOption)) {
38+
for (String statement : List.of("CREATE INDEX idx ON t (id) ",
39+
"DROP INDEX idx ON t ")) {
40+
TestUtils.assertSqlCanBeParsedAndDeparsed(statement + options, true);
41+
}
42+
}
43+
}
44+
}
45+
}
46+
}
47+
48+
@Test
49+
void preservesLegacyOptionTokensAndTheirOrder() throws Exception {
50+
CreateIndex create = (CreateIndex) CCJSqlParserUtil.parse(
51+
"CREATE INDEX idx ON t (id) LOCK EXCLUSIVE ALGORITHM = DEFAULT");
52+
assertEquals(List.of("LOCK", "EXCLUSIVE", "ALGORITHM", "=", "DEFAULT"),
53+
create.getTailParameters());
54+
Drop drop = (Drop) CCJSqlParserUtil.parse(
55+
"DROP INDEX idx ON t LOCK = DEFAULT ALGORITHM COPY");
56+
assertEquals(List.of("ON", "t", "LOCK", "=", "DEFAULT", "ALGORITHM", "COPY"),
57+
drop.getParameters());
58+
assertEquals(create.toString(), CCJSqlParserUtil.parse(create.toString()).toString());
59+
assertEquals(drop.toString(), CCJSqlParserUtil.parse(drop.toString()).toString());
60+
}
61+
62+
@ParameterizedTest
63+
@ValueSource(strings = {"CREATE INDEX idx ON t (id)", "DROP INDEX idx ON t"})
64+
void keepsFollowingLockStatementSeparate(String index) throws Exception {
65+
Statements statements = CCJSqlParserUtil.parseStatements(
66+
index + "; LOCK TABLE t IN SHARE MODE");
67+
assertEquals(2, statements.size());
68+
assertInstanceOf(LockStatement.class, statements.get(1));
69+
net.sf.jsqlparser.parser.CCJSqlParser parser = CCJSqlParserUtil.newParser(
70+
index + " LOCK TABLE t IN SHARE MODE");
71+
parser.SingleStatement();
72+
assertEquals(net.sf.jsqlparser.parser.CCJSqlParserConstants.K_LOCK,
73+
parser.getNextToken().kind);
74+
}
75+
76+
@ParameterizedTest
77+
@ValueSource(strings = {"CREATE INDEX idx ON t (id) LOCK", "DROP INDEX idx ON t LOCK =",
78+
"CREATE INDEX idx ON t (id) ALGORITHM =", "DROP INDEX idx ON t ALGORITHM"})
79+
void rejectsMissingOptionValues(String sql) {
80+
assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql));
81+
}
82+
83+
@ParameterizedTest
84+
@ValueSource(strings = {
85+
"CREATE INDEX idx ON t (id) LOCK EXCLUSIVE ALGORITHM DEFAULT",
86+
"CREATE INDEX idx USING BTREE ON t (id) KEY_BLOCK_SIZE = 8 COMMENT 'test' INVISIBLE",
87+
"CREATE INDEX idx ON t (id) parallel compress nologging",
88+
"CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS idx ON ONLY t USING btree (id DESC) INCLUDE (value) NULLS NOT DISTINCT WITH (fillfactor = 80) TABLESPACE fast WHERE active"
89+
})
90+
void sharesRenderingForMySqlPostgreSqlAndLegacyTails(String sql) throws Exception {
91+
CreateIndex create = (CreateIndex) TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true);
92+
create.getIndex().setName("renamed");
93+
StringBuilder deparsed = new StringBuilder("prefix ");
94+
new CreateIndexDeParser(deparsed).deParse(create);
95+
assertEquals("prefix " + create, deparsed.toString());
96+
assertEquals(create.toString(), CCJSqlParserUtil.parse(create.toString()).toString());
97+
}
98+
}

0 commit comments

Comments
 (0)