Skip to content

Commit 897fbd4

Browse files
authored
feat(parser): model MySQL schema options (#2544)
1 parent bb37719 commit 897fbd4

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;
@@ -11533,6 +11539,7 @@ CreateSchema CreateSchema():
1153311539
CreateSchema schema = new CreateSchema();
1153411540
List<String> schemaPath = null;
1153511541
List<Statement> statements = new ArrayList<Statement>();
11542+
DatabaseOption databaseOption = null;
1153611543
}
1153711544
{
1153811545
<K_SCHEMA>
@@ -11546,6 +11553,11 @@ CreateSchema CreateSchema():
1154611553
)?
1154711554
]
1154811555

11556+
(
11557+
LOOKAHEAD({ isMySqlDatabaseOptionAhead() })
11558+
databaseOption=MySqlDatabaseOption() { schema.addDatabaseOptions(databaseOption); }
11559+
)*
11560+
1154911561
[ <K_AUTHORIZATION>
1155011562
(tk=<S_IDENTIFIER> | tk=<S_QUOTED_IDENTIFIER>) { schema.setAuthorization(tk.image); }
1155111563
]
@@ -11574,6 +11586,38 @@ CreateSchema CreateSchema():
1157411586
}
1157511587
}
1157611588

11589+
DatabaseOption MySqlDatabaseOption():
11590+
{
11591+
boolean useDefault = false;
11592+
boolean useEquals = false;
11593+
DatabaseOption.Kind kind = DatabaseOption.Kind.OTHER;
11594+
String name = null;
11595+
String value = null;
11596+
Token valueToken = null;
11597+
}
11598+
{
11599+
[ <K_DEFAULT> { useDefault = true; } ]
11600+
(
11601+
<K_CHARACTER> <K_SET> {
11602+
kind = DatabaseOption.Kind.CHARACTER_SET;
11603+
name = "CHARACTER SET";
11604+
}
11605+
|
11606+
<K_COLLATE> {
11607+
kind = DatabaseOption.Kind.COLLATE;
11608+
name = "COLLATE";
11609+
}
11610+
|
11611+
<K_ENCRYPTION> {
11612+
kind = DatabaseOption.Kind.ENCRYPTION;
11613+
name = "ENCRYPTION";
11614+
}
11615+
)
11616+
[ "=" { useEquals = true; } ]
11617+
( value=RelObjectName() | valueToken=<S_CHAR_LITERAL> { value = valueToken.image; } )
11618+
{ return new DatabaseOption(kind, name, value, useDefault, useEquals); }
11619+
}
11620+
1157711621
List<String> PathSpecification():
1157811622
{
1157911623
Token tk;
@@ -11592,12 +11636,19 @@ CreateDatabase CreateDatabase():
1159211636
CreateDatabase createDatabase = new CreateDatabase();
1159311637
String databaseName = null;
1159411638
List<String> databaseOptions = null;
11639+
DatabaseOption databaseOption = null;
1159511640
}
1159611641
{
1159711642
<K_DATABASE>
1159811643
[ LOOKAHEAD(2) <K_IF> <K_NOT> <K_EXISTS> { createDatabase.setIfNotExists(true); } ]
1159911644
databaseName = RelObjectName() { createDatabase.setDatabaseName(databaseName); }
11600-
[ databaseOptions = captureRest() { createDatabase.setDatabaseOptions(databaseOptions); } ]
11645+
(
11646+
LOOKAHEAD({ isMySqlDatabaseOptionAhead() })
11647+
databaseOption=MySqlDatabaseOption() { createDatabase.addOptions(databaseOption); }
11648+
)*
11649+
[ databaseOptions = captureRest() {
11650+
createDatabase.addOptions(DatabaseOption.raw(databaseOptions));
11651+
} ]
1160111652
{
1160211653
return createDatabase;
1160311654
}

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)