Skip to content

Commit 796cad3

Browse files
committed
feat: support MySQL named foreign key and check drops
Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent 60c8dc2 commit 796cad3

6 files changed

Lines changed: 149 additions & 14 deletions

File tree

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,11 @@ public void setColumnOldName(String columnOldName) {
504504
this.columnOldName = columnOldName;
505505
}
506506

507+
/**
508+
* Returns the constraint name, including any SQL identifier quotes. For named
509+
* {@link AlterOperation#DROP_FOREIGN_KEY} and {@link AlterOperation#DROP_CHECK} actions, this
510+
* is the deletion target; {@link #getPkColumns()} is not used.
511+
*/
507512
public String getConstraintName() {
508513
return this.constraintName;
509514
}
@@ -884,6 +889,7 @@ protected boolean isDropSpecialOperation() {
884889
case DROP_PRIMARY_KEY:
885890
case DROP_UNIQUE:
886891
case DROP_FOREIGN_KEY:
892+
case DROP_CHECK:
887893
return true;
888894
case DROP:
889895
return columnName == null && pkColumns != null && !pkColumns.isEmpty();
@@ -1041,8 +1047,15 @@ protected void toStringDropSpecial(StringBuilder b) {
10411047
b.append("DROP UNIQUE (").append(PlainSelect.getStringList(pkColumns)).append(')');
10421048
break;
10431049
case DROP_FOREIGN_KEY:
1044-
b.append("DROP FOREIGN KEY (").append(PlainSelect.getStringList(pkColumns))
1045-
.append(')');
1050+
b.append("DROP FOREIGN KEY ");
1051+
if (constraintName != null) {
1052+
b.append(constraintName);
1053+
} else {
1054+
b.append('(').append(PlainSelect.getStringList(pkColumns)).append(')');
1055+
}
1056+
break;
1057+
case DROP_CHECK:
1058+
b.append("DROP CHECK ").append(constraintName);
10461059
break;
10471060
default:
10481061
// Oracle Multi Column Drop

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

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,11 @@ public class AlterExpressionDrop extends AlterExpression {
1919

2020
@Override
2121
protected void appendBody(StringBuilder b) {
22+
if (isDropSpecialOperation()) {
23+
toStringDropSpecial(b);
24+
return;
25+
}
2226
switch (getOperation()) {
23-
case DROP_PRIMARY_KEY:
24-
b.append("DROP PRIMARY KEY ");
25-
break;
26-
case DROP_UNIQUE:
27-
b.append("DROP UNIQUE (")
28-
.append(PlainSelect.getStringList(getPkColumns())).append(')');
29-
break;
30-
case DROP_FOREIGN_KEY:
31-
b.append("DROP FOREIGN KEY (")
32-
.append(PlainSelect.getStringList(getPkColumns())).append(')');
33-
break;
3427
case DROP_PARTITION:
3528
b.append("DROP PARTITION ")
3629
.append(PlainSelect.getStringList(getPartitions()));

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import java.util.Locale;
1313

1414
public enum AlterOperation {
15-
ADD, ALTER, DROP, DROP_PRIMARY_KEY, DROP_UNIQUE, DROP_FOREIGN_KEY, MODIFY, CHANGE, CONVERT, COLLATE, ALGORITHM, RENAME, RENAME_TABLE, RENAME_INDEX, RENAME_KEY, RENAME_CONSTRAINT, COMMENT, COMMENT_WITH_EQUAL_SIGN, UNSPECIFIC, ADD_PARTITION, DROP_PARTITION, ATTACH_PARTITION, DETACH_PARTITION, DISCARD_PARTITION, IMPORT_PARTITION, TRUNCATE_PARTITION, COALESCE_PARTITION, REORGANIZE_PARTITION, EXCHANGE_PARTITION, ANALYZE_PARTITION, CHECK_PARTITION, OPTIMIZE_PARTITION, REBUILD_PARTITION, REPAIR_PARTITION, REMOVE_PARTITIONING, PARTITION_BY, SET_TABLE_OPTION, ENGINE, FORCE, KEY_BLOCK_SIZE, LOCK, DISCARD_TABLESPACE, IMPORT_TABLESPACE, DISABLE_KEYS, ENABLE_KEYS, ENABLE_ROW_LEVEL_SECURITY, DISABLE_ROW_LEVEL_SECURITY, FORCE_ROW_LEVEL_SECURITY, NO_FORCE_ROW_LEVEL_SECURITY, ALTER_PRIMARY_KEY;
15+
ADD, ALTER, DROP, DROP_PRIMARY_KEY, DROP_UNIQUE, DROP_FOREIGN_KEY, DROP_CHECK, MODIFY, CHANGE, CONVERT, COLLATE, ALGORITHM, RENAME, RENAME_TABLE, RENAME_INDEX, RENAME_KEY, RENAME_CONSTRAINT, COMMENT, COMMENT_WITH_EQUAL_SIGN, UNSPECIFIC, ADD_PARTITION, DROP_PARTITION, ATTACH_PARTITION, DETACH_PARTITION, DISCARD_PARTITION, IMPORT_PARTITION, TRUNCATE_PARTITION, COALESCE_PARTITION, REORGANIZE_PARTITION, EXCHANGE_PARTITION, ANALYZE_PARTITION, CHECK_PARTITION, OPTIMIZE_PARTITION, REBUILD_PARTITION, REPAIR_PARTITION, REMOVE_PARTITIONING, PARTITION_BY, SET_TABLE_OPTION, ENGINE, FORCE, KEY_BLOCK_SIZE, LOCK, DISCARD_TABLESPACE, IMPORT_TABLESPACE, DISABLE_KEYS, ENABLE_KEYS, ENABLE_ROW_LEVEL_SECURITY, DISABLE_ROW_LEVEL_SECURITY, FORCE_ROW_LEVEL_SECURITY, NO_FORCE_ROW_LEVEL_SECURITY, ALTER_PRIMARY_KEY;
1616

1717
public static AlterOperation from(String operation) {
1818
return Enum.valueOf(AlterOperation.class, operation.toUpperCase(Locale.ROOT));

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17053,6 +17053,12 @@ AlterExpression AlterExpressionDrop():
1705317053
{
1705417054
<K_DROP> { alterExp.setOperation(AlterOperation.DROP); }
1705517055
(
17056+
LOOKAHEAD({ Dialect.MYSQL.name().equals(getAsString(Feature.dialect))
17057+
&& (getToken(1).kind == K_CHECK
17058+
|| getToken(1).kind == K_FOREIGN && getToken(2).kind == K_KEY
17059+
&& getToken(3).kind != OPENING_BRACKET) })
17060+
MySqlDropNamedConstraint(alterExp)
17061+
|
1705617062
(
1705717063
<K_PARTITION> {
1705817064
alterExp = new AlterExpressionPartition();
@@ -17118,6 +17124,15 @@ AlterExpression AlterExpressionDrop():
1711817124
{ return alterExp; }
1711917125
}
1712017126

17127+
/** A MySQL deletion target is a constraint name, not a list of key columns. */
17128+
void MySqlDropNamedConstraint(AlterExpression alterExp):
17129+
{ String name; }
17130+
{
17131+
( <K_FOREIGN> <K_KEY> { alterExp.setOperation(AlterOperation.DROP_FOREIGN_KEY); }
17132+
| <K_CHECK> { alterExp.setOperation(AlterOperation.DROP_CHECK); } )
17133+
name=RelObjectName() { alterExp.setConstraintName(name); }
17134+
}
17135+
1712117136
/**
1712217137
* Parses partition maintenance operations within AlterExpression.
1712317138
* Handles: TRUNCATE/ANALYZE/CHECK/OPTIMIZE/REBUILD/REPAIR PARTITION,

src/site/sphinx/usage.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,29 @@ explicit index owners, without reporting catalog-only object names as tables.
252252
The statement deparser delegates real tables to its configured select deparser.
253253
MySQL ALGORITHM/LOCK tokens retain their order and optional equals signs.
254254

255+
MySQL named constraint drops
256+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
257+
258+
With ``Dialect.MYSQL``, ``DROP FOREIGN KEY name`` and ``DROP CHECK name``
259+
expose the target through ``AlterExpression.getConstraintName()``. Inspect
260+
``getOperation()`` for ``DROP_FOREIGN_KEY`` or ``DROP_CHECK``. Identifier
261+
quotes are preserved, and changing the name changes both SQL renderers.
262+
These named targets do not populate the legacy ``getPkColumns()`` list.
263+
264+
.. code-block:: java
265+
266+
Alter alter = (Alter) CCJSqlParserUtil.parse(
267+
"ALTER TABLE child DROP FOREIGN KEY fk_parent",
268+
parser -> parser.withDialect(Dialect.MYSQL));
269+
AlterExpression drop = alter.getAlterExpressions().get(0);
270+
drop.setConstraintName("fk_customer");
271+
// ALTER TABLE child DROP FOREIGN KEY fk_customer
272+
String sql = alter.toString();
273+
274+
// The same API also supports constructing an action without parsing SQL.
275+
AlterExpression checkDrop = new AlterExpression()
276+
.withOperation(AlterOperation.DROP_CHECK).withConstraintName("positive_id");
277+
255278
Inspect PostgreSQL schema statements
256279
------------------------------------
257280

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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.alter;
11+
12+
import static org.junit.jupiter.api.Assertions.*;
13+
import java.util.List;
14+
import net.sf.jsqlparser.JSQLParserException;
15+
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
16+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
17+
import net.sf.jsqlparser.schema.Table;
18+
import net.sf.jsqlparser.util.deparser.StatementDeParser;
19+
import org.junit.jupiter.api.Test;
20+
import org.junit.jupiter.params.ParameterizedTest;
21+
import org.junit.jupiter.params.provider.ValueSource;
22+
23+
class MySqlDropConstraintTest {
24+
@ParameterizedTest
25+
@ValueSource(strings = {"DROP FOREIGN KEY fk1", "DROP FOREIGN KEY `fk name`",
26+
"DROP CHECK c1", "DROP CHECK `check name`",
27+
"DROP FOREIGN KEY fk1, DROP CHECK c1",
28+
"DROP FOREIGN KEY fk1, ALGORITHM = INPLACE, LOCK = NONE"})
29+
void roundTripsNamedDrops(String actions) throws JSQLParserException {
30+
Alter alter = parse("ALTER TABLE t " + actions);
31+
assertEquals("ALTER TABLE t " + actions, alter.toString());
32+
assertRoundTrip(alter);
33+
}
34+
35+
@Test
36+
void namesAndKindsAreEditableWithoutColumnLists() throws JSQLParserException {
37+
Alter alter = parse("ALTER TABLE t DROP FOREIGN KEY fk1, DROP CHECK c1");
38+
AlterExpression foreign = alter.getAlterExpressions().get(0);
39+
AlterExpression check = alter.getAlterExpressions().get(1);
40+
assertEquals(AlterOperation.DROP_FOREIGN_KEY, foreign.getOperation());
41+
assertEquals("fk1", foreign.getConstraintName());
42+
assertNull(foreign.getPkColumns());
43+
assertEquals(AlterOperation.DROP_CHECK, check.getOperation());
44+
assertEquals("c1", check.getConstraintName());
45+
check.setConstraintName("`new check`");
46+
foreign.setConstraintName("fk2");
47+
assertEquals("ALTER TABLE t DROP FOREIGN KEY fk2, DROP CHECK `new check`",
48+
alter.toString());
49+
assertRoundTrip(alter);
50+
}
51+
52+
@Test
53+
void constructedActionsUseTheSameRendererAsParsedActions() throws JSQLParserException {
54+
for (AlterOperation operation : List.of(AlterOperation.DROP_FOREIGN_KEY,
55+
AlterOperation.DROP_CHECK)) {
56+
Alter alter = new Alter();
57+
alter.setTable(new Table("t"));
58+
alter.addAlterExpression(new AlterExpression().withOperation(operation)
59+
.withConstraintName("`target constraint`"));
60+
Alter parsed = parse(alter.toString());
61+
assertEquals(operation, parsed.getAlterExpressions().get(0).getOperation());
62+
assertEquals("`target constraint`",
63+
parsed.getAlterExpressions().get(0).getConstraintName());
64+
assertRoundTrip(alter);
65+
// Editing the operation on either concrete class must have the same effect.
66+
parsed.getAlterExpressions().get(0).setOperation(AlterOperation.DROP_CHECK);
67+
assertEquals("ALTER TABLE t DROP CHECK `target constraint`", parsed.toString());
68+
}
69+
}
70+
71+
@ParameterizedTest
72+
@ValueSource(strings = {"DROP FOREIGN KEY (id)", "DROP UNIQUE (a, b)",
73+
"DROP PRIMARY KEY", "DROP CONSTRAINT IF EXISTS ck CASCADE", "DROP (a, b)",
74+
"DROP COLUMN c", "DROP INDEX idx"})
75+
void preservesDefaultDropForms(String action) throws JSQLParserException {
76+
String sql = "ALTER TABLE t " + action;
77+
Alter alter = (Alter) CCJSqlParserUtil.parse(sql);
78+
assertEquals(sql, alter.toString().trim());
79+
}
80+
81+
private static Alter parse(String sql) throws JSQLParserException {
82+
return (Alter) CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.MYSQL));
83+
}
84+
85+
private static void assertRoundTrip(Alter alter) throws JSQLParserException {
86+
StringBuilder sql = new StringBuilder();
87+
alter.accept(new StatementDeParser(sql), null);
88+
assertEquals(alter.toString(), sql.toString());
89+
assertEquals(alter.toString(), parse(sql.toString()).toString());
90+
}
91+
}

0 commit comments

Comments
 (0)