From b4e839f619b43d6719a326dc36f1d808a2833a11 Mon Sep 17 00:00:00 2001 From: minleejae Date: Fri, 25 Sep 2026 16:43:38 +0900 Subject: [PATCH 1/2] feat: share policy options across PostgreSQL policy DDL Signed-off-by: minleejae --- .../statement/StatementVisitor.java | 19 +++ .../statement/alter/AlterPolicy.java | 82 +++++++++++++ .../statement/create/policy/CreatePolicy.java | 64 +++++----- .../create/policy/PolicyOptions.java | 75 ++++++++++++ .../jsqlparser/statement/drop/DropPolicy.java | 82 +++++++++++++ .../sf/jsqlparser/util/TablesNamesFinder.java | 16 +++ .../util/deparser/CreatePolicyDeParser.java | 27 +---- .../util/deparser/StatementDeParser.java | 14 +++ .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 46 ++++++-- .../create/PostgreSqlPolicyDdlTest.java | 111 ++++++++++++++++++ 10 files changed, 462 insertions(+), 74 deletions(-) create mode 100644 src/main/java/net/sf/jsqlparser/statement/alter/AlterPolicy.java create mode 100644 src/main/java/net/sf/jsqlparser/statement/create/policy/PolicyOptions.java create mode 100644 src/main/java/net/sf/jsqlparser/statement/drop/DropPolicy.java create mode 100644 src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlPolicyDdlTest.java diff --git a/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java b/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java index e1a0d055e..e3fafd555 100644 --- a/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java +++ b/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java @@ -9,6 +9,9 @@ */ package net.sf.jsqlparser.statement; +import net.sf.jsqlparser.statement.alter.AlterPolicy; +import net.sf.jsqlparser.statement.drop.DropPolicy; + import net.sf.jsqlparser.statement.alter.database.AlterDatabase; import net.sf.jsqlparser.statement.alter.schema.AlterSchema; import net.sf.jsqlparser.statement.oracle.OracleBlock; @@ -680,4 +683,20 @@ default void visit(AlterDatabase statement) { visit(statement, null); } + + default T visit(AlterPolicy statement, S context) { + return null; + } + + default void visit(AlterPolicy statement) { + visit(statement, null); + } + + default T visit(DropPolicy statement, S context) { + return null; + } + + default void visit(DropPolicy statement) { + visit(statement, null); + } } diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterPolicy.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterPolicy.java new file mode 100644 index 000000000..bdc71bc57 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterPolicy.java @@ -0,0 +1,82 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2025 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.alter; + +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementVisitor; +import net.sf.jsqlparser.statement.create.policy.PolicyOptions; + +public class AlterPolicy implements Statement { + private String policyName; + private net.sf.jsqlparser.schema.Table table; + + public String getPolicyName() { + return policyName; + } + + public AlterPolicy setPolicyName(String policyName) { + this.policyName = policyName; + return this; + } + + public net.sf.jsqlparser.schema.Table getTable() { + return table; + } + + public AlterPolicy setTable(net.sf.jsqlparser.schema.Table table) { + this.table = table; + return this; + } + + private String newName; + private PolicyOptions options = new PolicyOptions(); + + public String getNewName() { + return newName; + } + + public void setNewName(String newName) { + this.newName = newName; + if (newName != null) { + options = new PolicyOptions(); + } + } + + public PolicyOptions getOptions() { + return options; + } + + public void setOptions(PolicyOptions options) { + this.options = java.util.Objects.requireNonNull(options); + newName = null; + } + + public StringBuilder appendTo(StringBuilder sql, + java.util.function.Consumer printer) { + sql.append("ALTER POLICY ").append(policyName).append(" ON ").append(table); + if (newName != null) { + sql.append(" RENAME TO ").append(newName); + } else { + options.appendTo(sql, printer); + } + return sql; + } + + @Override + public String toString() { + StringBuilder b = new StringBuilder(); + return appendTo(b, b::append).toString(); + } + + @Override + public T accept(StatementVisitor visitor, S context) { + return visitor.visit(this, context); + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/create/policy/CreatePolicy.java b/src/main/java/net/sf/jsqlparser/statement/create/policy/CreatePolicy.java index 2158e7efa..caf424359 100644 --- a/src/main/java/net/sf/jsqlparser/statement/create/policy/CreatePolicy.java +++ b/src/main/java/net/sf/jsqlparser/statement/create/policy/CreatePolicy.java @@ -14,7 +14,7 @@ import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.StatementVisitor; -import java.util.ArrayList; +import java.util.function.Consumer; import java.util.List; /** @@ -30,9 +30,16 @@ public class CreatePolicy implements Statement { private Table table; private PolicyMode policyMode; private PolicyCommand policyCommand; - private List roles = new ArrayList<>(); - private Expression usingExpression; - private Expression withCheckExpression; + private PolicyOptions options = new PolicyOptions(); + + public PolicyOptions getOptions() { + return options; + } + + public CreatePolicy setOptions(PolicyOptions options) { + this.options = java.util.Objects.requireNonNull(options); + return this; + } public String getPolicyName() { return policyName; @@ -118,34 +125,34 @@ public PolicyCommand getEffectivePolicyCommand() { } public List getRoles() { - return roles; + return options.getRoles(); } public CreatePolicy setRoles(List roles) { - this.roles = roles; + options.setRoles(roles); return this; } public CreatePolicy addRole(String role) { - this.roles.add(role); + options.getRoles().add(role); return this; } public Expression getUsingExpression() { - return usingExpression; + return options.getUsingExpression(); } public CreatePolicy setUsingExpression(Expression usingExpression) { - this.usingExpression = usingExpression; + options.setUsingExpression(usingExpression); return this; } public Expression getWithCheckExpression() { - return withCheckExpression; + return options.getWithCheckExpression(); } public CreatePolicy setWithCheckExpression(Expression withCheckExpression) { - this.withCheckExpression = withCheckExpression; + options.setWithCheckExpression(withCheckExpression); return this; } @@ -154,39 +161,22 @@ public T accept(StatementVisitor statementVisitor, S context) { return statementVisitor.visit(this, context); } - @Override - public String toString() { - StringBuilder builder = new StringBuilder("CREATE POLICY "); - builder.append(policyName); - builder.append(" ON "); - builder.append(table.toString()); + public StringBuilder appendTo(StringBuilder builder, Consumer printer) { + builder.append("CREATE POLICY ").append(policyName).append(" ON ").append(table); if (policyMode != null) { builder.append(" AS ").append(policyMode); } - if (policyCommand != null) { builder.append(" FOR ").append(policyCommand); } + options.appendTo(builder, printer); + return builder; + } - if (roles != null && !roles.isEmpty()) { - builder.append(" TO "); - for (int i = 0; i < roles.size(); i++) { - if (i > 0) { - builder.append(", "); - } - builder.append(roles.get(i)); - } - } - - if (usingExpression != null) { - builder.append(" USING (").append(usingExpression.toString()).append(")"); - } - - if (withCheckExpression != null) { - builder.append(" WITH CHECK (").append(withCheckExpression.toString()).append(")"); - } - - return builder.toString(); + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + return appendTo(builder, builder::append).toString(); } } diff --git a/src/main/java/net/sf/jsqlparser/statement/create/policy/PolicyOptions.java b/src/main/java/net/sf/jsqlparser/statement/create/policy/PolicyOptions.java new file mode 100644 index 000000000..53c9d6087 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/policy/PolicyOptions.java @@ -0,0 +1,75 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2025 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create.policy; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import net.sf.jsqlparser.expression.Expression; + +/** Roles and predicates shared by PostgreSQL CREATE and ALTER POLICY. */ +public class PolicyOptions implements Serializable { + private List roles = new ArrayList<>(); + private Expression usingExpression; + private Expression withCheckExpression; + + public List getRoles() { + return roles; + } + + public PolicyOptions setRoles(List roles) { + this.roles = roles; + return this; + } + + public Expression getUsingExpression() { + return usingExpression; + } + + public PolicyOptions setUsingExpression(Expression usingExpression) { + this.usingExpression = usingExpression; + return this; + } + + public Expression getWithCheckExpression() { + return withCheckExpression; + } + + public PolicyOptions setWithCheckExpression(Expression withCheckExpression) { + this.withCheckExpression = withCheckExpression; + return this; + } + + public void visitExpressions(Consumer visitor) { + if (usingExpression != null) { + visitor.accept(usingExpression); + } + if (withCheckExpression != null) { + visitor.accept(withCheckExpression); + } + } + + public void appendTo(StringBuilder sql, Consumer printer) { + if (roles != null && !roles.isEmpty()) { + sql.append(" TO ").append(String.join(", ", roles)); + } + if (usingExpression != null) { + sql.append(" USING ("); + printer.accept(usingExpression); + sql.append(')'); + } + if (withCheckExpression != null) { + sql.append(" WITH CHECK ("); + printer.accept(withCheckExpression); + sql.append(')'); + } + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/drop/DropPolicy.java b/src/main/java/net/sf/jsqlparser/statement/drop/DropPolicy.java new file mode 100644 index 000000000..f9500f771 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/drop/DropPolicy.java @@ -0,0 +1,82 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2025 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.drop; + +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.StatementVisitor; + +public class DropPolicy implements Statement { + private String policyName; + private net.sf.jsqlparser.schema.Table table; + private boolean ifExists = false; + private Behavior behavior; + + public String getPolicyName() { + return policyName; + } + + public DropPolicy setPolicyName(String policyName) { + this.policyName = policyName; + return this; + } + + public net.sf.jsqlparser.schema.Table getTable() { + return table; + } + + public DropPolicy setTable(net.sf.jsqlparser.schema.Table table) { + this.table = table; + return this; + } + + public boolean isIfExists() { + return ifExists; + } + + public DropPolicy setIfExists(boolean ifExists) { + this.ifExists = ifExists; + return this; + } + + public Behavior getBehavior() { + return behavior; + } + + public DropPolicy setBehavior(Behavior behavior) { + this.behavior = behavior; + return this; + } + + public enum Behavior { + CASCADE, RESTRICT + } + + public StringBuilder appendTo(StringBuilder sql) { + sql.append("DROP POLICY "); + if (ifExists) { + sql.append("IF EXISTS "); + } + sql.append(policyName).append(" ON ").append(table); + if (behavior != null) { + sql.append(' ').append(behavior); + } + return sql; + } + + @Override + public String toString() { + return appendTo(new StringBuilder()).toString(); + } + + @Override + public T accept(StatementVisitor visitor, S context) { + return visitor.visit(this, context); + } +} diff --git a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java index a223ce783..fe14e7800 100644 --- a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java +++ b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java @@ -9,6 +9,9 @@ */ package net.sf.jsqlparser.util; +import net.sf.jsqlparser.statement.alter.AlterPolicy; +import net.sf.jsqlparser.statement.drop.DropPolicy; + import net.sf.jsqlparser.statement.alter.database.AlterDatabase; import net.sf.jsqlparser.statement.alter.schema.AlterSchema; import net.sf.jsqlparser.statement.select.MatchRecognize; @@ -2921,4 +2924,17 @@ public Void visit(AlterDatabase statement, S context) { return null; } + + @Override + public Void visit(AlterPolicy statement, S context) { + visit(statement.getTable(), context); + statement.getOptions().visitExpressions(expression -> expression.accept(this, context)); + return null; + } + + @Override + public Void visit(DropPolicy statement, S context) { + visit(statement.getTable(), context); + return null; + } } diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/CreatePolicyDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/CreatePolicyDeParser.java index a470990bf..4d604494e 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/CreatePolicyDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/CreatePolicyDeParser.java @@ -31,31 +31,6 @@ public CreatePolicyDeParser(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(")"); - } + createPolicy.appendTo(builder, expression -> expression.accept(expressionVisitor, null)); } } diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java index 83a40d6fd..1142423cb 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java @@ -9,6 +9,9 @@ */ package net.sf.jsqlparser.util.deparser; +import net.sf.jsqlparser.statement.alter.AlterPolicy; +import net.sf.jsqlparser.statement.drop.DropPolicy; + import net.sf.jsqlparser.statement.alter.database.AlterDatabase; import net.sf.jsqlparser.statement.alter.schema.AlterSchema; import net.sf.jsqlparser.statement.oracle.OracleBlock; @@ -815,4 +818,15 @@ public StringBuilder visit(AlterDatabase statement, S context) { return statement.appendTo(builder); } + + @Override + public StringBuilder visit(AlterPolicy statement, S context) { + return statement.appendTo(builder, + expression -> expression.accept(expressionDeParser, context)); + } + + @Override + public StringBuilder visit(DropPolicy statement, S context) { + return statement.appendTo(builder); + } } diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 2c2608ae8..7ed4b59f1 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -3201,6 +3201,8 @@ Statement SingleStatement() : | stm = Create() | + LOOKAHEAD( ) stm = DropPolicy() + | stm = Drop() | stm = Analyze() @@ -18169,6 +18171,8 @@ Statement Alter(): ( ( + LOOKAHEAD() statement = AlterPolicy() + | LOOKAHEAD({ isKeywordAhead("ROLE") || isKeywordAhead("USER") || isKeywordAhead("GROUP") }) statement = AlterRole() | @@ -19893,9 +19897,7 @@ CreatePolicy CreatePolicy() #CreatePolicy: Table table; String policyMode = null; Token commandToken = null; - String roleName; - Expression usingExpr = null; - Expression checkExpr = null; + PolicyOptions options; } { policyName=RelObjectName() { createPolicy.setPolicyName(policyName); } @@ -19917,14 +19919,7 @@ CreatePolicy CreatePolicy() #CreatePolicy: { createPolicy.setPolicyCommand(PolicyCommand.from(commandToken.image)); } ] - [ - roleName=RelObjectName() { createPolicy.addRole(roleName); } - ( "," roleName=RelObjectName() { createPolicy.addRole(roleName); } )* - ] - - [ "(" usingExpr=Expression() ")" { createPolicy.setUsingExpression(usingExpr); } ] - - [ LOOKAHEAD(2) "(" checkExpr=Expression() ")" { createPolicy.setWithCheckExpression(checkExpr); } ] + options=PolicyOptions() { createPolicy.setOptions(options); } { @@ -19932,6 +19927,35 @@ CreatePolicy CreatePolicy() #CreatePolicy: } } + +PolicyOptions PolicyOptions(): +{ PolicyOptions options = new PolicyOptions(); String role; Expression expression; } +{ + [ role=RelObjectName() { options.getRoles().add(role); } + ( "," role=RelObjectName() { options.getRoles().add(role); } )* ] + [ "(" expression=Expression() ")" { options.setUsingExpression(expression); } ] + [ LOOKAHEAD(2) "(" expression=Expression() ")" { options.setWithCheckExpression(expression); } ] + { return options; } +} +AlterPolicy AlterPolicy(): +{ AlterPolicy statement = new AlterPolicy(); String name; Table table; PolicyOptions options; } +{ + name=RelObjectName() { statement.setPolicyName(name); } + table=Table() { statement.setTable(table); } + ( name=RelObjectName() { statement.setNewName(name); } + | options=PolicyOptions() { statement.setOptions(options); } ) + { return statement; } +} +DropPolicy DropPolicy(): +{ DropPolicy statement = new DropPolicy(); String name; Table table; Token behavior; } +{ + [ LOOKAHEAD(2) { statement.setIfExists(true); } ] + name=RelObjectName() { statement.setPolicyName(name); } + table=Table() { statement.setTable(table); } + [ (behavior= | behavior=) { statement.setBehavior(DropPolicy.Behavior.valueOf(behavior.image.toUpperCase(Locale.ROOT))); } ] + { return statement; } +} + UnsupportedStatement UnsupportedStatement(): { List tokens = new LinkedList(); diff --git a/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlPolicyDdlTest.java b/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlPolicyDdlTest.java new file mode 100644 index 000000000..e7059cb4d --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlPolicyDdlTest.java @@ -0,0 +1,111 @@ +/*- + * #%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; + +import static org.junit.jupiter.api.Assertions.*; +import java.util.Set; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.alter.AlterPolicy; +import net.sf.jsqlparser.statement.create.policy.CreatePolicy; +import net.sf.jsqlparser.statement.create.policy.PolicyOptions; +import net.sf.jsqlparser.statement.drop.DropPolicy; +import net.sf.jsqlparser.util.TablesNamesFinder; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class PostgreSqlPolicyDdlTest { + @ParameterizedTest + @ValueSource(strings = { + "CREATE POLICY pol ON t", + "CREATE POLICY pol ON t AS RESTRICTIVE FOR SELECT TO PUBLIC USING(id > 0)", + "CREATE POLICY pol ON t FOR INSERT TO CURRENT_USER WITH CHECK(id>0)", + "CREATE POLICY pol ON t FOR UPDATE TO CURRENT_ROLE, SESSION_USER USING(id>0) WITH CHECK(id<100)", + "ALTER POLICY pol ON t RENAME TO pol2", "ALTER POLICY pol ON t TO PUBLIC", + "ALTER POLICY pol ON t TO CURRENT_USER, SESSION_USER", + "ALTER POLICY pol ON t USING(id>1)", + "ALTER POLICY pol ON t WITH CHECK(id<10)", + "ALTER POLICY pol ON t TO CURRENT_ROLE USING(id>0) WITH CHECK(id<100)", + "DROP POLICY pol ON t", "DROP POLICY IF EXISTS pol ON t", + "DROP POLICY pol ON t CASCADE", + "DROP POLICY IF EXISTS pol ON t RESTRICT"}) + void statementsAndFollowingBoundariesRoundTrip(String sql) throws JSQLParserException { + assertRoundTrip(parse(sql)); + assertEquals(2, CCJSqlParserUtil.parseStatements(sql + "; SELECT 1", + p -> p.withDialect(Dialect.POSTGRESQL)).size()); + } + + @Test + void createAndAlterShareRolesAndPredicates() throws JSQLParserException { + CreatePolicy create = (CreatePolicy) parse("CREATE POLICY pol ON t TO PUBLIC USING(id>0)"); + AlterPolicy alter = (AlterPolicy) parse("ALTER POLICY pol ON t TO PUBLIC USING(id>0)"); + assertEquals(create.getRoles(), alter.getOptions().getRoles()); + assertEquals(create.getUsingExpression().toString(), + alter.getOptions().getUsingExpression().toString()); + alter.getOptions().setUsingExpression(CCJSqlParserUtil.parseCondExpression("id > 10")); + alter.setPolicyName("new_policy"); + alter.setTable(new Table("new_table")); + assertEquals("ALTER POLICY new_policy ON new_table TO PUBLIC USING (id > 10)", + alter.toString()); + assertRoundTrip(alter); + alter.setNewName("renamed"); + assertEquals("ALTER POLICY new_policy ON new_table RENAME TO renamed", alter.toString()); + alter.setOptions(new PolicyOptions() + .setUsingExpression(CCJSqlParserUtil.parseCondExpression("id > 1"))); + assertNull(alter.getNewName()); + assertRoundTrip(alter); + } + + @Test + void predicatesAndTargetsParticipateInTraversal() throws JSQLParserException { + String sql = "ALTER POLICY pol ON target USING (EXISTS (SELECT 1 FROM permissions))" + + " WITH CHECK (EXISTS (SELECT 1 FROM allowed_values))"; + assertEquals(Set.of("target", "permissions", "allowed_values"), + TablesNamesFinder.findTables(sql)); + assertEquals(Set.of("target"), TablesNamesFinder.findTables("DROP POLICY pol ON target")); + assertRoundTrip(parse(sql)); + } + + @Test + void dropPolicyNamesAndBehaviorAreMutable() throws JSQLParserException { + DropPolicy drop = (DropPolicy) parse("DROP POLICY IF EXISTS pol ON t CASCADE"); + assertTrue(drop.isIfExists()); + drop.setPolicyName("new_pol").setTable(new Table("new_t")) + .setBehavior(DropPolicy.Behavior.RESTRICT); + assertEquals("DROP POLICY IF EXISTS new_pol ON new_t RESTRICT", drop.toString()); + assertRoundTrip(drop); + } + + @Test + void createOnlyOptionsAndMalformedDropAreRejected() { + for (String sql : new String[] {"ALTER POLICY pol ON t AS RESTRICTIVE", + "ALTER POLICY pol ON t FOR SELECT", + "ALTER POLICY pol ON t RENAME TO pol2 USING(id>0)", "DROP POLICY pol", + "DROP POLICY pol ON t, u"}) { + assertThrows(JSQLParserException.class, () -> parse(sql)); + } + } + + private static Statement parse(String sql) throws JSQLParserException { + return CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.POSTGRESQL)); + } + + private static void assertRoundTrip(Statement statement) throws JSQLParserException { + StringBuilder builder = new StringBuilder(); + statement.accept(new StatementDeParser(builder), null); + assertEquals(statement.toString(), builder.toString()); + assertEquals(statement.toString(), parse(builder.toString()).toString()); + } +} From 1e2c780ce54ff952c07ff1fef99e5a53ce982293 Mon Sep 17 00:00:00 2001 From: minleejae Date: Fri, 25 Sep 2026 17:19:39 +0900 Subject: [PATCH 2/2] style: group policy fields before accessors Signed-off-by: minleejae --- .../java/net/sf/jsqlparser/statement/alter/AlterPolicy.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterPolicy.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterPolicy.java index bdc71bc57..6cb23c086 100644 --- a/src/main/java/net/sf/jsqlparser/statement/alter/AlterPolicy.java +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterPolicy.java @@ -16,6 +16,9 @@ public class AlterPolicy implements Statement { private String policyName; private net.sf.jsqlparser.schema.Table table; + private String newName; + private PolicyOptions options = new PolicyOptions(); + public String getPolicyName() { return policyName; @@ -35,8 +38,6 @@ public AlterPolicy setTable(net.sf.jsqlparser.schema.Table table) { return this; } - private String newName; - private PolicyOptions options = new PolicyOptions(); public String getNewName() { return newName;