Skip to content

Commit 386f84b

Browse files
kwoodhouse93Brigitte Lamarche
authored and
Brigitte Lamarche
committed
Differentiate between BINARY and CHAR (go-sql-driver#724)
* Differentiate between BINARY and CHAR When looking up the database type name, we now check the character set for the following field types: * CHAR * VARCHAR * BLOB * TINYBLOB * MEDIUMBLOB * LONGBLOB If the character set is 63 (which is the binary pseudo character set), we return the binary names, which are (respectively): * BINARY * VARBINARY * BLOB * TINYBLOB * MEDIUMBLOB * LONGBLOB If any other character set is in use, we return the text names, which are (again, respectively): * CHAR * VARCHAR * TEXT * TINYTEXT * MEDIUMTEXT * LONGTEXT To facilitate this, mysqlField has been extended to include a uint8 field for character set, which is read from the appropriate packet. Column type tests have been updated to ensure coverage of binary and text types. * Increase test coverage for column types
1 parent 6992fad commit 386f84b

File tree

6 files changed

+112
-36
lines changed

6 files changed

+112
-36
lines changed

AUTHORS

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ Justin Li <jli at j-li.net>
4545
Justin Nuß <nuss.justin at gmail.com>
4646
Kamil Dziedzic <kamil at klecza.pl>
4747
Kevin Malachowski <kevin at chowski.com>
48+
Kieron Woodhouse <kieron.woodhouse at infosum.com>
4849
Lennart Rudolph <lrudolph at hmc.edu>
4950
Leonardo YongUk Kim <dalinaum at gmail.com>
5051
Linh Tran Tuan <linhduonggnu at gmail.com>
@@ -76,6 +77,7 @@ Zhenye Xie <xiezhenye at gmail.com>
7677
Barracuda Networks, Inc.
7778
Counting Ltd.
7879
Google Inc.
80+
InfoSum Ltd.
7981
Keybase Inc.
8082
Pivotal Inc.
8183
Stripe Inc.

collations.go

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
package mysql
1010

1111
const defaultCollation = "utf8_general_ci"
12+
const binaryCollation = "binary"
1213

1314
// A list of available collations mapped to the internal ID.
1415
// To update this map use the following MySQL query:

driver_go18_test.go

+20-2
Original file line numberDiff line numberDiff line change
@@ -588,10 +588,16 @@ func TestRowsColumnTypes(t *testing.T) {
588588
nt1 := NullTime{Time: time.Date(2006, 01, 02, 15, 04, 05, 100000000, time.UTC), Valid: true}
589589
nt2 := NullTime{Time: time.Date(2006, 01, 02, 15, 04, 05, 110000000, time.UTC), Valid: true}
590590
nt6 := NullTime{Time: time.Date(2006, 01, 02, 15, 04, 05, 111111000, time.UTC), Valid: true}
591+
nd1 := NullTime{Time: time.Date(2006, 01, 02, 0, 0, 0, 0, time.UTC), Valid: true}
592+
nd2 := NullTime{Time: time.Date(2006, 03, 04, 0, 0, 0, 0, time.UTC), Valid: true}
593+
ndNULL := NullTime{Time: time.Time{}, Valid: false}
591594
rbNULL := sql.RawBytes(nil)
592595
rb0 := sql.RawBytes("0")
593596
rb42 := sql.RawBytes("42")
594597
rbTest := sql.RawBytes("Test")
598+
rb0pad4 := sql.RawBytes("0\x00\x00\x00") // BINARY right-pads values with 0x00
599+
rbx0 := sql.RawBytes("\x00")
600+
rbx42 := sql.RawBytes("\x42")
595601

596602
var columns = []struct {
597603
name string
@@ -604,13 +610,15 @@ func TestRowsColumnTypes(t *testing.T) {
604610
valuesIn [3]string
605611
valuesOut [3]interface{}
606612
}{
613+
{"bit8null", "BIT(8)", "BIT", scanTypeRawBytes, true, 0, 0, [3]string{"0x0", "NULL", "0x42"}, [3]interface{}{rbx0, rbNULL, rbx42}},
607614
{"boolnull", "BOOL", "TINYINT", scanTypeNullInt, true, 0, 0, [3]string{"NULL", "true", "0"}, [3]interface{}{niNULL, ni1, ni0}},
608615
{"bool", "BOOL NOT NULL", "TINYINT", scanTypeInt8, false, 0, 0, [3]string{"1", "0", "FALSE"}, [3]interface{}{int8(1), int8(0), int8(0)}},
609616
{"intnull", "INTEGER", "INT", scanTypeNullInt, true, 0, 0, [3]string{"0", "NULL", "42"}, [3]interface{}{ni0, niNULL, ni42}},
610617
{"smallint", "SMALLINT NOT NULL", "SMALLINT", scanTypeInt16, false, 0, 0, [3]string{"0", "-32768", "32767"}, [3]interface{}{int16(0), int16(-32768), int16(32767)}},
611618
{"smallintnull", "SMALLINT", "SMALLINT", scanTypeNullInt, true, 0, 0, [3]string{"0", "NULL", "42"}, [3]interface{}{ni0, niNULL, ni42}},
612619
{"int3null", "INT(3)", "INT", scanTypeNullInt, true, 0, 0, [3]string{"0", "NULL", "42"}, [3]interface{}{ni0, niNULL, ni42}},
613620
{"int7", "INT(7) NOT NULL", "INT", scanTypeInt32, false, 0, 0, [3]string{"0", "-1337", "42"}, [3]interface{}{int32(0), int32(-1337), int32(42)}},
621+
{"mediumintnull", "MEDIUMINT", "MEDIUMINT", scanTypeNullInt, true, 0, 0, [3]string{"0", "42", "NULL"}, [3]interface{}{ni0, ni42, niNULL}},
614622
{"bigint", "BIGINT NOT NULL", "BIGINT", scanTypeInt64, false, 0, 0, [3]string{"0", "65535", "-42"}, [3]interface{}{int64(0), int64(65535), int64(-42)}},
615623
{"bigintnull", "BIGINT", "BIGINT", scanTypeNullInt, true, 0, 0, [3]string{"NULL", "1", "42"}, [3]interface{}{niNULL, ni1, ni42}},
616624
{"tinyuint", "TINYINT UNSIGNED NOT NULL", "TINYINT", scanTypeUint8, false, 0, 0, [3]string{"0", "255", "42"}, [3]interface{}{uint8(0), uint8(255), uint8(42)}},
@@ -630,11 +638,21 @@ func TestRowsColumnTypes(t *testing.T) {
630638
{"decimal3null", "DECIMAL(5,0)", "DECIMAL", scanTypeRawBytes, true, 5, 0, [3]string{"0", "NULL", "-12345.123456"}, [3]interface{}{rb0, rbNULL, sql.RawBytes("-12345")}},
631639
{"char25null", "CHAR(25)", "CHAR", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
632640
{"varchar42", "VARCHAR(42) NOT NULL", "VARCHAR", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
633-
{"textnull", "TEXT", "BLOB", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
634-
{"longtext", "LONGTEXT NOT NULL", "BLOB", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
641+
{"binary4null", "BINARY(4)", "BINARY", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0pad4, rbNULL, rbTest}},
642+
{"varbinary42", "VARBINARY(42) NOT NULL", "VARBINARY", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
643+
{"tinyblobnull", "TINYBLOB", "BLOB", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
644+
{"tinytextnull", "TINYTEXT", "TEXT", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
645+
{"blobnull", "BLOB", "BLOB", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
646+
{"textnull", "TEXT", "TEXT", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
647+
{"mediumblob", "MEDIUMBLOB NOT NULL", "BLOB", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
648+
{"mediumtext", "MEDIUMTEXT NOT NULL", "TEXT", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
649+
{"longblob", "LONGBLOB NOT NULL", "BLOB", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
650+
{"longtext", "LONGTEXT NOT NULL", "TEXT", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
635651
{"datetime", "DATETIME", "DATETIME", scanTypeNullTime, true, 0, 0, [3]string{"'2006-01-02 15:04:05'", "'2006-01-02 15:04:05.1'", "'2006-01-02 15:04:05.111111'"}, [3]interface{}{nt0, nt0, nt0}},
636652
{"datetime2", "DATETIME(2)", "DATETIME", scanTypeNullTime, true, 2, 2, [3]string{"'2006-01-02 15:04:05'", "'2006-01-02 15:04:05.1'", "'2006-01-02 15:04:05.111111'"}, [3]interface{}{nt0, nt1, nt2}},
637653
{"datetime6", "DATETIME(6)", "DATETIME", scanTypeNullTime, true, 6, 6, [3]string{"'2006-01-02 15:04:05'", "'2006-01-02 15:04:05.1'", "'2006-01-02 15:04:05.111111'"}, [3]interface{}{nt0, nt1, nt6}},
654+
{"date", "DATE", "DATE", scanTypeNullTime, true, 0, 0, [3]string{"'2006-01-02'", "NULL", "'2006-03-04'"}, [3]interface{}{nd1, ndNULL, nd2}},
655+
{"year", "YEAR NOT NULL", "YEAR", scanTypeUint16, false, 0, 0, [3]string{"2006", "2000", "1994"}, [3]interface{}{uint16(2006), uint16(2000), uint16(1994)}},
638656
}
639657

640658
schema := ""

fields.go

+83-29
Original file line numberDiff line numberDiff line change
@@ -13,35 +13,88 @@ import (
1313
"reflect"
1414
)
1515

16-
var typeDatabaseName = map[fieldType]string{
17-
fieldTypeBit: "BIT",
18-
fieldTypeBLOB: "BLOB",
19-
fieldTypeDate: "DATE",
20-
fieldTypeDateTime: "DATETIME",
21-
fieldTypeDecimal: "DECIMAL",
22-
fieldTypeDouble: "DOUBLE",
23-
fieldTypeEnum: "ENUM",
24-
fieldTypeFloat: "FLOAT",
25-
fieldTypeGeometry: "GEOMETRY",
26-
fieldTypeInt24: "MEDIUMINT",
27-
fieldTypeJSON: "JSON",
28-
fieldTypeLong: "INT",
29-
fieldTypeLongBLOB: "LONGBLOB",
30-
fieldTypeLongLong: "BIGINT",
31-
fieldTypeMediumBLOB: "MEDIUMBLOB",
32-
fieldTypeNewDate: "DATE",
33-
fieldTypeNewDecimal: "DECIMAL",
34-
fieldTypeNULL: "NULL",
35-
fieldTypeSet: "SET",
36-
fieldTypeShort: "SMALLINT",
37-
fieldTypeString: "CHAR",
38-
fieldTypeTime: "TIME",
39-
fieldTypeTimestamp: "TIMESTAMP",
40-
fieldTypeTiny: "TINYINT",
41-
fieldTypeTinyBLOB: "TINYBLOB",
42-
fieldTypeVarChar: "VARCHAR",
43-
fieldTypeVarString: "VARCHAR",
44-
fieldTypeYear: "YEAR",
16+
func (mf *mysqlField) typeDatabaseName() string {
17+
switch mf.fieldType {
18+
case fieldTypeBit:
19+
return "BIT"
20+
case fieldTypeBLOB:
21+
if mf.charSet != collations[binaryCollation] {
22+
return "TEXT"
23+
}
24+
return "BLOB"
25+
case fieldTypeDate:
26+
return "DATE"
27+
case fieldTypeDateTime:
28+
return "DATETIME"
29+
case fieldTypeDecimal:
30+
return "DECIMAL"
31+
case fieldTypeDouble:
32+
return "DOUBLE"
33+
case fieldTypeEnum:
34+
return "ENUM"
35+
case fieldTypeFloat:
36+
return "FLOAT"
37+
case fieldTypeGeometry:
38+
return "GEOMETRY"
39+
case fieldTypeInt24:
40+
return "MEDIUMINT"
41+
case fieldTypeJSON:
42+
return "JSON"
43+
case fieldTypeLong:
44+
return "INT"
45+
case fieldTypeLongBLOB:
46+
if mf.charSet != collations[binaryCollation] {
47+
return "LONGTEXT"
48+
}
49+
return "LONGBLOB"
50+
case fieldTypeLongLong:
51+
return "BIGINT"
52+
case fieldTypeMediumBLOB:
53+
if mf.charSet != collations[binaryCollation] {
54+
return "MEDIUMTEXT"
55+
}
56+
return "MEDIUMBLOB"
57+
case fieldTypeNewDate:
58+
return "DATE"
59+
case fieldTypeNewDecimal:
60+
return "DECIMAL"
61+
case fieldTypeNULL:
62+
return "NULL"
63+
case fieldTypeSet:
64+
return "SET"
65+
case fieldTypeShort:
66+
return "SMALLINT"
67+
case fieldTypeString:
68+
if mf.charSet == collations[binaryCollation] {
69+
return "BINARY"
70+
}
71+
return "CHAR"
72+
case fieldTypeTime:
73+
return "TIME"
74+
case fieldTypeTimestamp:
75+
return "TIMESTAMP"
76+
case fieldTypeTiny:
77+
return "TINYINT"
78+
case fieldTypeTinyBLOB:
79+
if mf.charSet != collations[binaryCollation] {
80+
return "TINYTEXT"
81+
}
82+
return "TINYBLOB"
83+
case fieldTypeVarChar:
84+
if mf.charSet == collations[binaryCollation] {
85+
return "VARBINARY"
86+
}
87+
return "VARCHAR"
88+
case fieldTypeVarString:
89+
if mf.charSet == collations[binaryCollation] {
90+
return "VARBINARY"
91+
}
92+
return "VARCHAR"
93+
case fieldTypeYear:
94+
return "YEAR"
95+
default:
96+
return ""
97+
}
4598
}
4699

47100
var (
@@ -69,6 +122,7 @@ type mysqlField struct {
69122
flags fieldFlag
70123
fieldType fieldType
71124
decimals byte
125+
charSet uint8
72126
}
73127

74128
func (mf *mysqlField) scanType() reflect.Type {

packets.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -706,10 +706,14 @@ func (mc *mysqlConn) readColumns(count int) ([]mysqlField, error) {
706706
if err != nil {
707707
return nil, err
708708
}
709+
pos += n
709710

710711
// Filler [uint8]
712+
pos++
713+
711714
// Charset [charset, collation uint8]
712-
pos += n + 1 + 2
715+
columns[i].charSet = data[pos]
716+
pos += 2
713717

714718
// Length [uint32]
715719
columns[i].length = binary.LittleEndian.Uint32(data[pos : pos+4])

rows.go

+1-4
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,7 @@ func (rows *mysqlRows) Columns() []string {
6060
}
6161

6262
func (rows *mysqlRows) ColumnTypeDatabaseTypeName(i int) string {
63-
if name, ok := typeDatabaseName[rows.rs.columns[i].fieldType]; ok {
64-
return name
65-
}
66-
return ""
63+
return rows.rs.columns[i].typeDatabaseName()
6764
}
6865

6966
// func (rows *mysqlRows) ColumnTypeLength(i int) (length int64, ok bool) {

0 commit comments

Comments
 (0)