Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,9 @@ public <S> T visit(LockStatement lock, S context) {

@Override
public <S> T visit(CreatePolicy createPolicy, S context) {
fromItemVisitor.visitFromItem(createPolicy.getTable(), context);
expressionVisitor.visitExpression(createPolicy.getUsingExpression(), context);
expressionVisitor.visitExpression(createPolicy.getWithCheckExpression(), context);

return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,16 @@
/**
* PostgreSQL CREATE POLICY statement for Row Level Security (RLS).
*
* Syntax: CREATE POLICY name ON table_name [ FOR { ALL | SELECT | INSERT | UPDATE | DELETE } ] [ TO
* { role_name | PUBLIC | CURRENT_USER | SESSION_USER } [, ...] ] [ USING ( using_expression ) ] [
* WITH CHECK ( check_expression ) ]
* Syntax: CREATE POLICY name ON table_name [ AS { PERMISSIVE | RESTRICTIVE } ] [ FOR { ALL | SELECT
* | INSERT | UPDATE | DELETE } ] [ TO { role_name | PUBLIC | CURRENT_ROLE | CURRENT_USER |
* SESSION_USER } [, ...] ] [ USING ( using_expression ) ] [ WITH CHECK ( check_expression ) ]
*/
public class CreatePolicy implements Statement {

private String policyName;
private Table table;
private String command; // ALL, SELECT, INSERT, UPDATE, DELETE
private PolicyMode policyMode;
private PolicyCommand policyCommand;
private List<String> roles = new ArrayList<>();
private Expression usingExpression;
private Expression withCheckExpression;
Expand All @@ -51,15 +52,71 @@ public CreatePolicy setTable(Table table) {
return this;
}

/**
* Returns the explicitly specified policy mode, or {@code null} when the {@code AS} clause was
* omitted.
*
* @return the explicitly specified policy mode
*/
public PolicyMode getPolicyMode() {
return policyMode;
}

public CreatePolicy setPolicyMode(PolicyMode policyMode) {
this.policyMode = policyMode;
return this;
}

/**
* Returns the effective PostgreSQL policy mode, including the default for an omitted {@code AS}
* clause.
*
* @return the explicit policy mode, or {@link PolicyMode#PERMISSIVE}
*/
public PolicyMode getEffectivePolicyMode() {
return policyMode != null ? policyMode : PolicyMode.PERMISSIVE;
}

/**
* Returns the explicitly specified command as a string for backwards compatibility.
*
* @return the explicitly specified command, or {@code null} when the {@code FOR} clause was
* omitted
*/
public String getCommand() {
return command;
return policyCommand != null ? policyCommand.name() : null;
}

public CreatePolicy setCommand(String command) {
this.command = command;
this.policyCommand = command != null ? PolicyCommand.from(command) : null;
return this;
}

/**
* Returns the explicitly specified command, or {@code null} when the {@code FOR} clause was
* omitted.
*
* @return the explicitly specified command
*/
public PolicyCommand getPolicyCommand() {
return policyCommand;
}

public CreatePolicy setPolicyCommand(PolicyCommand policyCommand) {
this.policyCommand = policyCommand;
return this;
}

/**
* Returns the effective PostgreSQL command, including the default for an omitted {@code FOR}
* clause.
*
* @return the explicit command, or {@link PolicyCommand#ALL}
*/
public PolicyCommand getEffectivePolicyCommand() {
return policyCommand != null ? policyCommand : PolicyCommand.ALL;
}

public List<String> getRoles() {
return roles;
}
Expand Down Expand Up @@ -104,8 +161,12 @@ public String toString() {
builder.append(" ON ");
builder.append(table.toString());

if (command != null) {
builder.append(" FOR ").append(command);
if (policyMode != null) {
builder.append(" AS ").append(policyMode);
}

if (policyCommand != null) {
builder.append(" FOR ").append(policyCommand);
}

if (roles != null && !roles.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2026 JSQLParser
* %%
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
* #L%
*/
package net.sf.jsqlparser.statement.create.policy;

import java.util.Locale;

/**
* Commands to which a PostgreSQL policy can apply.
*/
public enum PolicyCommand {
ALL, SELECT, INSERT, UPDATE, DELETE;

public static PolicyCommand from(String command) {
return Enum.valueOf(PolicyCommand.class, command.toUpperCase(Locale.ROOT));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2026 JSQLParser
* %%
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
* #L%
*/
package net.sf.jsqlparser.statement.create.policy;

import java.util.Locale;

/**
* PostgreSQL policy evaluation mode.
*/
public enum PolicyMode {
PERMISSIVE, RESTRICTIVE;

public static PolicyMode from(String mode) {
return Enum.valueOf(PolicyMode.class, mode.toUpperCase(Locale.ROOT));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2026 JSQLParser
* %%
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
* #L%
*/
package net.sf.jsqlparser.util.deparser;

import net.sf.jsqlparser.expression.ExpressionVisitor;
import net.sf.jsqlparser.statement.create.policy.CreatePolicy;

public class CreatePolicyDeParser extends AbstractDeParser<CreatePolicy> {

private final ExpressionVisitor<StringBuilder> expressionVisitor;

public CreatePolicyDeParser(StringBuilder builder) {
super(builder);
ExpressionDeParser expressionDeParser = new ExpressionDeParser();
expressionDeParser.setBuilder(builder);
this.expressionVisitor = expressionDeParser;
}

public CreatePolicyDeParser(ExpressionVisitor<StringBuilder> expressionVisitor,
StringBuilder builder) {
super(builder);
this.expressionVisitor = expressionVisitor;
}

@Override
public void deParse(CreatePolicy createPolicy) {
builder.append("CREATE POLICY ").append(createPolicy.getPolicyName());
builder.append(" ON ").append(createPolicy.getTable());

if (createPolicy.getPolicyMode() != null) {
builder.append(" AS ").append(createPolicy.getPolicyMode());
}

if (createPolicy.getPolicyCommand() != null) {
builder.append(" FOR ").append(createPolicy.getPolicyCommand());
}

if (createPolicy.getRoles() != null && !createPolicy.getRoles().isEmpty()) {
builder.append(" TO ").append(String.join(", ", createPolicy.getRoles()));
}

if (createPolicy.getUsingExpression() != null) {
builder.append(" USING (");
createPolicy.getUsingExpression().accept(expressionVisitor, null);
builder.append(")");
}

if (createPolicy.getWithCheckExpression() != null) {
builder.append(" WITH CHECK (");
createPolicy.getWithCheckExpression().accept(expressionVisitor, null);
builder.append(")");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,7 @@ public <S> StringBuilder visit(LockStatement lock, S context) {

@Override
public <S> StringBuilder visit(CreatePolicy createPolicy, S context) {
builder.append(createPolicy.toString());
new CreatePolicyDeParser(expressionDeParser, builder).deParse(createPolicy);
return builder;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,9 @@ public void visit(Export export) {

@Override
public <S> Void visit(CreatePolicy createPolicy, S context) {
// TODO: not yet implemented
validateOptionalFromItem(createPolicy.getTable());
validateOptionalExpression(createPolicy.getUsingExpression());
validateOptionalExpression(createPolicy.getWithCheckExpression());
return null;
}

Expand Down
9 changes: 8 additions & 1 deletion src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -15131,6 +15131,7 @@ CreatePolicy CreatePolicy() #CreatePolicy:
CreatePolicy createPolicy = new CreatePolicy();
String policyName;
Table table;
String policyMode = null;
Token commandToken = null;
String roleName;
Expression usingExpr = null;
Expand All @@ -15140,14 +15141,20 @@ CreatePolicy CreatePolicy() #CreatePolicy:
<K_POLICY> policyName=RelObjectName() { createPolicy.setPolicyName(policyName); }
<K_ON> table=Table() { createPolicy.setTable(table); }

[ <K_AS>
LOOKAHEAD({ isKeywordAhead("PERMISSIVE") || isKeywordAhead("RESTRICTIVE") })
policyMode=RelObjectName()
{ createPolicy.setPolicyMode(PolicyMode.from(policyMode)); }
]

[ <K_FOR>
( commandToken=<K_ALL>
| commandToken=<K_SELECT>
| commandToken=<K_INSERT>
| commandToken=<K_UPDATE>
| commandToken=<K_DELETE>
)
{ createPolicy.setCommand(commandToken.image); }
{ createPolicy.setPolicyCommand(PolicyCommand.from(commandToken.image)); }
]

[ <K_TO>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.create.policy.CreatePolicy;
import net.sf.jsqlparser.statement.create.policy.PolicyCommand;
import net.sf.jsqlparser.statement.create.policy.PolicyMode;
import org.junit.jupiter.api.Test;

import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed;
Expand All @@ -33,6 +35,10 @@ public void testCreatePolicyBasic() throws JSQLParserException {
CreatePolicy policy = (CreatePolicy) stmt;
assertEquals("policy_name", policy.getPolicyName());
assertEquals("table_name", policy.getTable().getName());
assertNull(policy.getPolicyMode());
assertEquals(PolicyMode.PERMISSIVE, policy.getEffectivePolicyMode());
assertNull(policy.getPolicyCommand());
assertEquals(PolicyCommand.ALL, policy.getEffectivePolicyCommand());
}

@Test
Expand All @@ -55,6 +61,7 @@ public void testCreatePolicyWithForClause() throws JSQLParserException {

CreatePolicy policy = (CreatePolicy) CCJSqlParserUtil.parse(sql);
assertEquals("SELECT", policy.getCommand());
assertEquals(PolicyCommand.SELECT, policy.getPolicyCommand());
}

@Test
Expand All @@ -65,9 +72,40 @@ public void testCreatePolicyWithAllCommands() throws JSQLParserException {
assertSqlCanBeParsedAndDeparsed(sql, true);
CreatePolicy policy = (CreatePolicy) CCJSqlParserUtil.parse(sql);
assertEquals(cmd, policy.getCommand());
assertEquals(PolicyCommand.from(cmd), policy.getPolicyCommand());
}
}

@Test
public void testCreateRestrictivePolicy() throws JSQLParserException {
String sql = "CREATE POLICY tenant_policy ON users AS RESTRICTIVE FOR SELECT TO app "
+ "USING (tenant_id = current_user)";
assertSqlCanBeParsedAndDeparsed(sql, true);

CreatePolicy policy = (CreatePolicy) CCJSqlParserUtil.parse(sql);
assertEquals(PolicyMode.RESTRICTIVE, policy.getPolicyMode());
assertEquals(PolicyMode.RESTRICTIVE, policy.getEffectivePolicyMode());
assertEquals(PolicyCommand.SELECT, policy.getPolicyCommand());
assertEquals("app", policy.getRoles().get(0));
assertNotNull(policy.getUsingExpression());
}

@Test
public void testCreateExplicitPermissivePolicy() throws JSQLParserException {
String sql = "CREATE POLICY tenant_policy ON users AS PERMISSIVE";
assertSqlCanBeParsedAndDeparsed(sql, true);

CreatePolicy policy = (CreatePolicy) CCJSqlParserUtil.parse(sql);
assertEquals(PolicyMode.PERMISSIVE, policy.getPolicyMode());
assertEquals(PolicyMode.PERMISSIVE, policy.getEffectivePolicyMode());
}

@Test
public void testCreatePolicyRejectsUnknownMode() {
assertThrows(JSQLParserException.class,
() -> CCJSqlParserUtil.parse("CREATE POLICY p ON t AS UNKNOWN"));
}

@Test
public void testCreatePolicyWithSingleRole() throws JSQLParserException {
String sql = "CREATE POLICY policy1 ON table1 TO role1";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2026 JSQLParser
* %%
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
* #L%
*/
package net.sf.jsqlparser.util.deparser;

import static org.junit.jupiter.api.Assertions.assertEquals;

import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.create.policy.CreatePolicy;

import org.junit.jupiter.api.Test;

public class CreatePolicyDeParserTest {

@Test
public void testUseExternalExpressionDeParser() throws JSQLParserException {
StringBuilder builder = new StringBuilder();
ExpressionDeParser expressionDeParser = new ExpressionDeParser() {
@Override
public <S> StringBuilder visit(Column column, S context) {
getBuilder().append('"').append(column.getColumnName()).append('"');
return getBuilder();
}
};
expressionDeParser.setBuilder(builder);

CreatePolicy policy = (CreatePolicy) CCJSqlParserUtil.parse(
"CREATE POLICY tenant_policy ON users AS RESTRICTIVE USING (tenant_id = owner_id)");
new CreatePolicyDeParser(expressionDeParser, builder).deParse(policy);

assertEquals("CREATE POLICY tenant_policy ON users AS RESTRICTIVE "
+ "USING (\"tenant_id\" = \"owner_id\")", builder.toString());
}
}
Loading