Skip to content

Commit 7b64825

Browse files
authored
feat: support CREATE DATABASE statement (#2070) (#2471)
Parse CREATE DATABASE [IF NOT EXISTS] <name> into a typed CreateDatabase statement instead of the generic UnsupportedStatement fallback. Vendor-specific options after the name (e.g. MySQL's DEFAULT CHARACTER SET / COLLATE) are captured verbatim and round-trip unchanged. Signed-off-by: 付典 <fudianchn@gmail.com>
1 parent 138947d commit 7b64825

9 files changed

Lines changed: 255 additions & 0 deletions

File tree

src/main/java/net/sf/jsqlparser/parser/feature/Feature.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import net.sf.jsqlparser.statement.alter.sequence.AlterSequence;
3131
import net.sf.jsqlparser.statement.analyze.Analyze;
3232
import net.sf.jsqlparser.statement.comment.Comment;
33+
import net.sf.jsqlparser.statement.create.database.CreateDatabase;
3334
import net.sf.jsqlparser.statement.create.function.CreateFunction;
3435
import net.sf.jsqlparser.statement.create.index.CreateIndex;
3536
import net.sf.jsqlparser.statement.create.procedure.CreateProcedure;
@@ -475,6 +476,12 @@ public enum Feature {
475476
*/
476477
drop, dropTable, dropIndex, dropView, dropSchema, dropSequence, dropTableIfExists, dropIndexIfExists, dropViewIfExists, dropSchemaIfExists, dropSequenceIfExists,
477478

479+
/**
480+
* SQL "CREATE DATABASE" statement is allowed
481+
*
482+
* @see CreateDatabase
483+
*/
484+
createDatabase,
478485
/**
479486
* SQL "CREATE SCHEMA" statement is allowed
480487
*

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import net.sf.jsqlparser.statement.alter.sequence.AlterSequence;
1717
import net.sf.jsqlparser.statement.analyze.Analyze;
1818
import net.sf.jsqlparser.statement.comment.Comment;
19+
import net.sf.jsqlparser.statement.create.database.CreateDatabase;
1920
import net.sf.jsqlparser.statement.create.index.CreateIndex;
2021
import net.sf.jsqlparser.statement.create.policy.CreatePolicy;
2122
import net.sf.jsqlparser.statement.create.schema.CreateSchema;
@@ -118,6 +119,12 @@ default void visit(CreateSchema createSchema) {
118119
this.visit(createSchema, null);
119120
}
120121

122+
<S> T visit(CreateDatabase createDatabase, S context);
123+
124+
default void visit(CreateDatabase createDatabase) {
125+
this.visit(createDatabase, null);
126+
}
127+
121128
<S> T visit(CreateTable createTable, S context);
122129

123130
default void visit(CreateTable createTable) {

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import net.sf.jsqlparser.statement.alter.sequence.AlterSequence;
2121
import net.sf.jsqlparser.statement.analyze.Analyze;
2222
import net.sf.jsqlparser.statement.comment.Comment;
23+
import net.sf.jsqlparser.statement.create.database.CreateDatabase;
2324
import net.sf.jsqlparser.statement.create.index.CreateIndex;
2425
import net.sf.jsqlparser.statement.create.policy.CreatePolicy;
2526
import net.sf.jsqlparser.statement.create.schema.CreateSchema;
@@ -261,6 +262,11 @@ public <S> T visit(CreateSchema createSchema, S context) {
261262
return null;
262263
}
263264

265+
@Override
266+
public <S> T visit(CreateDatabase createDatabase, S context) {
267+
return null;
268+
}
269+
264270
@Override
265271
public <S> T visit(CreateTable createTable, S context) {
266272
return createTable.getTable().accept(fromItemVisitor, context);
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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.create.database;
11+
12+
import java.util.ArrayList;
13+
import java.util.Collection;
14+
import java.util.Collections;
15+
import java.util.List;
16+
import java.util.Optional;
17+
18+
import net.sf.jsqlparser.statement.Statement;
19+
import net.sf.jsqlparser.statement.StatementVisitor;
20+
21+
/**
22+
* A {@code CREATE DATABASE} statement. Modelled after MySQL, where the statement takes an optional
23+
* {@code IF NOT EXISTS} and vendor-specific options, which are captured verbatim.
24+
*/
25+
public class CreateDatabase implements Statement {
26+
27+
private String databaseName;
28+
private boolean hasIfNotExists = false;
29+
private List<String> databaseOptions = null;
30+
31+
@Override
32+
public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
33+
return statementVisitor.visit(this, context);
34+
}
35+
36+
/**
37+
* The name of the database.
38+
*
39+
* @return the database name
40+
*/
41+
public String getDatabaseName() {
42+
return databaseName;
43+
}
44+
45+
public CreateDatabase setDatabaseName(String databaseName) {
46+
this.databaseName = databaseName;
47+
return this;
48+
}
49+
50+
public boolean hasIfNotExists() {
51+
return hasIfNotExists;
52+
}
53+
54+
public CreateDatabase setIfNotExists(boolean hasIfNotExists) {
55+
this.hasIfNotExists = hasIfNotExists;
56+
return this;
57+
}
58+
59+
/**
60+
* The vendor-specific options of the database, captured as raw tokens.
61+
*
62+
* @return the list of option tokens, {@code null} when no options were given
63+
*/
64+
public List<String> getDatabaseOptions() {
65+
return databaseOptions;
66+
}
67+
68+
public CreateDatabase setDatabaseOptions(List<String> databaseOptions) {
69+
this.databaseOptions = databaseOptions;
70+
return this;
71+
}
72+
73+
public CreateDatabase withDatabaseName(String databaseName) {
74+
return this.setDatabaseName(databaseName);
75+
}
76+
77+
public CreateDatabase withIfNotExists(boolean hasIfNotExists) {
78+
return this.setIfNotExists(hasIfNotExists);
79+
}
80+
81+
public CreateDatabase withDatabaseOptions(List<String> databaseOptions) {
82+
return this.setDatabaseOptions(databaseOptions);
83+
}
84+
85+
public CreateDatabase addDatabaseOptions(String... databaseOptions) {
86+
List<String> collection =
87+
Optional.ofNullable(getDatabaseOptions()).orElseGet(ArrayList::new);
88+
Collections.addAll(collection, databaseOptions);
89+
return this.withDatabaseOptions(collection);
90+
}
91+
92+
public CreateDatabase addDatabaseOptions(Collection<String> databaseOptions) {
93+
List<String> collection =
94+
Optional.ofNullable(getDatabaseOptions()).orElseGet(ArrayList::new);
95+
collection.addAll(databaseOptions);
96+
return this.withDatabaseOptions(collection);
97+
}
98+
99+
@Override
100+
public String toString() {
101+
String sql = "CREATE DATABASE";
102+
if (hasIfNotExists) {
103+
sql += " IF NOT EXISTS";
104+
}
105+
if (databaseName != null) {
106+
sql += " " + databaseName;
107+
}
108+
if (databaseOptions != null && !databaseOptions.isEmpty()) {
109+
sql += " " + String.join(" ", databaseOptions);
110+
}
111+
return sql;
112+
}
113+
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
import net.sf.jsqlparser.statement.alter.sequence.AlterSequence;
9595
import net.sf.jsqlparser.statement.analyze.Analyze;
9696
import net.sf.jsqlparser.statement.comment.Comment;
97+
import net.sf.jsqlparser.statement.create.database.CreateDatabase;
9798
import net.sf.jsqlparser.statement.create.index.CreateIndex;
9899
import net.sf.jsqlparser.statement.create.policy.CreatePolicy;
99100
import net.sf.jsqlparser.statement.create.schema.CreateSchema;
@@ -1166,6 +1167,17 @@ public void visit(CreateSchema createSchema) {
11661167
StatementVisitor.super.visit(createSchema);
11671168
}
11681169

1170+
@Override
1171+
public <S> Void visit(CreateDatabase createDatabase, S context) {
1172+
throwUnsupported(createDatabase);
1173+
return null;
1174+
}
1175+
1176+
@Override
1177+
public void visit(CreateDatabase createDatabase) {
1178+
StatementVisitor.super.visit(createDatabase);
1179+
}
1180+
11691181
@Override
11701182
public <S> Void visit(CreateTable create, S context) {
11711183
visit(create.getTable(), null);

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import net.sf.jsqlparser.statement.alter.sequence.AlterSequence;
4242
import net.sf.jsqlparser.statement.analyze.Analyze;
4343
import net.sf.jsqlparser.statement.comment.Comment;
44+
import net.sf.jsqlparser.statement.create.database.CreateDatabase;
4445
import net.sf.jsqlparser.statement.create.index.CreateIndex;
4546
import net.sf.jsqlparser.statement.create.policy.CreatePolicy;
4647
import net.sf.jsqlparser.statement.create.schema.CreateSchema;
@@ -431,6 +432,12 @@ public <S> StringBuilder visit(CreateSchema aThis, S context) {
431432
return builder;
432433
}
433434

435+
@Override
436+
public <S> StringBuilder visit(CreateDatabase aThis, S context) {
437+
builder.append(aThis.toString());
438+
return builder;
439+
}
440+
434441
@Override
435442
public <S> StringBuilder visit(CreateSequence createSequence, S context) {
436443
new CreateSequenceDeParser(builder).deParse(createSequence);

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import net.sf.jsqlparser.statement.alter.sequence.AlterSequence;
3838
import net.sf.jsqlparser.statement.analyze.Analyze;
3939
import net.sf.jsqlparser.statement.comment.Comment;
40+
import net.sf.jsqlparser.statement.create.database.CreateDatabase;
4041
import net.sf.jsqlparser.statement.create.function.CreateFunction;
4142
import net.sf.jsqlparser.statement.create.index.CreateIndex;
4243
import net.sf.jsqlparser.statement.create.policy.CreatePolicy;
@@ -299,6 +300,13 @@ public <S> Void visit(CreateSchema aThis, S context) {
299300
return null;
300301
}
301302

303+
@Override
304+
public <S> Void visit(CreateDatabase aThis, S context) {
305+
validateFeatureAndName(Feature.createDatabase, NamedObject.database,
306+
aThis.getDatabaseName());
307+
return null;
308+
}
309+
302310
@Override
303311
public <S> Void visit(CreateSequence createSequence, S context) {
304312
getValidator(CreateSequenceValidator.class).validate(createSequence);

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import net.sf.jsqlparser.statement.analyze.*;
4747
import net.sf.jsqlparser.statement.alter.*;
4848
import net.sf.jsqlparser.statement.alter.sequence.*;
4949
import net.sf.jsqlparser.statement.comment.*;
50+
import net.sf.jsqlparser.statement.create.database.*;
5051
import net.sf.jsqlparser.statement.create.function.*;
5152
import net.sf.jsqlparser.statement.create.index.*;
5253
import net.sf.jsqlparser.statement.create.policy.*;
@@ -10935,6 +10936,22 @@ List<String> PathSpecification():
1093510936
}
1093610937
}
1093710938

10939+
CreateDatabase CreateDatabase():
10940+
{
10941+
CreateDatabase createDatabase = new CreateDatabase();
10942+
String databaseName = null;
10943+
List<String> databaseOptions = null;
10944+
}
10945+
{
10946+
<K_DATABASE>
10947+
[ LOOKAHEAD(2) <K_IF> <K_NOT> <K_EXISTS> { createDatabase.setIfNotExists(true); } ]
10948+
databaseName = RelObjectName() { createDatabase.setDatabaseName(databaseName); }
10949+
[ databaseOptions = captureRest() { createDatabase.setDatabaseOptions(databaseOptions); } ]
10950+
{
10951+
return createDatabase;
10952+
}
10953+
}
10954+
1093810955
/**
1093910956
* Parses a single table-level constraint inside CREATE TABLE (...).
1094010957
* Handles INDEX, PRIMARY KEY, UNIQUE, KEY, FOREIGN KEY, CHECK, EXCLUDE.
@@ -13382,6 +13399,8 @@ Statement Create():
1338213399
|
1338313400
statement = CreateSchema()
1338413401
|
13402+
statement = CreateDatabase()
13403+
|
1338513404
statement = CreateSequence()
1338613405
|
1338713406
statement = CreateSynonym(isUsingOrReplace)
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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.create.database;
11+
12+
import net.sf.jsqlparser.JSQLParserException;
13+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
14+
import net.sf.jsqlparser.statement.Statement;
15+
import static net.sf.jsqlparser.test.TestUtils.assertDeparse;
16+
import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed;
17+
import static org.junit.jupiter.api.Assertions.assertEquals;
18+
import static org.junit.jupiter.api.Assertions.assertFalse;
19+
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
20+
import static org.junit.jupiter.api.Assertions.assertTrue;
21+
22+
import java.util.Arrays;
23+
import org.junit.jupiter.api.Test;
24+
25+
public class CreateDatabaseTest {
26+
27+
@Test
28+
void testCreateDatabaseIssue2070() throws JSQLParserException {
29+
String statement = "CREATE DATABASE USERS";
30+
assertSqlCanBeParsedAndDeparsed(statement);
31+
assertDeparse(new CreateDatabase().withDatabaseName("USERS"), statement);
32+
}
33+
34+
@Test
35+
void testCreateDatabaseIfNotExists() throws JSQLParserException {
36+
String statement = "CREATE DATABASE IF NOT EXISTS mydb";
37+
assertSqlCanBeParsedAndDeparsed(statement);
38+
assertDeparse(new CreateDatabase().withDatabaseName("mydb").withIfNotExists(true),
39+
statement);
40+
}
41+
42+
@Test
43+
void testCreateDatabaseQuotedName() throws JSQLParserException {
44+
assertSqlCanBeParsedAndDeparsed("CREATE DATABASE `my db`");
45+
assertSqlCanBeParsedAndDeparsed("CREATE DATABASE \"my db\"");
46+
}
47+
48+
@Test
49+
void testCreateDatabaseWithOptions() throws JSQLParserException {
50+
String statement =
51+
"CREATE DATABASE IF NOT EXISTS mydb DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci";
52+
assertSqlCanBeParsedAndDeparsed(statement);
53+
54+
Statement parsed = CCJSqlParserUtil.parse(statement);
55+
CreateDatabase createDatabase = assertInstanceOf(CreateDatabase.class, parsed);
56+
assertEquals("mydb", createDatabase.getDatabaseName());
57+
assertTrue(createDatabase.hasIfNotExists());
58+
assertEquals(
59+
Arrays.asList("DEFAULT", "CHARACTER", "SET", "utf8mb4", "COLLATE",
60+
"utf8mb4_unicode_ci"),
61+
createDatabase.getDatabaseOptions());
62+
}
63+
64+
@Test
65+
void testCreateDatabaseAST() throws JSQLParserException {
66+
Statement statement = CCJSqlParserUtil.parse("CREATE DATABASE USERS");
67+
CreateDatabase createDatabase = assertInstanceOf(CreateDatabase.class, statement);
68+
assertEquals("USERS", createDatabase.getDatabaseName());
69+
assertFalse(createDatabase.hasIfNotExists());
70+
71+
statement = CCJSqlParserUtil.parse("CREATE DATABASE IF NOT EXISTS mydb");
72+
createDatabase = assertInstanceOf(CreateDatabase.class, statement);
73+
assertEquals("mydb", createDatabase.getDatabaseName());
74+
assertTrue(createDatabase.hasIfNotExists());
75+
}
76+
}

0 commit comments

Comments
 (0)