Skip to content

Commit b8fd921

Browse files
authored
Merge pull request #89 from metowolf/fix/mysql8-display-width
Fix MySQL 8.0.19+ integer display width compatibility
2 parents bdb9c56 + 39ccd85 commit b8fd921

File tree

4 files changed

+342
-1
lines changed

4 files changed

+342
-1
lines changed

internal/db.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,18 @@ func (f *FieldInfo) Equals(other *FieldInfo) bool {
6868
if f.ColumnName != other.ColumnName ||
6969
f.IsNullAble != other.IsNullAble ||
7070
f.DataType != other.DataType ||
71-
f.ColumnType != other.ColumnType ||
7271
f.Extra != other.Extra {
7372
return false
7473
}
7574

75+
// Compare ColumnType with normalization for integer display width
76+
// MySQL 8.0.19+ removed display width for integer types (int(11) -> int)
77+
normalizedSourceType := normalizeIntegerType(f.ColumnType)
78+
normalizedDestType := normalizeIntegerType(other.ColumnType)
79+
if normalizedSourceType != normalizedDestType {
80+
return false
81+
}
82+
7683
// Compare default values
7784
if (f.ColumnDefault == nil && other.ColumnDefault != nil) ||
7885
(f.ColumnDefault != nil && other.ColumnDefault == nil) {

internal/field_test.go

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,167 @@ func TestFieldInfo_Equals(t *testing.T) {
125125
},
126126
equal: true,
127127
},
128+
// Integer type display width tests (MySQL 5.7 vs 8.0 compatibility)
129+
{
130+
name: "int(11) vs int should be equal",
131+
field1: &FieldInfo{
132+
ColumnName: "id",
133+
ColumnType: "int(11)",
134+
DataType: "int",
135+
IsNullAble: "NO",
136+
},
137+
field2: &FieldInfo{
138+
ColumnName: "id",
139+
ColumnType: "int",
140+
DataType: "int",
141+
IsNullAble: "NO",
142+
},
143+
equal: true,
144+
},
145+
{
146+
name: "bigint(20) vs bigint should be equal",
147+
field1: &FieldInfo{
148+
ColumnName: "user_id",
149+
ColumnType: "bigint(20)",
150+
DataType: "bigint",
151+
IsNullAble: "NO",
152+
},
153+
field2: &FieldInfo{
154+
ColumnName: "user_id",
155+
ColumnType: "bigint",
156+
DataType: "bigint",
157+
IsNullAble: "NO",
158+
},
159+
equal: true,
160+
},
161+
{
162+
name: "tinyint(1) vs tinyint should be equal",
163+
field1: &FieldInfo{
164+
ColumnName: "is_active",
165+
ColumnType: "tinyint(1)",
166+
DataType: "tinyint",
167+
IsNullAble: "NO",
168+
},
169+
field2: &FieldInfo{
170+
ColumnName: "is_active",
171+
ColumnType: "tinyint",
172+
DataType: "tinyint",
173+
IsNullAble: "NO",
174+
},
175+
equal: true,
176+
},
177+
{
178+
name: "tinyint(4) vs tinyint should be equal",
179+
field1: &FieldInfo{
180+
ColumnName: "status",
181+
ColumnType: "tinyint(4)",
182+
DataType: "tinyint",
183+
IsNullAble: "NO",
184+
},
185+
field2: &FieldInfo{
186+
ColumnName: "status",
187+
ColumnType: "tinyint",
188+
DataType: "tinyint",
189+
IsNullAble: "NO",
190+
},
191+
equal: true,
192+
},
193+
{
194+
name: "int(11) unsigned vs int unsigned should be equal",
195+
field1: &FieldInfo{
196+
ColumnName: "count",
197+
ColumnType: "int(11) unsigned",
198+
DataType: "int",
199+
IsNullAble: "NO",
200+
},
201+
field2: &FieldInfo{
202+
ColumnName: "count",
203+
ColumnType: "int unsigned",
204+
DataType: "int",
205+
IsNullAble: "NO",
206+
},
207+
equal: true,
208+
},
209+
{
210+
name: "bigint(20) unsigned vs bigint unsigned should be equal",
211+
field1: &FieldInfo{
212+
ColumnName: "total",
213+
ColumnType: "bigint(20) unsigned",
214+
DataType: "bigint",
215+
IsNullAble: "NO",
216+
},
217+
field2: &FieldInfo{
218+
ColumnName: "total",
219+
ColumnType: "bigint unsigned",
220+
DataType: "bigint",
221+
IsNullAble: "NO",
222+
},
223+
equal: true,
224+
},
225+
{
226+
name: "int(10) zerofill vs int zerofill should be equal",
227+
field1: &FieldInfo{
228+
ColumnName: "order_id",
229+
ColumnType: "int(10) zerofill",
230+
DataType: "int",
231+
IsNullAble: "NO",
232+
},
233+
field2: &FieldInfo{
234+
ColumnName: "order_id",
235+
ColumnType: "int zerofill",
236+
DataType: "int",
237+
IsNullAble: "NO",
238+
},
239+
equal: true,
240+
},
241+
{
242+
name: "int(10) unsigned zerofill vs int unsigned zerofill should be equal",
243+
field1: &FieldInfo{
244+
ColumnName: "code",
245+
ColumnType: "int(10) unsigned zerofill",
246+
DataType: "int",
247+
IsNullAble: "NO",
248+
},
249+
field2: &FieldInfo{
250+
ColumnName: "code",
251+
ColumnType: "int unsigned zerofill",
252+
DataType: "int",
253+
IsNullAble: "NO",
254+
},
255+
equal: true,
256+
},
257+
{
258+
name: "int vs bigint should not be equal",
259+
field1: &FieldInfo{
260+
ColumnName: "value",
261+
ColumnType: "int",
262+
DataType: "int",
263+
IsNullAble: "NO",
264+
},
265+
field2: &FieldInfo{
266+
ColumnName: "value",
267+
ColumnType: "bigint",
268+
DataType: "bigint",
269+
IsNullAble: "NO",
270+
},
271+
equal: false,
272+
},
273+
{
274+
name: "int unsigned vs int should not be equal (unsigned modifier difference)",
275+
field1: &FieldInfo{
276+
ColumnName: "amount",
277+
ColumnType: "int unsigned",
278+
DataType: "int",
279+
IsNullAble: "NO",
280+
},
281+
field2: &FieldInfo{
282+
ColumnName: "amount",
283+
ColumnType: "int",
284+
DataType: "int",
285+
IsNullAble: "NO",
286+
},
287+
equal: false,
288+
},
128289
}
129290

130291
for _, tt := range tests {

internal/util.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,32 @@ func errString(err error) string {
8383
}
8484
return xcolor.RedString("%s", err.Error())
8585
}
86+
87+
// normalizeIntegerType removes display width from integer types for MySQL 8.0.19+ compatibility.
88+
// MySQL 8.0.19+ deprecated display width for integer types (TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT).
89+
// This function normalizes types like "int(11)" to "int" while preserving modifiers like "unsigned" and "zerofill".
90+
//
91+
// Examples:
92+
// - "int(11)" -> "int"
93+
// - "int(11) unsigned" -> "int unsigned"
94+
// - "bigint(20)" -> "bigint"
95+
// - "tinyint(1)" -> "tinyint"
96+
// - "varchar(255)" -> "varchar(255)" (unchanged, not an integer type)
97+
func normalizeIntegerType(columnType string) string {
98+
// Pattern matches: (tinyint|smallint|mediumint|int|bigint) followed by optional (digits)
99+
// Captures the type name and everything after the display width
100+
re := regexp.MustCompile(`(?i)^(tinyint|smallint|mediumint|int|bigint)\(\d+\)(\s+.+)?$`)
101+
102+
matches := re.FindStringSubmatch(columnType)
103+
if len(matches) > 0 {
104+
// matches[1] is the type name (e.g., "int")
105+
// matches[2] is the modifiers (e.g., " unsigned", " zerofill"), may be empty
106+
if len(matches) > 2 && matches[2] != "" {
107+
return matches[1] + matches[2] // e.g., "int unsigned"
108+
}
109+
return matches[1] // e.g., "int"
110+
}
111+
112+
// Not an integer type with display width, return as-is
113+
return columnType
114+
}

internal/util_test.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package internal
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestNormalizeIntegerType(t *testing.T) {
8+
tests := []struct {
9+
name string
10+
input string
11+
expected string
12+
}{
13+
// Basic integer types with display width
14+
{
15+
name: "int with display width",
16+
input: "int(11)",
17+
expected: "int",
18+
},
19+
{
20+
name: "bigint with display width",
21+
input: "bigint(20)",
22+
expected: "bigint",
23+
},
24+
{
25+
name: "tinyint with display width",
26+
input: "tinyint(1)",
27+
expected: "tinyint",
28+
},
29+
{
30+
name: "tinyint(4) with display width",
31+
input: "tinyint(4)",
32+
expected: "tinyint",
33+
},
34+
{
35+
name: "smallint with display width",
36+
input: "smallint(5)",
37+
expected: "smallint",
38+
},
39+
{
40+
name: "mediumint with display width",
41+
input: "mediumint(8)",
42+
expected: "mediumint",
43+
},
44+
45+
// Integer types with unsigned modifier
46+
{
47+
name: "int(11) unsigned",
48+
input: "int(11) unsigned",
49+
expected: "int unsigned",
50+
},
51+
{
52+
name: "bigint(20) unsigned",
53+
input: "bigint(20) unsigned",
54+
expected: "bigint unsigned",
55+
},
56+
{
57+
name: "tinyint(1) unsigned",
58+
input: "tinyint(1) unsigned",
59+
expected: "tinyint unsigned",
60+
},
61+
62+
// Integer types with zerofill modifier
63+
{
64+
name: "int(11) zerofill",
65+
input: "int(11) zerofill",
66+
expected: "int zerofill",
67+
},
68+
{
69+
name: "int(10) unsigned zerofill",
70+
input: "int(10) unsigned zerofill",
71+
expected: "int unsigned zerofill",
72+
},
73+
74+
// Integer types without display width (already normalized)
75+
{
76+
name: "int without display width",
77+
input: "int",
78+
expected: "int",
79+
},
80+
{
81+
name: "bigint without display width",
82+
input: "bigint",
83+
expected: "bigint",
84+
},
85+
{
86+
name: "int unsigned without display width",
87+
input: "int unsigned",
88+
expected: "int unsigned",
89+
},
90+
91+
// Non-integer types (should not be affected)
92+
{
93+
name: "varchar with length",
94+
input: "varchar(255)",
95+
expected: "varchar(255)",
96+
},
97+
{
98+
name: "char with length",
99+
input: "char(10)",
100+
expected: "char(10)",
101+
},
102+
{
103+
name: "decimal with precision",
104+
input: "decimal(10,2)",
105+
expected: "decimal(10,2)",
106+
},
107+
{
108+
name: "text type",
109+
input: "text",
110+
expected: "text",
111+
},
112+
{
113+
name: "timestamp",
114+
input: "timestamp",
115+
expected: "timestamp",
116+
},
117+
118+
// Case insensitive matching
119+
{
120+
name: "INT(11) uppercase",
121+
input: "INT(11)",
122+
expected: "INT",
123+
},
124+
{
125+
name: "BIGINT(20) UNSIGNED uppercase",
126+
input: "BIGINT(20) UNSIGNED",
127+
expected: "BIGINT UNSIGNED",
128+
},
129+
{
130+
name: "TinyInt(1) mixed case",
131+
input: "TinyInt(1)",
132+
expected: "TinyInt",
133+
},
134+
}
135+
136+
for _, tt := range tests {
137+
t.Run(tt.name, func(t *testing.T) {
138+
result := normalizeIntegerType(tt.input)
139+
if result != tt.expected {
140+
t.Errorf("normalizeIntegerType(%q) = %q, want %q", tt.input, result, tt.expected)
141+
}
142+
})
143+
}
144+
}

0 commit comments

Comments
 (0)