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
2 changes: 2 additions & 0 deletions src/main/java/net/sf/jsqlparser/parser/feature/Feature.java
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,8 @@ public enum Feature {
* @see CreateSchema
*/
createSchema,
/** SQL ALTER SCHEMA rename and owner changes. */
alterSchema,
/**
* SQL "CREATE VIEW" statement is allowed
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/
package net.sf.jsqlparser.statement;

import net.sf.jsqlparser.statement.alter.schema.AlterSchema;
import net.sf.jsqlparser.statement.oracle.OracleBlock;
import net.sf.jsqlparser.statement.oracle.OracleAssignment;
import net.sf.jsqlparser.statement.oracle.OracleNullStatement;
Expand Down Expand Up @@ -534,6 +535,14 @@ default void visit(CreateRole statement) {
visit(statement, null);
}

default <S> T visit(AlterSchema statement, S context) {
return null;
}

default void visit(AlterSchema statement) {
visit(statement, null);
}

default <S> T visit(AlterRole statement, S context) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*-
* #%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.alter.schema;

import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.StatementVisitor;

/** PostgreSQL schema name or ownership change. Names retain SQL identifier quoting. */
public class AlterSchema implements Statement {
public enum Action {
RENAME, OWNER
}

private String schemaName;
private Action action;
private String newName;
private String owner;

public String getSchemaName() {
return schemaName;
}

public void setSchemaName(String schemaName) {
this.schemaName = schemaName;
}

public Action getAction() {
return action;
}

public void setAction(Action action) {
this.action = action;
}

public String getNewName() {
return newName;
}

public void setNewName(String newName) {
this.newName = newName;
}

public String getOwner() {
return owner;
}

public void setOwner(String owner) {
this.owner = owner;
}

public StringBuilder appendTo(StringBuilder builder) {
builder.append("ALTER SCHEMA ").append(schemaName);
if (action == Action.RENAME) {
builder.append(" RENAME TO ").append(newName);
} else if (action == Action.OWNER) {
builder.append(" OWNER TO ").append(owner);
} else {
throw new IllegalStateException("Expected a schema alteration action");
}
return builder;
}

@Override
public String toString() {
return appendTo(new StringBuilder()).toString();
}

@Override
public <T, S> T accept(StatementVisitor<T> visitor, S context) {
return visitor.visit(this, context);
}
}
7 changes: 7 additions & 0 deletions src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/
package net.sf.jsqlparser.util;

import net.sf.jsqlparser.statement.alter.schema.AlterSchema;
import net.sf.jsqlparser.statement.select.MatchRecognize;
import net.sf.jsqlparser.expression.RowPatternFunction;

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

@Override
public <S> Void visit(AlterSchema statement, S context) {
// Schema and owner names do not refer to tables.
return null;
}

@Override
public <S> Void visit(AlterRole statement, S context) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/
package net.sf.jsqlparser.util.deparser;

import net.sf.jsqlparser.statement.alter.schema.AlterSchema;
import net.sf.jsqlparser.statement.oracle.OracleBlock;
import net.sf.jsqlparser.statement.oracle.OracleAssignment;
import net.sf.jsqlparser.statement.oracle.OracleNullStatement;
Expand Down Expand Up @@ -708,6 +709,11 @@ public <S> StringBuilder visit(CreateRole statement, S context) {
return builder;
}

@Override
public <S> StringBuilder visit(AlterSchema statement, S context) {
return statement.appendTo(builder);
}

@Override
public <S> StringBuilder visit(AlterRole statement, S context) {
statement.appendTo(builder, e -> e.accept(expressionDeParser, context));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public class FeaturesAllowed implements FeatureSetValidation, ModifyableFeatureS
*/
public static final FeaturesAllowed ALTER =
new FeaturesAllowed("ALTER", Feature.alterTable, Feature.alterSequence,
Feature.alterView, Feature.alterIndex)
Feature.alterView, Feature.alterIndex, Feature.alterSchema)
.unmodifyable();
/**
* all "DROP" {@link Feature}'s
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public enum PostgresqlVersion implements Version {
Feature.createDomain, Feature.alterDomain,
Feature.createExtension, Feature.alterExtension, // https://www.postgresql.org/docs/current/sql-altersequence.html
Feature.alterSequence, // https://www.postgresql.org/docs/current/sql-createschema.html
Feature.createSchema, // https://www.postgresql.org/docs/current/sql-createindex.html
Feature.createSchema, Feature.alterSchema, // https://www.postgresql.org/docs/current/sql-createindex.html
Feature.createIndex, // https://www.postgresql.org/docs/current/sql-createtable.html
Feature.createTable, Feature.createTableUnlogged,
Feature.createTableCreateOptionStrings, Feature.createTableTableOptionStrings,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/
package net.sf.jsqlparser.util.validation.validator;

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

@Override
public <S> Void visit(AlterSchema statement, S context) {
validateFeature(Feature.alterSchema);
return null;
}

@Override
public <S> Void visit(AlterRole statement, S context) {
validateFeature(Feature.alterRole);
Expand Down
20 changes: 20 additions & 0 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import net.sf.jsqlparser.schema.*;
import net.sf.jsqlparser.statement.*;
import net.sf.jsqlparser.statement.analyze.*;
import net.sf.jsqlparser.statement.alter.*;
import net.sf.jsqlparser.statement.alter.schema.AlterSchema;
import net.sf.jsqlparser.statement.alter.sequence.*;
import net.sf.jsqlparser.statement.comment.*;
import net.sf.jsqlparser.statement.create.database.*;
Expand Down Expand Up @@ -17656,6 +17657,8 @@ Statement Alter():
|
statement = AlterDefaultPrivileges()
|
LOOKAHEAD(<K_SCHEMA>) statement = AlterSchema()
|
LOOKAHEAD(<K_TYPE>) statement = AlterType()
|
LOOKAHEAD(<K_DOMAIN>) statement = AlterDomain()
Expand Down Expand Up @@ -18095,6 +18098,23 @@ CreateRole CreateRole():
{ requireAccessSyntax(!role.isUseWith() || !role.getOptions().isEmpty(), "WITH requires role attributes"); return role; }
}

AlterSchema AlterSchema():
{
AlterSchema statement = new AlterSchema();
String name;
}
{
<K_SCHEMA> name=RelObjectName() { statement.setSchemaName(name); }
(
<K_RENAME> <K_TO> name=RelObjectName()
{ statement.setAction(AlterSchema.Action.RENAME); statement.setNewName(name); }
|
ContextualKeyword("OWNER") <K_TO> name=RelObjectName()
{ statement.setAction(AlterSchema.Action.OWNER); statement.setOwner(name); }
)
{ return statement; }
}

AlterRole AlterRole():
{
AlterRole role = new AlterRole(); String name; RoleOption option; Token token; Expression value;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*-
* #%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.alter;

import static org.junit.jupiter.api.Assertions.*;
import java.util.List;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.parser.feature.Feature;
import net.sf.jsqlparser.statement.StatementVisitorAdapter;
import net.sf.jsqlparser.statement.Statements;
import net.sf.jsqlparser.statement.alter.schema.AlterSchema;
import net.sf.jsqlparser.util.TablesNamesFinder;
import net.sf.jsqlparser.util.deparser.StatementDeParser;
import net.sf.jsqlparser.util.validation.Validation;
import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed;
import net.sf.jsqlparser.util.validation.feature.PostgresqlVersion;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class AlterSchemaTest {
@ParameterizedTest
@ValueSource(strings = {"ALTER SCHEMA analytics RENAME TO reporting",
"ALTER SCHEMA \"a.b\" RENAME TO \"next.schema\"",
"ALTER SCHEMA s OWNER TO app", "ALTER SCHEMA s OWNER TO CURRENT_USER",
"ALTER SCHEMA s OWNER TO CURRENT_ROLE", "ALTER SCHEMA s OWNER TO SESSION_USER",
"ALTER SCHEMA \"Case\" OWNER TO \"role.with.dot\""})
void parsesAndRendersSchemaActions(String sql) throws Exception {
AlterSchema schema = parse(sql);
assertEquals(sql, schema.toString());
assertTrue(new TablesNamesFinder().getTables(schema).isEmpty());
roundTrip(schema);
assertTrue(new Validation(List.of(PostgresqlVersion.V14), sql).validate().isEmpty());
}

@Test
void supportsAstMutationAndVisitorDispatch() throws Exception {
AlterSchema schema = parse("ALTER SCHEMA s RENAME TO renamed");
assertEquals(AlterSchema.Action.RENAME, schema.getAction());
assertEquals("renamed", schema.getNewName());
schema.setSchemaName("\"schema.name\"");
schema.setNewName("replacement");
roundTrip(schema);
schema.setAction(AlterSchema.Action.OWNER);
schema.setOwner("CURRENT_ROLE");
assertEquals("ALTER SCHEMA \"schema.name\" OWNER TO CURRENT_ROLE", schema.toString());
roundTrip(schema);
Object context = new Object();
assertEquals("visited", schema.accept(new StatementVisitorAdapter<String>() {
@Override
public <S> String visit(AlterSchema statement, S supplied) {
assertSame(schema, statement);
assertSame(context, supplied);
return "visited";
}
}, context));
}

@Test
void respectsStatementBoundariesAndValidationCapabilities() throws Exception {
Statements statements = CCJSqlParserUtil.parseStatements(
"ALTER SCHEMA s OWNER TO CURRENT_USER; SELECT 1;");
assertEquals(2, statements.size());
assertInstanceOf(AlterSchema.class, statements.get(0));
assertFalse(new Validation(List.of(FeaturesAllowed.SELECT),
"ALTER SCHEMA s RENAME TO r").validate().isEmpty());
assertTrue(new Validation(List.of(new FeaturesAllowed().add(Feature.alterSchema)),
"ALTER SCHEMA s RENAME TO r").validate().isEmpty());
}

@ParameterizedTest
@ValueSource(strings = {"ALTER SCHEMA s RENAME", "ALTER SCHEMA s OWNER TO",
"ALTER SCHEMA s RENAME TO 'not an identifier'", "ALTER SCHEMA s OWNER TO a.b",
"ALTER SCHEMA s OWNER TO a, b", "ALTER SCHEMA a.b RENAME TO c"})
void rejectsIncompleteActionsAndQualifiedSchemaOrRoleNames(String sql) {
assertThrows(JSQLParserException.class, () -> parse(sql));
}

private static AlterSchema parse(String sql) throws JSQLParserException {
return (AlterSchema) CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.POSTGRESQL));
}

private static void roundTrip(AlterSchema schema) throws Exception {
StringBuilder out = new StringBuilder();
schema.accept(new StatementDeParser(out), null);
assertEquals(schema.toString(), out.toString());
assertEquals(schema.toString(), parse(out.toString()).toString());
}
}
Loading