Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
feat: enhance ALTER TABLE support with UNIQUE KEY and INVISIBLE index…
… options
  • Loading branch information
minleejae committed Apr 30, 2025
commit f0296978bf969e3fc58c13cd66973d6158248b4c
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,17 @@
*/
package net.sf.jsqlparser.statement.alter;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.statement.ReferentialAction;
import net.sf.jsqlparser.statement.ReferentialAction.Action;
Expand All @@ -20,9 +30,6 @@
import net.sf.jsqlparser.statement.create.table.PartitionDefinition;
import net.sf.jsqlparser.statement.select.PlainSelect;

import java.io.Serializable;
import java.util.*;

@SuppressWarnings({"PMD.CyclomaticComplexity"})
public class AlterExpression implements Serializable {

Expand Down Expand Up @@ -94,6 +101,7 @@ public class AlterExpression implements Serializable {
private String constraintSymbol;
private boolean enforced;
private String constraintType;
private boolean invisible;

public Index getOldIndex() {
return oldIndex;
Expand Down Expand Up @@ -628,6 +636,14 @@ public void setConstraintType(String constraintType) {
this.constraintType = constraintType;
}

public boolean isInvisible() {
return invisible;
}

public void setInvisible(boolean invisible) {
this.invisible = invisible;
}

@Override
@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity",
"PMD.ExcessiveMethodLength", "PMD.SwitchStmtsShouldHaveDefault"})
Expand All @@ -637,17 +653,27 @@ public String toString() {

if (operation == AlterOperation.UNSPECIFIC) {
b.append(optionalSpecifier);
} else if (operation == AlterOperation.ALTER && constraintType != null) {
b.append("ALTER");
b.append(" ").append(constraintType);

if (constraintSymbol != null) {
b.append(" ").append(constraintSymbol);
} else if (operation == AlterOperation.ALTER && constraintType != null
&& constraintSymbol != null) {
// This is for ALTER INDEX ... INVISIBLE
b.append("ALTER ").append(constraintType).append(" ").append(constraintSymbol);

if (invisible) {
b.append(" INVISIBLE");
} else if (!isEnforced()) {
b.append(" NOT ENFORCED");
} else if (enforced) {
b.append(" ENFORCED");
}
if (!isEnforced()) {
b.append(" NOT ");
} else if (operation == AlterOperation.ADD && constraintType != null
&& constraintSymbol != null) {
b.append("ADD CONSTRAINT ").append(constraintType).append(" ").append(constraintSymbol)
.append(" ");

if (index != null && index.getColumnsNames() != null) {
b.append(" ")
.append(PlainSelect.getStringList(index.getColumnsNames(), true, true));
}
b.append(" ENFORCED");
} else if (operation == AlterOperation.ALTER
&& columnDropDefaultList != null && !columnDropDefaultList.isEmpty()) {
b.append("ALTER ");
Expand Down
168 changes: 93 additions & 75 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -8034,89 +8034,107 @@ AlterExpression AlterExpression():
)
|
(
<K_CONSTRAINT> sk3=RelObjectName()
<K_CONSTRAINT>
(
( tk=<K_FOREIGN> tk2=<K_KEY>
columnNames=ColumnsNamesList()
{
fkIndex = new ForeignKeyIndex()
.withName(sk3)
.withType(tk.image + " " + tk2.image)
.withColumnsNames(columnNames);
columnNames = null;
}
<K_REFERENCES> fkTable=Table() [ LOOKAHEAD(2) columnNames=ColumnsNamesList() ]
{
fkIndex.withTable(fkTable).withReferencedColumnNames(columnNames);
alterExp.setIndex(fkIndex);
}

[LOOKAHEAD(2) (<K_ON> (tk=<K_DELETE> | tk=<K_UPDATE>) action = Action()
{ fkIndex.setReferentialAction(ReferentialAction.Type.from(tk.image), action); }
)]
[LOOKAHEAD(2) (<K_ON> (tk=<K_DELETE> | tk=<K_UPDATE>) action = Action()
{ fkIndex.setReferentialAction(ReferentialAction.Type.from(tk.image), action); }
)]
constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); }
)
|
( tk=<K_PRIMARY> tk2=<K_KEY>
columnNames=ColumnsNamesList()
{
index = new NamedConstraint()
.withName(sk3)
.withType(tk.image + " " + tk2.image)
.withColumnsNames(columnNames);
alterExp.setIndex(index);
}
constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); }
[ <K_USING> sk4=RelObjectName() { alterExp.addParameters("USING", sk4); }]
[ LOOKAHEAD(2) index = IndexWithComment(index) { alterExp.setIndex(index); } ]
)
|
LOOKAHEAD(2) (
{ boolean enforced = true; }
[ tk = <K_NOT> { enforced = false; } ]
<K_ENFORCED> {
alterExp.setEnforced(enforced);
alterExp.setConstraintType("CONSTRAINT");
alterExp.setConstraintSymbol(sk3);
}
)
|
LOOKAHEAD(2)
(
<K_CHECK> {Expression exp = null;} (LOOKAHEAD(2) "(" exp = Expression() ")")* {
CheckConstraint checkCs = new CheckConstraint().withName(sk3).withExpression(exp);
alterExp.setIndex(checkCs);
<K_UNIQUE> (<K_KEY> { alterExp.setConstraintType("UNIQUE KEY"); }
| <K_INDEX> { alterExp.setConstraintType("UNIQUE INDEX"); }
| { alterExp.setConstraintType("UNIQUE"); } )
sk3=RelObjectName() {
alterExp.setConstraintSymbol(sk3);
index = new Index();
}
columnNames=ColumnsNamesList() {
index.setColumnsNames(columnNames);
alterExp.setIndex(index);
}
)
|
sk3=RelObjectName()
(
tk=<K_UNIQUE> (tk2=<K_KEY> { alterExp.setUk(true); } | tk2=<K_INDEX>)?
columnNames=ColumnsNamesList()
{
index = new NamedConstraint()
( tk=<K_FOREIGN> tk2=<K_KEY>
columnNames=ColumnsNamesList()
{
fkIndex = new ForeignKeyIndex()
.withName(sk3)
.withType(tk.image + (tk2!=null?" " + tk2.image:""))
.withType(tk.image + " " + tk2.image)
.withColumnsNames(columnNames);
alterExp.setIndex(index);
}
constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); }
[ <K_USING> sk4=RelObjectName() { alterExp.addParameters("USING", sk4); }]
[ LOOKAHEAD(2) index = IndexWithComment(index) { alterExp.setIndex(index); } ]
)
|
(
tk=<K_KEY>
columnNames=ColumnsNamesList()
{
index = new NamedConstraint()
.withName(sk3)
.withType(tk.image)
.withColumnsNames(columnNames);
alterExp.setIndex(index);
}
constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); }
columnNames = null;
}
<K_REFERENCES> fkTable=Table() [ LOOKAHEAD(2) columnNames=ColumnsNamesList() ]
{
fkIndex.withTable(fkTable).withReferencedColumnNames(columnNames);
alterExp.setIndex(fkIndex);
}

[LOOKAHEAD(2) (<K_ON> (tk=<K_DELETE> | tk=<K_UPDATE>) action = Action()
{ fkIndex.setReferentialAction(ReferentialAction.Type.from(tk.image), action); }
)]
[LOOKAHEAD(2) (<K_ON> (tk=<K_DELETE> | tk=<K_UPDATE>) action = Action()
{ fkIndex.setReferentialAction(ReferentialAction.Type.from(tk.image), action); }
)]
constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); }
)
|
( tk=<K_PRIMARY> tk2=<K_KEY>
columnNames=ColumnsNamesList()
{
index = new NamedConstraint()
.withName(sk3)
.withType(tk.image + " " + tk2.image)
.withColumnsNames(columnNames);
alterExp.setIndex(index);
}
constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); }
[ <K_USING> sk4=RelObjectName() { alterExp.addParameters("USING", sk4); }]
[ LOOKAHEAD(2) index = IndexWithComment(index) { alterExp.setIndex(index); } ]
)
|
LOOKAHEAD(2) (
{ boolean enforced = true; }
[ tk = <K_NOT> { enforced = false; } ]
<K_ENFORCED> {
alterExp.setEnforced(enforced);
alterExp.setConstraintType("CONSTRAINT");
alterExp.setConstraintSymbol(sk3);
}
)
|
(
<K_CHECK> {Expression exp = null;} (LOOKAHEAD(2) "(" exp = Expression() ")")* {
CheckConstraint checkCs = new CheckConstraint().withName(sk3).withExpression(exp);
alterExp.setIndex(checkCs);
}
)
|
(
tk=<K_UNIQUE> (tk2=<K_KEY> { alterExp.setUk(true); } | tk2=<K_INDEX>)?
columnNames=ColumnsNamesList()
{
index = new NamedConstraint()
.withName(sk3)
.withType(tk.image + (tk2!=null?" " + tk2.image:""))
.withColumnsNames(columnNames);
alterExp.setIndex(index);
}
constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); }
[ <K_USING> sk4=RelObjectName() { alterExp.addParameters("USING", sk4); }]
[ LOOKAHEAD(2) index = IndexWithComment(index) { alterExp.setIndex(index); } ]
)
|
(
tk=<K_KEY>
columnNames=ColumnsNamesList()
{
index = new NamedConstraint()
.withName(sk3)
.withType(tk.image)
.withColumnsNames(columnNames);
alterExp.setIndex(index);
}
constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); }
)
)
)
)
Expand Down
42 changes: 42 additions & 0 deletions src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2138,4 +2138,46 @@ public void testAlterTableAlterCheckNotEnforced() throws JSQLParserException {

assertSqlCanBeParsedAndDeparsed(sql);
}

@Test
public void testAlterTableAddConstraintUniqueKey() throws JSQLParserException {
String sql = "ALTER TABLE sbtest1 ADD CONSTRAINT UNIQUE KEY ux_c3 (c3)";
Statement stmt = CCJSqlParserUtil.parse(sql);
assertInstanceOf(Alter.class, stmt);

Alter alter = (Alter) stmt;
assertEquals("sbtest1", alter.getTable().getFullyQualifiedName());

List<AlterExpression> alterExpressions = alter.getAlterExpressions();
assertNotNull(alterExpressions);
assertEquals(1, alterExpressions.size());

AlterExpression alterExp = alterExpressions.get(0);
assertEquals(AlterOperation.ADD, alterExp.getOperation());
assertEquals("UNIQUE KEY", alterExp.getConstraintType());
assertEquals("ux_c3", alterExp.getConstraintSymbol());

assertSqlCanBeParsedAndDeparsed(sql);
}

@Test
public void testAlterTableAlterIndexInvisible() throws JSQLParserException {
String sql = "ALTER TABLE sbtest1 ALTER INDEX c4 INVISIBLE";
Statement stmt = CCJSqlParserUtil.parse(sql);
assertInstanceOf(Alter.class, stmt);

Alter alter = (Alter) stmt;
assertEquals("sbtest1", alter.getTable().getFullyQualifiedName());

List<AlterExpression> alterExpressions = alter.getAlterExpressions();
assertNotNull(alterExpressions);
assertEquals(1, alterExpressions.size());

AlterExpression alterExp = alterExpressions.get(0);
assertEquals(AlterOperation.ALTER, alterExp.getOperation());
assertEquals("c4", alterExp.getIndex().getName());
assertEquals("INVISIBLE", alterExp.getIndex().getIndexSpec().get(0));

assertSqlCanBeParsedAndDeparsed(sql);
}
}