diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index eb904885a..cb0ad9c7c 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -1843,6 +1843,21 @@ public class CCJSqlParser extends AbstractJSqlParser { return Dialect.MYSQL.name().equals(dialect) || Dialect.MARIADB.name().equals(dialect); } + /** + * Whether ALTER TABLE DROP names a MySQL FOREIGN KEY or CHECK constraint. The default + * configuration accepts both like MySQL; MariaDB has no DROP CHECK. A parenthesized FOREIGN KEY + * column list keeps the legacy form, and other dialects use DROP CONSTRAINT. + */ + private boolean isNamedConstraintDropAhead() { + String dialect = getAsString(Feature.dialect); + boolean namedForeignKey = getToken(1).kind == K_FOREIGN && getToken(2).kind == K_KEY + && getToken(3).kind != OPENING_BRACKET; + if (dialect == null || Dialect.MYSQL.name().equals(dialect)) { + return namedForeignKey || getToken(1).kind == K_CHECK; + } + return Dialect.MARIADB.name().equals(dialect) && namedForeignKey; + } + private boolean isBeginTransactionAhead() { String dialect = getAsString(Feature.dialect); if (getToken(1).kind != K_BEGIN || !(isMySqlDialect() @@ -18154,10 +18169,7 @@ AlterExpression AlterExpressionDrop(): { { 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) }) + LOOKAHEAD({ isNamedConstraintDropAhead() }) MySqlDropNamedConstraint(alterExp) | ( diff --git a/src/site/sphinx/usage.rst b/src/site/sphinx/usage.rst index 1d204c6e9..ad078b63f 100644 --- a/src/site/sphinx/usage.rst +++ b/src/site/sphinx/usage.rst @@ -268,8 +268,11 @@ 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 +Without a dialect and with ``Dialect.MYSQL``, ``DROP FOREIGN KEY name`` and +``DROP CHECK name`` expose the target through ``AlterExpression.getConstraintName()``. +``Dialect.MARIADB`` accepts the named FOREIGN KEY form; MariaDB has no ``DROP CHECK``. +Other dialects drop constraints with ``DROP CONSTRAINT``. A parenthesized +``DROP FOREIGN KEY (...)`` keeps the legacy column-list form. 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. diff --git a/src/test/java/net/sf/jsqlparser/statement/alter/MySqlDropConstraintTest.java b/src/test/java/net/sf/jsqlparser/statement/alter/MySqlDropConstraintTest.java index 8e9d5f7a7..fa35bdf22 100644 --- a/src/test/java/net/sf/jsqlparser/statement/alter/MySqlDropConstraintTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/alter/MySqlDropConstraintTest.java @@ -78,6 +78,53 @@ void preservesDefaultDropForms(String action) throws JSQLParserException { assertEquals(sql, alter.toString().trim()); } + @ParameterizedTest + @ValueSource(strings = {"DROP FOREIGN KEY fk1", "DROP FOREIGN KEY `fk name`", + "DROP CHECK c1", "DROP FOREIGN KEY fk1, DROP CHECK c1"}) + void acceptsNamedDropsWithoutDialect(String actions) throws JSQLParserException { + String sql = "ALTER TABLE t " + actions; + Alter alter = (Alter) CCJSqlParserUtil.parse(sql); + + assertEquals(sql, alter.toString()); + for (AlterExpression drop : alter.getAlterExpressions()) { + assertNotNull(drop.getConstraintName()); + assertNull(drop.getDropColumns()); + } + assertEquals(sql, CCJSqlParserUtil.parse(alter.toString()).toString()); + } + + @Test + void mariaDbAcceptsNamedForeignKeyButNotCheckDrops() throws JSQLParserException { + Alter alter = (Alter) CCJSqlParserUtil.parse("ALTER TABLE t DROP FOREIGN KEY fk1", + p -> p.withDialect(Dialect.MARIADB)); + assertEquals(AlterOperation.DROP_FOREIGN_KEY, + alter.getAlterExpressions().get(0).getOperation()); + assertEquals("fk1", alter.getAlterExpressions().get(0).getConstraintName()); + + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil + .parse("ALTER TABLE t DROP CHECK c1", p -> p.withDialect(Dialect.MARIADB))); + } + + @ParameterizedTest + @ValueSource(strings = {"DROP FOREIGN KEY fk1", "DROP CHECK c1"}) + void otherDialectsKeepRejectingMySqlNamedDrops(String action) { + for (Dialect dialect : List.of(Dialect.POSTGRESQL, Dialect.ORACLE, Dialect.SQLSERVER)) { + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil + .parse("ALTER TABLE t " + action, p -> p.withDialect(dialect)), + dialect.name()); + } + } + + @Test + void parenthesizedForeignKeyDropKeepsColumnsWithoutDialect() throws JSQLParserException { + Alter alter = (Alter) CCJSqlParserUtil.parse("ALTER TABLE t DROP FOREIGN KEY (a, b)"); + AlterExpression drop = alter.getAlterExpressions().get(0); + + assertEquals(AlterOperation.DROP_FOREIGN_KEY, drop.getOperation()); + assertEquals(List.of("a", "b"), drop.getDropColumns()); + assertNull(drop.getConstraintName()); + } + private static Alter parse(String sql) throws JSQLParserException { return (Alter) CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.MYSQL)); }