Skip to content

Commit 78318bb

Browse files
authored
feat: enhance ALTER TABLE support with UNIQUE KEY and INVISIBLE index (#2234)
* feat: enhance ALTER TABLE support with UNIQUE KEY and INVISIBLE index options * test: add unit test for ALTER TABLE with INVISIBLE index option * fix formatting
1 parent 98e03d4 commit 78318bb

File tree

3 files changed

+225
-95
lines changed

3 files changed

+225
-95
lines changed

src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java

+38-12
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,17 @@
99
*/
1010
package net.sf.jsqlparser.statement.alter;
1111

12+
import java.io.Serializable;
13+
import java.util.ArrayList;
14+
import java.util.Arrays;
15+
import java.util.Collection;
16+
import java.util.Collections;
17+
import java.util.LinkedHashSet;
18+
import java.util.List;
19+
import java.util.Optional;
20+
import java.util.Set;
1221
import java.util.stream.Collectors;
22+
1323
import net.sf.jsqlparser.expression.Expression;
1424
import net.sf.jsqlparser.statement.ReferentialAction;
1525
import net.sf.jsqlparser.statement.ReferentialAction.Action;
@@ -20,9 +30,6 @@
2030
import net.sf.jsqlparser.statement.create.table.PartitionDefinition;
2131
import net.sf.jsqlparser.statement.select.PlainSelect;
2232

23-
import java.io.Serializable;
24-
import java.util.*;
25-
2633
@SuppressWarnings({"PMD.CyclomaticComplexity"})
2734
public class AlterExpression implements Serializable {
2835

@@ -94,6 +101,7 @@ public class AlterExpression implements Serializable {
94101
private String constraintSymbol;
95102
private boolean enforced;
96103
private String constraintType;
104+
private boolean invisible;
97105

98106
public Index getOldIndex() {
99107
return oldIndex;
@@ -628,6 +636,14 @@ public void setConstraintType(String constraintType) {
628636
this.constraintType = constraintType;
629637
}
630638

639+
public boolean isInvisible() {
640+
return invisible;
641+
}
642+
643+
public void setInvisible(boolean invisible) {
644+
this.invisible = invisible;
645+
}
646+
631647
@Override
632648
@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity",
633649
"PMD.ExcessiveMethodLength", "PMD.SwitchStmtsShouldHaveDefault"})
@@ -637,17 +653,27 @@ public String toString() {
637653

638654
if (operation == AlterOperation.UNSPECIFIC) {
639655
b.append(optionalSpecifier);
640-
} else if (operation == AlterOperation.ALTER && constraintType != null) {
641-
b.append("ALTER");
642-
b.append(" ").append(constraintType);
643-
644-
if (constraintSymbol != null) {
645-
b.append(" ").append(constraintSymbol);
656+
} else if (operation == AlterOperation.ALTER && constraintType != null
657+
&& constraintSymbol != null) {
658+
// This is for ALTER INDEX ... INVISIBLE
659+
b.append("ALTER ").append(constraintType).append(" ").append(constraintSymbol);
660+
661+
if (invisible) {
662+
b.append(" INVISIBLE");
663+
} else if (!isEnforced()) {
664+
b.append(" NOT ENFORCED");
665+
} else if (enforced) {
666+
b.append(" ENFORCED");
646667
}
647-
if (!isEnforced()) {
648-
b.append(" NOT ");
668+
} else if (operation == AlterOperation.ADD && constraintType != null
669+
&& constraintSymbol != null) {
670+
b.append("ADD CONSTRAINT ").append(constraintType).append(" ").append(constraintSymbol)
671+
.append(" ");
672+
673+
if (index != null && index.getColumnsNames() != null) {
674+
b.append(" ")
675+
.append(PlainSelect.getStringList(index.getColumnsNames(), true, true));
649676
}
650-
b.append(" ENFORCED");
651677
} else if (operation == AlterOperation.ALTER
652678
&& columnDropDefaultList != null && !columnDropDefaultList.isEmpty()) {
653679
b.append("ALTER ");

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

+93-75
Original file line numberDiff line numberDiff line change
@@ -8034,89 +8034,107 @@ AlterExpression AlterExpression():
80348034
)
80358035
|
80368036
(
8037-
<K_CONSTRAINT> sk3=RelObjectName()
8037+
<K_CONSTRAINT>
80388038
(
8039-
( tk=<K_FOREIGN> tk2=<K_KEY>
8040-
columnNames=ColumnsNamesList()
8041-
{
8042-
fkIndex = new ForeignKeyIndex()
8043-
.withName(sk3)
8044-
.withType(tk.image + " " + tk2.image)
8045-
.withColumnsNames(columnNames);
8046-
columnNames = null;
8047-
}
8048-
<K_REFERENCES> fkTable=Table() [ LOOKAHEAD(2) columnNames=ColumnsNamesList() ]
8049-
{
8050-
fkIndex.withTable(fkTable).withReferencedColumnNames(columnNames);
8051-
alterExp.setIndex(fkIndex);
8052-
}
8053-
8054-
[LOOKAHEAD(2) (<K_ON> (tk=<K_DELETE> | tk=<K_UPDATE>) action = Action()
8055-
{ fkIndex.setReferentialAction(ReferentialAction.Type.from(tk.image), action); }
8056-
)]
8057-
[LOOKAHEAD(2) (<K_ON> (tk=<K_DELETE> | tk=<K_UPDATE>) action = Action()
8058-
{ fkIndex.setReferentialAction(ReferentialAction.Type.from(tk.image), action); }
8059-
)]
8060-
constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); }
8061-
)
8062-
|
8063-
( tk=<K_PRIMARY> tk2=<K_KEY>
8064-
columnNames=ColumnsNamesList()
8065-
{
8066-
index = new NamedConstraint()
8067-
.withName(sk3)
8068-
.withType(tk.image + " " + tk2.image)
8069-
.withColumnsNames(columnNames);
8070-
alterExp.setIndex(index);
8071-
}
8072-
constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); }
8073-
[ <K_USING> sk4=RelObjectName() { alterExp.addParameters("USING", sk4); }]
8074-
[ LOOKAHEAD(2) index = IndexWithComment(index) { alterExp.setIndex(index); } ]
8075-
)
8076-
|
8077-
LOOKAHEAD(2) (
8078-
{ boolean enforced = true; }
8079-
[ tk = <K_NOT> { enforced = false; } ]
8080-
<K_ENFORCED> {
8081-
alterExp.setEnforced(enforced);
8082-
alterExp.setConstraintType("CONSTRAINT");
8083-
alterExp.setConstraintSymbol(sk3);
8084-
}
8085-
)
8086-
|
8039+
LOOKAHEAD(2)
80878040
(
8088-
<K_CHECK> {Expression exp = null;} (LOOKAHEAD(2) "(" exp = Expression() ")")* {
8089-
CheckConstraint checkCs = new CheckConstraint().withName(sk3).withExpression(exp);
8090-
alterExp.setIndex(checkCs);
8041+
<K_UNIQUE> (<K_KEY> { alterExp.setConstraintType("UNIQUE KEY"); }
8042+
| <K_INDEX> { alterExp.setConstraintType("UNIQUE INDEX"); }
8043+
| { alterExp.setConstraintType("UNIQUE"); } )
8044+
sk3=RelObjectName() {
8045+
alterExp.setConstraintSymbol(sk3);
8046+
index = new Index();
8047+
}
8048+
columnNames=ColumnsNamesList() {
8049+
index.setColumnsNames(columnNames);
8050+
alterExp.setIndex(index);
80918051
}
80928052
)
80938053
|
8054+
sk3=RelObjectName()
80948055
(
8095-
tk=<K_UNIQUE> (tk2=<K_KEY> { alterExp.setUk(true); } | tk2=<K_INDEX>)?
8096-
columnNames=ColumnsNamesList()
8097-
{
8098-
index = new NamedConstraint()
8056+
( tk=<K_FOREIGN> tk2=<K_KEY>
8057+
columnNames=ColumnsNamesList()
8058+
{
8059+
fkIndex = new ForeignKeyIndex()
80998060
.withName(sk3)
8100-
.withType(tk.image + (tk2!=null?" " + tk2.image:""))
8061+
.withType(tk.image + " " + tk2.image)
81018062
.withColumnsNames(columnNames);
8102-
alterExp.setIndex(index);
8103-
}
8104-
constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); }
8105-
[ <K_USING> sk4=RelObjectName() { alterExp.addParameters("USING", sk4); }]
8106-
[ LOOKAHEAD(2) index = IndexWithComment(index) { alterExp.setIndex(index); } ]
8107-
)
8108-
|
8109-
(
8110-
tk=<K_KEY>
8111-
columnNames=ColumnsNamesList()
8112-
{
8113-
index = new NamedConstraint()
8114-
.withName(sk3)
8115-
.withType(tk.image)
8116-
.withColumnsNames(columnNames);
8117-
alterExp.setIndex(index);
8118-
}
8119-
constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); }
8063+
columnNames = null;
8064+
}
8065+
<K_REFERENCES> fkTable=Table() [ LOOKAHEAD(2) columnNames=ColumnsNamesList() ]
8066+
{
8067+
fkIndex.withTable(fkTable).withReferencedColumnNames(columnNames);
8068+
alterExp.setIndex(fkIndex);
8069+
}
8070+
8071+
[LOOKAHEAD(2) (<K_ON> (tk=<K_DELETE> | tk=<K_UPDATE>) action = Action()
8072+
{ fkIndex.setReferentialAction(ReferentialAction.Type.from(tk.image), action); }
8073+
)]
8074+
[LOOKAHEAD(2) (<K_ON> (tk=<K_DELETE> | tk=<K_UPDATE>) action = Action()
8075+
{ fkIndex.setReferentialAction(ReferentialAction.Type.from(tk.image), action); }
8076+
)]
8077+
constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); }
8078+
)
8079+
|
8080+
( tk=<K_PRIMARY> tk2=<K_KEY>
8081+
columnNames=ColumnsNamesList()
8082+
{
8083+
index = new NamedConstraint()
8084+
.withName(sk3)
8085+
.withType(tk.image + " " + tk2.image)
8086+
.withColumnsNames(columnNames);
8087+
alterExp.setIndex(index);
8088+
}
8089+
constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); }
8090+
[ <K_USING> sk4=RelObjectName() { alterExp.addParameters("USING", sk4); }]
8091+
[ LOOKAHEAD(2) index = IndexWithComment(index) { alterExp.setIndex(index); } ]
8092+
)
8093+
|
8094+
LOOKAHEAD(2) (
8095+
{ boolean enforced = true; }
8096+
[ tk = <K_NOT> { enforced = false; } ]
8097+
<K_ENFORCED> {
8098+
alterExp.setEnforced(enforced);
8099+
alterExp.setConstraintType("CONSTRAINT");
8100+
alterExp.setConstraintSymbol(sk3);
8101+
}
8102+
)
8103+
|
8104+
(
8105+
<K_CHECK> {Expression exp = null;} (LOOKAHEAD(2) "(" exp = Expression() ")")* {
8106+
CheckConstraint checkCs = new CheckConstraint().withName(sk3).withExpression(exp);
8107+
alterExp.setIndex(checkCs);
8108+
}
8109+
)
8110+
|
8111+
(
8112+
tk=<K_UNIQUE> (tk2=<K_KEY> { alterExp.setUk(true); } | tk2=<K_INDEX>)?
8113+
columnNames=ColumnsNamesList()
8114+
{
8115+
index = new NamedConstraint()
8116+
.withName(sk3)
8117+
.withType(tk.image + (tk2!=null?" " + tk2.image:""))
8118+
.withColumnsNames(columnNames);
8119+
alterExp.setIndex(index);
8120+
}
8121+
constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); }
8122+
[ <K_USING> sk4=RelObjectName() { alterExp.addParameters("USING", sk4); }]
8123+
[ LOOKAHEAD(2) index = IndexWithComment(index) { alterExp.setIndex(index); } ]
8124+
)
8125+
|
8126+
(
8127+
tk=<K_KEY>
8128+
columnNames=ColumnsNamesList()
8129+
{
8130+
index = new NamedConstraint()
8131+
.withName(sk3)
8132+
.withType(tk.image)
8133+
.withColumnsNames(columnNames);
8134+
alterExp.setIndex(index);
8135+
}
8136+
constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); }
8137+
)
81208138
)
81218139
)
81228140
)

src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java

+94-8
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,22 @@
99
*/
1010
package net.sf.jsqlparser.statement.alter;
1111

12-
import static net.sf.jsqlparser.test.TestUtils.*;
13-
import static org.junit.jupiter.api.Assertions.*;
14-
1512
import java.util.Arrays;
1613
import java.util.Collections;
1714
import java.util.List;
1815
import java.util.stream.Stream;
16+
17+
import static org.junit.jupiter.api.Assertions.assertEquals;
18+
import static org.junit.jupiter.api.Assertions.assertFalse;
19+
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
20+
import static org.junit.jupiter.api.Assertions.assertNotNull;
21+
import static org.junit.jupiter.api.Assertions.assertNull;
22+
import static org.junit.jupiter.api.Assertions.assertTrue;
23+
import org.junit.jupiter.api.Test;
24+
import org.junit.jupiter.params.ParameterizedTest;
25+
import org.junit.jupiter.params.provider.Arguments;
26+
import org.junit.jupiter.params.provider.MethodSource;
27+
1928
import net.sf.jsqlparser.JSQLParserException;
2029
import net.sf.jsqlparser.expression.StringValue;
2130
import net.sf.jsqlparser.expression.operators.relational.NotEqualsTo;
@@ -28,12 +37,16 @@
2837
import net.sf.jsqlparser.statement.Statement;
2938
import net.sf.jsqlparser.statement.alter.AlterExpression.ColumnDataType;
3039
import net.sf.jsqlparser.statement.create.index.CreateIndex;
31-
import net.sf.jsqlparser.statement.create.table.*;
40+
import net.sf.jsqlparser.statement.create.table.CheckConstraint;
41+
import net.sf.jsqlparser.statement.create.table.ForeignKeyIndex;
42+
import net.sf.jsqlparser.statement.create.table.Index;
3243
import net.sf.jsqlparser.statement.create.table.Index.ColumnParams;
33-
import org.junit.jupiter.api.Test;
34-
import org.junit.jupiter.params.ParameterizedTest;
35-
import org.junit.jupiter.params.provider.Arguments;
36-
import org.junit.jupiter.params.provider.MethodSource;
44+
import net.sf.jsqlparser.statement.create.table.NamedConstraint;
45+
import net.sf.jsqlparser.statement.create.table.PartitionDefinition;
46+
import static net.sf.jsqlparser.test.TestUtils.assertDeparse;
47+
import static net.sf.jsqlparser.test.TestUtils.assertEqualsObjectTree;
48+
import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed;
49+
import static net.sf.jsqlparser.test.TestUtils.assertStatementCanBeDeparsedAs;
3750

3851
public class AlterTest {
3952

@@ -2138,4 +2151,77 @@ public void testAlterTableAlterCheckNotEnforced() throws JSQLParserException {
21382151

21392152
assertSqlCanBeParsedAndDeparsed(sql);
21402153
}
2154+
2155+
@Test
2156+
public void testAlterTableAddConstraintUniqueKey() throws JSQLParserException {
2157+
String sql = "ALTER TABLE sbtest1 ADD CONSTRAINT UNIQUE KEY ux_c3 (c3)";
2158+
Statement stmt = CCJSqlParserUtil.parse(sql);
2159+
assertInstanceOf(Alter.class, stmt);
2160+
2161+
Alter alter = (Alter) stmt;
2162+
assertEquals("sbtest1", alter.getTable().getFullyQualifiedName());
2163+
2164+
List<AlterExpression> alterExpressions = alter.getAlterExpressions();
2165+
assertNotNull(alterExpressions);
2166+
assertEquals(1, alterExpressions.size());
2167+
2168+
AlterExpression alterExp = alterExpressions.get(0);
2169+
assertEquals(AlterOperation.ADD, alterExp.getOperation());
2170+
assertEquals("UNIQUE KEY", alterExp.getConstraintType());
2171+
assertEquals("ux_c3", alterExp.getConstraintSymbol());
2172+
2173+
assertSqlCanBeParsedAndDeparsed(sql);
2174+
}
2175+
2176+
@Test
2177+
public void testAlterTableAlterIndexInvisible() throws JSQLParserException {
2178+
String sql = "ALTER TABLE sbtest1 ALTER INDEX c4 INVISIBLE";
2179+
Statement stmt = CCJSqlParserUtil.parse(sql);
2180+
assertInstanceOf(Alter.class, stmt);
2181+
2182+
Alter alter = (Alter) stmt;
2183+
assertEquals("sbtest1", alter.getTable().getFullyQualifiedName());
2184+
2185+
List<AlterExpression> alterExpressions = alter.getAlterExpressions();
2186+
assertNotNull(alterExpressions);
2187+
assertEquals(1, alterExpressions.size());
2188+
2189+
AlterExpression alterExp = alterExpressions.get(0);
2190+
assertEquals(AlterOperation.ALTER, alterExp.getOperation());
2191+
assertEquals("c4", alterExp.getIndex().getName());
2192+
assertEquals("INVISIBLE", alterExp.getIndex().getIndexSpec().get(0));
2193+
2194+
assertSqlCanBeParsedAndDeparsed(sql);
2195+
}
2196+
2197+
@Test
2198+
public void testAlterTableAddIndexInvisible() throws JSQLParserException {
2199+
String sql = "ALTER TABLE t1 ADD INDEX k_idx (k) INVISIBLE";
2200+
Statement stmt = CCJSqlParserUtil.parse(sql);
2201+
assertInstanceOf(Alter.class, stmt);
2202+
2203+
Alter alter = (Alter) stmt;
2204+
assertEquals("t1", alter.getTable().getFullyQualifiedName());
2205+
2206+
List<AlterExpression> alterExpressions = alter.getAlterExpressions();
2207+
assertNotNull(alterExpressions);
2208+
assertEquals(1, alterExpressions.size());
2209+
2210+
AlterExpression alterExp = alterExpressions.get(0);
2211+
assertEquals(AlterOperation.ADD, alterExp.getOperation());
2212+
assertNotNull(alterExp.getIndex());
2213+
assertEquals("k_idx", alterExp.getIndex().getName());
2214+
assertEquals("INDEX", alterExp.getIndex().getIndexKeyword());
2215+
2216+
List<String> columnNames = alterExp.getIndex().getColumnsNames();
2217+
assertNotNull(columnNames);
2218+
assertEquals(1, columnNames.size());
2219+
assertEquals("k", columnNames.get(0));
2220+
2221+
List<String> indexSpec = alterExp.getIndex().getIndexSpec();
2222+
assertNotNull(indexSpec);
2223+
assertTrue(indexSpec.contains("INVISIBLE"));
2224+
2225+
assertSqlCanBeParsedAndDeparsed(sql);
2226+
}
21412227
}

0 commit comments

Comments
 (0)