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 aa0fba53b..53a963d6c 100644 --- a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java @@ -134,6 +134,11 @@ public enum TableRenameKeyword { private String constraintType; private boolean invisible; + /** Returns the active structured column rename, or null when no such action is exposed. */ + public ColumnRenameAction getColumnRename() { + return null; + } + public Index getOldIndex() { return oldIndex; } diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionRename.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionRename.java index 3fa50fe73..8355bc5ff 100644 --- a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionRename.java +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionRename.java @@ -13,7 +13,32 @@ * Internal subclass for RENAME operations within ALTER TABLE. Handles RENAME COLUMN, RENAME TO * (table), RENAME INDEX/KEY/CONSTRAINT. */ -public class AlterExpressionRename extends AlterExpression { +public class AlterExpressionRename extends AlterExpression implements ColumnRenameAction { + + @Override + public ColumnRenameAction getColumnRename() { + return getOperation() == AlterOperation.RENAME ? this : null; + } + + @Override + public String getSourceName() { + return getColumnOldName(); + } + + @Override + public void setSourceName(String name) { + setColumnOldName(name); + } + + @Override + public String getTargetName() { + return getColumnName(); + } + + @Override + public void setTargetName(String name) { + setColumnName(name); + } @Override protected void appendBody(StringBuilder b) { diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/ColumnRenameAction.java b/src/main/java/net/sf/jsqlparser/statement/alter/ColumnRenameAction.java new file mode 100644 index 000000000..98316c474 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/alter/ColumnRenameAction.java @@ -0,0 +1,25 @@ +/*- + * #%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; + +/** + * Common access to column renames on tables and views. Obtain the active rename through + * {@link AlterExpression#getColumnRename()}. Names retain their SQL identifier quotes, and changes + * update the existing action without changing its operation or optional COLUMN keyword. + */ +public interface ColumnRenameAction { + String getSourceName(); + + void setSourceName(String name); + + String getTargetName(); + + void setTargetName(String name); +} diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/RelationAlterAction.java b/src/main/java/net/sf/jsqlparser/statement/alter/RelationAlterAction.java index d00479dc0..383205fa9 100644 --- a/src/main/java/net/sf/jsqlparser/statement/alter/RelationAlterAction.java +++ b/src/main/java/net/sf/jsqlparser/statement/alter/RelationAlterAction.java @@ -17,7 +17,7 @@ import net.sf.jsqlparser.statement.select.PlainSelect; /** Structured PostgreSQL property actions shared by tables, indexes and views. */ -public class RelationAlterAction extends AlterExpression { +public class RelationAlterAction extends AlterExpression implements ColumnRenameAction { private EnableState enableState; private TriggerTarget triggerTarget; private Kind kind; @@ -132,6 +132,31 @@ public void setNewName(String newName) { this.newName = newName; } + @Override + public ColumnRenameAction getColumnRename() { + return kind == Kind.RENAME_COLUMN ? this : null; + } + + @Override + public String getSourceName() { + return getColumnName(); + } + + @Override + public void setSourceName(String name) { + setColumnName(name); + } + + @Override + public String getTargetName() { + return getNewName(); + } + + @Override + public void setTargetName(String name) { + setNewName(name); + } + public String getValue() { return value; } diff --git a/src/site/sphinx/usage.rst b/src/site/sphinx/usage.rst index a4a8a304f..d641401a6 100644 --- a/src/site/sphinx/usage.rst +++ b/src/site/sphinx/usage.rst @@ -288,6 +288,32 @@ These named targets do not populate the legacy ``getPkColumns()`` list. AlterExpression checkDrop = new AlterExpression() .withOperation(AlterOperation.DROP_CHECK).withConstraintName("positive_id"); +Edit table and view column renames +---------------------------------- + +``AlterExpression.getColumnRename()`` exposes a ``ColumnRenameAction`` with +``getSourceName()`` and ``getTargetName()``. Their setters update the existing +AST directly, preserving the optional ``COLUMN`` keyword. Names include their +written identifier quotes; include quotes in replacement names when needed. + +.. code-block:: java + + Alter alter = (Alter) CCJSqlParserUtil.parse( + "ALTER TABLE accounts RENAME COLUMN old_id TO new_id", + parser -> parser.withDialect(Dialect.MYSQL)); + ColumnRenameAction rename = alter.getAlterExpressions().get(0).getColumnRename(); + rename.getSourceName(); // old_id + rename.setTargetName("customer_id"); + // ALTER TABLE accounts RENAME COLUMN old_id TO customer_id + String sql = alter.toString(); + +Import ``ColumnRenameAction`` from ``net.sf.jsqlparser.statement.alter``. +PostgreSQL table renames of columns use the same API. With ``Dialect.POSTGRESQL``, +column renames on views and materialized views expose it through the entries in +``AlterRelation.getActions()``. The lookup returns ``null`` for other operations, +including renaming the table, view, index or constraint itself. Existing +action-specific getters and setters remain available and see the same changes. + Inspect PostgreSQL schema statements ------------------------------------ diff --git a/src/test/java/net/sf/jsqlparser/statement/alter/ColumnRenameActionTest.java b/src/test/java/net/sf/jsqlparser/statement/alter/ColumnRenameActionTest.java new file mode 100644 index 000000000..d3a91e401 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/alter/ColumnRenameActionTest.java @@ -0,0 +1,165 @@ +/*- + * #%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.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.Statement; +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.CsvSource; + +class ColumnRenameActionTest { + @ParameterizedTest + @CsvSource({"MYSQL, ALTER TABLE t RENAME COLUMN old_col TO new_col", + "POSTGRESQL, ALTER TABLE t RENAME COLUMN old_col TO new_col", + "POSTGRESQL, ALTER TABLE t RENAME old_col TO new_col", + "POSTGRESQL, ALTER VIEW v RENAME COLUMN old_col TO new_col", + "POSTGRESQL, ALTER VIEW v RENAME old_col TO new_col", + "POSTGRESQL, ALTER MATERIALIZED VIEW v RENAME COLUMN old_col TO new_col"}) + void commonAndLegacyAccessorsEditTheSameNames(Dialect dialect, String sql) + throws JSQLParserException { + Statement statement = parse(sql, dialect); + AlterExpression action = action(statement); + ColumnRenameAction rename = action.getColumnRename(); + assertNotNull(rename); + assertEquals("old_col", rename.getSourceName()); + assertEquals("new_col", rename.getTargetName()); + + String quote = dialect == Dialect.MYSQL ? "`" : "\""; + String sourceName = quote + "old column" + quote; + String targetName = quote + "new column" + quote; + rename.setSourceName(sourceName); + rename.setTargetName(targetName); + if (action instanceof RelationAlterAction) { + RelationAlterAction relation = (RelationAlterAction) action; + assertEquals(sourceName, relation.getColumnName()); + assertEquals(targetName, relation.getNewName()); + } else { + assertEquals(sourceName, action.getColumnOldName()); + assertEquals(targetName, action.getColumnName()); + } + assertRoundTrip(sql.replace("old_col", sourceName) + .replace("new_col", targetName), statement, dialect); + + if (action instanceof RelationAlterAction) { + RelationAlterAction relation = (RelationAlterAction) action; + relation.setColumnName("legacy_source"); + relation.setNewName("legacy_target"); + } else { + action.setColumnOldName("legacy_source"); + action.setColumnName("legacy_target"); + } + assertEquals("legacy_source", rename.getSourceName()); + assertEquals("legacy_target", rename.getTargetName()); + assertSame(rename, action.getColumnRename()); + assertRoundTrip(sql.replace("old_col", "legacy_source") + .replace("new_col", "legacy_target"), statement, dialect); + } + + @ParameterizedTest + @CsvSource({"POSTGRESQL, ALTER TABLE t RENAME TO other_table", + "MYSQL, ALTER TABLE t RENAME INDEX old_index TO new_index", + "MYSQL, ALTER TABLE t RENAME KEY old_key TO new_key", + "POSTGRESQL, ALTER TABLE t RENAME CONSTRAINT old_constraint TO new_constraint", + "POSTGRESQL, ALTER VIEW v RENAME TO other_view", + "POSTGRESQL, ALTER INDEX ix RENAME TO other_index", + "POSTGRESQL, ALTER TABLE t ADD COLUMN c INT", + "POSTGRESQL, ALTER VIEW v SET SCHEMA other_schema"}) + void otherOperationsDoNotExposeAColumnRename(Dialect dialect, String sql) + throws JSQLParserException { + Statement statement = parse(sql, dialect); + assertNull(action(statement).getColumnRename()); + assertRoundTrip(statement.toString(), statement, dialect); + } + + @Test + void switchingTableOperationsKeepsInactiveNamesWithoutExposingThem() + throws JSQLParserException { + Statement statement = parse("ALTER TABLE t RENAME COLUMN old_col TO new_col"); + AlterExpression action = action(statement); + ColumnRenameAction rename = action.getColumnRename(); + action.setOperation(AlterOperation.RENAME_TABLE); + action.setNewTableName("other_table"); + assertNull(action.getColumnRename()); + assertEquals("old_col", action.getColumnOldName()); + assertEquals("new_col", action.getColumnName()); + assertRoundTrip("ALTER TABLE t RENAME TO other_table", statement); + + action.setOperation(AlterOperation.RENAME); + assertSame(rename, action.getColumnRename()); + assertRoundTrip("ALTER TABLE t RENAME COLUMN old_col TO new_col", statement); + } + + @Test + void switchingRelationKindsKeepsInactiveNamesWithoutExposingThem() + throws JSQLParserException { + Statement statement = parse("ALTER VIEW v RENAME COLUMN old_col TO new_col"); + RelationAlterAction action = (RelationAlterAction) action(statement); + ColumnRenameAction rename = action.getColumnRename(); + action.setKind(RelationAlterAction.Kind.SET_SCHEMA); + action.setValue("other_schema"); + assertNull(action.getColumnRename()); + assertEquals("old_col", action.getColumnName()); + assertEquals("new_col", action.getNewName()); + assertRoundTrip("ALTER VIEW v SET SCHEMA other_schema", statement); + + action.setKind(RelationAlterAction.Kind.RENAME_COLUMN); + assertSame(rename, action.getColumnRename()); + assertRoundTrip("ALTER VIEW v RENAME COLUMN old_col TO new_col", statement); + } + + private static Statement parse(String sql) throws JSQLParserException { + return parse(sql, Dialect.POSTGRESQL); + } + + private static Statement parse(String sql, Dialect dialect) throws JSQLParserException { + return CCJSqlParserUtil.parse(sql, parser -> parser.withDialect(dialect)); + } + + private static AlterExpression action(Statement statement) { + return statement instanceof Alter + ? ((Alter) statement).getAlterExpressions().get(0) + : ((AlterRelation) statement).getActions().get(0); + } + + private static void assertRoundTrip(String expected, Statement statement) + throws JSQLParserException { + assertRoundTrip(expected, statement, Dialect.POSTGRESQL); + } + + private static void assertRoundTrip(String expected, Statement statement, Dialect dialect) + throws JSQLParserException { + assertEquals(expected, statement.toString()); + StringBuilder output = new StringBuilder(); + statement.accept(new StatementDeParser(output)); + assertEquals(expected, output.toString()); + Statement reparsed = parse(output.toString(), dialect); + assertEquals(statement.getClass(), reparsed.getClass()); + assertEquals(action(statement).getClass(), action(reparsed).getClass()); + assertEquals(expected, reparsed.toString()); + ColumnRenameAction rename = action(statement).getColumnRename(); + ColumnRenameAction reparsedRename = action(reparsed).getColumnRename(); + if (rename == null) { + assertNull(reparsedRename); + } else { + assertNotNull(reparsedRename); + assertEquals(rename.getSourceName(), reparsedRename.getSourceName()); + assertEquals(rename.getTargetName(), reparsedRename.getTargetName()); + } + } +}