Skip to content

Commit 3e8324b

Browse files
authored
feat: model PostgreSQL extended statistics DDL (#2712)
Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent e85e54f commit 3e8324b

7 files changed

Lines changed: 418 additions & 0 deletions

File tree

‎src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
import net.sf.jsqlparser.statement.alter.AlterPolicy;
1313
import net.sf.jsqlparser.statement.drop.DropPolicy;
14+
import net.sf.jsqlparser.statement.create.statistics.CreateStatistics;
15+
import net.sf.jsqlparser.statement.alter.AlterStatistics;
1416
import net.sf.jsqlparser.statement.alter.AlterRelation;
1517
import net.sf.jsqlparser.statement.alter.AlterTablespaceMove;
1618
import net.sf.jsqlparser.statement.alter.database.AlterDatabase;
@@ -715,4 +717,20 @@ default <S> T visit(DropPolicy statement, S context) {
715717
default void visit(DropPolicy statement) {
716718
visit(statement, null);
717719
}
720+
721+
default <S> T visit(CreateStatistics statement, S context) {
722+
return null;
723+
}
724+
725+
default void visit(CreateStatistics statement) {
726+
visit(statement, null);
727+
}
728+
729+
default <S> T visit(AlterStatistics statement, S context) {
730+
return null;
731+
}
732+
733+
default void visit(AlterStatistics statement) {
734+
visit(statement, null);
735+
}
718736
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2025 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 net.sf.jsqlparser.statement.Statement;
13+
import net.sf.jsqlparser.statement.StatementVisitor;
14+
15+
/** PostgreSQL statistics object metadata; a null target denotes SET STATISTICS DEFAULT. */
16+
public class AlterStatistics implements Statement {
17+
public enum Action {
18+
RENAME, OWNER, SET_SCHEMA, SET_STATISTICS
19+
}
20+
21+
private String name;
22+
private Action action;
23+
private String newName;
24+
private String owner;
25+
private String schemaName;
26+
private Integer statistics;
27+
28+
public String getName() {
29+
return name;
30+
}
31+
32+
public AlterStatistics setName(String name) {
33+
this.name = name;
34+
return this;
35+
}
36+
37+
public Action getAction() {
38+
return action;
39+
}
40+
41+
public AlterStatistics setAction(Action action) {
42+
this.action = action;
43+
return this;
44+
}
45+
46+
public String getNewName() {
47+
return newName;
48+
}
49+
50+
public AlterStatistics setNewName(String newName) {
51+
this.newName = newName;
52+
return this;
53+
}
54+
55+
public String getOwner() {
56+
return owner;
57+
}
58+
59+
public AlterStatistics setOwner(String owner) {
60+
this.owner = owner;
61+
return this;
62+
}
63+
64+
public String getSchemaName() {
65+
return schemaName;
66+
}
67+
68+
public AlterStatistics setSchemaName(String schemaName) {
69+
this.schemaName = schemaName;
70+
return this;
71+
}
72+
73+
public Integer getStatistics() {
74+
return statistics;
75+
}
76+
77+
public AlterStatistics setStatistics(Integer statistics) {
78+
this.statistics = statistics;
79+
return this;
80+
}
81+
82+
public StringBuilder appendTo(StringBuilder sql) {
83+
sql.append("ALTER STATISTICS ").append(name);
84+
if (action == Action.RENAME) {
85+
sql.append(" RENAME TO ").append(newName);
86+
} else if (action == Action.OWNER) {
87+
sql.append(" OWNER TO ").append(owner);
88+
} else if (action == Action.SET_SCHEMA) {
89+
sql.append(" SET SCHEMA ").append(schemaName);
90+
} else if (action == Action.SET_STATISTICS) {
91+
sql.append(" SET STATISTICS ").append(statistics == null ? "DEFAULT" : statistics);
92+
} else {
93+
throw new IllegalStateException("Expected statistics alteration action");
94+
}
95+
return sql;
96+
}
97+
98+
@Override
99+
public String toString() {
100+
return appendTo(new StringBuilder()).toString();
101+
}
102+
103+
@Override
104+
public <T, S> T accept(StatementVisitor<T> visitor, S context) {
105+
return visitor.visit(this, context);
106+
}
107+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2025 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.statement.create.statistics;
11+
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
import java.util.function.Consumer;
15+
import net.sf.jsqlparser.expression.Expression;
16+
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
17+
import net.sf.jsqlparser.schema.Table;
18+
import net.sf.jsqlparser.statement.Statement;
19+
import net.sf.jsqlparser.statement.StatementVisitor;
20+
21+
/** PostgreSQL extended statistics over columns and parenthesized expressions. */
22+
public class CreateStatistics implements Statement {
23+
public enum Kind {
24+
NDISTINCT, DEPENDENCIES, MCV
25+
}
26+
27+
private String name;
28+
private boolean ifNotExists = false;
29+
private List<Kind> kinds = new ArrayList<>();
30+
private ExpressionList<Expression> expressions = new ExpressionList<>();
31+
private Table table;
32+
33+
public String getName() {
34+
return name;
35+
}
36+
37+
public CreateStatistics setName(String name) {
38+
this.name = name;
39+
return this;
40+
}
41+
42+
public boolean isIfNotExists() {
43+
return ifNotExists;
44+
}
45+
46+
public CreateStatistics setIfNotExists(boolean ifNotExists) {
47+
this.ifNotExists = ifNotExists;
48+
return this;
49+
}
50+
51+
public List<Kind> getKinds() {
52+
return kinds;
53+
}
54+
55+
public CreateStatistics setKinds(List<Kind> kinds) {
56+
this.kinds = kinds;
57+
return this;
58+
}
59+
60+
public ExpressionList<Expression> getExpressions() {
61+
return expressions;
62+
}
63+
64+
public CreateStatistics setExpressions(ExpressionList<Expression> expressions) {
65+
this.expressions = expressions;
66+
return this;
67+
}
68+
69+
public Table getTable() {
70+
return table;
71+
}
72+
73+
public CreateStatistics setTable(Table table) {
74+
this.table = table;
75+
return this;
76+
}
77+
78+
public StringBuilder appendTo(StringBuilder sql, Consumer<Expression> printer) {
79+
sql.append("CREATE STATISTICS");
80+
if (ifNotExists) {
81+
sql.append(" IF NOT EXISTS");
82+
}
83+
if (name != null) {
84+
sql.append(' ').append(name);
85+
}
86+
if (kinds != null && !kinds.isEmpty()) {
87+
sql.append(" (");
88+
for (int i = 0; i < kinds.size(); i++) {
89+
if (i > 0) {
90+
sql.append(", ");
91+
}
92+
sql.append(kinds.get(i).name().toLowerCase(java.util.Locale.ROOT));
93+
}
94+
sql.append(')');
95+
}
96+
sql.append(" ON ");
97+
printer.accept(expressions);
98+
return sql.append(" FROM ").append(table);
99+
}
100+
101+
@Override
102+
public String toString() {
103+
StringBuilder sql = new StringBuilder();
104+
return appendTo(sql, sql::append).toString();
105+
}
106+
107+
@Override
108+
public <T, S> T accept(StatementVisitor<T> visitor, S context) {
109+
return visitor.visit(this, context);
110+
}
111+
}

‎src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
import net.sf.jsqlparser.statement.alter.AlterPolicy;
1313
import net.sf.jsqlparser.statement.drop.DropPolicy;
14+
import net.sf.jsqlparser.statement.create.statistics.CreateStatistics;
15+
import net.sf.jsqlparser.statement.alter.AlterStatistics;
1416
import net.sf.jsqlparser.statement.alter.AlterRelation;
1517
import net.sf.jsqlparser.statement.alter.AlterTablespaceMove;
1618
import net.sf.jsqlparser.statement.alter.database.AlterDatabase;
@@ -2954,4 +2956,16 @@ public <S> Void visit(DropPolicy statement, S context) {
29542956
visit(statement.getTable(), context);
29552957
return null;
29562958
}
2959+
2960+
@Override
2961+
public <S> Void visit(CreateStatistics statement, S context) {
2962+
visit(statement.getTable(), context);
2963+
statement.getExpressions().accept(this, context);
2964+
return null;
2965+
}
2966+
2967+
@Override
2968+
public <S> Void visit(AlterStatistics statement, S context) {
2969+
return null;
2970+
}
29572971
}

‎src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
import net.sf.jsqlparser.statement.alter.AlterPolicy;
1313
import net.sf.jsqlparser.statement.drop.DropPolicy;
14+
import net.sf.jsqlparser.statement.create.statistics.CreateStatistics;
15+
import net.sf.jsqlparser.statement.alter.AlterStatistics;
1416
import net.sf.jsqlparser.statement.alter.AlterRelation;
1517
import net.sf.jsqlparser.statement.alter.AlterTablespaceMove;
1618
import net.sf.jsqlparser.statement.alter.database.AlterDatabase;
@@ -840,4 +842,15 @@ public <S> StringBuilder visit(AlterPolicy statement, S context) {
840842
public <S> StringBuilder visit(DropPolicy statement, S context) {
841843
return statement.appendTo(builder);
842844
}
845+
846+
@Override
847+
public <S> StringBuilder visit(CreateStatistics statement, S context) {
848+
return statement.appendTo(builder,
849+
expression -> expression.accept(expressionDeParser, context));
850+
}
851+
852+
@Override
853+
public <S> StringBuilder visit(AlterStatistics statement, S context) {
854+
return statement.appendTo(builder);
855+
}
843856
}

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

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import net.sf.jsqlparser.statement.create.event.*;
6363
import net.sf.jsqlparser.statement.create.function.*;
6464
import net.sf.jsqlparser.statement.create.index.*;
6565
import net.sf.jsqlparser.statement.create.macro.*;
66+
import net.sf.jsqlparser.statement.create.statistics.CreateStatistics;
6667
import net.sf.jsqlparser.statement.create.policy.*;
6768
import net.sf.jsqlparser.statement.create.procedure.*;
6869
import net.sf.jsqlparser.statement.create.schema.*;
@@ -18303,6 +18304,8 @@ Statement Alter():
1830318304
(
1830418305
<K_ALTER>
1830518306
(
18307+
LOOKAHEAD({ isKeywordAhead("STATISTICS") }) statement = AlterStatistics()
18308+
|
1830618309
LOOKAHEAD(<K_POLICY>) statement = AlterPolicy()
1830718310
|
1830818311
LOOKAHEAD({ isKeywordAhead("ROLE") || isKeywordAhead("USER") || isKeywordAhead("GROUP") })
@@ -19478,6 +19481,8 @@ Statement Create():
1947819481
{
1947919482
<K_CREATE> [ <K_OR> <K_REPLACE> { isUsingOrReplace = true; } ]
1948019483
(
19484+
LOOKAHEAD({ isKeywordAhead("STATISTICS") }) statement = CreateStatistics()
19485+
|
1948119486
LOOKAHEAD({ isPostgreSqlRoleAhead() }) statement = CreateRole()
1948219487
{ requireAccessSyntax(!isUsingOrReplace, "CREATE ROLE does not accept OR REPLACE"); }
1948319488
|
@@ -20733,3 +20738,48 @@ String TimeTravelAfterAlias():
2073320738
return builder.toString();
2073420739
}
2073520740
}
20741+
20742+
CreateStatistics CreateStatistics():
20743+
{
20744+
CreateStatistics statement = new CreateStatistics();
20745+
String name; CreateStatistics.Kind kind; Expression expression; Table table;
20746+
}
20747+
{
20748+
ContextualKeyword("STATISTICS")
20749+
[ LOOKAHEAD(2) <K_IF> <K_NOT> <K_EXISTS> { statement.setIfNotExists(true); } ]
20750+
[ LOOKAHEAD({ getToken(1).kind != K_ON && getToken(1).kind != OPENING_BRACKET })
20751+
name=TypeDdlName() { statement.setName(name); } ]
20752+
[ "(" name=RelObjectName() { kind=accessEnum(CreateStatistics.Kind.class,name); statement.getKinds().add(kind); }
20753+
( "," name=RelObjectName() { kind=accessEnum(CreateStatistics.Kind.class,name);
20754+
requireDdlSyntax(!statement.getKinds().contains(kind),"Duplicate statistics kind"); statement.getKinds().add(kind); } )* ")" ]
20755+
<K_ON> expression=PostgreSqlStatisticsExpression() { statement.getExpressions().add(expression); }
20756+
( "," expression=PostgreSqlStatisticsExpression() { statement.getExpressions().add(expression); } )*
20757+
<K_FROM> table=Table() { statement.setTable(table); }
20758+
{
20759+
requireDdlSyntax(!statement.isIfNotExists() || statement.getName()!=null,"IF NOT EXISTS requires a statistics name");
20760+
requireDdlSyntax(statement.getExpressions().size()>1 || statement.getKinds().isEmpty()
20761+
&& statement.getExpressions().get(0) instanceof ParenthesedExpressionList,
20762+
"Single-expression statistics require a parenthesized expression and no explicit kinds");
20763+
return statement;
20764+
}
20765+
}
20766+
Expression PostgreSqlStatisticsExpression():
20767+
{ Expression expression; String name; }
20768+
{
20769+
( "(" expression=Expression() ")" { expression = new ParenthesedExpressionList<Expression>(expression); }
20770+
| name=RelObjectName() { expression = createColumn(name); } )
20771+
{ return expression; }
20772+
}
20773+
AlterStatistics AlterStatistics():
20774+
{ AlterStatistics statement = new AlterStatistics(); String name; Long target; }
20775+
{
20776+
ContextualKeyword("STATISTICS") name=TypeDdlName() { statement.setName(name); }
20777+
( <K_RENAME> <K_TO> name=RelObjectName() { statement.setAction(AlterStatistics.Action.RENAME); statement.setNewName(name); }
20778+
| ContextualKeyword("OWNER") <K_TO> name=RelObjectName() { statement.setAction(AlterStatistics.Action.OWNER); statement.setOwner(name); }
20779+
| <K_SET> ( <K_SCHEMA> name=RelObjectName() { statement.setAction(AlterStatistics.Action.SET_SCHEMA); statement.setSchemaName(name); }
20780+
| ContextualKeyword("STATISTICS") { statement.setAction(AlterStatistics.Action.SET_STATISTICS); }
20781+
( <K_DEFAULT> | target=SequenceParameterValue() {
20782+
requireDdlSyntax(target>=-1 && target<=10000,"Statistics target must be DEFAULT or between -1 and 10000");
20783+
statement.setStatistics(target.intValue()); } ) ) )
20784+
{ return statement; }
20785+
}

0 commit comments

Comments
 (0)