From 0bc1e87a1a2c9bd638b482f87045394c39f62175 Mon Sep 17 00:00:00 2001 From: minleejae Date: Sun, 20 Sep 2026 20:54:02 +0900 Subject: [PATCH] fix: preserve qualified ALTER TABLE rename targets Signed-off-by: minleejae --- .../statement/alter/AlterExpression.java | 23 ++++- .../util/TableDefinitionTraversal.java | 4 + .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 7 +- .../alter/MySqlQualifiedRenameTest.java | 93 +++++++++++++++++++ 4 files changed, 120 insertions(+), 7 deletions(-) create mode 100644 src/test/java/net/sf/jsqlparser/statement/alter/MySqlQualifiedRenameTest.java 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 29c9b1ced..58c277a88 100644 --- a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java @@ -21,6 +21,7 @@ import java.util.function.Consumer; import java.util.stream.Collectors; import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.ReferentialAction; import net.sf.jsqlparser.statement.ReferentialAction.Action; import net.sf.jsqlparser.statement.ReferentialAction.Type; @@ -40,7 +41,7 @@ public enum TableRenameKeyword { private final Set referentialActions = new LinkedHashSet<>(2); private AlterOperation operation; private String optionalSpecifier; - private String newTableName; + private Table newTable; private TableRenameKeyword tableRenameKeyword = TableRenameKeyword.TO; private String columnName; // private ColDataType dataType; @@ -448,11 +449,25 @@ public void setFkSourceColumns(List fkSourceColumns) { } public String getNewTableName() { - return newTableName; + return newTable == null ? null : newTable.getFullyQualifiedName(); } public void setNewTableName(String newTableName) { - this.newTableName = newTableName; + newTable = newTableName == null ? null : new Table(newTableName, false); + } + + /** Returns the structured destination of a table rename. */ + public Table getNewTable() { + return newTable; + } + + public void setNewTable(Table newTable) { + this.newTable = newTable; + } + + public AlterExpression withNewTable(Table newTable) { + setNewTable(newTable); + return this; } public TableRenameKeyword getTableRenameKeyword() { @@ -1013,7 +1028,7 @@ protected void toStringRename(StringBuilder b) { } b.append(getOldIndex().getName()).append(" TO ").append(getIndex().getName()); } else { - b.append("RENAME TO ").append(newTableName); + b.append("RENAME TO ").append(getNewTableName()); } } diff --git a/src/main/java/net/sf/jsqlparser/util/TableDefinitionTraversal.java b/src/main/java/net/sf/jsqlparser/util/TableDefinitionTraversal.java index f6883922e..56a6ca9f5 100644 --- a/src/main/java/net/sf/jsqlparser/util/TableDefinitionTraversal.java +++ b/src/main/java/net/sf/jsqlparser/util/TableDefinitionTraversal.java @@ -15,6 +15,7 @@ import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.LikeClause; import net.sf.jsqlparser.statement.alter.AlterExpression; +import net.sf.jsqlparser.statement.alter.AlterOperation; import net.sf.jsqlparser.statement.alter.AlterExpressionPartition; import net.sf.jsqlparser.statement.alter.AlterExpressionPrimaryKey; import net.sf.jsqlparser.statement.create.index.CreateIndex; @@ -47,6 +48,9 @@ public static void visit(CreateIndex createIndex, Consumer expressio /** Visits the structured definitions and expressions belonging to a single ALTER action. */ public static void visit(AlterExpression action, Consumer expressions, Consumer tables) { + if (action.getOperation() == AlterOperation.RENAME_TABLE) { + accept(action.getNewTable(), tables); + } if (action.getColumnSetDefaultList() != null) { action.getColumnSetDefaultList() .forEach(column -> accept(column.getDefaultExpression(), expressions)); diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 5e6d9c46e..6d2b22965 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -17216,6 +17216,7 @@ AlterExpression AlterExpressionRenameOp(): Token tk; Token tk2; Index index; + Table newTable; } { @@ -17237,13 +17238,13 @@ AlterExpression AlterExpressionRenameOp(): alterExp.setIndex(index); } | - LOOKAHEAD(2) ( + LOOKAHEAD({ getToken(1).kind == K_TO || getToken(1).kind == K_AS }) ( (tk= | tk=) { alterExp.setOperation(AlterOperation.RENAME_TABLE); alterExp.setTableRenameKeyword(AlterExpression.TableRenameKeyword.valueOf( tk.image.toUpperCase(Locale.ROOT))); } - tk2=KeywordOrIdentifier() { alterExp.setNewTableName(tk2.image); } + newTable=Table() { alterExp.setNewTable(newTable); } ) | LOOKAHEAD(3) @@ -17260,7 +17261,7 @@ AlterExpression AlterExpressionRenameOp(): alterExp.setOperation(AlterOperation.RENAME_TABLE); alterExp.setTableRenameKeyword(AlterExpression.TableRenameKeyword.NONE); } - tk2=KeywordOrIdentifier() { alterExp.setNewTableName(tk2.image); } + newTable=Table() { alterExp.setNewTable(newTable); } ) ) { return alterExp; } diff --git a/src/test/java/net/sf/jsqlparser/statement/alter/MySqlQualifiedRenameTest.java b/src/test/java/net/sf/jsqlparser/statement/alter/MySqlQualifiedRenameTest.java new file mode 100644 index 000000000..ef22243bc --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/alter/MySqlQualifiedRenameTest.java @@ -0,0 +1,93 @@ +/*- + * #%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.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +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.TablesNamesFinder; +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 MySqlQualifiedRenameTest { + @ParameterizedTest + @ValueSource(strings = { + "ALTER TABLE db1.t RENAME TO db2.t2", + "ALTER TABLE db1.t RENAME AS db2.t2", + "ALTER TABLE db1.t RENAME db2.t2", + "ALTER TABLE `db1`.`t` RENAME TO `db2`.`t2`", + "ALTER TABLE db1.t RENAME TO `db.with.dot`.`table.with.dot`", + "ALTER TABLE db1.t RENAME TO db2.t2, ADD COLUMN other INT", + "ALTER TABLE db1.t RENAME TO t2" + }) + void preservesQualifiedTargetsAndRenameKeywords(String sql) throws JSQLParserException { + Alter alter = parse(sql); + assertEquals(sql, alter.toString()); + assertRoundTrip(alter); + assertEquals(sql, CCJSqlParserUtil.parse(sql).toString()); + } + + @Test + void structuredTargetAndLegacyNameStayInSync() throws JSQLParserException { + Alter alter = parse("ALTER TABLE db1.t RENAME TO db2.t2"); + AlterExpression rename = alter.getAlterExpressions().get(0); + assertEquals("db2", rename.getNewTable().getSchemaName()); + assertEquals("t2", rename.getNewTable().getName()); + rename.getNewTable().setName("renamed"); + assertEquals("db2.renamed", rename.getNewTableName()); + assertThat(new TablesNamesFinder().getTables(alter)) + .containsExactlyInAnyOrder("db1.t", "db2.renamed"); + assertRoundTrip(alter); + rename.setNewTableName("legacy_name"); + assertEquals("legacy_name", rename.getNewTable().getName()); + assertNull(rename.getNewTable().getSchemaName()); + assertRoundTrip(alter); + rename.withNewTable(new Table("db3", "replacement")); + assertEquals("db3.replacement", rename.getNewTableName()); + assertRoundTrip(alter); + rename.setNewTableName(null); + assertNull(rename.getNewTable()); + assertNull(rename.getNewTableName()); + } + + @ParameterizedTest + @ValueSource(strings = {"RENAME COLUMN old_name TO new_name", "RENAME INDEX old_idx TO new_idx", + "RENAME KEY old_idx TO new_idx"}) + void doesNotConfuseOtherRenameOperations(String operation) throws JSQLParserException { + Alter alter = parse("ALTER TABLE t " + operation); + assertNull(alter.getAlterExpressions().get(0).getNewTable()); + assertRoundTrip(alter); + } + + @ParameterizedTest + @ValueSource(strings = {"RENAME TO", "RENAME AS", "RENAME TO db2.", "RENAME TO db2.t2."}) + void rejectsIncompleteTargetNames(String operation) { + assertThrows(JSQLParserException.class, () -> parse("ALTER TABLE t " + operation)); + } + + 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 buffer = new StringBuilder(); + alter.accept(new StatementDeParser(buffer), null); + assertEquals(alter.toString(), buffer.toString()); + assertEquals(alter.toString(), parse(buffer.toString()).toString()); + } +}