Skip to content
Open
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 @@ -137,15 +137,28 @@ public void setTailParameters(List<String> tailParameters) {

@Override
public String toString() {
StringBuilder buffer = new StringBuilder();
return appendTo(new StringBuilder()).toString();
}

/** Shared rendering for the statement model and CreateIndexDeParser. */
public StringBuilder appendTo(StringBuilder buffer) {
appendIndexHeader(buffer);
appendIndexTarget(buffer);
appendIndexColumns(buffer);
appendPostgreSqlTail(buffer);
if (tailParameters != null) {
for (String param : tailParameters) {
buffer.append(" ").append(param);
}
}
return buffer;
}

private void appendIndexHeader(StringBuilder buffer) {
buffer.append("CREATE ");

if (index.getType() != null) {
buffer.append(index.getType());
buffer.append(" ");
buffer.append(index.getType()).append(" ");
}

buffer.append("INDEX ");
if (concurrently) {
buffer.append("CONCURRENTLY ");
Expand All @@ -156,42 +169,30 @@ public String toString() {
if (index.getName() != null) {
buffer.append(index.getName()).append(" ");
}
}

private void appendIndexTarget(StringBuilder buffer) {
if (index.getUsing() != null && isIndexTypeBeforeOn()) {
buffer.append("USING ").append(index.getUsing()).append(" ");
}

buffer.append("ON ");
if (only) {
buffer.append("ONLY ");
}
buffer.append(table.getFullyQualifiedName());

if (index.getUsing() != null && !isIndexTypeBeforeOn()) {
buffer.append(" USING ");
buffer.append(index.getUsing());
buffer.append(" USING ").append(index.getUsing());
}
}

private void appendIndexColumns(StringBuilder buffer) {
if (index.getColumnsNames() != null) {
buffer.append(" (");

buffer.append(
index.getColumns().stream()
.map(Index.ColumnParams::toString)
.collect(joining(", ")));

buffer.append(index.getColumns().stream()
.map(Index.ColumnParams::toString)
.collect(joining(", ")));
buffer.append(")");

appendPostgreSqlTail(buffer);

if (tailParameters != null) {
for (String param : tailParameters) {
buffer.append(" ").append(param);
}
}
}

return buffer.toString();
}

private void appendPostgreSqlTail(StringBuilder buffer) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@
*/
package net.sf.jsqlparser.util.deparser;

import static java.util.stream.Collectors.joining;

import net.sf.jsqlparser.statement.create.index.CreateIndex;
import net.sf.jsqlparser.statement.create.table.Index;
import net.sf.jsqlparser.statement.select.PlainSelect;

public class CreateIndexDeParser extends AbstractDeParser<CreateIndex> {

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

@Override
public void deParse(CreateIndex createIndex) {
Index index = createIndex.getIndex();

builder.append("CREATE ");

if (index.getType() != null) {
builder.append(index.getType());
builder.append(" ");
}

builder.append("INDEX ");
if (createIndex.isConcurrently()) {
builder.append("CONCURRENTLY ");
}
if (createIndex.isUsingIfNotExists()) {
builder.append("IF NOT EXISTS ");
}
if (index.getName() != null) {
builder.append(index.getName()).append(" ");
}

String using = index.getUsing();
if (using != null && createIndex.isIndexTypeBeforeOn()) {
builder.append("USING ").append(using).append(" ");
}

builder.append("ON ");
if (createIndex.isOnly()) {
builder.append("ONLY ");
}
builder.append(createIndex.getTable().getFullyQualifiedName());

if (using != null && !createIndex.isIndexTypeBeforeOn()) {
builder.append(" USING ");
builder.append(using);
}

if (index.getColumnsNames() != null) {
builder.append(" (");
builder.append(index.getColumnWithParams().stream()
.map(Index.ColumnParams::toString)
.collect(joining(", ")));
builder.append(")");
}

if (createIndex.getIncludeColumns() != null) {
builder.append(" INCLUDE (")
.append(String.join(", ", createIndex.getIncludeColumns())).append(")");
}
if (createIndex.getNullsDistinct() != null) {
builder.append(" NULLS ")
.append(createIndex.getNullsDistinct() ? "DISTINCT" : "NOT DISTINCT");
}
if (createIndex.getStorageParameters() != null) {
builder.append(" WITH ").append(PlainSelect.getStringList(
createIndex.getStorageParameters(), true, true));
}
if (createIndex.getTableSpace() != null) {
builder.append(" TABLESPACE ").append(createIndex.getTableSpace());
}
if (createIndex.getWhere() != null) {
builder.append(" WHERE ").append(createIndex.getWhere());
}

if (createIndex.getTailParameters() != null) {
for (String param : createIndex.getTailParameters()) {
builder.append(" ").append(param);
}
}
createIndex.appendTo(builder);
}

}
37 changes: 36 additions & 1 deletion src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -11794,7 +11794,8 @@ CreateIndex CreateIndex():
[ LOOKAHEAD(2) <K_TABLESPACE> tableSpace=RelObjectName()
{ createIndex.setTableSpace(tableSpace); } ]
[ LOOKAHEAD(2) <K_WHERE> where=Expression() { createIndex.setWhere(where); } ]
( LOOKAHEAD(2) parameter=CreateParameter() { tailParameters.addAll(parameter); } )*
( LOOKAHEAD(2, { getToken(1).kind != K_LOCK || getToken(2).kind != K_TABLE })
parameter=CreateIndexTailOption() { tailParameters.addAll(parameter); } )*
{
index.setColumns(colNames);
createIndex.setIndex(index);
Expand All @@ -11804,6 +11805,35 @@ CreateIndex CreateIndex():
}
}

List<String> CreateIndexTailOption():
{
List<String> option;
}
{
(
LOOKAHEAD({ getToken(1).kind == K_ALGORITHM || getToken(1).kind == K_LOCK })
option=IndexAlgorithmOrLockOption()
|
option=CreateParameter()
)
{ return option; }
}

// CREATE INDEX and DROP INDEX share MySQL's algorithm_option / lock_option syntax.
// Keep the legacy token list, including optional equals signs and the original order.
List<String> IndexAlgorithmOrLockOption():
{
List<String> option = new ArrayList<String>();
Token keyword;
Token value;
}
{
( keyword=<K_ALGORITHM> | keyword=<K_LOCK> ) { option.add(keyword.image); }
[ "=" { option.add("="); } ]
( value=<K_DEFAULT> | value=<K_NONE> | value=<K_EXCLUSIVE> | value=<S_IDENTIFIER> )
{ option.add(value.image); return option; }
}

List<String> ColumnDefinitionParameter(): {
Token tk = null;
List<String> parameter = new ArrayList<String>();
Expand Down Expand Up @@ -13602,6 +13632,7 @@ Drop Drop():
Table name;
List<String> dropArgs = new ArrayList<String>();
List<String> funcArgs = null;
List<String> indexOption;
boolean useTemporary = false;
}
{
Expand Down Expand Up @@ -13638,6 +13669,10 @@ Drop Drop():
( "," name = Table() { drop.addNames(name); } )*
[ LOOKAHEAD(2) funcArgs = FuncArgsList() ]
(
LOOKAHEAD({ getToken(1).kind == K_ALGORITHM
|| (getToken(1).kind == K_LOCK && getToken(2).kind != K_TABLE) })
indexOption=IndexAlgorithmOrLockOption() { dropArgs.addAll(indexOption); }
|
(
tk=<S_IDENTIFIER> | tk=<K_CASCADE> | tk=<K_RESTRICT>
// MySQL DROP INDEX accepts a trailing algorithm_option / lock_option,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2026 JSQLParser
* %%
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
* #L%
*/
package net.sf.jsqlparser.statement.create;

import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statements;
import net.sf.jsqlparser.statement.create.index.CreateIndex;
import net.sf.jsqlparser.statement.drop.Drop;
import net.sf.jsqlparser.statement.lock.LockStatement;
import net.sf.jsqlparser.test.TestUtils;
import net.sf.jsqlparser.util.deparser.CreateIndexDeParser;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

class MySQLIndexOptionsTest {
@Test
void parsesAlgorithmAndLockOptionsInEitherOrder() throws Exception {
for (String algorithm : List.of("DEFAULT", "INPLACE", "COPY")) {
for (String lock : List.of("DEFAULT", "NONE", "SHARED", "EXCLUSIVE")) {
for (String equals : List.of(" ", " = ")) {
String algorithmOption = "ALGORITHM" + equals + algorithm;
String lockOption = "LOCK" + equals + lock;
for (String options : List.of(algorithmOption + " " + lockOption,
lockOption + " " + algorithmOption)) {
for (String statement : List.of("CREATE INDEX idx ON t (id) ",
"DROP INDEX idx ON t ")) {
TestUtils.assertSqlCanBeParsedAndDeparsed(statement + options, true);
}
}
}
}
}
}

@Test
void preservesLegacyOptionTokensAndTheirOrder() throws Exception {
CreateIndex create = (CreateIndex) CCJSqlParserUtil.parse(
"CREATE INDEX idx ON t (id) LOCK EXCLUSIVE ALGORITHM = DEFAULT");
assertEquals(List.of("LOCK", "EXCLUSIVE", "ALGORITHM", "=", "DEFAULT"),
create.getTailParameters());
Drop drop = (Drop) CCJSqlParserUtil.parse(
"DROP INDEX idx ON t LOCK = DEFAULT ALGORITHM COPY");
assertEquals(List.of("ON", "t", "LOCK", "=", "DEFAULT", "ALGORITHM", "COPY"),
drop.getParameters());
assertEquals(create.toString(), CCJSqlParserUtil.parse(create.toString()).toString());
assertEquals(drop.toString(), CCJSqlParserUtil.parse(drop.toString()).toString());
}

@ParameterizedTest
@ValueSource(strings = {"CREATE INDEX idx ON t (id)", "DROP INDEX idx ON t"})
void keepsFollowingLockStatementSeparate(String index) throws Exception {
Statements statements = CCJSqlParserUtil.parseStatements(
index + "; LOCK TABLE t IN SHARE MODE");
assertEquals(2, statements.size());
assertInstanceOf(LockStatement.class, statements.get(1));
net.sf.jsqlparser.parser.CCJSqlParser parser = CCJSqlParserUtil.newParser(
index + " LOCK TABLE t IN SHARE MODE");
parser.SingleStatement();
assertEquals(net.sf.jsqlparser.parser.CCJSqlParserConstants.K_LOCK,
parser.getNextToken().kind);
}

@ParameterizedTest
@ValueSource(strings = {"CREATE INDEX idx ON t (id) LOCK", "DROP INDEX idx ON t LOCK =",
"CREATE INDEX idx ON t (id) ALGORITHM =", "DROP INDEX idx ON t ALGORITHM"})
void rejectsMissingOptionValues(String sql) {
assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql));
}

@ParameterizedTest
@ValueSource(strings = {
"CREATE INDEX idx ON t (id) LOCK EXCLUSIVE ALGORITHM DEFAULT",
"CREATE INDEX idx USING BTREE ON t (id) KEY_BLOCK_SIZE = 8 COMMENT 'test' INVISIBLE",
"CREATE INDEX idx ON t (id) parallel compress nologging",
"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"
})
void sharesRenderingForMySqlPostgreSqlAndLegacyTails(String sql) throws Exception {
CreateIndex create = (CreateIndex) TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true);
create.getIndex().setName("renamed");
StringBuilder deparsed = new StringBuilder("prefix ");
new CreateIndexDeParser(deparsed).deParse(create);
assertEquals("prefix " + create, deparsed.toString());
assertEquals(create.toString(), CCJSqlParserUtil.parse(create.toString()).toString());
}
}
Loading
Loading