Skip to content

Commit b1d3d7f

Browse files
committed
feat: support PostgreSQL ALTER SCHEMA actions
Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent 7f91819 commit b1d3d7f

10 files changed

Lines changed: 230 additions & 2 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
@@ -507,6 +507,8 @@ public enum Feature {
507507
* @see CreateSchema
508508
*/
509509
createSchema,
510+
/** SQL ALTER SCHEMA rename and owner changes. */
511+
alterSchema,
510512
/**
511513
* SQL "CREATE VIEW" statement is allowed
512514
*

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

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

12+
import net.sf.jsqlparser.statement.alter.schema.AlterSchema;
1213
import net.sf.jsqlparser.statement.oracle.OracleBlock;
1314
import net.sf.jsqlparser.statement.oracle.OracleAssignment;
1415
import net.sf.jsqlparser.statement.oracle.OracleNullStatement;
@@ -534,6 +535,14 @@ default void visit(CreateRole statement) {
534535
visit(statement, null);
535536
}
536537

538+
default <S> T visit(AlterSchema statement, S context) {
539+
return null;
540+
}
541+
542+
default void visit(AlterSchema statement) {
543+
visit(statement, null);
544+
}
545+
537546
default <S> T visit(AlterRole statement, S context) {
538547
return null;
539548
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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.alter.schema;
11+
12+
import net.sf.jsqlparser.statement.Statement;
13+
import net.sf.jsqlparser.statement.StatementVisitor;
14+
15+
/** PostgreSQL schema name or ownership change. Names retain SQL identifier quoting. */
16+
public class AlterSchema implements Statement {
17+
public enum Action {
18+
RENAME, OWNER
19+
}
20+
21+
private String schemaName;
22+
private Action action;
23+
private String newName;
24+
private String owner;
25+
26+
public String getSchemaName() {
27+
return schemaName;
28+
}
29+
30+
public void setSchemaName(String schemaName) {
31+
this.schemaName = schemaName;
32+
}
33+
34+
public Action getAction() {
35+
return action;
36+
}
37+
38+
public void setAction(Action action) {
39+
this.action = action;
40+
}
41+
42+
public String getNewName() {
43+
return newName;
44+
}
45+
46+
public void setNewName(String newName) {
47+
this.newName = newName;
48+
}
49+
50+
public String getOwner() {
51+
return owner;
52+
}
53+
54+
public void setOwner(String owner) {
55+
this.owner = owner;
56+
}
57+
58+
public StringBuilder appendTo(StringBuilder builder) {
59+
builder.append("ALTER SCHEMA ").append(schemaName);
60+
if (action == Action.RENAME) {
61+
builder.append(" RENAME TO ").append(newName);
62+
} else if (action == Action.OWNER) {
63+
builder.append(" OWNER TO ").append(owner);
64+
} else {
65+
throw new IllegalStateException("Expected a schema alteration action");
66+
}
67+
return builder;
68+
}
69+
70+
@Override
71+
public String toString() {
72+
return appendTo(new StringBuilder()).toString();
73+
}
74+
75+
@Override
76+
public <T, S> T accept(StatementVisitor<T> visitor, S context) {
77+
return visitor.visit(this, context);
78+
}
79+
}

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

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

12+
import net.sf.jsqlparser.statement.alter.schema.AlterSchema;
1213
import net.sf.jsqlparser.statement.select.MatchRecognize;
1314
import net.sf.jsqlparser.expression.RowPatternFunction;
1415

@@ -2783,6 +2784,12 @@ public <S> Void visit(CreateRole statement, S context) {
27832784
return null;
27842785
}
27852786

2787+
@Override
2788+
public <S> Void visit(AlterSchema statement, S context) {
2789+
// Schema and owner names do not refer to tables.
2790+
return null;
2791+
}
2792+
27862793
@Override
27872794
public <S> Void visit(AlterRole statement, S context) {
27882795
return null;

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

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

12+
import net.sf.jsqlparser.statement.alter.schema.AlterSchema;
1213
import net.sf.jsqlparser.statement.oracle.OracleBlock;
1314
import net.sf.jsqlparser.statement.oracle.OracleAssignment;
1415
import net.sf.jsqlparser.statement.oracle.OracleNullStatement;
@@ -708,6 +709,11 @@ public <S> StringBuilder visit(CreateRole statement, S context) {
708709
return builder;
709710
}
710711

712+
@Override
713+
public <S> StringBuilder visit(AlterSchema statement, S context) {
714+
return statement.appendTo(builder);
715+
}
716+
711717
@Override
712718
public <S> StringBuilder visit(AlterRole statement, S context) {
713719
statement.appendTo(builder, e -> e.accept(expressionDeParser, context));

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public class FeaturesAllowed implements FeatureSetValidation, ModifyableFeatureS
113113
*/
114114
public static final FeaturesAllowed ALTER =
115115
new FeaturesAllowed("ALTER", Feature.alterTable, Feature.alterSequence,
116-
Feature.alterView, Feature.alterIndex)
116+
Feature.alterView, Feature.alterIndex, Feature.alterSchema)
117117
.unmodifyable();
118118
/**
119119
* all "DROP" {@link Feature}'s

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public enum PostgresqlVersion implements Version {
103103
Feature.createDomain, Feature.alterDomain,
104104
Feature.createExtension, Feature.alterExtension, // https://www.postgresql.org/docs/current/sql-altersequence.html
105105
Feature.alterSequence, // https://www.postgresql.org/docs/current/sql-createschema.html
106-
Feature.createSchema, // https://www.postgresql.org/docs/current/sql-createindex.html
106+
Feature.createSchema, Feature.alterSchema, // https://www.postgresql.org/docs/current/sql-createindex.html
107107
Feature.createIndex, // https://www.postgresql.org/docs/current/sql-createtable.html
108108
Feature.createTable, Feature.createTableUnlogged,
109109
Feature.createTableCreateOptionStrings, Feature.createTableTableOptionStrings,

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

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

12+
import net.sf.jsqlparser.statement.alter.schema.AlterSchema;
1213
import net.sf.jsqlparser.statement.oracle.OracleBlock;
1314
import net.sf.jsqlparser.statement.oracle.OracleAssignment;
1415
import net.sf.jsqlparser.statement.oracle.OracleNullStatement;
@@ -882,6 +883,12 @@ public <S> Void visit(CreateRole statement, S context) {
882883
return null;
883884
}
884885

886+
@Override
887+
public <S> Void visit(AlterSchema statement, S context) {
888+
validateFeature(Feature.alterSchema);
889+
return null;
890+
}
891+
885892
@Override
886893
public <S> Void visit(AlterRole statement, S context) {
887894
validateFeature(Feature.alterRole);

src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import net.sf.jsqlparser.schema.*;
4949
import net.sf.jsqlparser.statement.*;
5050
import net.sf.jsqlparser.statement.analyze.*;
5151
import net.sf.jsqlparser.statement.alter.*;
52+
import net.sf.jsqlparser.statement.alter.schema.AlterSchema;
5253
import net.sf.jsqlparser.statement.alter.sequence.*;
5354
import net.sf.jsqlparser.statement.comment.*;
5455
import net.sf.jsqlparser.statement.create.database.*;
@@ -17656,6 +17657,8 @@ Statement Alter():
1765617657
|
1765717658
statement = AlterDefaultPrivileges()
1765817659
|
17660+
LOOKAHEAD(<K_SCHEMA>) statement = AlterSchema()
17661+
|
1765917662
LOOKAHEAD(<K_TYPE>) statement = AlterType()
1766017663
|
1766117664
LOOKAHEAD(<K_DOMAIN>) statement = AlterDomain()
@@ -18095,6 +18098,23 @@ CreateRole CreateRole():
1809518098
{ requireAccessSyntax(!role.isUseWith() || !role.getOptions().isEmpty(), "WITH requires role attributes"); return role; }
1809618099
}
1809718100

18101+
AlterSchema AlterSchema():
18102+
{
18103+
AlterSchema statement = new AlterSchema();
18104+
String name;
18105+
}
18106+
{
18107+
<K_SCHEMA> name=RelObjectName() { statement.setSchemaName(name); }
18108+
(
18109+
<K_RENAME> <K_TO> name=RelObjectName()
18110+
{ statement.setAction(AlterSchema.Action.RENAME); statement.setNewName(name); }
18111+
|
18112+
ContextualKeyword("OWNER") <K_TO> name=RelObjectName()
18113+
{ statement.setAction(AlterSchema.Action.OWNER); statement.setOwner(name); }
18114+
)
18115+
{ return statement; }
18116+
}
18117+
1809818118
AlterRole AlterRole():
1809918119
{
1810018120
AlterRole role = new AlterRole(); String name; RoleOption option; Token token; Expression value;
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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.alter;
11+
12+
import static org.junit.jupiter.api.Assertions.*;
13+
import java.util.List;
14+
import net.sf.jsqlparser.JSQLParserException;
15+
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
16+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
17+
import net.sf.jsqlparser.parser.feature.Feature;
18+
import net.sf.jsqlparser.statement.StatementVisitorAdapter;
19+
import net.sf.jsqlparser.statement.Statements;
20+
import net.sf.jsqlparser.statement.alter.schema.AlterSchema;
21+
import net.sf.jsqlparser.util.TablesNamesFinder;
22+
import net.sf.jsqlparser.util.deparser.StatementDeParser;
23+
import net.sf.jsqlparser.util.validation.Validation;
24+
import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed;
25+
import net.sf.jsqlparser.util.validation.feature.PostgresqlVersion;
26+
import org.junit.jupiter.api.Test;
27+
import org.junit.jupiter.params.ParameterizedTest;
28+
import org.junit.jupiter.params.provider.ValueSource;
29+
30+
class AlterSchemaTest {
31+
@ParameterizedTest
32+
@ValueSource(strings = {"ALTER SCHEMA analytics RENAME TO reporting",
33+
"ALTER SCHEMA \"a.b\" RENAME TO \"next.schema\"",
34+
"ALTER SCHEMA s OWNER TO app", "ALTER SCHEMA s OWNER TO CURRENT_USER",
35+
"ALTER SCHEMA s OWNER TO CURRENT_ROLE", "ALTER SCHEMA s OWNER TO SESSION_USER",
36+
"ALTER SCHEMA \"Case\" OWNER TO \"role.with.dot\""})
37+
void parsesAndRendersSchemaActions(String sql) throws Exception {
38+
AlterSchema schema = parse(sql);
39+
assertEquals(sql, schema.toString());
40+
assertTrue(new TablesNamesFinder().getTables(schema).isEmpty());
41+
roundTrip(schema);
42+
assertTrue(new Validation(List.of(PostgresqlVersion.V14), sql).validate().isEmpty());
43+
}
44+
45+
@Test
46+
void supportsAstMutationAndVisitorDispatch() throws Exception {
47+
AlterSchema schema = parse("ALTER SCHEMA s RENAME TO renamed");
48+
assertEquals(AlterSchema.Action.RENAME, schema.getAction());
49+
assertEquals("renamed", schema.getNewName());
50+
schema.setSchemaName("\"schema.name\"");
51+
schema.setNewName("replacement");
52+
roundTrip(schema);
53+
schema.setAction(AlterSchema.Action.OWNER);
54+
schema.setOwner("CURRENT_ROLE");
55+
assertEquals("ALTER SCHEMA \"schema.name\" OWNER TO CURRENT_ROLE", schema.toString());
56+
roundTrip(schema);
57+
Object context = new Object();
58+
assertEquals("visited", schema.accept(new StatementVisitorAdapter<String>() {
59+
@Override
60+
public <S> String visit(AlterSchema statement, S supplied) {
61+
assertSame(schema, statement);
62+
assertSame(context, supplied);
63+
return "visited";
64+
}
65+
}, context));
66+
}
67+
68+
@Test
69+
void respectsStatementBoundariesAndValidationCapabilities() throws Exception {
70+
Statements statements = CCJSqlParserUtil.parseStatements(
71+
"ALTER SCHEMA s OWNER TO CURRENT_USER; SELECT 1;");
72+
assertEquals(2, statements.size());
73+
assertInstanceOf(AlterSchema.class, statements.get(0));
74+
assertFalse(new Validation(List.of(FeaturesAllowed.SELECT),
75+
"ALTER SCHEMA s RENAME TO r").validate().isEmpty());
76+
assertTrue(new Validation(List.of(new FeaturesAllowed().add(Feature.alterSchema)),
77+
"ALTER SCHEMA s RENAME TO r").validate().isEmpty());
78+
}
79+
80+
@ParameterizedTest
81+
@ValueSource(strings = {"ALTER SCHEMA s RENAME", "ALTER SCHEMA s OWNER TO",
82+
"ALTER SCHEMA s RENAME TO 'not an identifier'", "ALTER SCHEMA s OWNER TO a.b",
83+
"ALTER SCHEMA s OWNER TO a, b", "ALTER SCHEMA a.b RENAME TO c"})
84+
void rejectsIncompleteActionsAndQualifiedSchemaOrRoleNames(String sql) {
85+
assertThrows(JSQLParserException.class, () -> parse(sql));
86+
}
87+
88+
private static AlterSchema parse(String sql) throws JSQLParserException {
89+
return (AlterSchema) CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.POSTGRESQL));
90+
}
91+
92+
private static void roundTrip(AlterSchema schema) throws Exception {
93+
StringBuilder out = new StringBuilder();
94+
schema.accept(new StatementDeParser(out), null);
95+
assertEquals(schema.toString(), out.toString());
96+
assertEquals(schema.toString(), parse(out.toString()).toString());
97+
}
98+
}

0 commit comments

Comments
 (0)