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 @@ -9,6 +9,7 @@
*/
package net.sf.jsqlparser.statement;

import net.sf.jsqlparser.statement.alter.database.AlterDatabase;
import net.sf.jsqlparser.statement.alter.schema.AlterSchema;
import net.sf.jsqlparser.statement.oracle.OracleBlock;
import net.sf.jsqlparser.statement.oracle.OracleAssignment;
Expand Down Expand Up @@ -671,4 +672,12 @@ default void visit(OracleNullStatement statement) {
visit(statement, null);
}

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

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

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*-
* #%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.database;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.StatementVisitor;
import net.sf.jsqlparser.statement.create.database.DatabaseOption;

/** MySQL ALTER DATABASE (or SCHEMA); a missing name selects the current database. */
public class AlterDatabase implements Statement {
private String databaseName;
private boolean useSchemaKeyword;
private final List<DatabaseOption> options = new ArrayList<>();

public String getDatabaseName() {
return databaseName;
}

public void setDatabaseName(String databaseName) {
this.databaseName = databaseName;
}

public boolean isUseSchemaKeyword() {
return useSchemaKeyword;
}

public void setUseSchemaKeyword(boolean useSchemaKeyword) {
this.useSchemaKeyword = useSchemaKeyword;
}

public List<DatabaseOption> getOptions() {
return options;
}

public Optional<DatabaseOption> getOption(DatabaseOption.Kind kind) {
return options.stream().filter(option -> option.getKind() == kind).findFirst();
}

public StringBuilder appendTo(StringBuilder builder) {
builder.append(useSchemaKeyword ? "ALTER SCHEMA" : "ALTER DATABASE");
if (databaseName != null) {
builder.append(' ').append(databaseName);
}
options.forEach(option -> builder.append(' ').append(option));
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@
import java.util.Collections;
import java.util.List;

/** A structured option shared by MySQL {@code CREATE DATABASE} and {@code CREATE SCHEMA}. */
/**
* A structured option shared by MySQL {@code CREATE/ALTER DATABASE} and
* {@code CREATE/ALTER SCHEMA}.
*/
public class DatabaseOption implements Serializable {

public enum Kind {
CHARACTER_SET, COLLATE, ENCRYPTION, OTHER
CHARACTER_SET, COLLATE, ENCRYPTION, READ_ONLY, OTHER
}

private Kind kind = Kind.OTHER;
Expand Down
6 changes: 6 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.database.AlterDatabase;
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 @@ -2912,4 +2913,9 @@ public <S> Void visit(OracleNullStatement statement, S context) {
return null;
}

@Override
public <S> Void visit(AlterDatabase statement, S context) {
return null;
}

}
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.database.AlterDatabase;
import net.sf.jsqlparser.statement.alter.schema.AlterSchema;
import net.sf.jsqlparser.statement.oracle.OracleBlock;
import net.sf.jsqlparser.statement.oracle.OracleAssignment;
Expand Down Expand Up @@ -809,4 +810,9 @@ public <S> StringBuilder visit(OracleNullStatement statement, S context) {
return builder.append("NULL");
}

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

}
34 changes: 34 additions & 0 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ 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.database.AlterDatabase;
import net.sf.jsqlparser.statement.alter.sequence.*;
import net.sf.jsqlparser.statement.comment.*;
import net.sf.jsqlparser.statement.create.database.*;
Expand Down Expand Up @@ -14652,6 +14653,35 @@ DatabaseOption MySqlDatabaseOption():
{ return new DatabaseOption(kind, name, value, useDefault, useEquals); }
}

/** Reuses CREATE's database options; READ ONLY is specific to ALTER. */
AlterDatabase AlterDatabase():
{
AlterDatabase result = new AlterDatabase();
String name;
DatabaseOption option;
Token value;
boolean equals;
}
{
( <K_DATABASE> | <K_SCHEMA> { result.setUseSchemaKeyword(true); } )
[ LOOKAHEAD({ !isMySqlDatabaseOptionAhead() && getToken(1).kind != K_READ })
name=RelObjectName() { result.setDatabaseName(name); } ]
(
(
LOOKAHEAD({ isMySqlDatabaseOptionAhead() }) option=MySqlDatabaseOption()
|
<K_READ> <K_ONLY> { equals = false; }
[ "=" { equals = true; } ]
( value=<K_DEFAULT> | value=<S_LONG> ) {
requireDdlSyntax(value.kind == K_DEFAULT || "0".equals(value.image) || "1".equals(value.image),
"READ ONLY requires DEFAULT, 0, or 1");
option = new DatabaseOption(DatabaseOption.Kind.READ_ONLY, "READ ONLY", value.image, false, equals);
}
) { result.getOptions().add(option); }
)+
{ return result; }
}

List<String> PathSpecification():
{
Token tk;
Expand Down Expand Up @@ -17951,6 +17981,10 @@ Statement Alter():
|
statement = AlterDefaultPrivileges()
|
LOOKAHEAD({ getToken(1).kind == K_DATABASE || (getToken(1).kind == K_SCHEMA
&& Dialect.MYSQL.name().equals(getAsString(Feature.dialect))) })
statement = AlterDatabase()
|
LOOKAHEAD(<K_SCHEMA>) statement = AlterSchema()
|
LOOKAHEAD(<K_TYPE>) statement = AlterType()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*-
* #%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.database;

import static org.junit.jupiter.api.Assertions.*;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.StatementVisitorAdapter;
import net.sf.jsqlparser.statement.create.database.DatabaseOption;
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 AlterDatabaseTest {
@ParameterizedTest
@ValueSource(strings = {
"ALTER DATABASE d CHARACTER SET utf8mb4 COLLATE utf8mb4_bin",
"ALTER SCHEMA `d` DEFAULT CHARACTER SET = utf8mb4 DEFAULT COLLATE = utf8mb4_bin",
"ALTER DATABASE d DEFAULT ENCRYPTION = 'N'",
"ALTER DATABASE d READ ONLY = 0", "ALTER DATABASE d READ ONLY 1",
"ALTER DATABASE READ ONLY = DEFAULT", "ALTER SCHEMA DEFAULT CHARACTER SET utf8mb4"})
void roundTripAndStatementBoundary(String sql) throws JSQLParserException {
AlterDatabase statement = parse(sql);
assertEquals(sql, statement.toString());
StringBuilder buffer = new StringBuilder();
statement.accept(new StatementDeParser(buffer), null);
assertEquals(sql, buffer.toString());
assertEquals(sql, parse(buffer.toString()).toString());
assertTrue(new TablesNamesFinder().getTables(statement).isEmpty());
assertEquals(2, CCJSqlParserUtil.parseStatements(sql + "; SELECT 1",
p -> p.withDialect(Dialect.MYSQL)).size());
}

@Test
void optionMutationAndVisitorDispatch() throws JSQLParserException {
AlterDatabase statement = parse("ALTER DATABASE old_db READ ONLY = 0");
statement.setDatabaseName("new_db");
statement.getOption(DatabaseOption.Kind.READ_ONLY).orElseThrow().setValue("1");
assertEquals("ALTER DATABASE new_db READ ONLY = 1", statement.toString());
assertEquals("context:new_db", statement.accept(new StatementVisitorAdapter<String>() {
@Override
public <S> String visit(AlterDatabase database, S context) {
return context + ":" + database.getDatabaseName();
}
}, "context"));
}

@Test
void rejectIncompleteAndInvalidReadOnly() {
for (String sql : new String[] {"ALTER DATABASE d", "ALTER DATABASE d READ ONLY = 2",
"ALTER DATABASE d DEFAULT READ ONLY = 0"}) {
assertThrows(JSQLParserException.class, () -> parse(sql));
}
}

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