Skip to content

Commit b79f20d

Browse files
committed
fix: render single spaces in ALTER visibility, FORCE and row level security actions
Rendered SQL had stray spaces in several ALTER TABLE actions: SET INVISIBLE, ALTER INDEX i INVISIBLE, a trailing space after FORCE and [NO] FORCE/ENABLE/DISABLE ROW LEVEL SECURITY, MODIFY ( c INT ), and ADD CONSTRAINT UNIQUE KEY uk (a). An index without a declaration printed a leading space, and argument-less JSON_ARRAY()/JSON_OBJECT() rendered as JSON_ARRAY( ) and JSON_OBJECT( ) with a trailing space. Separate these words with exactly one space and add no leading or trailing space. toString() and StatementDeParser stay identical. Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent bdf8ac3 commit b79f20d

4 files changed

Lines changed: 130 additions & 39 deletions

File tree

‎src/main/java/net/sf/jsqlparser/expression/JsonFunction.java‎

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -436,12 +436,11 @@ public StringBuilder appendObject(StringBuilder builder) {
436436
@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
437437
public StringBuilder appendObject(StringBuilder builder,
438438
Consumer<Expression> expressionPrinter) {
439-
builder.append("JSON_OBJECT( ");
439+
builder.append("JSON_OBJECT(");
440+
int start = builder.length();
440441
int i = 0;
441442
for (JsonKeyValuePair keyValuePair : keyValuePairs) {
442-
if (i > 0) {
443-
builder.append(", ");
444-
}
443+
builder.append(i > 0 ? ", " : " ");
445444
keyValuePair.appendTo(builder, expressionPrinter);
446445
i++;
447446
}
@@ -453,7 +452,8 @@ public StringBuilder appendObject(StringBuilder builder,
453452
appendUniqueKeys(builder);
454453
appendReturningClause(builder, true);
455454

456-
builder.append(" ) ");
455+
// An empty argument list renders as JSON_OBJECT()
456+
builder.append(builder.length() > start ? " )" : ")");
457457

458458
return builder;
459459
}
@@ -495,20 +495,18 @@ public StringBuilder appendArray(StringBuilder builder) {
495495
@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
496496
public StringBuilder appendArray(StringBuilder builder,
497497
Consumer<Expression> expressionPrinter) {
498-
builder.append("JSON_ARRAY( ");
498+
builder.append("JSON_ARRAY(");
499499
int i = 0;
500500

501501
for (JsonFunctionExpression expr : expressions) {
502-
if (i > 0) {
503-
builder.append(", ");
504-
}
502+
builder.append(i > 0 ? ", " : " ");
505503
expr.appendTo(builder, expressionPrinter);
506504
i++;
507505
}
508506

509507
appendOnNullType(builder);
510508
appendReturningClause(builder, true);
511-
builder.append(") ");
509+
builder.append(')');
512510

513511
return builder;
514512
}

‎src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java‎

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1304,8 +1304,7 @@ protected void toStringConstraintAlter(StringBuilder b) {
13041304
}
13051305
} else {
13061306
b.append("ADD CONSTRAINT ").append(getConstraintType()).append(" ")
1307-
.append(getConstraintSymbol())
1308-
.append(" ");
1307+
.append(getConstraintSymbol());
13091308
if (constraint instanceof KeyConstraint) {
13101309
b.append(" ")
13111310
.append(PlainSelect.getStringList(
@@ -1519,9 +1518,9 @@ protected void toStringPartition(StringBuilder b) {
15191518
b.append(partitions.get(0)).append(" WITH TABLE ")
15201519
.append(exchangePartitionTableName);
15211520
if (exchangePartitionWithValidation) {
1522-
b.append(" WITH VALIDATION ");
1521+
b.append(" WITH VALIDATION");
15231522
} else if (exchangePartitionWithoutValidation) {
1524-
b.append(" WITHOUT VALIDATION ");
1523+
b.append(" WITHOUT VALIDATION");
15251524
}
15261525
break;
15271526
case ANALYZE_PARTITION:
@@ -1575,18 +1574,20 @@ public void appendColumnDefinitionsTo(StringBuilder b, Consumer<Expression> expr
15751574
"PMD.ExcessiveMethodLength"})
15761575
private void toStringGeneral(StringBuilder b, Consumer<ColumnDataType> columnPrinter) {
15771576
if (operation == AlterOperation.COMMENT_WITH_EQUAL_SIGN) {
1578-
b.append("COMMENT =").append(" ");
1577+
b.append("COMMENT =");
15791578
} else if (operation == AlterOperation.ENABLE_ROW_LEVEL_SECURITY) {
1580-
b.append("ENABLE ROW LEVEL SECURITY").append(" ");
1579+
b.append("ENABLE ROW LEVEL SECURITY");
15811580
} else if (operation == AlterOperation.DISABLE_ROW_LEVEL_SECURITY) {
1582-
b.append("DISABLE ROW LEVEL SECURITY").append(" ");
1581+
b.append("DISABLE ROW LEVEL SECURITY");
15831582
} else if (operation == AlterOperation.FORCE_ROW_LEVEL_SECURITY) {
1584-
b.append("FORCE ROW LEVEL SECURITY").append(" ");
1583+
b.append("FORCE ROW LEVEL SECURITY");
15851584
} else if (operation == AlterOperation.NO_FORCE_ROW_LEVEL_SECURITY) {
1586-
b.append("NO FORCE ROW LEVEL SECURITY").append(" ");
1585+
b.append("NO FORCE ROW LEVEL SECURITY");
15871586
} else {
1588-
b.append(operation).append(" ");
1587+
b.append(operation);
15891588
}
1589+
int keywordEnd = b.length();
1590+
b.append(' ');
15901591
if (commentText != null) {
15911592
if (columnName != null) {
15921593
b.append(columnName).append(" COMMENT ");
@@ -1624,7 +1625,7 @@ private void toStringGeneral(StringBuilder b, Consumer<ColumnDataType> columnPri
16241625
}
16251626
}
16261627
if (useBrackets && colDataTypeList.size() == 1) {
1627-
b.append(" ( ");
1628+
b.append('(');
16281629
}
16291630
for (int i = 0; i < colDataTypeList.size(); i++) {
16301631
if (i > 0) {
@@ -1633,7 +1634,7 @@ private void toStringGeneral(StringBuilder b, Consumer<ColumnDataType> columnPri
16331634
columnPrinter.accept(colDataTypeList.get(i));
16341635
}
16351636
if (useBrackets && colDataTypeList.size() == 1) {
1636-
b.append(" ) ");
1637+
b.append(')');
16371638
}
16381639
if (colDataTypeList.size() > 1) {
16391640
b.append(")");
@@ -1658,6 +1659,10 @@ private void toStringGeneral(StringBuilder b, Consumer<ColumnDataType> columnPri
16581659
} else if (index != null) {
16591660
b.append(index);
16601661
}
1662+
if (b.length() == keywordEnd + 1) {
1663+
// a keyword without payload, such as FORCE, ends without a separator
1664+
b.setLength(keywordEnd);
1665+
}
16611666

16621667
if (getConstraints() != null && !getConstraints().isEmpty()) {
16631668
b.append(' ').append(PlainSelect.getStringList(constraints, false, false));
@@ -2125,7 +2130,7 @@ public boolean isVisible() {
21252130

21262131
@Override
21272132
public String toString() {
2128-
return columnName + " SET " + (visible ? " VISIBLE" : " INVISIBLE");
2133+
return columnName + " SET " + (visible ? "VISIBLE" : "INVISIBLE");
21292134
}
21302135
}
21312136

‎src/main/java/net/sf/jsqlparser/statement/create/table/Index.java‎

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -211,16 +211,16 @@ public String toString() {
211211
}
212212

213213
public void appendTo(StringBuilder sql, Consumer<Expression> expressionPrinter) {
214-
appendDeclarationTo(sql);
215-
appendColumnsAndSpecTo(sql, expressionPrinter);
214+
int start = sql.length();
215+
appendDeclarationTo(sql, start);
216+
appendColumnsAndSpecTo(sql, start, expressionPrinter);
216217
appendIndexOptionsTo(sql, expressionPrinter);
217218
if (tailParameters != null && !tailParameters.isEmpty()) {
218219
sql.append(' ').append(PlainSelect.getStringList(tailParameters, false, false));
219220
}
220221
}
221222

222-
private void appendDeclarationTo(StringBuilder sql) {
223-
int start = sql.length();
223+
private void appendDeclarationTo(StringBuilder sql, int start) {
224224
if (type != null) {
225225
sql.append(type);
226226
}
@@ -237,24 +237,27 @@ private void appendDeclarationTo(StringBuilder sql) {
237237
sql.append(nullsDistinctClause()).append(clusteringClause());
238238
}
239239

240-
/** Separates {@code word} from earlier declaration words, without a leading space. */
240+
/** Separates {@code word} from earlier index words, without a leading space. */
241241
private static void appendWord(StringBuilder sql, int start, String word) {
242+
appendSeparator(sql, start);
243+
sql.append(word);
244+
}
245+
246+
private static void appendSeparator(StringBuilder sql, int start) {
242247
if (sql.length() > start) {
243248
sql.append(' ');
244249
}
245-
sql.append(word);
246250
}
247251

248-
private void appendColumnsAndSpecTo(StringBuilder sql,
252+
private void appendColumnsAndSpecTo(StringBuilder sql, int start,
249253
Consumer<Expression> expressionPrinter) {
250-
String spec = PlainSelect.getStringList(getIndexSpec(), false, false);
251-
boolean hasColumns = columns != null && !columns.isEmpty();
252-
if (hasColumns) {
253-
sql.append(' ');
254+
if (columns != null && !columns.isEmpty()) {
255+
appendSeparator(sql, start);
254256
appendColumnsTo(sql, expressionPrinter);
255257
}
258+
String spec = PlainSelect.getStringList(getIndexSpec(), false, false);
256259
if (!spec.isEmpty()) {
257-
sql.append(hasColumns ? " " : " ").append(spec);
260+
appendWord(sql, start, spec);
258261
}
259262
}
260263
}

‎src/test/java/net/sf/jsqlparser/statement/alter/DeparseWhitespaceTest.java‎

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@
1010
package net.sf.jsqlparser.statement.alter;
1111

1212
import static org.junit.jupiter.api.Assertions.assertEquals;
13+
import static org.junit.jupiter.api.Assertions.assertFalse;
1314

15+
import java.util.List;
1416
import net.sf.jsqlparser.JSQLParserException;
1517
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
1618
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
1719
import net.sf.jsqlparser.statement.Statement;
20+
import net.sf.jsqlparser.statement.create.index.CreateIndex;
1821
import net.sf.jsqlparser.statement.create.table.Index;
1922
import net.sf.jsqlparser.util.deparser.StatementDeParser;
2023
import org.junit.jupiter.params.ParameterizedTest;
@@ -34,15 +37,97 @@ class DeparseWhitespaceTest {
3437
"RENAME a TO b",
3538
"ALTER TABLE t ADD INDEX i (body (10))",
3639
"ALTER TABLE t ADD KEY k (a, b)",
37-
"CREATE TABLE t (a INT, INDEX i (a))"})
40+
"CREATE TABLE t (a INT, INDEX i (a))",
41+
"ALTER TABLE t ALTER COLUMN c SET INVISIBLE",
42+
"ALTER TABLE t ALTER COLUMN c SET VISIBLE",
43+
"ALTER TABLE t ALTER c SET INVISIBLE",
44+
"ALTER TABLE t ALTER INDEX i INVISIBLE",
45+
"ALTER TABLE t ALTER INDEX i VISIBLE",
46+
"ALTER TABLE t ALTER COLUMN c SET VISIBLE, ALTER INDEX i VISIBLE",
47+
"ALTER TABLE t FORCE",
48+
"ALTER TABLE t FORCE, ALGORITHM = INPLACE",
49+
"CREATE TABLE t (a JSON DEFAULT (JSON_ARRAY()))",
50+
"ALTER TABLE t ADD COLUMN j JSON DEFAULT (JSON_OBJECT())"})
3851
void rendersSingleSpaces(String sql) throws JSQLParserException {
39-
Statement statement =
40-
CCJSqlParserUtil.parse(sql, parser -> parser.withDialect(Dialect.MYSQL));
52+
assertRendersExactly(sql, Dialect.MYSQL);
53+
}
54+
55+
@ParameterizedTest
56+
@ValueSource(strings = {"ALTER TABLE t ENABLE ROW LEVEL SECURITY",
57+
"ALTER TABLE t DISABLE ROW LEVEL SECURITY",
58+
"ALTER TABLE t FORCE ROW LEVEL SECURITY",
59+
"ALTER TABLE t NO FORCE ROW LEVEL SECURITY",
60+
"ALTER TABLE t ENABLE ROW LEVEL SECURITY, FORCE ROW LEVEL SECURITY",
61+
"CREATE INDEX ON t (a)"})
62+
void rendersSingleSpacesForPostgreSql(String sql) throws JSQLParserException {
63+
assertRendersExactly(sql, Dialect.POSTGRESQL);
64+
}
65+
66+
@ParameterizedTest
67+
@ValueSource(strings = {"ALTER TABLE t FORCE",
68+
"ALTER TABLE t MODIFY (c INT)",
69+
"ALTER TABLE t MODIFY COLUMN (c INT NOT NULL)",
70+
"ALTER TABLE t ADD (c INT), ADD d INT",
71+
"ALTER TABLE t ADD CONSTRAINT UNIQUE KEY uk (a)",
72+
"SELECT JSON_ARRAY() FROM t",
73+
"SELECT JSON_OBJECT() FROM t"})
74+
void rendersSingleSpacesWithoutDialect(String sql) throws JSQLParserException {
75+
assertRendersExactly(sql, null);
76+
}
77+
78+
@ParameterizedTest
79+
@ValueSource(strings = {"SELECT JSON_ARRAY(1, 2) AS a FROM t",
80+
"SELECT JSON_ARRAY(NULL ON NULL) AS a FROM t",
81+
"SELECT JSON_OBJECT(KEY 'a' VALUE 1) AS o FROM t",
82+
"SELECT JSON_OBJECT(ABSENT ON NULL) AS o FROM t"})
83+
void jsonConstructorsAddNoDoubleOrTrailingSpace(String sql) throws JSQLParserException {
84+
Statement statement = CCJSqlParserUtil.parse(sql);
85+
String rendered = statement.toString();
86+
assertFalse(rendered.contains(" "), rendered);
87+
assertEquals(rendered, deparse(statement));
88+
}
89+
90+
@Test
91+
void columnVisibilityHasSingleSpaces() {
92+
assertEquals("c SET INVISIBLE",
93+
new AlterExpression.ColumnSetVisibility("c", false).toString());
94+
assertEquals("c SET VISIBLE",
95+
new AlterExpression.ColumnSetVisibility("c", true).toString());
96+
}
97+
98+
@Test
99+
void exchangePartitionHasNoTrailingSpace() {
100+
AlterExpression expression = new AlterExpression()
101+
.withOperation(AlterOperation.EXCHANGE_PARTITION);
102+
expression.setPartitions(List.of("p0"));
103+
expression.setExchangePartitionTableName("t2");
104+
expression.setExchangePartitionWithValidation(true);
105+
assertEquals("EXCHANGE PARTITION p0 WITH TABLE t2 WITH VALIDATION",
106+
expression.toString());
107+
}
108+
109+
@Test
110+
void indexWithoutDeclarationHasNoLeadingSpace() throws JSQLParserException {
111+
CreateIndex createIndex = (CreateIndex) CCJSqlParserUtil.parse("CREATE INDEX ON t (a)",
112+
parser -> parser.withDialect(Dialect.POSTGRESQL));
113+
assertEquals("(a)", createIndex.getIndex().toString());
114+
assertEquals("(a)", new Index().withColumnsNames(List.of("a")).toString());
115+
assertEquals("INDEX i INVISIBLE", new Index().withIndexKeyword("INDEX").withName("i")
116+
.withIndexSpec(List.of("INVISIBLE")).toString());
117+
}
118+
119+
private static void assertRendersExactly(String sql, Dialect dialect)
120+
throws JSQLParserException {
121+
Statement statement = dialect == null ? CCJSqlParserUtil.parse(sql)
122+
: CCJSqlParserUtil.parse(sql, parser -> parser.withDialect(dialect));
41123
assertEquals(sql, statement.toString());
124+
assertEquals(sql, deparse(statement));
125+
}
42126

127+
private static String deparse(Statement statement) {
43128
StringBuilder deparsed = new StringBuilder();
44129
statement.accept(new StatementDeParser(deparsed));
45-
assertEquals(sql, deparsed.toString());
130+
return deparsed.toString();
46131
}
47132

48133
@Test

0 commit comments

Comments
 (0)