Skip to content

Commit 7e62119

Browse files
committed
feat: add PostgreSQL Row Level Security (RLS) support
Add support for PostgreSQL Row Level Security statements: - CREATE POLICY with full syntax (FOR, TO, USING, WITH CHECK clauses) - ALTER TABLE ENABLE/DISABLE/FORCE/NO FORCE ROW LEVEL SECURITY Changes: - New CreatePolicy AST class for CREATE POLICY statements - Added RLS operations to AlterOperation enum - Updated grammar with POLICY, LEVEL, SECURITY keywords - Fixed grammar conflicts with LOOKAHEAD directives - Updated all visitor interfaces and implementations - Added comprehensive unit tests (19 tests, 100% passing) - Updated README.md with new features All code quality checks passing: - CheckStyle: 0 violations - PMD: passed
1 parent f19f17e commit 7e62119

File tree

12 files changed

+534
-8
lines changed

12 files changed

+534
-8
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ JSQLParserBenchmark.parseSQLStatements 5.1 avgt 15 86.592 ± 5.781 m
9090
| RDBMS | Statements |
9191
|-----------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------|
9292
| BigQuery<br>Snowflake<br>DuckDB<br>Redshift<br>Oracle<br>MS SQL Server and Sybase<br>Postgres<br>MySQL and MariaDB<br>DB2<br>H2 and HSQLDB and Derby<br>SQLite | `SELECT`<br>`INSERT`, `UPDATE`, `UPSERT`, `MERGE`<br>`DELETE`, `TRUNCATE TABLE`<br>`CREATE ...`, `ALTER ....`, `DROP ...`<br>`WITH ...` |
93+
| PostgreSQL Row Level Security | `CREATE POLICY`<br>`ALTER TABLE ... ENABLE/DISABLE/FORCE/NO FORCE ROW LEVEL SECURITY` |
9394
| Salesforce SOQL | `INCLUDES`, `EXCLUDES` |
9495
| Piped SQL (also known as FROM SQL) | |
9596

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import net.sf.jsqlparser.statement.analyze.Analyze;
1818
import net.sf.jsqlparser.statement.comment.Comment;
1919
import net.sf.jsqlparser.statement.create.index.CreateIndex;
20+
import net.sf.jsqlparser.statement.create.policy.CreatePolicy;
2021
import net.sf.jsqlparser.statement.create.schema.CreateSchema;
2122
import net.sf.jsqlparser.statement.create.sequence.CreateSequence;
2223
import net.sf.jsqlparser.statement.create.synonym.CreateSynonym;
@@ -351,4 +352,10 @@ default void visit(LockStatement lock) {
351352
this.visit(lock, null);
352353
}
353354

355+
<S> T visit(CreatePolicy createPolicy, S context);
356+
357+
default void visit(CreatePolicy createPolicy) {
358+
this.visit(createPolicy, null);
359+
}
360+
354361
}

src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import net.sf.jsqlparser.statement.analyze.Analyze;
2222
import net.sf.jsqlparser.statement.comment.Comment;
2323
import net.sf.jsqlparser.statement.create.index.CreateIndex;
24+
import net.sf.jsqlparser.statement.create.policy.CreatePolicy;
2425
import net.sf.jsqlparser.statement.create.schema.CreateSchema;
2526
import net.sf.jsqlparser.statement.create.sequence.CreateSequence;
2627
import net.sf.jsqlparser.statement.create.synonym.CreateSynonym;
@@ -296,6 +297,12 @@ public <S> T visit(LockStatement lock, S context) {
296297
return null;
297298
}
298299

300+
@Override
301+
public <S> T visit(CreatePolicy createPolicy, S context) {
302+
303+
return null;
304+
}
305+
299306
@Override
300307
public <S> T visit(SetStatement set, S context) {
301308

src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -856,6 +856,14 @@ public String toString() {
856856
} else {
857857
if (operation == AlterOperation.COMMENT_WITH_EQUAL_SIGN) {
858858
b.append("COMMENT =").append(" ");
859+
} else if (operation == AlterOperation.ENABLE_ROW_LEVEL_SECURITY) {
860+
b.append("ENABLE ROW LEVEL SECURITY").append(" ");
861+
} else if (operation == AlterOperation.DISABLE_ROW_LEVEL_SECURITY) {
862+
b.append("DISABLE ROW LEVEL SECURITY").append(" ");
863+
} else if (operation == AlterOperation.FORCE_ROW_LEVEL_SECURITY) {
864+
b.append("FORCE ROW LEVEL SECURITY").append(" ");
865+
} else if (operation == AlterOperation.NO_FORCE_ROW_LEVEL_SECURITY) {
866+
b.append("NO FORCE ROW LEVEL SECURITY").append(" ");
859867
} else {
860868
b.append(operation).append(" ");
861869
}

src/main/java/net/sf/jsqlparser/statement/alter/AlterOperation.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
package net.sf.jsqlparser.statement.alter;
1111

1212
public enum AlterOperation {
13-
ADD, ALTER, DROP, DROP_PRIMARY_KEY, DROP_UNIQUE, DROP_FOREIGN_KEY, MODIFY, CHANGE, CONVERT, COLLATE, ALGORITHM, RENAME, RENAME_TABLE, RENAME_INDEX, RENAME_KEY, RENAME_CONSTRAINT, COMMENT, COMMENT_WITH_EQUAL_SIGN, UNSPECIFIC, ADD_PARTITION, DROP_PARTITION, DISCARD_PARTITION, IMPORT_PARTITION, TRUNCATE_PARTITION, COALESCE_PARTITION, REORGANIZE_PARTITION, EXCHANGE_PARTITION, ANALYZE_PARTITION, CHECK_PARTITION, OPTIMIZE_PARTITION, REBUILD_PARTITION, REPAIR_PARTITION, REMOVE_PARTITIONING, PARTITION_BY, SET_TABLE_OPTION, ENGINE, FORCE, KEY_BLOCK_SIZE, LOCK, DISCARD_TABLESPACE, IMPORT_TABLESPACE, DISABLE_KEYS, ENABLE_KEYS;
13+
ADD, ALTER, DROP, DROP_PRIMARY_KEY, DROP_UNIQUE, DROP_FOREIGN_KEY, MODIFY, CHANGE, CONVERT, COLLATE, ALGORITHM, RENAME, RENAME_TABLE, RENAME_INDEX, RENAME_KEY, RENAME_CONSTRAINT, COMMENT, COMMENT_WITH_EQUAL_SIGN, UNSPECIFIC, ADD_PARTITION, DROP_PARTITION, DISCARD_PARTITION, IMPORT_PARTITION, TRUNCATE_PARTITION, COALESCE_PARTITION, REORGANIZE_PARTITION, EXCHANGE_PARTITION, ANALYZE_PARTITION, CHECK_PARTITION, OPTIMIZE_PARTITION, REBUILD_PARTITION, REPAIR_PARTITION, REMOVE_PARTITIONING, PARTITION_BY, SET_TABLE_OPTION, ENGINE, FORCE, KEY_BLOCK_SIZE, LOCK, DISCARD_TABLESPACE, IMPORT_TABLESPACE, DISABLE_KEYS, ENABLE_KEYS, ENABLE_ROW_LEVEL_SECURITY, DISABLE_ROW_LEVEL_SECURITY, FORCE_ROW_LEVEL_SECURITY, NO_FORCE_ROW_LEVEL_SECURITY;
1414

1515
public static AlterOperation from(String operation) {
1616
return Enum.valueOf(AlterOperation.class, operation.toUpperCase());
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
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.policy;
11+
12+
import net.sf.jsqlparser.expression.Expression;
13+
import net.sf.jsqlparser.schema.Table;
14+
import net.sf.jsqlparser.statement.Statement;
15+
import net.sf.jsqlparser.statement.StatementVisitor;
16+
17+
import java.util.ArrayList;
18+
import java.util.List;
19+
20+
/**
21+
* PostgreSQL CREATE POLICY statement for Row Level Security (RLS).
22+
*
23+
* Syntax:
24+
* CREATE POLICY name ON table_name
25+
* [ FOR { ALL | SELECT | INSERT | UPDATE | DELETE } ]
26+
* [ TO { role_name | PUBLIC | CURRENT_USER | SESSION_USER } [, ...] ]
27+
* [ USING ( using_expression ) ]
28+
* [ WITH CHECK ( check_expression ) ]
29+
*/
30+
public class CreatePolicy implements Statement {
31+
32+
private String policyName;
33+
private Table table;
34+
private String command; // ALL, SELECT, INSERT, UPDATE, DELETE
35+
private List<String> roles = new ArrayList<>();
36+
private Expression usingExpression;
37+
private Expression withCheckExpression;
38+
39+
public String getPolicyName() {
40+
return policyName;
41+
}
42+
43+
public CreatePolicy setPolicyName(String policyName) {
44+
this.policyName = policyName;
45+
return this;
46+
}
47+
48+
public Table getTable() {
49+
return table;
50+
}
51+
52+
public CreatePolicy setTable(Table table) {
53+
this.table = table;
54+
return this;
55+
}
56+
57+
public String getCommand() {
58+
return command;
59+
}
60+
61+
public CreatePolicy setCommand(String command) {
62+
this.command = command;
63+
return this;
64+
}
65+
66+
public List<String> getRoles() {
67+
return roles;
68+
}
69+
70+
public CreatePolicy setRoles(List<String> roles) {
71+
this.roles = roles;
72+
return this;
73+
}
74+
75+
public CreatePolicy addRole(String role) {
76+
this.roles.add(role);
77+
return this;
78+
}
79+
80+
public Expression getUsingExpression() {
81+
return usingExpression;
82+
}
83+
84+
public CreatePolicy setUsingExpression(Expression usingExpression) {
85+
this.usingExpression = usingExpression;
86+
return this;
87+
}
88+
89+
public Expression getWithCheckExpression() {
90+
return withCheckExpression;
91+
}
92+
93+
public CreatePolicy setWithCheckExpression(Expression withCheckExpression) {
94+
this.withCheckExpression = withCheckExpression;
95+
return this;
96+
}
97+
98+
@Override
99+
public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
100+
return statementVisitor.visit(this, context);
101+
}
102+
103+
@Override
104+
public String toString() {
105+
StringBuilder builder = new StringBuilder("CREATE POLICY ");
106+
builder.append(policyName);
107+
builder.append(" ON ");
108+
builder.append(table.toString());
109+
110+
if (command != null) {
111+
builder.append(" FOR ").append(command);
112+
}
113+
114+
if (roles != null && !roles.isEmpty()) {
115+
builder.append(" TO ");
116+
for (int i = 0; i < roles.size(); i++) {
117+
if (i > 0) {
118+
builder.append(", ");
119+
}
120+
builder.append(roles.get(i));
121+
}
122+
}
123+
124+
if (usingExpression != null) {
125+
builder.append(" USING (").append(usingExpression.toString()).append(")");
126+
}
127+
128+
if (withCheckExpression != null) {
129+
builder.append(" WITH CHECK (").append(withCheckExpression.toString()).append(")");
130+
}
131+
132+
return builder.toString();
133+
}
134+
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
import net.sf.jsqlparser.statement.analyze.Analyze;
9191
import net.sf.jsqlparser.statement.comment.Comment;
9292
import net.sf.jsqlparser.statement.create.index.CreateIndex;
93+
import net.sf.jsqlparser.statement.create.policy.CreatePolicy;
9394
import net.sf.jsqlparser.statement.create.schema.CreateSchema;
9495
import net.sf.jsqlparser.statement.create.sequence.CreateSequence;
9596
import net.sf.jsqlparser.statement.create.synonym.CreateSynonym;
@@ -1845,4 +1846,17 @@ public <S> Void visit(LockStatement lock, S context) {
18451846
public void visit(LockStatement lock) {
18461847
StatementVisitor.super.visit(lock);
18471848
}
1849+
1850+
@Override
1851+
public <S> Void visit(CreatePolicy createPolicy, S context) {
1852+
if (createPolicy.getTable() != null) {
1853+
visit(createPolicy.getTable(), context);
1854+
}
1855+
return null;
1856+
}
1857+
1858+
@Override
1859+
public void visit(CreatePolicy createPolicy) {
1860+
StatementVisitor.super.visit(createPolicy);
1861+
}
18481862
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import net.sf.jsqlparser.statement.analyze.Analyze;
4343
import net.sf.jsqlparser.statement.comment.Comment;
4444
import net.sf.jsqlparser.statement.create.index.CreateIndex;
45+
import net.sf.jsqlparser.statement.create.policy.CreatePolicy;
4546
import net.sf.jsqlparser.statement.create.schema.CreateSchema;
4647
import net.sf.jsqlparser.statement.create.sequence.CreateSequence;
4748
import net.sf.jsqlparser.statement.create.synonym.CreateSynonym;
@@ -520,4 +521,10 @@ public <S> StringBuilder visit(LockStatement lock, S context) {
520521
builder.append(lock.toString());
521522
return builder;
522523
}
524+
525+
@Override
526+
public <S> StringBuilder visit(CreatePolicy createPolicy, S context) {
527+
builder.append(createPolicy.toString());
528+
return builder;
529+
}
523530
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import net.sf.jsqlparser.statement.comment.Comment;
4040
import net.sf.jsqlparser.statement.create.function.CreateFunction;
4141
import net.sf.jsqlparser.statement.create.index.CreateIndex;
42+
import net.sf.jsqlparser.statement.create.policy.CreatePolicy;
4243
import net.sf.jsqlparser.statement.create.procedure.CreateProcedure;
4344
import net.sf.jsqlparser.statement.create.schema.CreateSchema;
4445
import net.sf.jsqlparser.statement.create.sequence.CreateSequence;
@@ -589,4 +590,14 @@ public void visit(Import imprt) {
589590
public void visit(Export export) {
590591
visit(export, null);
591592
}
593+
594+
@Override
595+
public <S> Void visit(CreatePolicy createPolicy, S context) {
596+
// TODO: not yet implemented
597+
return null;
598+
}
599+
600+
public void visit(CreatePolicy createPolicy) {
601+
visit(createPolicy, null);
602+
}
592603
}

0 commit comments

Comments
 (0)