Skip to content

Commit daaf7d4

Browse files
committed
feat: share MySQL database options with ALTER DATABASE
Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent dac8159 commit daaf7d4

7 files changed

Lines changed: 196 additions & 2 deletions

File tree

‎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.database.AlterDatabase;
1213
import net.sf.jsqlparser.statement.alter.schema.AlterSchema;
1314
import net.sf.jsqlparser.statement.oracle.OracleBlock;
1415
import net.sf.jsqlparser.statement.oracle.OracleAssignment;
@@ -671,4 +672,12 @@ default void visit(OracleNullStatement statement) {
671672
visit(statement, null);
672673
}
673674

675+
default <S> T visit(AlterDatabase statement, S context) {
676+
return null;
677+
}
678+
679+
default void visit(AlterDatabase statement) {
680+
visit(statement, null);
681+
}
682+
674683
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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.database;
11+
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
import java.util.Optional;
15+
import net.sf.jsqlparser.statement.Statement;
16+
import net.sf.jsqlparser.statement.StatementVisitor;
17+
import net.sf.jsqlparser.statement.create.database.DatabaseOption;
18+
19+
/** MySQL ALTER DATABASE (or SCHEMA); a missing name selects the current database. */
20+
public class AlterDatabase implements Statement {
21+
private String databaseName;
22+
private boolean useSchemaKeyword;
23+
private final List<DatabaseOption> options = new ArrayList<>();
24+
25+
public String getDatabaseName() {
26+
return databaseName;
27+
}
28+
29+
public void setDatabaseName(String databaseName) {
30+
this.databaseName = databaseName;
31+
}
32+
33+
public boolean isUseSchemaKeyword() {
34+
return useSchemaKeyword;
35+
}
36+
37+
public void setUseSchemaKeyword(boolean useSchemaKeyword) {
38+
this.useSchemaKeyword = useSchemaKeyword;
39+
}
40+
41+
public List<DatabaseOption> getOptions() {
42+
return options;
43+
}
44+
45+
public Optional<DatabaseOption> getOption(DatabaseOption.Kind kind) {
46+
return options.stream().filter(option -> option.getKind() == kind).findFirst();
47+
}
48+
49+
public StringBuilder appendTo(StringBuilder builder) {
50+
builder.append(useSchemaKeyword ? "ALTER SCHEMA" : "ALTER DATABASE");
51+
if (databaseName != null) {
52+
builder.append(' ').append(databaseName);
53+
}
54+
options.forEach(option -> builder.append(' ').append(option));
55+
return builder;
56+
}
57+
58+
@Override
59+
public String toString() {
60+
return appendTo(new StringBuilder()).toString();
61+
}
62+
63+
@Override
64+
public <T, S> T accept(StatementVisitor<T> visitor, S context) {
65+
return visitor.visit(this, context);
66+
}
67+
}

‎src/main/java/net/sf/jsqlparser/statement/create/database/DatabaseOption.java‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@
1414
import java.util.Collections;
1515
import java.util.List;
1616

17-
/** A structured option shared by MySQL {@code CREATE DATABASE} and {@code CREATE SCHEMA}. */
17+
/**
18+
* A structured option shared by MySQL {@code CREATE/ALTER DATABASE} and
19+
* {@code CREATE/ALTER SCHEMA}.
20+
*/
1821
public class DatabaseOption implements Serializable {
1922

2023
public enum Kind {
21-
CHARACTER_SET, COLLATE, ENCRYPTION, OTHER
24+
CHARACTER_SET, COLLATE, ENCRYPTION, READ_ONLY, OTHER
2225
}
2326

2427
private Kind kind = Kind.OTHER;

‎src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.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;
1111

12+
import net.sf.jsqlparser.statement.alter.database.AlterDatabase;
1213
import net.sf.jsqlparser.statement.alter.schema.AlterSchema;
1314
import net.sf.jsqlparser.statement.select.MatchRecognize;
1415
import net.sf.jsqlparser.expression.RowPatternFunction;
@@ -2912,4 +2913,9 @@ public <S> Void visit(OracleNullStatement statement, S context) {
29122913
return null;
29132914
}
29142915

2916+
@Override
2917+
public <S> Void visit(AlterDatabase statement, S context) {
2918+
return null;
2919+
}
2920+
29152921
}

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

813+
@Override
814+
public <S> StringBuilder visit(AlterDatabase statement, S context) {
815+
return statement.appendTo(builder);
816+
}
817+
812818
}

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import net.sf.jsqlparser.statement.*;
5050
import net.sf.jsqlparser.statement.analyze.*;
5151
import net.sf.jsqlparser.statement.alter.*;
5252
import net.sf.jsqlparser.statement.alter.schema.AlterSchema;
53+
import net.sf.jsqlparser.statement.alter.database.AlterDatabase;
5354
import net.sf.jsqlparser.statement.alter.sequence.*;
5455
import net.sf.jsqlparser.statement.comment.*;
5556
import net.sf.jsqlparser.statement.create.database.*;
@@ -14652,6 +14653,35 @@ DatabaseOption MySqlDatabaseOption():
1465214653
{ return new DatabaseOption(kind, name, value, useDefault, useEquals); }
1465314654
}
1465414655

14656+
/** Reuses CREATE's database options; READ ONLY is specific to ALTER. */
14657+
AlterDatabase AlterDatabase():
14658+
{
14659+
AlterDatabase result = new AlterDatabase();
14660+
String name;
14661+
DatabaseOption option;
14662+
Token value;
14663+
boolean equals;
14664+
}
14665+
{
14666+
( <K_DATABASE> | <K_SCHEMA> { result.setUseSchemaKeyword(true); } )
14667+
[ LOOKAHEAD({ !isMySqlDatabaseOptionAhead() && getToken(1).kind != K_READ })
14668+
name=RelObjectName() { result.setDatabaseName(name); } ]
14669+
(
14670+
(
14671+
LOOKAHEAD({ isMySqlDatabaseOptionAhead() }) option=MySqlDatabaseOption()
14672+
|
14673+
<K_READ> <K_ONLY> { equals = false; }
14674+
[ "=" { equals = true; } ]
14675+
( value=<K_DEFAULT> | value=<S_LONG> ) {
14676+
requireDdlSyntax(value.kind == K_DEFAULT || "0".equals(value.image) || "1".equals(value.image),
14677+
"READ ONLY requires DEFAULT, 0, or 1");
14678+
option = new DatabaseOption(DatabaseOption.Kind.READ_ONLY, "READ ONLY", value.image, false, equals);
14679+
}
14680+
) { result.getOptions().add(option); }
14681+
)+
14682+
{ return result; }
14683+
}
14684+
1465514685
List<String> PathSpecification():
1465614686
{
1465714687
Token tk;
@@ -17951,6 +17981,10 @@ Statement Alter():
1795117981
|
1795217982
statement = AlterDefaultPrivileges()
1795317983
|
17984+
LOOKAHEAD({ getToken(1).kind == K_DATABASE || (getToken(1).kind == K_SCHEMA
17985+
&& Dialect.MYSQL.name().equals(getAsString(Feature.dialect))) })
17986+
statement = AlterDatabase()
17987+
|
1795417988
LOOKAHEAD(<K_SCHEMA>) statement = AlterSchema()
1795517989
|
1795617990
LOOKAHEAD(<K_TYPE>) statement = AlterType()
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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.database;
11+
12+
import static org.junit.jupiter.api.Assertions.*;
13+
import net.sf.jsqlparser.JSQLParserException;
14+
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
15+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
16+
import net.sf.jsqlparser.statement.StatementVisitorAdapter;
17+
import net.sf.jsqlparser.statement.create.database.DatabaseOption;
18+
import net.sf.jsqlparser.util.TablesNamesFinder;
19+
import net.sf.jsqlparser.util.deparser.StatementDeParser;
20+
import org.junit.jupiter.api.Test;
21+
import org.junit.jupiter.params.ParameterizedTest;
22+
import org.junit.jupiter.params.provider.ValueSource;
23+
24+
class AlterDatabaseTest {
25+
@ParameterizedTest
26+
@ValueSource(strings = {
27+
"ALTER DATABASE d CHARACTER SET utf8mb4 COLLATE utf8mb4_bin",
28+
"ALTER SCHEMA `d` DEFAULT CHARACTER SET = utf8mb4 DEFAULT COLLATE = utf8mb4_bin",
29+
"ALTER DATABASE d DEFAULT ENCRYPTION = 'N'",
30+
"ALTER DATABASE d READ ONLY = 0", "ALTER DATABASE d READ ONLY 1",
31+
"ALTER DATABASE READ ONLY = DEFAULT", "ALTER SCHEMA DEFAULT CHARACTER SET utf8mb4"})
32+
void roundTripAndStatementBoundary(String sql) throws JSQLParserException {
33+
AlterDatabase statement = parse(sql);
34+
assertEquals(sql, statement.toString());
35+
StringBuilder buffer = new StringBuilder();
36+
statement.accept(new StatementDeParser(buffer), null);
37+
assertEquals(sql, buffer.toString());
38+
assertEquals(sql, parse(buffer.toString()).toString());
39+
assertTrue(new TablesNamesFinder().getTables(statement).isEmpty());
40+
assertEquals(2, CCJSqlParserUtil.parseStatements(sql + "; SELECT 1",
41+
p -> p.withDialect(Dialect.MYSQL)).size());
42+
}
43+
44+
@Test
45+
void optionMutationAndVisitorDispatch() throws JSQLParserException {
46+
AlterDatabase statement = parse("ALTER DATABASE old_db READ ONLY = 0");
47+
statement.setDatabaseName("new_db");
48+
statement.getOption(DatabaseOption.Kind.READ_ONLY).orElseThrow().setValue("1");
49+
assertEquals("ALTER DATABASE new_db READ ONLY = 1", statement.toString());
50+
assertEquals("context:new_db", statement.accept(new StatementVisitorAdapter<String>() {
51+
@Override
52+
public <S> String visit(AlterDatabase database, S context) {
53+
return context + ":" + database.getDatabaseName();
54+
}
55+
}, "context"));
56+
}
57+
58+
@Test
59+
void rejectIncompleteAndInvalidReadOnly() {
60+
for (String sql : new String[] {"ALTER DATABASE d", "ALTER DATABASE d READ ONLY = 2",
61+
"ALTER DATABASE d DEFAULT READ ONLY = 0"}) {
62+
assertThrows(JSQLParserException.class, () -> parse(sql));
63+
}
64+
}
65+
66+
private static AlterDatabase parse(String sql) throws JSQLParserException {
67+
return (AlterDatabase) CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.MYSQL));
68+
}
69+
}

0 commit comments

Comments
 (0)