Skip to content

Commit 623a2c1

Browse files
committed
feat: model PostgreSQL extended statistics DDL
Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent 8ffa565 commit 623a2c1

7 files changed

Lines changed: 421 additions & 0 deletions

File tree

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
*/
1010
package net.sf.jsqlparser.statement;
1111

12+
import net.sf.jsqlparser.statement.create.statistics.CreateStatistics;
13+
import net.sf.jsqlparser.statement.alter.AlterStatistics;
14+
1215
import net.sf.jsqlparser.statement.alter.database.AlterDatabase;
1316
import net.sf.jsqlparser.statement.alter.schema.AlterSchema;
1417
import net.sf.jsqlparser.statement.oracle.OracleBlock;
@@ -680,4 +683,20 @@ default void visit(AlterDatabase statement) {
680683
visit(statement, null);
681684
}
682685

686+
687+
default <S> T visit(CreateStatistics statement, S context) {
688+
return null;
689+
}
690+
691+
default void visit(CreateStatistics statement) {
692+
visit(statement, null);
693+
}
694+
695+
default <S> T visit(AlterStatistics statement, S context) {
696+
return null;
697+
}
698+
699+
default void visit(AlterStatistics statement) {
700+
visit(statement, null);
701+
}
683702
}
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: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
*/
1010
package net.sf.jsqlparser.util;
1111

12+
import net.sf.jsqlparser.statement.create.statistics.CreateStatistics;
13+
import net.sf.jsqlparser.statement.alter.AlterStatistics;
14+
1215
import net.sf.jsqlparser.statement.alter.database.AlterDatabase;
1316
import net.sf.jsqlparser.statement.alter.schema.AlterSchema;
1417
import net.sf.jsqlparser.statement.select.MatchRecognize;
@@ -2921,4 +2924,16 @@ public <S> Void visit(AlterDatabase statement, S context) {
29212924
return null;
29222925
}
29232926

2927+
2928+
@Override
2929+
public <S> Void visit(CreateStatistics statement, S context) {
2930+
visit(statement.getTable(), context);
2931+
statement.getExpressions().accept(this, context);
2932+
return null;
2933+
}
2934+
2935+
@Override
2936+
public <S> Void visit(AlterStatistics statement, S context) {
2937+
return null;
2938+
}
29242939
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
*/
1010
package net.sf.jsqlparser.util.deparser;
1111

12+
import net.sf.jsqlparser.statement.create.statistics.CreateStatistics;
13+
import net.sf.jsqlparser.statement.alter.AlterStatistics;
14+
1215
import net.sf.jsqlparser.statement.alter.database.AlterDatabase;
1316
import net.sf.jsqlparser.statement.alter.schema.AlterSchema;
1417
import net.sf.jsqlparser.statement.oracle.OracleBlock;
@@ -815,4 +818,15 @@ public <S> StringBuilder visit(AlterDatabase statement, S context) {
815818
return statement.appendTo(builder);
816819
}
817820

821+
822+
@Override
823+
public <S> StringBuilder visit(CreateStatistics statement, S context) {
824+
return statement.appendTo(builder,
825+
expression -> expression.accept(expressionDeParser, context));
826+
}
827+
828+
@Override
829+
public <S> StringBuilder visit(AlterStatistics statement, S context) {
830+
return statement.appendTo(builder);
831+
}
818832
}

‎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.*;
@@ -18169,6 +18170,8 @@ Statement Alter():
1816918170
(
1817018171
<K_ALTER>
1817118172
(
18173+
LOOKAHEAD({ isKeywordAhead("STATISTICS") }) statement = AlterStatistics()
18174+
|
1817218175
LOOKAHEAD({ isKeywordAhead("ROLE") || isKeywordAhead("USER") || isKeywordAhead("GROUP") })
1817318176
statement = AlterRole()
1817418177
|
@@ -19131,6 +19134,8 @@ Statement Create():
1913119134
{
1913219135
<K_CREATE> [ <K_OR> <K_REPLACE> { isUsingOrReplace = true; } ]
1913319136
(
19137+
LOOKAHEAD({ isKeywordAhead("STATISTICS") }) statement = CreateStatistics()
19138+
|
1913419139
LOOKAHEAD({ isPostgreSqlRoleAhead() }) statement = CreateRole()
1913519140
{ requireAccessSyntax(!isUsingOrReplace, "CREATE ROLE does not accept OR REPLACE"); }
1913619141
|
@@ -20366,3 +20371,48 @@ String TimeTravelAfterAlias():
2036620371
return builder.toString();
2036720372
}
2036820373
}
20374+
20375+
CreateStatistics CreateStatistics():
20376+
{
20377+
CreateStatistics statement = new CreateStatistics();
20378+
String name; CreateStatistics.Kind kind; Expression expression; Table table;
20379+
}
20380+
{
20381+
ContextualKeyword("STATISTICS")
20382+
[ LOOKAHEAD(2) <K_IF> <K_NOT> <K_EXISTS> { statement.setIfNotExists(true); } ]
20383+
[ LOOKAHEAD({ getToken(1).kind != K_ON && getToken(1).kind != OPENING_BRACKET })
20384+
name=TypeDdlName() { statement.setName(name); } ]
20385+
[ "(" name=RelObjectName() { kind=accessEnum(CreateStatistics.Kind.class,name); statement.getKinds().add(kind); }
20386+
( "," name=RelObjectName() { kind=accessEnum(CreateStatistics.Kind.class,name);
20387+
requireDdlSyntax(!statement.getKinds().contains(kind),"Duplicate statistics kind"); statement.getKinds().add(kind); } )* ")" ]
20388+
<K_ON> expression=PostgreSqlStatisticsExpression() { statement.getExpressions().add(expression); }
20389+
( "," expression=PostgreSqlStatisticsExpression() { statement.getExpressions().add(expression); } )*
20390+
<K_FROM> table=Table() { statement.setTable(table); }
20391+
{
20392+
requireDdlSyntax(!statement.isIfNotExists() || statement.getName()!=null,"IF NOT EXISTS requires a statistics name");
20393+
requireDdlSyntax(statement.getExpressions().size()>1 || statement.getKinds().isEmpty()
20394+
&& statement.getExpressions().get(0) instanceof ParenthesedExpressionList,
20395+
"Single-expression statistics require a parenthesized expression and no explicit kinds");
20396+
return statement;
20397+
}
20398+
}
20399+
Expression PostgreSqlStatisticsExpression():
20400+
{ Expression expression; String name; }
20401+
{
20402+
( "(" expression=Expression() ")" { expression = new ParenthesedExpressionList<Expression>(expression); }
20403+
| name=RelObjectName() { expression = createColumn(name); } )
20404+
{ return expression; }
20405+
}
20406+
AlterStatistics AlterStatistics():
20407+
{ AlterStatistics statement = new AlterStatistics(); String name; Long target; }
20408+
{
20409+
ContextualKeyword("STATISTICS") name=TypeDdlName() { statement.setName(name); }
20410+
( <K_RENAME> <K_TO> name=RelObjectName() { statement.setAction(AlterStatistics.Action.RENAME); statement.setNewName(name); }
20411+
| ContextualKeyword("OWNER") <K_TO> name=RelObjectName() { statement.setAction(AlterStatistics.Action.OWNER); statement.setOwner(name); }
20412+
| <K_SET> ( <K_SCHEMA> name=RelObjectName() { statement.setAction(AlterStatistics.Action.SET_SCHEMA); statement.setSchemaName(name); }
20413+
| ContextualKeyword("STATISTICS") { statement.setAction(AlterStatistics.Action.SET_STATISTICS); }
20414+
( <K_DEFAULT> | target=SequenceParameterValue() {
20415+
requireDdlSyntax(target>=-1 && target<=10000,"Statistics target must be DEFAULT or between -1 and 10000");
20416+
statement.setStatistics(target.intValue()); } ) ) )
20417+
{ return statement; }
20418+
}

0 commit comments

Comments
 (0)