From f0296978bf969e3fc58c13cd66973d6158248b4c Mon Sep 17 00:00:00 2001 From: minleejae Date: Wed, 30 Apr 2025 15:25:14 +0900 Subject: [PATCH 1/3] feat: enhance ALTER TABLE support with UNIQUE KEY and INVISIBLE index options --- .../statement/alter/AlterExpression.java | 50 ++++-- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 168 ++++++++++-------- .../jsqlparser/statement/alter/AlterTest.java | 42 +++++ 3 files changed, 173 insertions(+), 87 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java index 900c2deb9..372d9c790 100644 --- a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java @@ -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; @@ -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 { @@ -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; @@ -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"}) @@ -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 "); diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 5ce0711bc..89d7227e2 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -8034,89 +8034,107 @@ AlterExpression AlterExpression(): ) | ( - sk3=RelObjectName() + ( - ( tk= tk2= - columnNames=ColumnsNamesList() - { - fkIndex = new ForeignKeyIndex() - .withName(sk3) - .withType(tk.image + " " + tk2.image) - .withColumnsNames(columnNames); - columnNames = null; - } - fkTable=Table() [ LOOKAHEAD(2) columnNames=ColumnsNamesList() ] - { - fkIndex.withTable(fkTable).withReferencedColumnNames(columnNames); - alterExp.setIndex(fkIndex); - } - - [LOOKAHEAD(2) ( (tk= | tk=) action = Action() - { fkIndex.setReferentialAction(ReferentialAction.Type.from(tk.image), action); } - )] - [LOOKAHEAD(2) ( (tk= | tk=) action = Action() - { fkIndex.setReferentialAction(ReferentialAction.Type.from(tk.image), action); } - )] - constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); } - ) - | - ( tk= tk2= - columnNames=ColumnsNamesList() - { - index = new NamedConstraint() - .withName(sk3) - .withType(tk.image + " " + tk2.image) - .withColumnsNames(columnNames); - alterExp.setIndex(index); - } - constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); } - [ sk4=RelObjectName() { alterExp.addParameters("USING", sk4); }] - [ LOOKAHEAD(2) index = IndexWithComment(index) { alterExp.setIndex(index); } ] - ) - | - LOOKAHEAD(2) ( - { boolean enforced = true; } - [ tk = { enforced = false; } ] - { - alterExp.setEnforced(enforced); - alterExp.setConstraintType("CONSTRAINT"); - alterExp.setConstraintSymbol(sk3); - } - ) - | + LOOKAHEAD(2) ( - {Expression exp = null;} (LOOKAHEAD(2) "(" exp = Expression() ")")* { - CheckConstraint checkCs = new CheckConstraint().withName(sk3).withExpression(exp); - alterExp.setIndex(checkCs); + ( { alterExp.setConstraintType("UNIQUE KEY"); } + | { 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= (tk2= { alterExp.setUk(true); } | tk2=)? - columnNames=ColumnsNamesList() - { - index = new NamedConstraint() + ( tk= tk2= + 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); } - [ sk4=RelObjectName() { alterExp.addParameters("USING", sk4); }] - [ LOOKAHEAD(2) index = IndexWithComment(index) { alterExp.setIndex(index); } ] - ) - | - ( - tk= - columnNames=ColumnsNamesList() - { - index = new NamedConstraint() - .withName(sk3) - .withType(tk.image) - .withColumnsNames(columnNames); - alterExp.setIndex(index); - } - constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); } + columnNames = null; + } + fkTable=Table() [ LOOKAHEAD(2) columnNames=ColumnsNamesList() ] + { + fkIndex.withTable(fkTable).withReferencedColumnNames(columnNames); + alterExp.setIndex(fkIndex); + } + + [LOOKAHEAD(2) ( (tk= | tk=) action = Action() + { fkIndex.setReferentialAction(ReferentialAction.Type.from(tk.image), action); } + )] + [LOOKAHEAD(2) ( (tk= | tk=) action = Action() + { fkIndex.setReferentialAction(ReferentialAction.Type.from(tk.image), action); } + )] + constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); } + ) + | + ( tk= tk2= + columnNames=ColumnsNamesList() + { + index = new NamedConstraint() + .withName(sk3) + .withType(tk.image + " " + tk2.image) + .withColumnsNames(columnNames); + alterExp.setIndex(index); + } + constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); } + [ sk4=RelObjectName() { alterExp.addParameters("USING", sk4); }] + [ LOOKAHEAD(2) index = IndexWithComment(index) { alterExp.setIndex(index); } ] + ) + | + LOOKAHEAD(2) ( + { boolean enforced = true; } + [ tk = { enforced = false; } ] + { + alterExp.setEnforced(enforced); + alterExp.setConstraintType("CONSTRAINT"); + alterExp.setConstraintSymbol(sk3); + } + ) + | + ( + {Expression exp = null;} (LOOKAHEAD(2) "(" exp = Expression() ")")* { + CheckConstraint checkCs = new CheckConstraint().withName(sk3).withExpression(exp); + alterExp.setIndex(checkCs); + } + ) + | + ( + tk= (tk2= { alterExp.setUk(true); } | tk2=)? + columnNames=ColumnsNamesList() + { + index = new NamedConstraint() + .withName(sk3) + .withType(tk.image + (tk2!=null?" " + tk2.image:"")) + .withColumnsNames(columnNames); + alterExp.setIndex(index); + } + constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); } + [ sk4=RelObjectName() { alterExp.addParameters("USING", sk4); }] + [ LOOKAHEAD(2) index = IndexWithComment(index) { alterExp.setIndex(index); } ] + ) + | + ( + tk= + columnNames=ColumnsNamesList() + { + index = new NamedConstraint() + .withName(sk3) + .withType(tk.image) + .withColumnsNames(columnNames); + alterExp.setIndex(index); + } + constraints=AlterExpressionConstraintState() { alterExp.setConstraints(constraints); } + ) ) ) ) diff --git a/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java b/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java index a1a9b46d4..bb3e65925 100644 --- a/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java @@ -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 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 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); + } } From 6e70f25532888ee2c9d505b3c0c8809eae900160 Mon Sep 17 00:00:00 2001 From: minleejae Date: Wed, 30 Apr 2025 15:39:02 +0900 Subject: [PATCH 2/3] test: add unit test for ALTER TABLE with INVISIBLE index option --- .../jsqlparser/statement/alter/AlterTest.java | 60 ++++++++++++++++--- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java b/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java index bb3e65925..bf90694b2 100644 --- a/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java @@ -9,13 +9,22 @@ */ package net.sf.jsqlparser.statement.alter; -import static net.sf.jsqlparser.test.TestUtils.*; -import static org.junit.jupiter.api.Assertions.*; - import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.StringValue; import net.sf.jsqlparser.expression.operators.relational.NotEqualsTo; @@ -28,12 +37,16 @@ import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.alter.AlterExpression.ColumnDataType; import net.sf.jsqlparser.statement.create.index.CreateIndex; -import net.sf.jsqlparser.statement.create.table.*; +import net.sf.jsqlparser.statement.create.table.CheckConstraint; +import net.sf.jsqlparser.statement.create.table.ForeignKeyIndex; +import net.sf.jsqlparser.statement.create.table.Index; import net.sf.jsqlparser.statement.create.table.Index.ColumnParams; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; +import net.sf.jsqlparser.statement.create.table.NamedConstraint; +import net.sf.jsqlparser.statement.create.table.PartitionDefinition; +import static net.sf.jsqlparser.test.TestUtils.assertDeparse; +import static net.sf.jsqlparser.test.TestUtils.assertEqualsObjectTree; +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static net.sf.jsqlparser.test.TestUtils.assertStatementCanBeDeparsedAs; public class AlterTest { @@ -2180,4 +2193,35 @@ public void testAlterTableAlterIndexInvisible() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed(sql); } + + @Test + public void testAlterTableAddIndexInvisible() throws JSQLParserException { + String sql = "ALTER TABLE t1 ADD INDEX k_idx (k) INVISIBLE"; + Statement stmt = CCJSqlParserUtil.parse(sql); + assertInstanceOf(Alter.class, stmt); + + Alter alter = (Alter) stmt; + assertEquals("t1", alter.getTable().getFullyQualifiedName()); + + List alterExpressions = alter.getAlterExpressions(); + assertNotNull(alterExpressions); + assertEquals(1, alterExpressions.size()); + + AlterExpression alterExp = alterExpressions.get(0); + assertEquals(AlterOperation.ADD, alterExp.getOperation()); + assertNotNull(alterExp.getIndex()); + assertEquals("k_idx", alterExp.getIndex().getName()); + assertEquals("INDEX", alterExp.getIndex().getIndexKeyword()); + + List columnNames = alterExp.getIndex().getColumnsNames(); + assertNotNull(columnNames); + assertEquals(1, columnNames.size()); + assertEquals("k", columnNames.get(0)); + + List indexSpec = alterExp.getIndex().getIndexSpec(); + assertNotNull(indexSpec); + assertTrue(indexSpec.contains("INVISIBLE")); + + assertSqlCanBeParsedAndDeparsed(sql); + } } From 6b2b5e7fb22964386aacd38536c5d42a0c494b3f Mon Sep 17 00:00:00 2001 From: minleejae Date: Wed, 30 Apr 2025 15:46:10 +0900 Subject: [PATCH 3/3] fix formatting --- .../java/net/sf/jsqlparser/statement/alter/AlterTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java b/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java index bf90694b2..559517b9d 100644 --- a/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java @@ -2212,12 +2212,12 @@ public void testAlterTableAddIndexInvisible() throws JSQLParserException { assertNotNull(alterExp.getIndex()); assertEquals("k_idx", alterExp.getIndex().getName()); assertEquals("INDEX", alterExp.getIndex().getIndexKeyword()); - + List columnNames = alterExp.getIndex().getColumnsNames(); assertNotNull(columnNames); assertEquals(1, columnNames.size()); assertEquals("k", columnNames.get(0)); - + List indexSpec = alterExp.getIndex().getIndexSpec(); assertNotNull(indexSpec); assertTrue(indexSpec.contains("INVISIBLE"));