Skip to content

Commit 08de141

Browse files
authored
feat: preserve ALTER COLUMN default expressions in the AST (#2611)
* feat: preserve ALTER COLUMN default expressions in the AST * refactor: group default rendering with column action dispatch
1 parent 9caed41 commit 08de141

6 files changed

Lines changed: 163 additions & 13 deletions

File tree

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

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.util.List;
1919
import java.util.Optional;
2020
import java.util.Set;
21+
import java.util.function.Consumer;
2122
import java.util.stream.Collectors;
2223
import net.sf.jsqlparser.expression.Expression;
2324
import net.sf.jsqlparser.statement.ReferentialAction;
@@ -883,14 +884,29 @@ protected void toStringConstraintAlter(StringBuilder b) {
883884
}
884885

885886
protected void toStringAlterColumn(StringBuilder b) {
887+
appendAlterColumn(b, b::append);
888+
}
889+
890+
/** Renders ALTER COLUMN default/visibility actions with their common tail. */
891+
public void appendColumnActionTo(StringBuilder b, Consumer<Expression> expressionPrinter) {
892+
appendAlterColumn(b, expressionPrinter);
893+
appendCommonTail(b);
894+
}
895+
896+
private void appendAlterColumn(StringBuilder b, Consumer<Expression> expressionPrinter) {
886897
b.append("ALTER ");
887898
if (hasColumn) {
888899
b.append("COLUMN ");
889900
}
890901
if (columnDropDefaultList != null && !columnDropDefaultList.isEmpty()) {
891902
b.append(PlainSelect.getStringList(columnDropDefaultList));
892903
} else if (columnSetDefaultList != null && !columnSetDefaultList.isEmpty()) {
893-
b.append(PlainSelect.getStringList(columnSetDefaultList));
904+
for (int i = 0; i < columnSetDefaultList.size(); i++) {
905+
if (i > 0) {
906+
b.append(", ");
907+
}
908+
columnSetDefaultList.get(i).appendTo(b, expressionPrinter);
909+
}
894910
} else {
895911
b.append(PlainSelect.getStringList(columnSetVisibilityList));
896912
}
@@ -1575,7 +1591,8 @@ public String toString() {
15751591

15761592
public static final class ColumnSetDefault implements Serializable {
15771593
private final String columnName;
1578-
private final String defaultValue;
1594+
private String defaultValue;
1595+
private Expression defaultExpression;
15791596

15801597
public ColumnSetDefault(String columnName, String defaultValue) {
15811598
this.columnName = columnName;
@@ -1586,13 +1603,40 @@ public String getColumnName() {
15861603
return columnName;
15871604
}
15881605

1606+
/** Constructs a structured default without overloading the legacy nullable String API. */
1607+
public static ColumnSetDefault fromExpression(String columnName, Expression expression) {
1608+
ColumnSetDefault result = new ColumnSetDefault(columnName, null);
1609+
result.setDefaultExpression(expression);
1610+
return result;
1611+
}
1612+
1613+
public Expression getDefaultExpression() {
1614+
return defaultExpression;
1615+
}
1616+
1617+
public void setDefaultExpression(Expression defaultExpression) {
1618+
this.defaultExpression = defaultExpression;
1619+
this.defaultValue = null;
1620+
}
1621+
15891622
public String getDefaultValue() {
1590-
return defaultValue;
1623+
return defaultExpression == null ? defaultValue : defaultExpression.toString();
1624+
}
1625+
1626+
public void appendTo(StringBuilder sql, Consumer<Expression> expressionPrinter) {
1627+
sql.append(columnName).append(" SET DEFAULT ");
1628+
if (defaultExpression == null) {
1629+
sql.append(defaultValue);
1630+
} else {
1631+
expressionPrinter.accept(defaultExpression);
1632+
}
15911633
}
15921634

15931635
@Override
15941636
public String toString() {
1595-
return columnName + " SET DEFAULT " + defaultValue;
1637+
StringBuilder sql = new StringBuilder();
1638+
appendTo(sql, sql::append);
1639+
return sql.toString();
15961640
}
15971641
}
15981642

src/main/java/net/sf/jsqlparser/util/TableDefinitionTraversal.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ public static void visit(CreateIndex createIndex, Consumer<Expression> expressio
4444
/** Visits the structured definitions and expressions belonging to a single ALTER action. */
4545
public static void visit(AlterExpression action, Consumer<Expression> expressions,
4646
Consumer<Table> tables) {
47+
if (action.getColumnSetDefaultList() != null) {
48+
action.getColumnSetDefaultList()
49+
.forEach(column -> accept(column.getDefaultExpression(), expressions));
50+
}
4751
if (action.getColDataTypeList() != null) {
4852
action.getColDataTypeList().forEach(column -> visit(column, expressions, tables));
4953
}

src/main/java/net/sf/jsqlparser/util/deparser/AlterDeParser.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ private void deParseAction(AlterExpression action) {
6868
deParseTail(action);
6969
return;
7070
}
71+
if (action.getOperation() == net.sf.jsqlparser.statement.alter.AlterOperation.ALTER
72+
&& action.getColumnSetDefaultList() != null
73+
&& !action.getColumnSetDefaultList().isEmpty()) {
74+
action.appendColumnActionTo(builder,
75+
expression -> expression.accept(expressionVisitor, null));
76+
return;
77+
}
7178
if (action.getColDataTypeList() == null || action.getColDataTypeList().size() != 1
7279
|| action.getColDataTypeList().get(0).getUsingExpression() == null) {
7380
builder.append(action);

src/main/java/net/sf/jsqlparser/util/validation/validator/AlterValidator.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,8 @@ public void validate(Alter alter) {
4040
}
4141

4242
public void validate(Alter alter, AlterExpression e) {
43-
if (e.getColDataTypeList() != null) {
44-
e.getColDataTypeList().forEach(column -> TableDefinitionTraversal.visit(column,
45-
this::validateOptionalExpression, this::validateOptionalFromItem));
46-
}
47-
if (e.getIndex() != null) {
48-
TableDefinitionTraversal.visit(e.getIndex(), this::validateOptionalExpression,
49-
this::validateOptionalFromItem);
50-
}
43+
TableDefinitionTraversal.visit(e, this::validateOptionalExpression,
44+
this::validateOptionalFromItem);
5145
for (ValidationCapability c : getCapabilities()) {
5246

5347
validateOptionalColumnName(c, e.getColumnOldName());

src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14552,7 +14552,7 @@ AlterExpression.ColumnSetDefault AlterExpressionColumnSetDefault():
1455214552
{
1455314553
columnName = RelObjectName() <K_SET> <K_DEFAULT> defaultValue = Expression()
1455414554
{
14555-
return new AlterExpression.ColumnSetDefault(columnName, defaultValue.toString());
14555+
return AlterExpression.ColumnSetDefault.fromExpression(columnName, defaultValue);
1455614556
}
1455714557
}
1455814558

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2026 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.statement.alter;
11+
12+
import static org.assertj.core.api.Assertions.assertThat;
13+
import static org.junit.jupiter.api.Assertions.assertEquals;
14+
import static org.junit.jupiter.api.Assertions.assertNotNull;
15+
import static org.junit.jupiter.api.Assertions.assertNull;
16+
17+
import java.util.ArrayList;
18+
import java.util.List;
19+
import net.sf.jsqlparser.JSQLParserException;
20+
import net.sf.jsqlparser.expression.ExpressionVisitorAdapter;
21+
import net.sf.jsqlparser.expression.LongValue;
22+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
23+
import net.sf.jsqlparser.statement.StatementVisitorAdapter;
24+
import net.sf.jsqlparser.statement.alter.AlterExpression.ColumnSetDefault;
25+
import net.sf.jsqlparser.statement.select.SelectVisitorAdapter;
26+
import net.sf.jsqlparser.util.deparser.ExpressionDeParser;
27+
import net.sf.jsqlparser.util.deparser.SelectDeParser;
28+
import net.sf.jsqlparser.util.deparser.StatementDeParser;
29+
import org.junit.jupiter.api.Test;
30+
import org.junit.jupiter.params.ParameterizedTest;
31+
import org.junit.jupiter.params.provider.ValueSource;
32+
33+
class AlterColumnDefaultExpressionTest {
34+
@ParameterizedTest
35+
@ValueSource(strings = {"(1 + 2)", "NULL", "CURRENT_TIMESTAMP",
36+
"nextval('app.counter'::regclass)", "'value'::text", "-1"})
37+
void parsedDefaultRetainsExpressionAndLegacyText(String value) throws JSQLParserException {
38+
Alter alter = parse(value);
39+
ColumnSetDefault column =
40+
alter.getAlterExpressions().get(0).getColumnSetDefaultList().get(0);
41+
assertNotNull(column.getDefaultExpression());
42+
assertEquals(column.getDefaultExpression().toString(), column.getDefaultValue());
43+
StringBuilder buffer = new StringBuilder();
44+
alter.accept(new StatementDeParser(buffer), null);
45+
assertEquals(alter.toString(), buffer.toString());
46+
assertEquals(alter.toString(), CCJSqlParserUtil.parse(buffer.toString()).toString());
47+
}
48+
49+
@Test
50+
void visitorReachesDefaultsInEachAlterActionWithContext() throws JSQLParserException {
51+
List<Long> values = new ArrayList<>();
52+
ExpressionVisitorAdapter<Void> expressions = new ExpressionVisitorAdapter<Void>() {
53+
@Override
54+
public <S> Void visit(LongValue value, S context) {
55+
assertEquals("context", context);
56+
values.add(value.getValue());
57+
return null;
58+
}
59+
};
60+
CCJSqlParserUtil.parse("ALTER TABLE t ALTER COLUMN a SET DEFAULT (1 + 2), "
61+
+ "ALTER COLUMN b SET DEFAULT 3")
62+
.accept(new StatementVisitorAdapter<>(new SelectVisitorAdapter<>(expressions)),
63+
"context");
64+
assertThat(values).containsExactly(1L, 2L, 3L);
65+
}
66+
67+
@Test
68+
void customDeparserAndAstEditsUseTheStructuredDefault() throws JSQLParserException {
69+
Alter alter = parse("(1 + 2)");
70+
StringBuilder output = new StringBuilder();
71+
ExpressionDeParser expressions = new ExpressionDeParser() {
72+
@Override
73+
public <S> StringBuilder visit(LongValue value, S context) {
74+
return getBuilder().append(value.getValue() + 100);
75+
}
76+
};
77+
alter.accept(new StatementDeParser(expressions, new SelectDeParser(), output), null);
78+
assertEquals("ALTER TABLE t ALTER COLUMN a SET DEFAULT (101 + 102)", output.toString());
79+
ColumnSetDefault column =
80+
alter.getAlterExpressions().get(0).getColumnSetDefaultList().get(0);
81+
column.setDefaultExpression(new LongValue(42));
82+
assertEquals("42", column.getDefaultValue());
83+
assertEquals("ALTER TABLE t ALTER COLUMN a SET DEFAULT 42", alter.toString());
84+
}
85+
86+
@Test
87+
void legacyStringConstructorRemainsOpaqueAndAcceptsNull() {
88+
ColumnSetDefault column = new ColumnSetDefault("a", "vendor_default()");
89+
assertNull(column.getDefaultExpression());
90+
assertEquals("a SET DEFAULT vendor_default()", column.toString());
91+
assertEquals("a SET DEFAULT null", new ColumnSetDefault("a", null).toString());
92+
column.setDefaultExpression(new LongValue(1));
93+
assertEquals("1", column.getDefaultValue());
94+
column.setDefaultExpression(null);
95+
assertNull(column.getDefaultValue());
96+
}
97+
98+
private static Alter parse(String value) throws JSQLParserException {
99+
return (Alter) CCJSqlParserUtil.parse("ALTER TABLE t ALTER COLUMN a SET DEFAULT " + value);
100+
}
101+
}

0 commit comments

Comments
 (0)