Skip to content

Commit 731a817

Browse files
authored
fix: accept named FOREIGN KEY and CHECK drops without a dialect (#2789)
ALTER TABLE ... DROP FOREIGN KEY fk and DROP CHECK ck were only accepted with Dialect.MYSQL, so the default parser configuration rejected these common MySQL actions (#990, #1129). Accept them without a dialect as in MySQL, accept the named FOREIGN KEY form for MariaDB, which has no DROP CHECK, and keep rejecting them in other dialects. A parenthesized FOREIGN KEY column list keeps the legacy form. Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent 0986304 commit 731a817

3 files changed

Lines changed: 68 additions & 6 deletions

File tree

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

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2066,6 +2066,21 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
20662066
return Dialect.MYSQL.name().equals(dialect) || Dialect.MARIADB.name().equals(dialect);
20672067
}
20682068

2069+
/**
2070+
* Whether ALTER TABLE DROP names a MySQL FOREIGN KEY or CHECK constraint. The default
2071+
* configuration accepts both like MySQL; MariaDB has no DROP CHECK. A parenthesized FOREIGN KEY
2072+
* column list keeps the legacy form, and other dialects use DROP CONSTRAINT.
2073+
*/
2074+
private boolean isNamedConstraintDropAhead() {
2075+
String dialect = getAsString(Feature.dialect);
2076+
boolean namedForeignKey = getToken(1).kind == K_FOREIGN && getToken(2).kind == K_KEY
2077+
&& getToken(3).kind != OPENING_BRACKET;
2078+
if (dialect == null || Dialect.MYSQL.name().equals(dialect)) {
2079+
return namedForeignKey || getToken(1).kind == K_CHECK;
2080+
}
2081+
return Dialect.MARIADB.name().equals(dialect) && namedForeignKey;
2082+
}
2083+
20692084
private boolean isBeginTransactionAhead() {
20702085
String dialect = getAsString(Feature.dialect);
20712086
if (getToken(1).kind != K_BEGIN || !(isMySqlDialect()
@@ -18601,10 +18616,7 @@ AlterExpression AlterExpressionDrop():
1860118616
{
1860218617
<K_DROP> { alterExp.setOperation(AlterOperation.DROP); }
1860318618
(
18604-
LOOKAHEAD({ Dialect.MYSQL.name().equals(getAsString(Feature.dialect))
18605-
&& (getToken(1).kind == K_CHECK
18606-
|| getToken(1).kind == K_FOREIGN && getToken(2).kind == K_KEY
18607-
&& getToken(3).kind != OPENING_BRACKET) })
18619+
LOOKAHEAD({ isNamedConstraintDropAhead() })
1860818620
MySqlDropNamedConstraint(alterExp)
1860918621
|
1861018622
(

‎src/site/sphinx/usage.rst‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,11 @@ MySQL ALGORITHM/LOCK tokens retain their order and optional equals signs.
268268
MySQL named constraint drops
269269
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
270270

271-
With ``Dialect.MYSQL``, ``DROP FOREIGN KEY name`` and ``DROP CHECK name``
272-
expose the target through ``AlterExpression.getConstraintName()``. Inspect
271+
Without a dialect and with ``Dialect.MYSQL``, ``DROP FOREIGN KEY name`` and
272+
``DROP CHECK name`` expose the target through ``AlterExpression.getConstraintName()``.
273+
``Dialect.MARIADB`` accepts the named FOREIGN KEY form; MariaDB has no ``DROP CHECK``.
274+
Other dialects drop constraints with ``DROP CONSTRAINT``. A parenthesized
275+
``DROP FOREIGN KEY (...)`` keeps the legacy column-list form. Inspect
273276
``getOperation()`` for ``DROP_FOREIGN_KEY`` or ``DROP_CHECK``. Identifier
274277
quotes are preserved, and changing the name changes both SQL renderers.
275278
These named targets do not populate the legacy ``getPkColumns()`` list.

‎src/test/java/net/sf/jsqlparser/statement/alter/MySqlDropConstraintTest.java‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,53 @@ void preservesDefaultDropForms(String action) throws JSQLParserException {
7878
assertEquals(sql, alter.toString().trim());
7979
}
8080

81+
@ParameterizedTest
82+
@ValueSource(strings = {"DROP FOREIGN KEY fk1", "DROP FOREIGN KEY `fk name`",
83+
"DROP CHECK c1", "DROP FOREIGN KEY fk1, DROP CHECK c1"})
84+
void acceptsNamedDropsWithoutDialect(String actions) throws JSQLParserException {
85+
String sql = "ALTER TABLE t " + actions;
86+
Alter alter = (Alter) CCJSqlParserUtil.parse(sql);
87+
88+
assertEquals(sql, alter.toString());
89+
for (AlterExpression drop : alter.getAlterExpressions()) {
90+
assertNotNull(drop.getConstraintName());
91+
assertNull(drop.getDropColumns());
92+
}
93+
assertEquals(sql, CCJSqlParserUtil.parse(alter.toString()).toString());
94+
}
95+
96+
@Test
97+
void mariaDbAcceptsNamedForeignKeyButNotCheckDrops() throws JSQLParserException {
98+
Alter alter = (Alter) CCJSqlParserUtil.parse("ALTER TABLE t DROP FOREIGN KEY fk1",
99+
p -> p.withDialect(Dialect.MARIADB));
100+
assertEquals(AlterOperation.DROP_FOREIGN_KEY,
101+
alter.getAlterExpressions().get(0).getOperation());
102+
assertEquals("fk1", alter.getAlterExpressions().get(0).getConstraintName());
103+
104+
assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil
105+
.parse("ALTER TABLE t DROP CHECK c1", p -> p.withDialect(Dialect.MARIADB)));
106+
}
107+
108+
@ParameterizedTest
109+
@ValueSource(strings = {"DROP FOREIGN KEY fk1", "DROP CHECK c1"})
110+
void otherDialectsKeepRejectingMySqlNamedDrops(String action) {
111+
for (Dialect dialect : List.of(Dialect.POSTGRESQL, Dialect.ORACLE, Dialect.SQLSERVER)) {
112+
assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil
113+
.parse("ALTER TABLE t " + action, p -> p.withDialect(dialect)),
114+
dialect.name());
115+
}
116+
}
117+
118+
@Test
119+
void parenthesizedForeignKeyDropKeepsColumnsWithoutDialect() throws JSQLParserException {
120+
Alter alter = (Alter) CCJSqlParserUtil.parse("ALTER TABLE t DROP FOREIGN KEY (a, b)");
121+
AlterExpression drop = alter.getAlterExpressions().get(0);
122+
123+
assertEquals(AlterOperation.DROP_FOREIGN_KEY, drop.getOperation());
124+
assertEquals(List.of("a", "b"), drop.getDropColumns());
125+
assertNull(drop.getConstraintName());
126+
}
127+
81128
private static Alter parse(String sql) throws JSQLParserException {
82129
return (Alter) CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.MYSQL));
83130
}

0 commit comments

Comments
 (0)