Skip to content

Commit 7641ca2

Browse files
committed
feat(parser): model MySQL schema options
1 parent 4b89a35 commit 7641ca2

6 files changed

Lines changed: 296 additions & 4 deletions

File tree

src/main/java/net/sf/jsqlparser/statement/create/database/CreateDatabase.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.util.Collections;
1515
import java.util.List;
1616
import java.util.Optional;
17+
import java.util.stream.Collectors;
1718

1819
import net.sf.jsqlparser.statement.Statement;
1920
import net.sf.jsqlparser.statement.StatementVisitor;
@@ -27,6 +28,7 @@ public class CreateDatabase implements Statement {
2728
private String databaseName;
2829
private boolean hasIfNotExists = false;
2930
private List<String> databaseOptions = null;
31+
private List<DatabaseOption> options;
3032

3133
@Override
3234
public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
@@ -67,9 +69,27 @@ public List<String> getDatabaseOptions() {
6769

6870
public CreateDatabase setDatabaseOptions(List<String> databaseOptions) {
6971
this.databaseOptions = databaseOptions;
72+
this.options = null;
7073
return this;
7174
}
7275

76+
public List<DatabaseOption> getOptions() {
77+
return options;
78+
}
79+
80+
public CreateDatabase setOptions(List<DatabaseOption> options) {
81+
this.options = options;
82+
this.databaseOptions = options == null ? null
83+
: options.stream().flatMap(option -> option.getTokens().stream())
84+
.collect(Collectors.toList());
85+
return this;
86+
}
87+
88+
public Optional<DatabaseOption> getOption(DatabaseOption.Kind kind) {
89+
return Optional.ofNullable(options).orElseGet(Collections::emptyList).stream()
90+
.filter(option -> option.getKind() == kind).findFirst();
91+
}
92+
7393
public CreateDatabase withDatabaseName(String databaseName) {
7494
return this.setDatabaseName(databaseName);
7595
}
@@ -82,6 +102,17 @@ public CreateDatabase withDatabaseOptions(List<String> databaseOptions) {
82102
return this.setDatabaseOptions(databaseOptions);
83103
}
84104

105+
public CreateDatabase withOptions(List<DatabaseOption> options) {
106+
return setOptions(options);
107+
}
108+
109+
public CreateDatabase addOptions(DatabaseOption... options) {
110+
List<DatabaseOption> collection =
111+
Optional.ofNullable(getOptions()).orElseGet(ArrayList::new);
112+
Collections.addAll(collection, options);
113+
return withOptions(collection);
114+
}
115+
85116
public CreateDatabase addDatabaseOptions(String... databaseOptions) {
86117
List<String> collection =
87118
Optional.ofNullable(getDatabaseOptions()).orElseGet(ArrayList::new);
@@ -105,7 +136,9 @@ public String toString() {
105136
if (databaseName != null) {
106137
sql += " " + databaseName;
107138
}
108-
if (databaseOptions != null && !databaseOptions.isEmpty()) {
139+
if (options != null && !options.isEmpty()) {
140+
sql += " " + options.stream().map(Object::toString).collect(Collectors.joining(" "));
141+
} else if (databaseOptions != null && !databaseOptions.isEmpty()) {
109142
sql += " " + String.join(" ", databaseOptions);
110143
}
111144
return sql;
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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.io.Serializable;
13+
import java.util.ArrayList;
14+
import java.util.Collections;
15+
import java.util.List;
16+
17+
/** A structured option shared by MySQL {@code CREATE DATABASE} and {@code CREATE SCHEMA}. */
18+
public class DatabaseOption implements Serializable {
19+
20+
public enum Kind {
21+
CHARACTER_SET, COLLATE, ENCRYPTION, OTHER
22+
}
23+
24+
private Kind kind = Kind.OTHER;
25+
private String name;
26+
private String value;
27+
private boolean useDefault;
28+
private boolean useEquals;
29+
private List<String> rawTokens;
30+
31+
public DatabaseOption() {}
32+
33+
public DatabaseOption(Kind kind, String name, String value, boolean useDefault,
34+
boolean useEquals) {
35+
this.kind = kind;
36+
this.name = name;
37+
this.value = value;
38+
this.useDefault = useDefault;
39+
this.useEquals = useEquals;
40+
}
41+
42+
public static DatabaseOption raw(List<String> tokens) {
43+
DatabaseOption option = new DatabaseOption();
44+
option.setRawTokens(tokens);
45+
return option;
46+
}
47+
48+
public Kind getKind() {
49+
return kind;
50+
}
51+
52+
public void setKind(Kind kind) {
53+
this.kind = kind;
54+
}
55+
56+
public String getName() {
57+
return name;
58+
}
59+
60+
public void setName(String name) {
61+
this.name = name;
62+
}
63+
64+
public String getValue() {
65+
return value;
66+
}
67+
68+
public void setValue(String value) {
69+
this.value = value;
70+
}
71+
72+
public boolean isUseDefault() {
73+
return useDefault;
74+
}
75+
76+
public void setUseDefault(boolean useDefault) {
77+
this.useDefault = useDefault;
78+
}
79+
80+
public boolean isUseEquals() {
81+
return useEquals;
82+
}
83+
84+
public void setUseEquals(boolean useEquals) {
85+
this.useEquals = useEquals;
86+
}
87+
88+
public List<String> getRawTokens() {
89+
return rawTokens;
90+
}
91+
92+
public void setRawTokens(List<String> rawTokens) {
93+
this.rawTokens = rawTokens;
94+
}
95+
96+
/** Returns this option in the legacy flat-token representation. */
97+
public List<String> getTokens() {
98+
if (rawTokens != null) {
99+
return Collections.unmodifiableList(rawTokens);
100+
}
101+
List<String> tokens = new ArrayList<>();
102+
if (useDefault) {
103+
tokens.add("DEFAULT");
104+
}
105+
Collections.addAll(tokens, name.split(" "));
106+
if (useEquals) {
107+
tokens.add("=");
108+
}
109+
tokens.add(value);
110+
return tokens;
111+
}
112+
113+
@Override
114+
public String toString() {
115+
return String.join(" ", getTokens());
116+
}
117+
118+
public DatabaseOption withKind(Kind kind) {
119+
setKind(kind);
120+
return this;
121+
}
122+
123+
public DatabaseOption withName(String name) {
124+
setName(name);
125+
return this;
126+
}
127+
128+
public DatabaseOption withValue(String value) {
129+
setValue(value);
130+
return this;
131+
}
132+
133+
public DatabaseOption withUseDefault(boolean useDefault) {
134+
setUseDefault(useDefault);
135+
return this;
136+
}
137+
138+
public DatabaseOption withUseEquals(boolean useEquals) {
139+
setUseEquals(useEquals);
140+
return this;
141+
}
142+
}

src/main/java/net/sf/jsqlparser/statement/create/schema/CreateSchema.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
import java.util.Collections;
1515
import java.util.List;
1616
import java.util.Optional;
17+
import java.util.stream.Collectors;
1718

1819
import net.sf.jsqlparser.statement.Statement;
1920
import net.sf.jsqlparser.statement.StatementVisitor;
21+
import net.sf.jsqlparser.statement.create.database.DatabaseOption;
2022

2123
public class CreateSchema implements Statement {
2224

@@ -25,6 +27,7 @@ public class CreateSchema implements Statement {
2527
private String schemaName;
2628
private List<String> schemaPath;
2729
private List<Statement> statements = new ArrayList<>();
30+
private List<DatabaseOption> databaseOptions;
2831
private boolean hasIfNotExists = false;
2932

3033
@Override
@@ -114,6 +117,19 @@ public List<Statement> getStatements() {
114117
return statements;
115118
}
116119

120+
public List<DatabaseOption> getDatabaseOptions() {
121+
return databaseOptions;
122+
}
123+
124+
public void setDatabaseOptions(List<DatabaseOption> databaseOptions) {
125+
this.databaseOptions = databaseOptions;
126+
}
127+
128+
public Optional<DatabaseOption> getDatabaseOption(DatabaseOption.Kind kind) {
129+
return Optional.ofNullable(databaseOptions).orElseGet(Collections::emptyList).stream()
130+
.filter(option -> option.getKind() == kind).findFirst();
131+
}
132+
117133
public boolean hasIfNotExists() {
118134
return hasIfNotExists;
119135
}
@@ -131,11 +147,15 @@ public String toString() {
131147
if (schemaName != null) {
132148
sql += " ";
133149

134-
if (catalogName!=null) {
150+
if (catalogName != null) {
135151
sql += catalogName + ".";
136152
}
137153
sql += schemaName;
138154
}
155+
if (databaseOptions != null && !databaseOptions.isEmpty()) {
156+
sql += " " + databaseOptions.stream().map(Object::toString)
157+
.collect(Collectors.joining(" "));
158+
}
139159
if (authorization != null) {
140160
sql += " AUTHORIZATION " + authorization;
141161
}
@@ -157,6 +177,18 @@ public CreateSchema withSchemaPath(List<String> schemaPath) {
157177
return this;
158178
}
159179

180+
public CreateSchema withDatabaseOptions(List<DatabaseOption> databaseOptions) {
181+
setDatabaseOptions(databaseOptions);
182+
return this;
183+
}
184+
185+
public CreateSchema addDatabaseOptions(DatabaseOption... databaseOptions) {
186+
List<DatabaseOption> collection =
187+
Optional.ofNullable(getDatabaseOptions()).orElseGet(ArrayList::new);
188+
Collections.addAll(collection, databaseOptions);
189+
return withDatabaseOptions(collection);
190+
}
191+
160192
public CreateSchema addSchemaPath(String... schemaPath) {
161193
List<String> collection = Optional.ofNullable(getSchemaPath()).orElseGet(ArrayList::new);
162194
Collections.addAll(collection, schemaPath);

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

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,12 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
126126
return jjtree.rootNode();
127127
}
128128

129+
private boolean isMySqlDatabaseOptionAhead() {
130+
int offset = getToken(1).kind == K_DEFAULT ? 2 : 1;
131+
int kind = getToken(offset).kind;
132+
return kind == K_CHARACTER || kind == K_COLLATE || kind == K_ENCRYPTION;
133+
}
134+
129135
private static class ObjectNames {
130136

131137
private final List<String> names;
@@ -11448,6 +11454,7 @@ CreateSchema CreateSchema():
1144811454
CreateSchema schema = new CreateSchema();
1144911455
List<String> schemaPath = null;
1145011456
List<Statement> statements = new ArrayList<Statement>();
11457+
DatabaseOption databaseOption = null;
1145111458
}
1145211459
{
1145311460
<K_SCHEMA>
@@ -11461,6 +11468,11 @@ CreateSchema CreateSchema():
1146111468
)?
1146211469
]
1146311470

11471+
(
11472+
LOOKAHEAD({ isMySqlDatabaseOptionAhead() })
11473+
databaseOption=MySqlDatabaseOption() { schema.addDatabaseOptions(databaseOption); }
11474+
)*
11475+
1146411476
[ <K_AUTHORIZATION>
1146511477
(tk=<S_IDENTIFIER> | tk=<S_QUOTED_IDENTIFIER>) { schema.setAuthorization(tk.image); }
1146611478
]
@@ -11489,6 +11501,38 @@ CreateSchema CreateSchema():
1148911501
}
1149011502
}
1149111503

11504+
DatabaseOption MySqlDatabaseOption():
11505+
{
11506+
boolean useDefault = false;
11507+
boolean useEquals = false;
11508+
DatabaseOption.Kind kind = DatabaseOption.Kind.OTHER;
11509+
String name = null;
11510+
String value = null;
11511+
Token valueToken = null;
11512+
}
11513+
{
11514+
[ <K_DEFAULT> { useDefault = true; } ]
11515+
(
11516+
<K_CHARACTER> <K_SET> {
11517+
kind = DatabaseOption.Kind.CHARACTER_SET;
11518+
name = "CHARACTER SET";
11519+
}
11520+
|
11521+
<K_COLLATE> {
11522+
kind = DatabaseOption.Kind.COLLATE;
11523+
name = "COLLATE";
11524+
}
11525+
|
11526+
<K_ENCRYPTION> {
11527+
kind = DatabaseOption.Kind.ENCRYPTION;
11528+
name = "ENCRYPTION";
11529+
}
11530+
)
11531+
[ "=" { useEquals = true; } ]
11532+
( value=RelObjectName() | valueToken=<S_CHAR_LITERAL> { value = valueToken.image; } )
11533+
{ return new DatabaseOption(kind, name, value, useDefault, useEquals); }
11534+
}
11535+
1149211536
List<String> PathSpecification():
1149311537
{
1149411538
Token tk;
@@ -11507,12 +11551,19 @@ CreateDatabase CreateDatabase():
1150711551
CreateDatabase createDatabase = new CreateDatabase();
1150811552
String databaseName = null;
1150911553
List<String> databaseOptions = null;
11554+
DatabaseOption databaseOption = null;
1151011555
}
1151111556
{
1151211557
<K_DATABASE>
1151311558
[ LOOKAHEAD(2) <K_IF> <K_NOT> <K_EXISTS> { createDatabase.setIfNotExists(true); } ]
1151411559
databaseName = RelObjectName() { createDatabase.setDatabaseName(databaseName); }
11515-
[ databaseOptions = captureRest() { createDatabase.setDatabaseOptions(databaseOptions); } ]
11560+
(
11561+
LOOKAHEAD({ isMySqlDatabaseOptionAhead() })
11562+
databaseOption=MySqlDatabaseOption() { createDatabase.addOptions(databaseOption); }
11563+
)*
11564+
[ databaseOptions = captureRest() {
11565+
createDatabase.addOptions(DatabaseOption.raw(databaseOptions));
11566+
} ]
1151611567
{
1151711568
return createDatabase;
1151811569
}

src/test/java/net/sf/jsqlparser/statement/create/database/CreateDatabaseTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ void testCreateDatabaseWithOptions() throws JSQLParserException {
5959
Arrays.asList("DEFAULT", "CHARACTER", "SET", "utf8mb4", "COLLATE",
6060
"utf8mb4_unicode_ci"),
6161
createDatabase.getDatabaseOptions());
62+
DatabaseOption characterSet =
63+
createDatabase.getOption(DatabaseOption.Kind.CHARACTER_SET).orElseThrow();
64+
assertEquals("utf8mb4", characterSet.getValue());
65+
assertTrue(characterSet.isUseDefault());
66+
assertEquals("utf8mb4_unicode_ci",
67+
createDatabase.getOption(DatabaseOption.Kind.COLLATE).orElseThrow().getValue());
6268
}
6369

6470
@Test

0 commit comments

Comments
 (0)