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 @@ -504,6 +504,11 @@ public void setColumnOldName(String columnOldName) {
this.columnOldName = columnOldName;
}

/**
* Returns the constraint name, including any SQL identifier quotes. For named
* {@link AlterOperation#DROP_FOREIGN_KEY} and {@link AlterOperation#DROP_CHECK} actions, this
* is the deletion target; {@link #getPkColumns()} is not used.
*/
public String getConstraintName() {
return this.constraintName;
}
Expand Down Expand Up @@ -884,6 +889,7 @@ protected boolean isDropSpecialOperation() {
case DROP_PRIMARY_KEY:
case DROP_UNIQUE:
case DROP_FOREIGN_KEY:
case DROP_CHECK:
return true;
case DROP:
return columnName == null && pkColumns != null && !pkColumns.isEmpty();
Expand Down Expand Up @@ -1041,8 +1047,15 @@ protected void toStringDropSpecial(StringBuilder b) {
b.append("DROP UNIQUE (").append(PlainSelect.getStringList(pkColumns)).append(')');
break;
case DROP_FOREIGN_KEY:
b.append("DROP FOREIGN KEY (").append(PlainSelect.getStringList(pkColumns))
.append(')');
b.append("DROP FOREIGN KEY ");
if (constraintName != null) {
b.append(constraintName);
} else {
b.append('(').append(PlainSelect.getStringList(pkColumns)).append(')');
}
break;
case DROP_CHECK:
b.append("DROP CHECK ").append(constraintName);
break;
default:
// Oracle Multi Column Drop
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,11 @@ public class AlterExpressionDrop extends AlterExpression {

@Override
protected void appendBody(StringBuilder b) {
if (isDropSpecialOperation()) {
toStringDropSpecial(b);
return;
}
switch (getOperation()) {
case DROP_PRIMARY_KEY:
b.append("DROP PRIMARY KEY ");
break;
case DROP_UNIQUE:
b.append("DROP UNIQUE (")
.append(PlainSelect.getStringList(getPkColumns())).append(')');
break;
case DROP_FOREIGN_KEY:
b.append("DROP FOREIGN KEY (")
.append(PlainSelect.getStringList(getPkColumns())).append(')');
break;
case DROP_PARTITION:
b.append("DROP PARTITION ")
.append(PlainSelect.getStringList(getPartitions()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import java.util.Locale;

public enum AlterOperation {
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;
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;

public static AlterOperation from(String operation) {
return Enum.valueOf(AlterOperation.class, operation.toUpperCase(Locale.ROOT));
Expand Down
15 changes: 15 additions & 0 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -17053,6 +17053,12 @@ AlterExpression AlterExpressionDrop():
{
<K_DROP> { alterExp.setOperation(AlterOperation.DROP); }
(
LOOKAHEAD({ Dialect.MYSQL.name().equals(getAsString(Feature.dialect))
&& (getToken(1).kind == K_CHECK
|| getToken(1).kind == K_FOREIGN && getToken(2).kind == K_KEY
&& getToken(3).kind != OPENING_BRACKET) })
MySqlDropNamedConstraint(alterExp)
|
(
<K_PARTITION> {
alterExp = new AlterExpressionPartition();
Expand Down Expand Up @@ -17118,6 +17124,15 @@ AlterExpression AlterExpressionDrop():
{ return alterExp; }
}

/** A MySQL deletion target is a constraint name, not a list of key columns. */
void MySqlDropNamedConstraint(AlterExpression alterExp):
{ String name; }
{
( <K_FOREIGN> <K_KEY> { alterExp.setOperation(AlterOperation.DROP_FOREIGN_KEY); }
| <K_CHECK> { alterExp.setOperation(AlterOperation.DROP_CHECK); } )
name=RelObjectName() { alterExp.setConstraintName(name); }
}

/**
* Parses partition maintenance operations within AlterExpression.
* Handles: TRUNCATE/ANALYZE/CHECK/OPTIMIZE/REBUILD/REPAIR PARTITION,
Expand Down
23 changes: 23 additions & 0 deletions src/site/sphinx/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,29 @@ explicit index owners, without reporting catalog-only object names as tables.
The statement deparser delegates real tables to its configured select deparser.
MySQL ALGORITHM/LOCK tokens retain their order and optional equals signs.

MySQL named constraint drops
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

With ``Dialect.MYSQL``, ``DROP FOREIGN KEY name`` and ``DROP CHECK name``
expose the target through ``AlterExpression.getConstraintName()``. Inspect
``getOperation()`` for ``DROP_FOREIGN_KEY`` or ``DROP_CHECK``. Identifier
quotes are preserved, and changing the name changes both SQL renderers.
These named targets do not populate the legacy ``getPkColumns()`` list.

.. code-block:: java

Alter alter = (Alter) CCJSqlParserUtil.parse(
"ALTER TABLE child DROP FOREIGN KEY fk_parent",
parser -> parser.withDialect(Dialect.MYSQL));
AlterExpression drop = alter.getAlterExpressions().get(0);
drop.setConstraintName("fk_customer");
// ALTER TABLE child DROP FOREIGN KEY fk_customer
String sql = alter.toString();

// The same API also supports constructing an action without parsing SQL.
AlterExpression checkDrop = new AlterExpression()
.withOperation(AlterOperation.DROP_CHECK).withConstraintName("positive_id");

Inspect PostgreSQL schema statements
------------------------------------

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*-
* #%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.alter;

import static org.junit.jupiter.api.Assertions.*;
import java.util.List;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.util.deparser.StatementDeParser;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class MySqlDropConstraintTest {
@ParameterizedTest
@ValueSource(strings = {"DROP FOREIGN KEY fk1", "DROP FOREIGN KEY `fk name`",
"DROP CHECK c1", "DROP CHECK `check name`",
"DROP FOREIGN KEY fk1, DROP CHECK c1",
"DROP FOREIGN KEY fk1, ALGORITHM = INPLACE, LOCK = NONE"})
void roundTripsNamedDrops(String actions) throws JSQLParserException {
Alter alter = parse("ALTER TABLE t " + actions);
assertEquals("ALTER TABLE t " + actions, alter.toString());
assertRoundTrip(alter);
}

@Test
void namesAndKindsAreEditableWithoutColumnLists() throws JSQLParserException {
Alter alter = parse("ALTER TABLE t DROP FOREIGN KEY fk1, DROP CHECK c1");
AlterExpression foreign = alter.getAlterExpressions().get(0);
AlterExpression check = alter.getAlterExpressions().get(1);
assertEquals(AlterOperation.DROP_FOREIGN_KEY, foreign.getOperation());
assertEquals("fk1", foreign.getConstraintName());
assertNull(foreign.getPkColumns());
assertEquals(AlterOperation.DROP_CHECK, check.getOperation());
assertEquals("c1", check.getConstraintName());
check.setConstraintName("`new check`");
foreign.setConstraintName("fk2");
assertEquals("ALTER TABLE t DROP FOREIGN KEY fk2, DROP CHECK `new check`",
alter.toString());
assertRoundTrip(alter);
}

@Test
void constructedActionsUseTheSameRendererAsParsedActions() throws JSQLParserException {
for (AlterOperation operation : List.of(AlterOperation.DROP_FOREIGN_KEY,
AlterOperation.DROP_CHECK)) {
Alter alter = new Alter();
alter.setTable(new Table("t"));
alter.addAlterExpression(new AlterExpression().withOperation(operation)
.withConstraintName("`target constraint`"));
Alter parsed = parse(alter.toString());
assertEquals(operation, parsed.getAlterExpressions().get(0).getOperation());
assertEquals("`target constraint`",
parsed.getAlterExpressions().get(0).getConstraintName());
assertRoundTrip(alter);
// Editing the operation on either concrete class must have the same effect.
parsed.getAlterExpressions().get(0).setOperation(AlterOperation.DROP_CHECK);
assertEquals("ALTER TABLE t DROP CHECK `target constraint`", parsed.toString());
}
}

@ParameterizedTest
@ValueSource(strings = {"DROP FOREIGN KEY (id)", "DROP UNIQUE (a, b)",
"DROP PRIMARY KEY", "DROP CONSTRAINT IF EXISTS ck CASCADE", "DROP (a, b)",
"DROP COLUMN c", "DROP INDEX idx"})
void preservesDefaultDropForms(String action) throws JSQLParserException {
String sql = "ALTER TABLE t " + action;
Alter alter = (Alter) CCJSqlParserUtil.parse(sql);
assertEquals(sql, alter.toString().trim());
}

private static Alter parse(String sql) throws JSQLParserException {
return (Alter) CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.MYSQL));
}

private static void assertRoundTrip(Alter alter) throws JSQLParserException {
StringBuilder sql = new StringBuilder();
alter.accept(new StatementDeParser(sql), null);
assertEquals(alter.toString(), sql.toString());
assertEquals(alter.toString(), parse(sql.toString()).toString());
}
}
Loading