Skip to content

Commit db0f2ad

Browse files
authored
feat: support PostgreSQL rewrite rules with reusable statement actions (#2723)
Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent 4acc89f commit db0f2ad

12 files changed

Lines changed: 495 additions & 7 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,7 @@ public enum Feature {
582582
/** Publication and subscription definitions. */
583583
createForeignDataWrapper, alterForeignDataWrapper, createServer, alterServer, createUserMapping, alterUserMapping,
584584

585-
createTextSearchConfiguration, alterTextSearchConfiguration, createCollation, alterCollation,
585+
createRule, notifyStatement, createTextSearchConfiguration, alterTextSearchConfiguration, createCollation, alterCollation,
586586

587587
createPublication, alterPublication, createSubscription, alterSubscription,
588588
/**

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import net.sf.jsqlparser.statement.alter.AlterUserMapping;
1818
import net.sf.jsqlparser.statement.create.textsearch.CreateTextSearchConfiguration;
1919
import net.sf.jsqlparser.statement.alter.AlterTextSearchConfiguration;
20+
import net.sf.jsqlparser.statement.create.rule.CreateRule;
21+
import net.sf.jsqlparser.statement.notify.NotifyStatement;
2022
import net.sf.jsqlparser.statement.create.collation.CreateCollation;
2123
import net.sf.jsqlparser.statement.alter.AlterCollation;
2224
import net.sf.jsqlparser.statement.alter.AlterPolicy;
@@ -824,6 +826,22 @@ default void visit(AlterTextSearchConfiguration statement) {
824826
visit(statement, null);
825827
}
826828

829+
default <S> T visit(CreateRule statement, S context) {
830+
return null;
831+
}
832+
833+
default void visit(CreateRule statement) {
834+
visit(statement, null);
835+
}
836+
837+
default <S> T visit(NotifyStatement statement, S context) {
838+
return null;
839+
}
840+
841+
default void visit(NotifyStatement statement) {
842+
visit(statement, null);
843+
}
844+
827845
default <S> T visit(CreateCollation statement, S context) {
828846
return null;
829847
}
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
@@ -17,6 +17,8 @@
1717
import net.sf.jsqlparser.statement.alter.AlterUserMapping;
1818
import net.sf.jsqlparser.statement.create.textsearch.CreateTextSearchConfiguration;
1919
import net.sf.jsqlparser.statement.alter.AlterTextSearchConfiguration;
20+
import net.sf.jsqlparser.statement.create.rule.CreateRule;
21+
import net.sf.jsqlparser.statement.notify.NotifyStatement;
2022
import net.sf.jsqlparser.statement.create.collation.CreateCollation;
2123
import net.sf.jsqlparser.statement.alter.AlterCollation;
2224
import net.sf.jsqlparser.statement.alter.AlterPolicy;
@@ -3037,6 +3039,20 @@ public <S> Void visit(AlterTextSearchConfiguration statement, S context) {
30373039
return null;
30383040
}
30393041

3042+
@Override
3043+
public <S> Void visit(CreateRule statement, S context) {
3044+
statement.visitExpressions(expression -> expression.accept(this, context));
3045+
statement.visitTables(table -> visit(table, context));
3046+
statement.getActions().forEach(action -> action.accept(this, context));
3047+
return null;
3048+
}
3049+
3050+
@Override
3051+
public <S> Void visit(NotifyStatement statement, S context) {
3052+
statement.visitExpressions(expression -> expression.accept(this, context));
3053+
return null;
3054+
}
3055+
30403056
@Override
30413057
public <S> Void visit(CreateCollation statement, S context) {
30423058
statement.visitExpressions(expression -> expression.accept(this, context));

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import net.sf.jsqlparser.statement.alter.AlterUserMapping;
1818
import net.sf.jsqlparser.statement.create.textsearch.CreateTextSearchConfiguration;
1919
import net.sf.jsqlparser.statement.alter.AlterTextSearchConfiguration;
20+
import net.sf.jsqlparser.statement.create.rule.CreateRule;
21+
import net.sf.jsqlparser.statement.notify.NotifyStatement;
2022
import net.sf.jsqlparser.statement.create.collation.CreateCollation;
2123
import net.sf.jsqlparser.statement.alter.AlterCollation;
2224
import net.sf.jsqlparser.statement.alter.AlterPolicy;
@@ -903,6 +905,19 @@ public <S> StringBuilder visit(AlterTextSearchConfiguration statement, S context
903905
return statement.appendTo(builder);
904906
}
905907

908+
@Override
909+
public <S> StringBuilder visit(CreateRule statement, S context) {
910+
return statement.appendTo(builder,
911+
expression -> expression.accept(expressionDeParser, context),
912+
action -> action.accept(this, context));
913+
}
914+
915+
@Override
916+
public <S> StringBuilder visit(NotifyStatement statement, S context) {
917+
return statement.appendTo(builder,
918+
expression -> expression.accept(expressionDeParser, context));
919+
}
920+
906921
@Override
907922
public <S> StringBuilder visit(CreateCollation statement, S context) {
908923
return statement.appendTo(builder,

‎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
@@ -102,6 +102,7 @@ public class FeaturesAllowed implements FeatureSetValidation, ModifyableFeatureS
102102
*/
103103
public static final FeaturesAllowed CREATE = new FeaturesAllowed("CREATE", Feature.createIndex,
104104
Feature.createTextSearchConfiguration,
105+
Feature.createRule,
105106
Feature.createCollation,
106107
Feature.createSchema, Feature.createSequence, Feature.createTable,
107108
Feature.createForeignDataWrapper, Feature.createServer, Feature.createUserMapping,
@@ -163,7 +164,8 @@ public class FeaturesAllowed implements FeatureSetValidation, ModifyableFeatureS
163164
* all DML {@link Feature}'s
164165
*/
165166
public static final FeaturesAllowed DML =
166-
new FeaturesAllowed("DML").add(SELECT, INSERT, UPDATE, DELETE, MERGE)
167+
new FeaturesAllowed("DML", Feature.notifyStatement)
168+
.add(SELECT, INSERT, UPDATE, DELETE, MERGE)
167169
.unmodifyable();
168170
/**
169171
* 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
@@ -101,6 +102,7 @@ public enum PostgresqlVersion implements Version {
101102
Feature.createServer, Feature.alterServer, Feature.createUserMapping,
102103
Feature.alterUserMapping,
103104
Feature.createTextSearchConfiguration, Feature.alterTextSearchConfiguration,
105+
Feature.createRule, Feature.notifyStatement,
104106
Feature.createCollation, Feature.alterCollation,
105107
Feature.createPublication, Feature.alterPublication,
106108
Feature.createSubscription, Feature.alterSubscription,

0 commit comments

Comments
 (0)