Skip to content

Commit d2c4bde

Browse files
committed
feat: share PostgreSQL column statistics and attribute actions
Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent 2011dab commit d2c4bde

3 files changed

Lines changed: 136 additions & 8 deletions

File tree

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

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public enum Kind {
2323
}
2424

2525
public enum ColumnAction {
26-
SET_DEFAULT, DROP_DEFAULT, SET_STATISTICS, SET_STORAGE, SET_COMPRESSION, DROP_EXPRESSION
26+
SET_DEFAULT, DROP_DEFAULT, SET_STATISTICS, SET_STORAGE, SET_COMPRESSION, DROP_EXPRESSION, SET_OPTIONS, RESET_OPTIONS
2727
}
2828

2929
public enum ReplicaIdentity {
@@ -37,6 +37,7 @@ public enum ReplicaIdentity {
3737
private String value;
3838
private Integer columnNumber;
3939
private Long statistics;
40+
private boolean statisticsDefault;
4041
private Expression defaultExpression;
4142
private Table relation;
4243
private boolean noInherit;
@@ -102,6 +103,18 @@ public Long getStatistics() {
102103

103104
public void setStatistics(Long statistics) {
104105
this.statistics = statistics;
106+
statisticsDefault = false;
107+
}
108+
109+
public boolean isStatisticsDefault() {
110+
return statisticsDefault;
111+
}
112+
113+
public void setStatisticsDefault(boolean statisticsDefault) {
114+
this.statisticsDefault = statisticsDefault;
115+
if (statisticsDefault) {
116+
statistics = null;
117+
}
105118
}
106119

107120
public Expression getDefaultExpression() {
@@ -237,7 +250,16 @@ private void appendColumnChange(StringBuilder builder, Consumer<Expression> expr
237250
builder.append(" DROP DEFAULT");
238251
break;
239252
case SET_STATISTICS:
240-
builder.append(" SET STATISTICS ").append(statistics);
253+
builder.append(" SET STATISTICS ")
254+
.append(statisticsDefault ? "DEFAULT" : statistics);
255+
break;
256+
case SET_OPTIONS:
257+
builder.append(" SET ");
258+
Index.Option.appendListTo(builder, options, expressionPrinter);
259+
break;
260+
case RESET_OPTIONS:
261+
builder.append(" RESET ")
262+
.append(PlainSelect.getStringList(resetOptions, true, true));
241263
break;
242264
case SET_STORAGE:
243265
builder.append(" SET STORAGE ").append(value);
@@ -259,7 +281,8 @@ private void appendColumnChange(StringBuilder builder, Consumer<Expression> expr
259281
public void visitExpressions(Consumer<Expression> visitor) {
260282
if (kind == Kind.ALTER_COLUMN && columnAction == ColumnAction.SET_DEFAULT) {
261283
visitor.accept(defaultExpression);
262-
} else if (kind == Kind.SET_OPTIONS && options != null) {
284+
} else if ((kind == Kind.SET_OPTIONS || kind == Kind.ALTER_COLUMN
285+
&& columnAction == ColumnAction.SET_OPTIONS) && options != null) {
263286
options.stream().map(Index.Option::getValue).filter(java.util.Objects::nonNull)
264287
.forEach(visitor);
265288
}

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

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1829,8 +1829,10 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
18291829
|| getToken(4).kind == K_NO && "INHERIT".equalsIgnoreCase(getToken(5).image);
18301830
}
18311831
int offset = getToken(2).kind == K_COLUMN ? 4 : 3;
1832-
return getToken(offset).kind == K_DROP && "EXPRESSION".equalsIgnoreCase(getToken(offset + 1).image)
1833-
|| getToken(offset).kind == K_SET && (getToken(offset + 1).kind == K_STORAGE
1832+
return getToken(offset).kind == K_RESET
1833+
|| getToken(offset).kind == K_DROP && "EXPRESSION".equalsIgnoreCase(getToken(offset + 1).image)
1834+
|| getToken(offset).kind == K_SET && (getToken(offset + 1).kind == OPENING_BRACKET
1835+
|| getToken(offset + 1).kind == K_STORAGE
18341836
|| "COMPRESSION".equalsIgnoreCase(getToken(offset + 1).image)
18351837
|| "STATISTICS".equalsIgnoreCase(getToken(offset + 1).image));
18361838
}
@@ -1865,7 +1867,9 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
18651867
requireDdlSyntax(type == AlterRelation.ObjectType.VIEW ? defaults
18661868
: column == RelationAlterAction.ColumnAction.SET_STATISTICS
18671869
|| column == RelationAlterAction.ColumnAction.SET_STORAGE
1868-
|| column == RelationAlterAction.ColumnAction.SET_COMPRESSION,
1870+
|| column == RelationAlterAction.ColumnAction.SET_COMPRESSION
1871+
|| column == RelationAlterAction.ColumnAction.SET_OPTIONS
1872+
|| column == RelationAlterAction.ColumnAction.RESET_OPTIONS,
18691873
"Unsupported view column action: " + column);
18701874
}
18711875
}
@@ -18385,8 +18389,21 @@ RelationAlterAction PostgreSqlRelationAction():
1838518389
action.setDefaultExpression(expression);
1838618390
}
1838718391
|
18388-
LOOKAHEAD({ isKeywordAhead("STATISTICS") }) ContextualKeyword("STATISTICS") statistics=SequenceParameterValue()
18389-
{ action.setColumnAction(RelationAlterAction.ColumnAction.SET_STATISTICS); action.setStatistics(statistics); }
18392+
LOOKAHEAD({ isKeywordAhead("STATISTICS") }) ContextualKeyword("STATISTICS")
18393+
{ action.setColumnAction(RelationAlterAction.ColumnAction.SET_STATISTICS); }
18394+
( <K_DEFAULT> { action.setStatisticsDefault(true); }
18395+
| statistics=SequenceParameterValue() {
18396+
requireDdlSyntax(statistics >= -1 && statistics <= 10000, "Statistics target must be between -1 and 10000");
18397+
action.setStatistics(statistics);
18398+
} )
18399+
|
18400+
LOOKAHEAD("(") options=PostgreSqlIndexOptions() {
18401+
for (Index.Option option : options) {
18402+
requireDdlSyntax(option.getValue() != null && option.isUseEquals(),
18403+
"Column attributes require a value and an equals sign");
18404+
}
18405+
action.setColumnAction(RelationAlterAction.ColumnAction.SET_OPTIONS); action.setOptions(options);
18406+
}
1839018407
|
1839118408
<K_STORAGE> ( token=<K_EXTENDED> | token=<K_DEFAULT> | token=<S_IDENTIFIER> ) {
1839218409
requireDdlSyntax(Arrays.asList("PLAIN", "EXTERNAL", "EXTENDED", "MAIN", "DEFAULT")
@@ -18398,6 +18415,10 @@ RelationAlterAction PostgreSqlRelationAction():
1839818415
{ action.setColumnAction(RelationAlterAction.ColumnAction.SET_COMPRESSION); action.setValue(name); }
1839918416
)
1840018417
|
18418+
<K_RESET> "(" name=RelObjectName() { names.add(name); }
18419+
( "," name=RelObjectName() { names.add(name); } )* ")"
18420+
{ action.setColumnAction(RelationAlterAction.ColumnAction.RESET_OPTIONS); action.setResetOptions(names); }
18421+
|
1840118422
<K_DROP>
1840218423
(
1840318424
<K_DEFAULT> { action.setColumnAction(RelationAlterAction.ColumnAction.DROP_DEFAULT); }
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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.junit.jupiter.api.Assertions.*;
13+
import java.util.ArrayList;
14+
import java.util.List;
15+
import net.sf.jsqlparser.JSQLParserException;
16+
import net.sf.jsqlparser.expression.Expression;
17+
import net.sf.jsqlparser.expression.LongValue;
18+
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
19+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
20+
import net.sf.jsqlparser.statement.Statement;
21+
import net.sf.jsqlparser.util.deparser.StatementDeParser;
22+
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.params.ParameterizedTest;
24+
import org.junit.jupiter.params.provider.ValueSource;
25+
26+
class PostgreSqlColumnStatisticsTest {
27+
@ParameterizedTest
28+
@ValueSource(strings = {"SET STATISTICS DEFAULT", "SET STATISTICS -1", "SET STATISTICS 10000",
29+
"SET (n_distinct = -0.5)", "SET (n_distinct = -1, n_distinct_inherited = 20)",
30+
"RESET (n_distinct)", "RESET (n_distinct, n_distinct_inherited)"})
31+
void tablesAndMaterializedViewsShareColumnActions(String action) throws JSQLParserException {
32+
for (String object : List.of("TABLE t", "MATERIALIZED VIEW mv")) {
33+
String sql = "ALTER " + object + " ALTER COLUMN id " + action;
34+
Statement statement = parse(sql);
35+
assertRoundTrip(statement);
36+
assertEquals(2, CCJSqlParserUtil.parseStatements(sql + "; SELECT 1",
37+
p -> p.withDialect(Dialect.POSTGRESQL)).size());
38+
}
39+
}
40+
41+
@Test
42+
void defaultTargetsAndAttributeValuesAreMutable() throws JSQLParserException {
43+
AlterRelation index =
44+
(AlterRelation) parse("ALTER INDEX ix ALTER COLUMN 1 SET STATISTICS DEFAULT");
45+
RelationAlterAction action = index.getActions().get(0);
46+
assertTrue(action.isStatisticsDefault());
47+
assertNull(action.getStatistics());
48+
action.setStatistics(200L);
49+
assertFalse(action.isStatisticsDefault());
50+
assertEquals("ALTER INDEX ix ALTER COLUMN 1 SET STATISTICS 200", index.toString());
51+
assertRoundTrip(index);
52+
action.setStatisticsDefault(true);
53+
assertRoundTrip(index);
54+
Alter table = (Alter) parse("ALTER TABLE t ALTER COLUMN id SET (n_distinct=10)");
55+
action = (RelationAlterAction) table.getAlterExpressions().get(0);
56+
action.getOptions().get(0).setValue(new LongValue(20));
57+
List<Expression> visited = new ArrayList<>();
58+
action.visitExpressions(visited::add);
59+
assertEquals(List.of(new LongValue(20)), visited);
60+
assertEquals("ALTER TABLE t ALTER COLUMN id SET (n_distinct = 20)", table.toString());
61+
assertRoundTrip(table);
62+
}
63+
64+
@Test
65+
void invalidTargetsAndCrossObjectActionsFail() {
66+
for (String sql : new String[] {"ALTER VIEW v ALTER COLUMN id SET STATISTICS DEFAULT",
67+
"ALTER INDEX ix ALTER COLUMN 1 SET (n_distinct=10)",
68+
"ALTER TABLE t ALTER COLUMN id SET (n_distinct)",
69+
"ALTER TABLE t ALTER COLUMN id SET STATISTICS 10001"}) {
70+
assertThrows(JSQLParserException.class, () -> parse(sql), sql);
71+
}
72+
}
73+
74+
private static Statement parse(String sql) throws JSQLParserException {
75+
return CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.POSTGRESQL));
76+
}
77+
78+
private static void assertRoundTrip(Statement statement) throws JSQLParserException {
79+
StringBuilder sql = new StringBuilder();
80+
statement.accept(new StatementDeParser(sql), null);
81+
assertEquals(statement.toString(), sql.toString());
82+
assertEquals(statement.toString(), parse(sql.toString()).toString());
83+
}
84+
}

0 commit comments

Comments
 (0)