From 6e1f4ea343776ea63868f0eed9712980fae01c03 Mon Sep 17 00:00:00 2001 From: minleejae Date: Tue, 13 Oct 2026 13:42:47 +0900 Subject: [PATCH] fix: render single spaces in DROP PRIMARY KEY, RENAME TABLE and indexes DROP PRIMARY KEY ended with a space, which doubled before CASCADE or RESTRICT. RENAME TABLE rendered two spaces before the first table, and an index without a type rendered a leading space, so ALTER TABLE ... ADD INDEX and named-only indexes came out with doubled spaces. Separate these words with exactly one space; the SQL is otherwise unchanged. Signed-off-by: minleejae --- .../statement/alter/AlterExpression.java | 2 +- .../statement/alter/RenameTableStatement.java | 5 +- .../statement/create/table/Index.java | 26 ++++++--- .../alter/AlterConstraintSeparationTest.java | 6 +- .../alter/DeparseWhitespaceTest.java | 56 +++++++++++++++++++ .../alter/IndexOptionBoundaryTest.java | 2 +- ...TableConstraintExpressionDeParserTest.java | 2 +- 7 files changed, 83 insertions(+), 16 deletions(-) create mode 100644 src/test/java/net/sf/jsqlparser/statement/alter/DeparseWhitespaceTest.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 d641b7c8ad..046e6bb23a 100644 --- a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java @@ -1421,7 +1421,7 @@ protected void toStringRename(StringBuilder b) { protected void toStringDropSpecial(StringBuilder b) { switch (operation) { case DROP_PRIMARY_KEY: - b.append("DROP PRIMARY KEY "); + b.append("DROP PRIMARY KEY"); break; case DROP_UNIQUE: b.append("DROP UNIQUE (").append(PlainSelect.getStringList(dropColumns)) diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/RenameTableStatement.java b/src/main/java/net/sf/jsqlparser/statement/alter/RenameTableStatement.java index 94eb39be0c..c119d1c4d2 100644 --- a/src/main/java/net/sf/jsqlparser/statement/alter/RenameTableStatement.java +++ b/src/main/java/net/sf/jsqlparser/statement/alter/RenameTableStatement.java @@ -125,8 +125,9 @@ public StringBuilder appendTo(StringBuilder builder) { if (i == 0) { builder .append("RENAME") - .append(usingTableKeyword ? " TABLE " : " ") - .append(usingIfExistsKeyword ? " IF EXISTS " : " ") + .append(usingTableKeyword ? " TABLE" : "") + .append(usingIfExistsKeyword ? " IF EXISTS" : "") + .append(' ') .append(e.getKey()) .append(waitDirective != null && waitDirective.length() > 0 ? " " + waitDirective diff --git a/src/main/java/net/sf/jsqlparser/statement/create/table/Index.java b/src/main/java/net/sf/jsqlparser/statement/create/table/Index.java index 2a10326cf7..f0b1a1deac 100644 --- a/src/main/java/net/sf/jsqlparser/statement/create/table/Index.java +++ b/src/main/java/net/sf/jsqlparser/statement/create/table/Index.java @@ -220,21 +220,31 @@ public void appendTo(StringBuilder sql, Consumer expressionPrinter) } private void appendDeclarationTo(StringBuilder sql) { - String keyword = indexKeyword != null && (type == null - || !type.toUpperCase(java.util.Locale.ROOT) - .endsWith(indexKeyword.toUpperCase(java.util.Locale.ROOT))) - ? " " + indexKeyword - : ""; - sql.append(type != null ? type : "").append(keyword); + int start = sql.length(); + if (type != null) { + sql.append(type); + } + if (indexKeyword != null && (type == null || !type.toUpperCase(java.util.Locale.ROOT) + .endsWith(indexKeyword.toUpperCase(java.util.Locale.ROOT)))) { + appendWord(sql, start, indexKeyword); + } if (!name.isEmpty()) { - sql.append(' ').append(getName()); + appendWord(sql, start, getName()); } if (getUsing() != null) { - sql.append(" USING ").append(getUsing()); + appendWord(sql, start, "USING " + getUsing()); } sql.append(nullsDistinctClause()).append(clusteringClause()); } + /** Separates {@code word} from earlier declaration words, without a leading space. */ + private static void appendWord(StringBuilder sql, int start, String word) { + if (sql.length() > start) { + sql.append(' '); + } + sql.append(word); + } + private void appendColumnsAndSpecTo(StringBuilder sql, Consumer expressionPrinter) { String spec = PlainSelect.getStringList(getIndexSpec(), false, false); diff --git a/src/test/java/net/sf/jsqlparser/statement/alter/AlterConstraintSeparationTest.java b/src/test/java/net/sf/jsqlparser/statement/alter/AlterConstraintSeparationTest.java index 9897eee228..f568b6eba5 100644 --- a/src/test/java/net/sf/jsqlparser/statement/alter/AlterConstraintSeparationTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/alter/AlterConstraintSeparationTest.java @@ -50,7 +50,7 @@ void uniqueConstraintAndOrdinaryIndexHaveSeparateEditableDefinitions() throws Ex indexAction.getIndex().setName("new_ix"); assertRoundTrip(statement, "ALTER TABLE t ADD CONSTRAINT unique_symbol UNIQUE INDEX legacy_uq (new_id), " - + "ADD INDEX new_ix (other)", + + "ADD INDEX new_ix (other)", Dialect.MYSQL); } @@ -98,7 +98,7 @@ void replacingDefinitionChangesTheRenderedActionAndItsClassification() throws Ex assertNull(action.getConstraint()); action.setConstraint(null); assertEquals(ConstraintKind.OTHER, action.getConstraintKind()); - assertRoundTrip(statement, "ALTER TABLE t ADD INDEX ix_new (id)", Dialect.MYSQL); + assertRoundTrip(statement, "ALTER TABLE t ADD INDEX ix_new (id)", Dialect.MYSQL); } @ParameterizedTest @@ -116,7 +116,7 @@ void replacingLegacyUniqueDefinitionClearsItsOldHeader(boolean replaceWithIndex) .withName("ix").withColumnsNames(List.of("id"))); assertNull(action.getConstraint()); assertEquals(ConstraintKind.OTHER, action.getConstraintKind()); - assertRoundTrip(statement, "ALTER TABLE t ADD INDEX ix (id)", Dialect.MYSQL); + assertRoundTrip(statement, "ALTER TABLE t ADD INDEX ix (id)", Dialect.MYSQL); } else { action.setConstraint(new CheckConstraint() .withExpression(CCJSqlParserUtil.parseCondExpression("id > 0"))); diff --git a/src/test/java/net/sf/jsqlparser/statement/alter/DeparseWhitespaceTest.java b/src/test/java/net/sf/jsqlparser/statement/alter/DeparseWhitespaceTest.java new file mode 100644 index 0000000000..ddd76cdcfc --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/alter/DeparseWhitespaceTest.java @@ -0,0 +1,56 @@ +/*- + * #%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 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.statement.create.table.Index; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.junit.jupiter.api.Test; + +/** Renderers separate words with exactly one space and add no trailing space. */ +class DeparseWhitespaceTest { + + @ParameterizedTest + @ValueSource(strings = {"ALTER TABLE t DROP PRIMARY KEY", + "ALTER TABLE t DROP PRIMARY KEY CASCADE", + "ALTER TABLE t DROP PRIMARY KEY, DROP COLUMN c", + "RENAME TABLE t TO t2", + "RENAME TABLE a TO b, c TO d", + "RENAME TABLE IF EXISTS a TO b", + "RENAME a TO b", + "ALTER TABLE t ADD INDEX i (body (10))", + "ALTER TABLE t ADD KEY k (a, b)", + "CREATE TABLE t (a INT, INDEX i (a))"}) + void rendersSingleSpaces(String sql) throws JSQLParserException { + Statement statement = + CCJSqlParserUtil.parse(sql, parser -> parser.withDialect(Dialect.MYSQL)); + assertEquals(sql, statement.toString()); + + StringBuilder deparsed = new StringBuilder(); + statement.accept(new StatementDeParser(deparsed)); + assertEquals(sql, deparsed.toString()); + } + + @Test + void indexDeclarationHasNoLeadingSpace() { + assertEquals("i2", new Index().withName("i2").toString()); + assertEquals("KEY k (a)", new Index().withIndexKeyword("KEY").withName("k") + .withColumnsNames(java.util.List.of("a")).toString()); + assertEquals("UNIQUE INDEX u (a)", new Index().withType("UNIQUE").withIndexKeyword("INDEX") + .withName("u").withColumnsNames(java.util.List.of("a")).toString()); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/alter/IndexOptionBoundaryTest.java b/src/test/java/net/sf/jsqlparser/statement/alter/IndexOptionBoundaryTest.java index 21a938c08a..f53b06491f 100644 --- a/src/test/java/net/sf/jsqlparser/statement/alter/IndexOptionBoundaryTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/alter/IndexOptionBoundaryTest.java @@ -47,7 +47,7 @@ void tablespaceBelongsToConstraintOptions(String key) throws JSQLParserException void mysqlAddIndexAttributes(String options) throws JSQLParserException { Alter alter = (Alter) parse("ALTER TABLE t ADD INDEX ix (id) " + options, Dialect.MYSQL); assertEquals("ix", alter.getAlterExpressions().get(0).getIndex().getName()); - assertEquals("ALTER TABLE t ADD INDEX ix (id) " + options, alter.toString()); + assertEquals("ALTER TABLE t ADD INDEX ix (id) " + options, alter.toString()); assertRoundTrip(alter, Dialect.MYSQL); } diff --git a/src/test/java/net/sf/jsqlparser/util/deparser/TableConstraintExpressionDeParserTest.java b/src/test/java/net/sf/jsqlparser/util/deparser/TableConstraintExpressionDeParserTest.java index 755eed426b..1dc4ce9604 100644 --- a/src/test/java/net/sf/jsqlparser/util/deparser/TableConstraintExpressionDeParserTest.java +++ b/src/test/java/net/sf/jsqlparser/util/deparser/TableConstraintExpressionDeParserTest.java @@ -48,7 +48,7 @@ void createAndAlterFunctionalIndexesUseExpressionVisitor() throws JSQLParserExce assertRewrite("CREATE TABLE t (id INT, INDEX idx ((id + 1)))", "CREATE TABLE t (id INT, INDEX idx ((mapped_id + 101)))"); assertRewrite("ALTER TABLE t ADD INDEX idx ((id + 1))", - "ALTER TABLE t ADD INDEX idx ((mapped_id + 101))"); + "ALTER TABLE t ADD INDEX idx ((mapped_id + 101))"); } @ParameterizedTest