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..2b3738aca 100644 --- a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java @@ -1034,7 +1034,11 @@ protected void toStringRename(StringBuilder b) { } b.append(getOldIndex().getName()).append(" TO ").append(getIndex().getName()); } else { - b.append("RENAME TO ").append(getNewTableName()); + b.append("RENAME"); + if (getTableRenameKeyword() != TableRenameKeyword.NONE) { + b.append(" ").append(getTableRenameKeyword()); + } + b.append(" ").append(getNewTableName()); } } diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionDrop.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionDrop.java index d8a56fbd7..2b24483e1 100644 --- a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionDrop.java +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionDrop.java @@ -29,32 +29,9 @@ protected void appendBody(StringBuilder b) { .append(PlainSelect.getStringList(getPartitions())); break; default: - toStringDropDefault(b); + toStringGeneral(b); break; } } - private void toStringDropDefault(StringBuilder b) { - b.append("DROP "); - if (getColumnName() == null && getPkColumns() != null && !getPkColumns().isEmpty()) { - // Oracle Multi Column Drop - b.append("(").append(PlainSelect.getStringList(getPkColumns())).append(')'); - } else if (getConstraintName() != null) { - b.append("CONSTRAINT "); - if (isUsingIfExists()) { - b.append("IF EXISTS "); - } - b.append(getConstraintName()); - } else if (getColumnName() != null) { - if (hasColumn()) { - b.append("COLUMN "); - } - if (isUsingIfExists()) { - b.append("IF EXISTS "); - } - b.append(getColumnName()); - } else if (getIndex() != null) { - b.append(getIndex()); - } - } } 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..f7560700f 100644 --- a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionRename.java +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionRename.java @@ -19,19 +19,9 @@ public class AlterExpressionRename extends AlterExpression { protected void appendBody(StringBuilder b) { switch (getOperation()) { case RENAME: - b.append("RENAME "); - if (hasColumn()) { - b.append("COLUMN "); - } - b.append(getColumnOldName()).append(" TO ").append(getColumnName()); + toStringGeneral(b); break; case RENAME_TABLE: - b.append("RENAME"); - if (getTableRenameKeyword() != TableRenameKeyword.NONE) { - b.append(" ").append(getTableRenameKeyword()); - } - b.append(" ").append(getNewTableName()); - break; case RENAME_INDEX: case RENAME_KEY: case RENAME_CONSTRAINT: diff --git a/src/test/java/net/sf/jsqlparser/statement/alter/AlterActionRenderingTest.java b/src/test/java/net/sf/jsqlparser/statement/alter/AlterActionRenderingTest.java new file mode 100644 index 000000000..13ea1da52 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/alter/AlterActionRenderingTest.java @@ -0,0 +1,169 @@ +/*- + * #%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.assertInstanceOf; + +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Stream; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.alter.AlterExpression.TableRenameKeyword; +import net.sf.jsqlparser.statement.create.table.Index; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +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.Arguments; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +class AlterActionRenderingTest { + + static Stream drops() { + return Stream.of( + drop("DROP old_column", action -> action.setColumnName("old_column")), + drop("DROP COLUMN IF EXISTS old_column CASCADE", action -> { + action.hasColumn(true); + action.setUsingIfExists(true); + action.setColumnName("old_column"); + action.addParameters("CASCADE"); + }), + drop("DROP CONSTRAINT IF EXISTS old_constraint RESTRICT", action -> { + action.setUsingIfExists(true); + action.setConstraintName("old_constraint"); + action.addParameters("RESTRICT"); + }), + drop("DROP INDEX old_index", action -> action.setIndex( + new Index().withType("INDEX").withName("old_index"))), + drop("DROP KEY old_key", action -> action.setIndex( + new Index().withType("KEY").withName("old_key"))), + drop("DROP (a, b) CASCADE CONSTRAINTS", action -> { + action.setPkColumns(List.of("a", "b")); + action.addParameters("CASCADE", "CONSTRAINTS"); + })); + } + + private static Arguments drop(String sql, Consumer configure) { + return Arguments.of(sql, configure); + } + + @ParameterizedTest + @MethodSource("drops") + void legacyAndTypedDropActionsShareRendering(String body, Consumer configure) + throws JSQLParserException { + for (AlterExpression action : List.of(new AlterExpression(), new AlterExpressionDrop())) { + action.setOperation(AlterOperation.DROP); + configure.accept(action); + assertRoundTrip(action, body, AlterExpressionDrop.class); + } + } + + @ParameterizedTest + @EnumSource(TableRenameKeyword.class) + void legacyAndTypedTableRenamesHonorTheSelectedKeyword(TableRenameKeyword keyword) + throws JSQLParserException { + String body = "RENAME" + (keyword == TableRenameKeyword.NONE ? "" : " " + keyword) + + " db2.renamed"; + for (AlterExpression action : List.of(new AlterExpression(), new AlterExpressionRename())) { + action.setOperation(AlterOperation.RENAME_TABLE); + action.setNewTable(new Table("db2", "renamed")); + action.setTableRenameKeyword(keyword); + assertRoundTrip(action, body, AlterExpressionRename.class); + } + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void legacyAndTypedColumnRenamesShareRendering(boolean hasColumn) throws JSQLParserException { + for (AlterExpression action : List.of(new AlterExpression(), new AlterExpressionRename())) { + action.setOperation(AlterOperation.RENAME); + action.hasColumn(hasColumn); + action.setColumnOldName("old_column"); + action.setColumnName("new_column"); + assertRoundTrip(action, "RENAME " + (hasColumn ? "COLUMN " : "") + + "old_column TO new_column", AlterExpressionRename.class); + } + } + + @ParameterizedTest + @EnumSource(value = AlterOperation.class, + names = {"RENAME_INDEX", "RENAME_KEY", "RENAME_CONSTRAINT"}) + void legacyAndTypedIndexRenamesShareRendering(AlterOperation operation) + throws JSQLParserException { + for (AlterExpression action : List.of(new AlterExpression(), new AlterExpressionRename())) { + action.setOperation(operation); + action.setOldIndex(new Index().withName("old_name")); + action.setIndex(new Index().withName("new_name")); + assertRoundTrip(action, operation.toString().replace('_', ' ') + + " old_name TO new_name", AlterExpressionRename.class); + } + } + + @Test + void commonTailIsAppendedOnceForTypedAndLegacyActions() { + for (AlterExpression action : List.of(new AlterExpression(), new AlterExpressionDrop())) { + Index index = new Index().withType("INDEX").withName("old_index"); + index.setCommentText("'index comment'"); + action.setOperation(AlterOperation.DROP); + action.setIndex(index); + action.addParameters("CASCADE"); + assertEquals("DROP INDEX old_index CASCADE COMMENT 'index comment'", action.toString()); + StringBuilder output = new StringBuilder(); + statement(action).accept(new StatementDeParser(output), null); + assertEquals("ALTER TABLE t DROP INDEX old_index CASCADE COMMENT 'index comment'", + output.toString()); + } + } + + @Test + void mixedActionsStillUseTheCustomExpressionDeparser() throws JSQLParserException { + Alter alter = (Alter) CCJSqlParserUtil.parse("ALTER TABLE t DROP COLUMN obsolete, " + + "RENAME COLUMN old_column TO new_column, ADD COLUMN amount INT DEFAULT (1 + 2)"); + StringBuilder output = new StringBuilder(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(LongValue value, S context) { + return getBuilder().append(value.getValue() + 100); + } + }; + alter.accept(new StatementDeParser(expressions, new SelectDeParser(), output), null); + String expected = "ALTER TABLE t DROP COLUMN obsolete, " + + "RENAME COLUMN old_column TO new_column, ADD COLUMN amount INT DEFAULT (101 + 102)"; + assertEquals(expected, output.toString()); + assertEquals(expected, CCJSqlParserUtil.parse(output.toString()).toString()); + } + + private static Alter statement(AlterExpression action) { + Alter alter = new Alter(); + alter.setTable(new Table("t")); + alter.addAlterExpression(action); + return alter; + } + + private static void assertRoundTrip(AlterExpression action, String expectedBody, + Class parsedType) throws JSQLParserException { + String expected = "ALTER TABLE t " + expectedBody; + Alter alter = statement(action); + assertEquals(expected, alter.toString()); + StringBuilder output = new StringBuilder(); + alter.accept(new StatementDeParser(output), null); + assertEquals(expected, output.toString()); + Alter reparsed = (Alter) CCJSqlParserUtil.parse(output.toString()); + assertInstanceOf(parsedType, reparsed.getAlterExpressions().get(0)); + assertEquals(expected, reparsed.toString()); + } +}