Skip to content

Commit c4a8a59

Browse files
committed
feat: support PostgreSQL rewrite rules with reusable statement actions
Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent 78d3de8 commit c4a8a59

12 files changed

Lines changed: 496 additions & 6 deletions

File tree

‎src/main/java/net/sf/jsqlparser/parser/feature/Feature.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,8 @@ public enum Feature {
580580
*/
581581
createSequence,
582582
/** Publication and subscription definitions. */
583+
createRule, notifyStatement,
584+
583585
createPublication, alterPublication, createSubscription, alterSubscription,
584586
/**
585587
* Structured type, domain and extension statements.

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

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

12+
import net.sf.jsqlparser.statement.create.rule.CreateRule;
13+
import net.sf.jsqlparser.statement.notify.NotifyStatement;
1214
import net.sf.jsqlparser.statement.alter.AlterPolicy;
1315
import net.sf.jsqlparser.statement.drop.DropPolicy;
1416
import net.sf.jsqlparser.statement.create.statistics.CreateStatistics;
@@ -733,4 +735,20 @@ default <S> T visit(AlterStatistics statement, S context) {
733735
default void visit(AlterStatistics statement) {
734736
visit(statement, null);
735737
}
738+
739+
default <S> T visit(CreateRule statement, S context) {
740+
return null;
741+
}
742+
743+
default void visit(CreateRule statement) {
744+
visit(statement, null);
745+
}
746+
747+
default <S> T visit(NotifyStatement statement, S context) {
748+
return null;
749+
}
750+
751+
default void visit(NotifyStatement statement) {
752+
visit(statement, null);
753+
}
736754
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
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.create.rule;
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.schema.Table;
17+
import net.sf.jsqlparser.statement.Statement;
18+
import net.sf.jsqlparser.statement.StatementVisitor;
19+
20+
/** PostgreSQL rewrite rule whose actions reuse ordinary statement AST nodes. */
21+
public class CreateRule implements Statement {
22+
public enum Event {
23+
SELECT, INSERT, UPDATE, DELETE
24+
}
25+
public enum Behavior {
26+
ALSO, INSTEAD
27+
}
28+
29+
private String name;
30+
private boolean orReplace;
31+
private Event event;
32+
private Table table;
33+
private Expression whereExpression;
34+
private Behavior behavior;
35+
private boolean useParentheses;
36+
private List<Statement> actions = new ArrayList<>();
37+
38+
public String getName() {
39+
return name;
40+
}
41+
42+
public void setName(String value) {
43+
name = value;
44+
}
45+
46+
public boolean isOrReplace() {
47+
return orReplace;
48+
}
49+
50+
public void setOrReplace(boolean value) {
51+
orReplace = value;
52+
}
53+
54+
public Event getEvent() {
55+
return event;
56+
}
57+
58+
public void setEvent(Event value) {
59+
event = value;
60+
}
61+
62+
public Table getTable() {
63+
return table;
64+
}
65+
66+
public void setTable(Table value) {
67+
table = value;
68+
}
69+
70+
public Expression getWhereExpression() {
71+
return whereExpression;
72+
}
73+
74+
public void setWhereExpression(Expression value) {
75+
whereExpression = value;
76+
}
77+
78+
public Behavior getBehavior() {
79+
return behavior;
80+
}
81+
82+
public void setBehavior(Behavior value) {
83+
behavior = value;
84+
}
85+
86+
public boolean isUseParentheses() {
87+
return useParentheses;
88+
}
89+
90+
public void setUseParentheses(boolean value) {
91+
useParentheses = value;
92+
}
93+
94+
public List<Statement> getActions() {
95+
return actions;
96+
}
97+
98+
public void setActions(List<Statement> value) {
99+
actions = value;
100+
}
101+
102+
public boolean isNothing() {
103+
return actions.isEmpty() && !useParentheses;
104+
}
105+
106+
public void setNothing(boolean nothing) {
107+
if (nothing) {
108+
actions.clear();
109+
useParentheses = false;
110+
}
111+
}
112+
113+
public void visitExpressions(Consumer<Expression> visitor) {
114+
if (whereExpression != null) {
115+
visitor.accept(whereExpression);
116+
}
117+
}
118+
119+
public void visitTables(Consumer<Table> visitor) {
120+
if (table != null) {
121+
visitor.accept(table);
122+
}
123+
}
124+
125+
public StringBuilder appendTo(StringBuilder sql, Consumer<Expression> expressions,
126+
Consumer<Statement> statements) {
127+
sql.append("CREATE ");
128+
if (orReplace) {
129+
sql.append("OR REPLACE ");
130+
}
131+
sql.append("RULE ").append(name).append(" AS ON ").append(event).append(" TO ")
132+
.append(table);
133+
if (whereExpression != null) {
134+
sql.append(" WHERE ");
135+
expressions.accept(whereExpression);
136+
}
137+
sql.append(" DO ");
138+
if (behavior != null) {
139+
sql.append(behavior).append(' ');
140+
}
141+
if (isNothing()) {
142+
return sql.append("NOTHING");
143+
}
144+
boolean grouped = useParentheses || actions.size() != 1;
145+
if (grouped) {
146+
sql.append('(');
147+
}
148+
for (int i = 0; i < actions.size(); i++) {
149+
if (i > 0) {
150+
sql.append("; ");
151+
}
152+
statements.accept(actions.get(i));
153+
}
154+
if (grouped) {
155+
sql.append(')');
156+
}
157+
return sql;
158+
}
159+
160+
@Override
161+
public String toString() {
162+
StringBuilder sql = new StringBuilder();
163+
return appendTo(sql, sql::append, sql::append).toString();
164+
}
165+
166+
@Override
167+
public <T, S> T accept(StatementVisitor<T> visitor, S context) {
168+
return visitor.visit(this, context);
169+
}
170+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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.notify;
11+
12+
import java.util.function.Consumer;
13+
import net.sf.jsqlparser.expression.Expression;
14+
import net.sf.jsqlparser.expression.StringValue;
15+
import net.sf.jsqlparser.statement.Statement;
16+
import net.sf.jsqlparser.statement.StatementVisitor;
17+
18+
/** PostgreSQL NOTIFY, usable independently and in CREATE RULE actions. */
19+
public class NotifyStatement implements Statement {
20+
private String channel;
21+
private StringValue payload;
22+
23+
public String getChannel() {
24+
return channel;
25+
}
26+
27+
public void setChannel(String value) {
28+
channel = value;
29+
}
30+
31+
public StringValue getPayload() {
32+
return payload;
33+
}
34+
35+
public void setPayload(StringValue value) {
36+
payload = value;
37+
}
38+
39+
public void visitExpressions(Consumer<Expression> visitor) {
40+
if (payload != null) {
41+
visitor.accept(payload);
42+
}
43+
}
44+
45+
public StringBuilder appendTo(StringBuilder sql, Consumer<Expression> printer) {
46+
sql.append("NOTIFY ").append(channel);
47+
if (payload != null) {
48+
sql.append(", ");
49+
printer.accept(payload);
50+
}
51+
return sql;
52+
}
53+
54+
@Override
55+
public String toString() {
56+
StringBuilder sql = new StringBuilder();
57+
return appendTo(sql, sql::append).toString();
58+
}
59+
60+
@Override
61+
public <T, S> T accept(StatementVisitor<T> visitor, S context) {
62+
return visitor.visit(this, context);
63+
}
64+
}

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

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

12+
import net.sf.jsqlparser.statement.create.rule.CreateRule;
13+
import net.sf.jsqlparser.statement.notify.NotifyStatement;
1214
import net.sf.jsqlparser.statement.alter.AlterPolicy;
1315
import net.sf.jsqlparser.statement.drop.DropPolicy;
1416
import net.sf.jsqlparser.statement.create.statistics.CreateStatistics;
@@ -2968,4 +2970,18 @@ public <S> Void visit(CreateStatistics statement, S context) {
29682970
public <S> Void visit(AlterStatistics statement, S context) {
29692971
return null;
29702972
}
2973+
2974+
@Override
2975+
public <S> Void visit(CreateRule statement, S context) {
2976+
statement.visitExpressions(expression -> expression.accept(this, context));
2977+
statement.visitTables(table -> visit(table, context));
2978+
statement.getActions().forEach(action -> action.accept(this, context));
2979+
return null;
2980+
}
2981+
2982+
@Override
2983+
public <S> Void visit(NotifyStatement statement, S context) {
2984+
statement.visitExpressions(expression -> expression.accept(this, context));
2985+
return null;
2986+
}
29712987
}

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

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

12+
import net.sf.jsqlparser.statement.create.rule.CreateRule;
13+
import net.sf.jsqlparser.statement.notify.NotifyStatement;
1214
import net.sf.jsqlparser.statement.alter.AlterPolicy;
1315
import net.sf.jsqlparser.statement.drop.DropPolicy;
1416
import net.sf.jsqlparser.statement.create.statistics.CreateStatistics;
@@ -853,4 +855,17 @@ public <S> StringBuilder visit(CreateStatistics statement, S context) {
853855
public <S> StringBuilder visit(AlterStatistics statement, S context) {
854856
return statement.appendTo(builder);
855857
}
858+
859+
@Override
860+
public <S> StringBuilder visit(CreateRule statement, S context) {
861+
return statement.appendTo(builder,
862+
expression -> expression.accept(expressionDeParser, context),
863+
action -> action.accept(this, context));
864+
}
865+
866+
@Override
867+
public <S> StringBuilder visit(NotifyStatement statement, S context) {
868+
return statement.appendTo(builder,
869+
expression -> expression.accept(expressionDeParser, context));
870+
}
856871
}

‎src/main/java/net/sf/jsqlparser/util/validation/feature/FeaturesAllowed.java‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ public class FeaturesAllowed implements FeatureSetValidation, ModifyableFeatureS
101101
* all "CREATE" {@link Feature}'s
102102
*/
103103
public static final FeaturesAllowed CREATE = new FeaturesAllowed("CREATE", Feature.createIndex,
104+
Feature.createRule,
104105
Feature.createSchema, Feature.createSequence, Feature.createTable,
105106
Feature.createTableUnlogged,
106107
Feature.createTableCreateOptionStrings, Feature.createTableTableOptionStrings,
@@ -157,7 +158,8 @@ public class FeaturesAllowed implements FeatureSetValidation, ModifyableFeatureS
157158
* all DML {@link Feature}'s
158159
*/
159160
public static final FeaturesAllowed DML =
160-
new FeaturesAllowed("DML").add(SELECT, INSERT, UPDATE, DELETE, MERGE)
161+
new FeaturesAllowed("DML", Feature.notifyStatement)
162+
.add(SELECT, INSERT, UPDATE, DELETE, MERGE)
161163
.unmodifyable();
162164
/**
163165
* all DDL {@link Feature}'s

‎src/main/java/net/sf/jsqlparser/util/validation/feature/PostgresqlVersion.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public enum PostgresqlVersion implements Version {
3131
Feature.exprLike,
3232
Feature.exprSimilarTo, // https://www.postgresql.org/docs/current/sql-select.html
3333
Feature.select,
34+
Feature.tableStatement,
3435
Feature.selectGroupBy, Feature.function, Feature.tableFunction,
3536
Feature.lateralSubSelect,
3637
Feature.selectHaving, // https://www.postgresql.org/docs/current/queries-table-expressions.html#QUERIES-GROUPING-SETS
@@ -97,6 +98,7 @@ public enum PostgresqlVersion implements Version {
9798

9899
// https://www.postgresql.org/docs/current/sql-createsequence.html
99100
Feature.createSequence,
101+
Feature.createRule, Feature.notifyStatement,
100102
Feature.createPublication, Feature.alterPublication,
101103
Feature.createSubscription, Feature.alterSubscription,
102104
Feature.createType, Feature.alterType,

‎src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java‎

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

12+
import net.sf.jsqlparser.statement.create.rule.CreateRule;
13+
import net.sf.jsqlparser.statement.notify.NotifyStatement;
1214
import net.sf.jsqlparser.statement.alter.schema.AlterSchema;
1315
import net.sf.jsqlparser.statement.oracle.OracleBlock;
1416
import net.sf.jsqlparser.statement.oracle.OracleAssignment;
@@ -1024,4 +1026,20 @@ public <S> Void visit(OracleNullStatement statement, S context) {
10241026
return null;
10251027
}
10261028

1029+
1030+
@Override
1031+
public <S> Void visit(CreateRule statement, S context) {
1032+
validateFeature(Feature.createRule);
1033+
statement.visitExpressions(this::validateOptionalExpression);
1034+
statement.visitTables(this::validateOptionalFromItem);
1035+
statement.getActions().forEach(action -> action.accept(this, context));
1036+
return null;
1037+
}
1038+
1039+
@Override
1040+
public <S> Void visit(NotifyStatement statement, S context) {
1041+
validateFeature(Feature.notifyStatement);
1042+
statement.visitExpressions(this::validateOptionalExpression);
1043+
return null;
1044+
}
10271045
}

0 commit comments

Comments
 (0)