From 5ea41f4055d6cbab6c0644eceab3b45491b5a8af Mon Sep 17 00:00:00 2001 From: Minjae Lee Date: Thu, 3 Sep 2026 16:38:33 +0900 Subject: [PATCH] fix(parser): model MySQL column type modifiers --- .../statement/create/table/ColDataType.java | 40 ++++++++++++++++++- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 12 ++++-- .../statement/create/CreateTableTest.java | 23 +++++++++++ 3 files changed, 71 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/statement/create/table/ColDataType.java b/src/main/java/net/sf/jsqlparser/statement/create/table/ColDataType.java index a3ddcdb9f..06490a686 100644 --- a/src/main/java/net/sf/jsqlparser/statement/create/table/ColDataType.java +++ b/src/main/java/net/sf/jsqlparser/statement/create/table/ColDataType.java @@ -24,11 +24,17 @@ public class ColDataType implements Serializable { + public enum Signedness { + SIGNED, UNSIGNED + } + private String dataType; private List argumentsStringList; private String characterSet; private IntervalQualifier intervalQualifier; private List arrayData = new ArrayList(); + private Signedness signedness; + private boolean zerofill; public ColDataType() { // empty constructor @@ -94,6 +100,22 @@ public void setArrayData(List arrayData) { this.arrayData = arrayData; } + public Signedness getSignedness() { + return signedness; + } + + public void setSignedness(Signedness signedness) { + this.signedness = signedness; + } + + public boolean isZerofill() { + return zerofill; + } + + public void setZerofill(boolean zerofill) { + this.zerofill = zerofill; + } + @Override public String toString() { StringBuilder arraySpec = new StringBuilder(); @@ -109,6 +131,8 @@ public String toString() { + (argumentsStringList != null ? " " + PlainSelect.getStringList(argumentsStringList, true, true) : "") + + (signedness != null ? " " + signedness : "") + + (zerofill ? " ZEROFILL" : "") + arraySpec.toString() + (characterSet != null ? " CHARACTER SET " + characterSet : ""); } @@ -138,6 +162,16 @@ public ColDataType withArrayData(List arrayData) { return this; } + public ColDataType withSignedness(Signedness signedness) { + setSignedness(signedness); + return this; + } + + public ColDataType withZerofill(boolean zerofill) { + setZerofill(zerofill); + return this; + } + public ColDataType addArgumentsStringList(String... argumentsStringList) { List collection = Optional.ofNullable(getArgumentsStringList()).orElseGet(ArrayList::new); @@ -178,7 +212,9 @@ public final boolean equals(Object o) { && Objects.equals(argumentsStringList, that.argumentsStringList) && Objects.equals(characterSet, that.characterSet) && Objects.equals(intervalQualifier, that.intervalQualifier) - && Objects.equals(arrayData, that.arrayData); + && Objects.equals(arrayData, that.arrayData) + && signedness == that.signedness + && zerofill == that.zerofill; } @Override @@ -188,6 +224,8 @@ public int hashCode() { result = 31 * result + Objects.hashCode(characterSet); result = 31 * result + Objects.hashCode(intervalQualifier); result = 31 * result + Objects.hashCode(arrayData); + result = 31 * result + Objects.hashCode(signedness); + result = 31 * result + Boolean.hashCode(zerofill); return result; } } diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 8ed4a18d5..64cc3394d 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -1815,6 +1815,7 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= ) { return tk.image; } @@ -11683,10 +11684,10 @@ ColDataType DataType(): | tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= ) { type = tk.image; } ( - // MySQL seems to allow: INT UNSIGNED. Do not consume CHARACTER when it starts - // the trailing CHARACTER SET clause of a character type. + // Signedness is parsed by ColDataType after optional precision/scale. Do not + // consume CHARACTER when it starts the trailing CHARACTER SET clause. LOOKAHEAD(2, { getToken(1).kind != K_CHARACTER || getToken(2).kind != K_SET }) - ( tk = | tk = | tk = + ( tk = | tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= ) { type += " " + tk.image; } )* @@ -11806,6 +11807,11 @@ ColDataType ColDataType(): )* ")" ] + [ LOOKAHEAD(2) + ( tk= { colDataType.setSignedness(ColDataType.Signedness.SIGNED); } + | tk= { colDataType.setSignedness(ColDataType.Signedness.UNSIGNED); } ) + ] + [ LOOKAHEAD(2) { colDataType.setZerofill(true); } ] [ LOOKAHEAD(2) ( LOOKAHEAD(2) "[" {tk=null;} [ tk= ] { array.add(tk!=null?Integer.valueOf(tk.image):null); } "]" )+ { colDataType.setArrayData(array); } ] [ LOOKAHEAD(2) (tk= | tk=) { colDataType.setCharacterSet(tk.image); } ] diff --git a/src/test/java/net/sf/jsqlparser/statement/create/CreateTableTest.java b/src/test/java/net/sf/jsqlparser/statement/create/CreateTableTest.java index a58caea80..fd8e5b952 100644 --- a/src/test/java/net/sf/jsqlparser/statement/create/CreateTableTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/create/CreateTableTest.java @@ -32,6 +32,7 @@ import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.create.table.ColDataType; +import net.sf.jsqlparser.statement.create.table.ColDataType.Signedness; import net.sf.jsqlparser.statement.create.table.ColumnDefinition; import net.sf.jsqlparser.statement.create.table.CreateTable; import net.sf.jsqlparser.statement.create.table.ExcludeConstraint; @@ -1247,4 +1248,26 @@ void testUniqueIndexIssue1893() throws JSQLParserException { // A plain INDEX must still parse unchanged. assertSqlCanBeParsedAndDeparsed("CREATE TABLE t (a int, INDEX idx (a))", true); } + + @Test + void testMySqlColumnTypeModifiers() throws JSQLParserException { + String sql = "CREATE TABLE t (a INT UNSIGNED, b INT (11) UNSIGNED ZEROFILL, " + + "c DECIMAL (10, 2) SIGNED NOT NULL)"; + CreateTable createTable = + (CreateTable) assertSqlCanBeParsedAndDeparsed(sql, true); + + ColDataType first = createTable.getColumnDefinitions().get(0).getColDataType(); + assertEquals("INT", first.getDataType()); + assertEquals(Signedness.UNSIGNED, first.getSignedness()); + assertFalse(first.isZerofill()); + + ColDataType second = createTable.getColumnDefinitions().get(1).getColDataType(); + assertEquals(Signedness.UNSIGNED, second.getSignedness()); + assertTrue(second.isZerofill()); + + ColDataType third = createTable.getColumnDefinitions().get(2).getColDataType(); + assertEquals(Signedness.SIGNED, third.getSignedness()); + assertEquals(List.of("NOT", "NULL"), + createTable.getColumnDefinitions().get(2).getColumnSpecs()); + } }