Skip to content

Commit a645052

Browse files
authored
Support structured MySQL table definitions (#2541)
* refactor(ast): model structured table definitions * fix(parser): unify MySQL table definitions * fix(parser): model common MySQL table options * fix(ast): initialize check constraint kind
1 parent 80305c7 commit a645052

14 files changed

Lines changed: 1376 additions & 255 deletions

File tree

‎src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,6 +1163,10 @@ protected void toStringGeneral(StringBuilder b) {
11631163
b.append("IF EXISTS ");
11641164
}
11651165
b.append(constraintName);
1166+
} else if (index != null) {
1167+
// The structured index is canonical. Legacy PK/UK/FK fields may also be populated for
1168+
// source compatibility, but cannot represent names, expressions, or index options.
1169+
b.append(index);
11661170
} else if (pkColumns != null) {
11671171
b.append("PRIMARY KEY (").append(PlainSelect.getStringList(pkColumns)).append(')');
11681172
} else if (ukColumns != null) {
@@ -1195,8 +1199,6 @@ protected void toStringGeneral(StringBuilder b) {
11951199
.append(PlainSelect.getStringList(fkSourceColumns))
11961200
.append(")");
11971201
referentialActions.forEach(b::append);
1198-
} else if (index != null) {
1199-
b.append(index);
12001202
}
12011203

12021204
if (getConstraints() != null && !getConstraints().isEmpty()) {

‎src/main/java/net/sf/jsqlparser/statement/create/table/CheckConstraint.java‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ public class CheckConstraint extends NamedConstraint {
2323

2424
private Boolean enforced;
2525

26+
public CheckConstraint() {
27+
setKind(Kind.CHECK);
28+
}
29+
2630
public Table getTable() {
2731
return table;
2832
}

‎src/main/java/net/sf/jsqlparser/statement/create/table/ColDataType.java‎

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ public enum Signedness {
2828
SIGNED, UNSIGNED
2929
}
3030

31+
public enum TypeModifier {
32+
SIGNED, UNSIGNED, ZEROFILL
33+
}
34+
35+
public enum NationalCharacterType {
36+
CHAR, VARCHAR
37+
}
38+
3139
private String dataType;
3240
private List<String> argumentsStringList;
3341
private String characterSet;
@@ -37,6 +45,8 @@ public enum Signedness {
3745
private boolean zerofill;
3846
private Integer precision;
3947
private Integer scale;
48+
private List<TypeModifier> typeModifiers;
49+
private NationalCharacterType nationalCharacterType;
4050

4151
public ColDataType() {
4252
// empty constructor
@@ -120,6 +130,58 @@ public void setZerofill(boolean zerofill) {
120130
this.zerofill = zerofill;
121131
}
122132

133+
/** Returns MySQL numeric modifiers in their original order, including repetitions. */
134+
public List<TypeModifier> getTypeModifiers() {
135+
return typeModifiers;
136+
}
137+
138+
public void setTypeModifiers(List<TypeModifier> typeModifiers) {
139+
this.typeModifiers = typeModifiers;
140+
signedness = null;
141+
zerofill = false;
142+
if (typeModifiers != null) {
143+
for (TypeModifier modifier : typeModifiers) {
144+
updateEffectiveModifier(modifier);
145+
}
146+
}
147+
}
148+
149+
public void addTypeModifier(TypeModifier typeModifier) {
150+
if (typeModifiers == null) {
151+
typeModifiers = new ArrayList<>();
152+
}
153+
typeModifiers.add(typeModifier);
154+
updateEffectiveModifier(typeModifier);
155+
}
156+
157+
private void updateEffectiveModifier(TypeModifier modifier) {
158+
switch (modifier) {
159+
case SIGNED:
160+
signedness = Signedness.SIGNED;
161+
break;
162+
case UNSIGNED:
163+
signedness = Signedness.UNSIGNED;
164+
break;
165+
case ZEROFILL:
166+
zerofill = true;
167+
break;
168+
default:
169+
break;
170+
}
171+
}
172+
173+
public NationalCharacterType getNationalCharacterType() {
174+
return nationalCharacterType;
175+
}
176+
177+
public void setNationalCharacterType(NationalCharacterType nationalCharacterType) {
178+
this.nationalCharacterType = nationalCharacterType;
179+
}
180+
181+
public boolean isNational() {
182+
return nationalCharacterType != null;
183+
}
184+
123185
/**
124186
* The first numeric type parameter, e.g. {@code 255} for {@code VARCHAR(255)} or {@code 10} for
125187
* {@code DECIMAL(10, 2)}. {@code MAX} is reported as {@link Integer#MAX_VALUE}. Returns
@@ -161,8 +223,10 @@ public String toString() {
161223
+ (argumentsStringList != null
162224
? " " + PlainSelect.getStringList(argumentsStringList, true, true)
163225
: "")
164-
+ (signedness != null ? " " + signedness : "")
165-
+ (zerofill ? " ZEROFILL" : "")
226+
+ (typeModifiers != null && !typeModifiers.isEmpty()
227+
? " " + PlainSelect.getStringList(typeModifiers, false, false)
228+
: (signedness != null ? " " + signedness : "")
229+
+ (zerofill ? " ZEROFILL" : ""))
166230
+ arraySpec.toString()
167231
+ (characterSet != null ? " CHARACTER SET " + characterSet : "");
168232
}
@@ -202,6 +266,16 @@ public ColDataType withZerofill(boolean zerofill) {
202266
return this;
203267
}
204268

269+
public ColDataType withTypeModifiers(List<TypeModifier> typeModifiers) {
270+
setTypeModifiers(typeModifiers);
271+
return this;
272+
}
273+
274+
public ColDataType withNationalCharacterType(NationalCharacterType nationalCharacterType) {
275+
setNationalCharacterType(nationalCharacterType);
276+
return this;
277+
}
278+
205279
public ColDataType withPrecision(Integer precision) {
206280
this.setPrecision(precision);
207281
return this;
@@ -254,7 +328,9 @@ public final boolean equals(Object o) {
254328
&& Objects.equals(intervalQualifier, that.intervalQualifier)
255329
&& Objects.equals(arrayData, that.arrayData)
256330
&& signedness == that.signedness
257-
&& zerofill == that.zerofill;
331+
&& zerofill == that.zerofill
332+
&& Objects.equals(typeModifiers, that.typeModifiers)
333+
&& nationalCharacterType == that.nationalCharacterType;
258334
}
259335

260336
@Override
@@ -266,6 +342,8 @@ public int hashCode() {
266342
result = 31 * result + Objects.hashCode(arrayData);
267343
result = 31 * result + Objects.hashCode(signedness);
268344
result = 31 * result + Boolean.hashCode(zerofill);
345+
result = 31 * result + Objects.hashCode(typeModifiers);
346+
result = 31 * result + Objects.hashCode(nationalCharacterType);
269347
return result;
270348
}
271349
}

‎src/main/java/net/sf/jsqlparser/statement/create/table/ColumnDefinition.java‎

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@
2222
/**
2323
* Globally used definition class for columns.
2424
*/
25-
public class ColumnDefinition implements ImportColumn, Serializable {
25+
public class ColumnDefinition implements ImportColumn, TableElement, Serializable {
2626

2727
private String columnName;
2828
private ColDataType colDataType;
2929
private List<String> columnSpecs;
30+
private List<ColumnOption> columnOptions;
3031

3132
public ColumnDefinition() {}
3233

@@ -46,6 +47,47 @@ public List<String> getColumnSpecs() {
4647

4748
public void setColumnSpecs(List<String> list) {
4849
columnSpecs = list;
50+
columnOptions = null;
51+
}
52+
53+
/**
54+
* Returns column options in source order, including structured references and MySQL
55+
* {@code SERIAL DEFAULT VALUE}.
56+
*/
57+
public List<ColumnOption> getColumnOptions() {
58+
return columnOptions;
59+
}
60+
61+
public void setColumnOptions(List<ColumnOption> columnOptions) {
62+
this.columnOptions = columnOptions;
63+
}
64+
65+
public boolean isSerialDefaultValue() {
66+
return columnOptions != null && columnOptions.stream()
67+
.anyMatch(option -> option.getKind() == ColumnOption.Kind.SERIAL_DEFAULT_VALUE);
68+
}
69+
70+
public ForeignKeyReference getForeignKeyReference() {
71+
if (columnOptions == null) {
72+
return null;
73+
}
74+
return columnOptions.stream()
75+
.filter(option -> option.getKind() == ColumnOption.Kind.REFERENCE)
76+
.map(ColumnOption::getForeignKeyReference)
77+
.findFirst()
78+
.orElse(null);
79+
}
80+
81+
public ColumnDefinition withColumnOptions(List<ColumnOption> columnOptions) {
82+
setColumnOptions(columnOptions);
83+
return this;
84+
}
85+
86+
public ColumnDefinition addColumnOptions(ColumnOption... columnOptions) {
87+
List<ColumnOption> collection =
88+
Optional.ofNullable(getColumnOptions()).orElseGet(ArrayList::new);
89+
Collections.addAll(collection, columnOptions);
90+
return withColumnOptions(collection);
4991
}
5092

5193
public ColDataType getColDataType() {
@@ -71,9 +113,11 @@ public String toString() {
71113

72114
public String toStringDataTypeAndSpec() {
73115
return (colDataType == null ? "" : colDataType)
74-
+ (columnSpecs != null && !columnSpecs.isEmpty()
75-
? " " + PlainSelect.getStringList(columnSpecs, false, false)
76-
: "");
116+
+ (columnOptions != null && !columnOptions.isEmpty()
117+
? " " + PlainSelect.getStringList(columnOptions, false, false)
118+
: columnSpecs != null && !columnSpecs.isEmpty()
119+
? " " + PlainSelect.getStringList(columnSpecs, false, false)
120+
: "");
77121
}
78122

79123
public ColumnDefinition withColumnName(String columnName) {
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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.table;
11+
12+
import java.io.Serializable;
13+
import java.util.Arrays;
14+
import java.util.Collections;
15+
import java.util.List;
16+
import net.sf.jsqlparser.statement.select.PlainSelect;
17+
18+
/** A structured option following a column data type. */
19+
public class ColumnOption implements Serializable {
20+
21+
public enum Kind {
22+
SERIAL_DEFAULT_VALUE, REFERENCE, OTHER
23+
}
24+
25+
private Kind kind = Kind.OTHER;
26+
private List<String> tokens;
27+
private ForeignKeyReference foreignKeyReference;
28+
29+
public static ColumnOption raw(List<String> tokens) {
30+
ColumnOption option = new ColumnOption();
31+
option.tokens = tokens;
32+
return option;
33+
}
34+
35+
public static ColumnOption raw(String... tokens) {
36+
return raw(Arrays.asList(tokens));
37+
}
38+
39+
public static ColumnOption serialDefaultValue() {
40+
ColumnOption option = raw("SERIAL", "DEFAULT", "VALUE");
41+
option.kind = Kind.SERIAL_DEFAULT_VALUE;
42+
return option;
43+
}
44+
45+
public static ColumnOption reference(ForeignKeyReference reference) {
46+
ColumnOption option = new ColumnOption();
47+
option.kind = Kind.REFERENCE;
48+
option.foreignKeyReference = reference;
49+
return option;
50+
}
51+
52+
public Kind getKind() {
53+
return kind;
54+
}
55+
56+
public List<String> getTokens() {
57+
return kind == Kind.REFERENCE ? Collections.singletonList(foreignKeyReference.toString())
58+
: tokens;
59+
}
60+
61+
public ForeignKeyReference getForeignKeyReference() {
62+
return foreignKeyReference;
63+
}
64+
65+
@Override
66+
public String toString() {
67+
return kind == Kind.REFERENCE ? foreignKeyReference.toString()
68+
: PlainSelect.getStringList(tokens, false, false);
69+
}
70+
}

0 commit comments

Comments
 (0)