Skip to content

Commit ef42d0b

Browse files
authored
fix: traverse and deparse structured table partitions (#2610)
* fix: traverse and deparse structured table partitions * fix: preserve unbounded partition markers during expression rewrites
1 parent 08de141 commit ef42d0b

7 files changed

Lines changed: 318 additions & 18 deletions

File tree

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

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import java.util.Collections;
1515
import java.util.List;
1616
import java.util.Optional;
17+
import java.util.function.Consumer;
18+
import net.sf.jsqlparser.expression.Expression;
1719
import net.sf.jsqlparser.schema.Table;
1820
import net.sf.jsqlparser.statement.create.table.PartitionBound;
1921
import net.sf.jsqlparser.statement.create.table.PartitionDefinition;
@@ -292,6 +294,15 @@ public AlterExpressionPartition addPartitionDefinitions(
292294

293295
@Override
294296
protected void appendBody(StringBuilder b) {
297+
appendBody(b, b::append);
298+
}
299+
300+
public void appendTo(StringBuilder b, Consumer<Expression> expressionPrinter) {
301+
appendBody(b, expressionPrinter);
302+
appendCommonTail(b);
303+
}
304+
305+
private void appendBody(StringBuilder b, Consumer<Expression> expressionPrinter) {
295306
switch (getOperation()) {
296307
case ADD_PARTITION:
297308
b.append("ADD PARTITION ")
@@ -302,8 +313,12 @@ protected void appendBody(StringBuilder b) {
302313
.append(PlainSelect.getStringList(getPartitionNames()));
303314
break;
304315
case ATTACH_PARTITION:
305-
b.append("ATTACH PARTITION ").append(partitionTable).append(" ")
306-
.append(partitionBound);
316+
b.append("ATTACH PARTITION ").append(partitionTable).append(' ');
317+
if (partitionBound == null) {
318+
b.append("null");
319+
} else {
320+
partitionBound.appendTo(b, expressionPrinter);
321+
}
307322
break;
308323
case DETACH_PARTITION:
309324
b.append("DETACH PARTITION ").append(partitionTable);
@@ -313,7 +328,7 @@ protected void appendBody(StringBuilder b) {
313328
break;
314329
case PARTITION_BY:
315330
if (partitioning != null) {
316-
b.append(partitioning);
331+
partitioning.appendTo(b, expressionPrinter);
317332
} else {
318333
toStringPartition(b);
319334
}

src/main/java/net/sf/jsqlparser/statement/create/table/PartitionBound.java

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
package net.sf.jsqlparser.statement.create.table;
1111

1212
import java.io.Serializable;
13+
import java.util.function.Consumer;
1314
import net.sf.jsqlparser.expression.Expression;
15+
import net.sf.jsqlparser.schema.Column;
1416
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
1517

1618
/** A PostgreSQL declarative-partition bound. */
@@ -113,17 +115,101 @@ public PartitionBound withRemainder(Expression remainder) {
113115

114116
@Override
115117
public String toString() {
118+
StringBuilder sql = new StringBuilder();
119+
appendTo(sql, sql::append);
120+
return sql.toString();
121+
}
122+
123+
public void appendTo(StringBuilder sql, Consumer<Expression> expressionPrinter) {
116124
switch (type) {
117125
case RANGE:
118-
return "FOR VALUES FROM (" + fromExpressions + ") TO (" + toExpressions + ")";
126+
sql.append("FOR VALUES FROM (");
127+
appendRangeValues(sql, fromExpressions, expressionPrinter);
128+
sql.append(") TO (");
129+
appendRangeValues(sql, toExpressions, expressionPrinter);
130+
sql.append(')');
131+
break;
119132
case LIST:
120-
return "FOR VALUES IN (" + inExpressions + ")";
133+
sql.append("FOR VALUES IN (");
134+
expressionPrinter.accept(inExpressions);
135+
sql.append(')');
136+
break;
121137
case HASH:
122-
return "FOR VALUES WITH (MODULUS " + modulus + ", REMAINDER " + remainder + ")";
138+
sql.append("FOR VALUES WITH (MODULUS ");
139+
expressionPrinter.accept(modulus);
140+
sql.append(", REMAINDER ");
141+
expressionPrinter.accept(remainder);
142+
sql.append(')');
143+
break;
123144
case DEFAULT:
124-
return "DEFAULT";
145+
sql.append("DEFAULT");
146+
break;
147+
default:
148+
break;
149+
}
150+
}
151+
152+
/** Visits active bound expressions, excluding the MINVALUE/MAXVALUE range markers. */
153+
public void visitExpressions(Consumer<Expression> expressions) {
154+
if (type == null) {
155+
return;
156+
}
157+
switch (type) {
158+
case RANGE:
159+
visitRangeValues(fromExpressions, expressions);
160+
visitRangeValues(toExpressions, expressions);
161+
break;
162+
case LIST:
163+
if (inExpressions != null) {
164+
expressions.accept(inExpressions);
165+
}
166+
break;
167+
case HASH:
168+
if (modulus != null) {
169+
expressions.accept(modulus);
170+
}
171+
if (remainder != null) {
172+
expressions.accept(remainder);
173+
}
174+
break;
125175
default:
126-
return "";
176+
break;
127177
}
128178
}
179+
180+
private static void visitRangeValues(ExpressionList<Expression> values,
181+
Consumer<Expression> expressions) {
182+
if (values != null) {
183+
values.stream().filter(value -> !isRangeMarker(value)).forEach(expressions);
184+
}
185+
}
186+
187+
private static void appendRangeValues(StringBuilder sql, ExpressionList<Expression> values,
188+
Consumer<Expression> expressions) {
189+
if (values == null) {
190+
sql.append("null");
191+
return;
192+
}
193+
for (int i = 0; i < values.size(); i++) {
194+
if (i > 0) {
195+
sql.append(", ");
196+
}
197+
Expression value = values.get(i);
198+
if (isRangeMarker(value)) {
199+
sql.append(value);
200+
} else {
201+
expressions.accept(value);
202+
}
203+
}
204+
}
205+
206+
private static boolean isRangeMarker(Expression expression) {
207+
if (!(expression instanceof Column)) {
208+
return false;
209+
}
210+
Column column = (Column) expression;
211+
return column.getTable() == null && ("MINVALUE".equalsIgnoreCase(column.getColumnName())
212+
|| "MAXVALUE".equalsIgnoreCase(column.getColumnName()));
213+
}
214+
129215
}

src/main/java/net/sf/jsqlparser/statement/create/table/TablePartitioning.java

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.util.Collections;
1616
import java.util.List;
1717
import java.util.Optional;
18+
import java.util.function.Consumer;
1819
import net.sf.jsqlparser.expression.Expression;
1920
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
2021
import net.sf.jsqlparser.schema.Column;
@@ -256,14 +257,20 @@ public TablePartitioning addPartitionOptions(Collection<String> partitionOptions
256257

257258
@Override
258259
public String toString() {
259-
StringBuilder builder = new StringBuilder("PARTITION BY ");
260-
appendMethod(builder);
260+
StringBuilder builder = new StringBuilder();
261+
appendTo(builder, builder::append);
262+
return builder.toString();
263+
}
264+
265+
public void appendTo(StringBuilder builder, Consumer<Expression> expressionPrinter) {
266+
builder.append("PARTITION BY ");
267+
appendMethod(builder, expressionPrinter);
261268
if (partitions != null) {
262269
builder.append(" PARTITIONS ").append(partitions);
263270
}
264271
if (subPartitioning != null) {
265272
builder.append(" SUBPARTITION BY ");
266-
subPartitioning.appendMethod(builder);
273+
subPartitioning.appendMethod(builder, expressionPrinter);
267274
if (subPartitioning.getPartitions() != null) {
268275
builder.append(" SUBPARTITIONS ").append(subPartitioning.getPartitions());
269276
}
@@ -275,10 +282,9 @@ public String toString() {
275282
if (partitionOptions != null && !partitionOptions.isEmpty()) {
276283
builder.append(" ").append(PlainSelect.getStringList(partitionOptions, false, false));
277284
}
278-
return builder.toString();
279285
}
280286

281-
private void appendMethod(StringBuilder builder) {
287+
private void appendMethod(StringBuilder builder, Consumer<Expression> expressionPrinter) {
282288
if (linear) {
283289
builder.append("LINEAR ");
284290
}
@@ -291,11 +297,17 @@ private void appendMethod(StringBuilder builder) {
291297
builder.append(" COLUMNS");
292298
}
293299
if (expression != null) {
294-
builder.append(" (").append(expression).append(")");
300+
builder.append(" (");
301+
expressionPrinter.accept(expression);
302+
builder.append(')');
295303
} else if (expressionList != null) {
296-
builder.append(" (").append(expressionList).append(")");
304+
builder.append(" (");
305+
expressionPrinter.accept(expressionList);
306+
builder.append(')');
297307
} else if (columns != null) {
298-
builder.append(" ").append(PlainSelect.getStringList(columns, true, true));
308+
builder.append(" (");
309+
expressionPrinter.accept(columns);
310+
builder.append(')');
299311
}
300312
}
301313
}

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import net.sf.jsqlparser.schema.Table;
1616
import net.sf.jsqlparser.statement.LikeClause;
1717
import net.sf.jsqlparser.statement.alter.AlterExpression;
18+
import net.sf.jsqlparser.statement.alter.AlterExpressionPartition;
1819
import net.sf.jsqlparser.statement.alter.AlterExpressionPrimaryKey;
1920
import net.sf.jsqlparser.statement.create.index.CreateIndex;
2021
import net.sf.jsqlparser.statement.create.table.CheckConstraint;
@@ -26,6 +27,8 @@
2627
import net.sf.jsqlparser.statement.create.table.ForeignKeyIndex;
2728
import net.sf.jsqlparser.statement.create.table.Index;
2829
import net.sf.jsqlparser.statement.create.table.TableElement;
30+
import net.sf.jsqlparser.statement.create.table.TablePartitioning;
31+
import net.sf.jsqlparser.statement.create.table.PartitionBound;
2932

3033
/** Traverses structured table definitions without interpreting legacy raw column options. */
3134
public final class TableDefinitionTraversal {
@@ -54,6 +57,26 @@ public static void visit(AlterExpression action, Consumer<Expression> expression
5457
if (action.getIndex() != null) {
5558
visit(action.getIndex(), expressions, tables);
5659
}
60+
if (action instanceof AlterExpressionPartition) {
61+
AlterExpressionPartition partition = (AlterExpressionPartition) action;
62+
switch (partition.getOperation()) {
63+
case ATTACH_PARTITION:
64+
accept(partition.getPartitionTable(), tables);
65+
visit(partition.getPartitionBound(), expressions);
66+
break;
67+
case DETACH_PARTITION:
68+
accept(partition.getPartitionTable(), tables);
69+
break;
70+
case EXCHANGE_PARTITION:
71+
accept(partition.getExchangeTable(), tables);
72+
break;
73+
case PARTITION_BY:
74+
visit(partition.getPartitioning(), expressions);
75+
break;
76+
default:
77+
break;
78+
}
79+
}
5780
if (action instanceof AlterExpressionPrimaryKey) {
5881
AlterExpressionPrimaryKey primaryKey = (AlterExpressionPrimaryKey) action;
5982
if (primaryKey.isUsingHash()) {
@@ -76,6 +99,30 @@ public static void visit(CreateTable table, Consumer<Expression> expressions,
7699
}
77100
accept(table.getTrailingLikeTable(), tables);
78101
accept(table.getPartitionOf(), tables);
102+
visit(table.getPartitioning(), expressions);
103+
visit(table.getPartitionBound(), expressions);
104+
}
105+
106+
/** Visits the active partition key and any subpartition key. Raw bounds remain opaque. */
107+
public static void visit(TablePartitioning partitioning, Consumer<Expression> expressions) {
108+
if (partitioning == null) {
109+
return;
110+
}
111+
if (partitioning.getExpression() != null) {
112+
accept(partitioning.getExpression(), expressions);
113+
} else if (partitioning.getExpressionList() != null) {
114+
accept(partitioning.getExpressionList(), expressions);
115+
} else {
116+
accept(partitioning.getColumns(), expressions);
117+
}
118+
visit(partitioning.getSubPartitioning(), expressions);
119+
}
120+
121+
/** Visits expressions belonging to the selected PostgreSQL bound type. */
122+
public static void visit(PartitionBound bound, Consumer<Expression> expressions) {
123+
if (bound != null) {
124+
bound.visitExpressions(expressions);
125+
}
79126
}
80127

81128
public static void visit(TableElement element, Consumer<Expression> expressions,

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import net.sf.jsqlparser.statement.alter.Alter;
1313
import net.sf.jsqlparser.expression.ExpressionVisitor;
1414
import net.sf.jsqlparser.statement.alter.AlterExpression;
15+
import net.sf.jsqlparser.statement.alter.AlterExpressionPartition;
1516
import net.sf.jsqlparser.statement.alter.AlterExpressionPrimaryKey;
1617
import net.sf.jsqlparser.statement.alter.AlterOperation;
1718
import net.sf.jsqlparser.statement.select.PlainSelect;
@@ -49,6 +50,11 @@ public void deParse(Alter alter) {
4950
}
5051

5152
private void deParseAction(AlterExpression action) {
53+
if (action instanceof AlterExpressionPartition) {
54+
((AlterExpressionPartition) action).appendTo(builder,
55+
expression -> expression.accept(expressionVisitor, null));
56+
return;
57+
}
5258
if (action instanceof AlterExpressionPrimaryKey) {
5359
((AlterExpressionPrimaryKey) action).appendTo(builder,
5460
expression -> expression.accept(expressionVisitor, null));

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,21 @@ public void deParse(CreateTable createTable) {
105105
}
106106

107107
if (createTable.getPartitionBound() != null) {
108-
builder.append(' ').append(createTable.getPartitionBound());
108+
builder.append(' ');
109+
createTable.getPartitionBound().appendTo(builder,
110+
expression -> expression.accept(statementDeParser.getExpressionDeParser(),
111+
null));
109112
}
110113

111114
params = PlainSelect.getStringList(createTable.getTableOptionsStrings(), false, false);
112115
if (!"".equals(params)) {
113116
builder.append(' ').append(params);
114117
}
115118
if (createTable.getPartitioning() != null) {
116-
builder.append(' ').append(createTable.getPartitioning());
119+
builder.append(' ');
120+
createTable.getPartitioning().appendTo(builder,
121+
expression -> expression.accept(statementDeParser.getExpressionDeParser(),
122+
null));
117123
}
118124

119125
if (createTable.getRowMovement() != null) {

0 commit comments

Comments
 (0)