From 4f5c0b7871476132f41d3ebb16811a04ad1462f9 Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Sat, 6 May 2017 03:52:22 +0800 Subject: [PATCH 01/25] rows: implement driver.RowsColumnTypeScanType Implementation for time.Time not yet complete! --- rows.go | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/rows.go b/rows.go index c7f5ee26c..9b8cdb2ed 100644 --- a/rows.go +++ b/rows.go @@ -9,8 +9,33 @@ package mysql import ( + "database/sql" "database/sql/driver" "io" + "reflect" + "time" +) + +var ( + scanTypeNil = reflect.TypeOf(nil) + scanTypeNullInt = reflect.TypeOf(sql.NullInt64{}) + scanTypeUint8 = reflect.TypeOf(uint8(0)) + scanTypeInt8 = reflect.TypeOf(int8(0)) + scanTypeUint16 = reflect.TypeOf(uint16(0)) + scanTypeInt16 = reflect.TypeOf(int16(0)) + scanTypeUint32 = reflect.TypeOf(uint32(0)) + scanTypeInt32 = reflect.TypeOf(int32(0)) + scanTypeUint64 = reflect.TypeOf(uint64(0)) + scanTypeInt64 = reflect.TypeOf(int64(0)) + scanTypeNullFloat = reflect.TypeOf(sql.NullFloat64{}) + scanTypeFloat32 = reflect.TypeOf(float32(0)) + scanTypeFloat64 = reflect.TypeOf(float64(0)) + scanTypeNullString = reflect.TypeOf(sql.NullString{}) + scanTypeString = reflect.TypeOf("") + scanTypeBytes = reflect.TypeOf([]byte{}) + scanTypeRawBytes = reflect.TypeOf(sql.RawBytes{}) + scanTypeTime = reflect.TypeOf(time.Time{}) + scanTypeUnknown = reflect.TypeOf(new(interface{})) ) type mysqlField struct { @@ -21,6 +46,83 @@ type mysqlField struct { decimals byte } +func (mf *mysqlField) scanType() reflect.Type { + switch mf.fieldType { + case fieldTypeNULL: + return scanTypeNil + + case fieldTypeTiny: + if mf.flags&flagNotNULL != 0 { + if mf.flags&flagUnsigned != 0 { + return scanTypeUint8 + } + return scanTypeInt8 + } + return scanTypeNullInt + + case fieldTypeShort, fieldTypeYear: + if mf.flags&flagNotNULL != 0 { + if mf.flags&flagUnsigned != 0 { + return scanTypeUint16 + } + return scanTypeInt16 + } + return scanTypeNullInt + + case fieldTypeInt24, fieldTypeLong: + if mf.flags&flagNotNULL != 0 { + if mf.flags&flagUnsigned != 0 { + return scanTypeUint32 + } + return scanTypeInt32 + } + return scanTypeNullInt + + case fieldTypeLongLong: + if mf.flags&flagNotNULL != 0 { + if mf.flags&flagUnsigned != 0 { + return scanTypeUint64 + } + return scanTypeInt64 + } + return scanTypeNullInt + + case fieldTypeFloat: + if mf.flags&flagNotNULL != 0 { + return scanTypeFloat32 + } + return scanTypeNullFloat + + case fieldTypeDouble: + if mf.flags&flagNotNULL != 0 { + return scanTypeFloat64 + } + return scanTypeNullFloat + + case fieldTypeDecimal, fieldTypeNewDecimal, fieldTypeVarChar, + fieldTypeBit, fieldTypeEnum, fieldTypeSet, fieldTypeTinyBLOB, + fieldTypeMediumBLOB, fieldTypeLongBLOB, fieldTypeBLOB, + fieldTypeVarString, fieldTypeString, fieldTypeGeometry, fieldTypeJSON, + fieldTypeTime: + if mf.flags&flagNotNULL != 0 { + // alternatively we could return []byte or even RawBytes + return scanTypeString + } + return scanTypeNullString + + case + fieldTypeDate, fieldTypeNewDate, + fieldTypeTimestamp, fieldTypeDateTime: + + // TODO: NULL + // TODO: respect rows.mc.parseTime + return scanTypeTime + + default: + return scanTypeUnknown + } +} + type resultSet struct { columns []mysqlField columnNames []string @@ -65,6 +167,10 @@ func (rows *mysqlRows) Columns() []string { return columns } +func (rows *mysqlRows) ColumnTypeScanType(i int) reflect.Type { + return rows.rs.columns[i].scanType() +} + func (rows *mysqlRows) Close() (err error) { if f := rows.finish; f != nil { f() From 0950d1b1abd15bfe56d7ea2f11a87016e738190a Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Sat, 6 May 2017 03:53:05 +0800 Subject: [PATCH 02/25] rows: implement driver.RowsColumnTypeNullable --- rows.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rows.go b/rows.go index 9b8cdb2ed..18b224f67 100644 --- a/rows.go +++ b/rows.go @@ -167,6 +167,10 @@ func (rows *mysqlRows) Columns() []string { return columns } +func (rows *mysqlRows) ColumnTypeNullable(i int) (nullable, ok bool) { + return rows.rs.columns[i].flags&flagNotNULL != 0, true +} + func (rows *mysqlRows) ColumnTypeScanType(i int) reflect.Type { return rows.rs.columns[i].scanType() } From 2f97a230b993888fc814a1e09c13acf53406e432 Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Sat, 6 May 2017 16:07:21 +0800 Subject: [PATCH 03/25] rows: move fields related code to fields.go --- fields.go | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ rows.go | 109 ------------------------------------------------ 2 files changed, 121 insertions(+), 109 deletions(-) create mode 100644 fields.go diff --git a/fields.go b/fields.go new file mode 100644 index 000000000..f6cc9e8fc --- /dev/null +++ b/fields.go @@ -0,0 +1,121 @@ +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package +// +// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. + +package mysql + +import ( + "database/sql" + "reflect" + "time" +) + +var ( + scanTypeNil = reflect.TypeOf(nil) + scanTypeNullInt = reflect.TypeOf(sql.NullInt64{}) + scanTypeUint8 = reflect.TypeOf(uint8(0)) + scanTypeInt8 = reflect.TypeOf(int8(0)) + scanTypeUint16 = reflect.TypeOf(uint16(0)) + scanTypeInt16 = reflect.TypeOf(int16(0)) + scanTypeUint32 = reflect.TypeOf(uint32(0)) + scanTypeInt32 = reflect.TypeOf(int32(0)) + scanTypeUint64 = reflect.TypeOf(uint64(0)) + scanTypeInt64 = reflect.TypeOf(int64(0)) + scanTypeNullFloat = reflect.TypeOf(sql.NullFloat64{}) + scanTypeFloat32 = reflect.TypeOf(float32(0)) + scanTypeFloat64 = reflect.TypeOf(float64(0)) + scanTypeNullString = reflect.TypeOf(sql.NullString{}) + scanTypeString = reflect.TypeOf("") + scanTypeBytes = reflect.TypeOf([]byte{}) + scanTypeRawBytes = reflect.TypeOf(sql.RawBytes{}) + scanTypeTime = reflect.TypeOf(time.Time{}) + scanTypeUnknown = reflect.TypeOf(new(interface{})) +) + +type mysqlField struct { + tableName string + name string + flags fieldFlag + fieldType byte + decimals byte +} + +func (mf *mysqlField) scanType() reflect.Type { + switch mf.fieldType { + case fieldTypeNULL: + return scanTypeNil + + case fieldTypeTiny: + if mf.flags&flagNotNULL != 0 { + if mf.flags&flagUnsigned != 0 { + return scanTypeUint8 + } + return scanTypeInt8 + } + return scanTypeNullInt + + case fieldTypeShort, fieldTypeYear: + if mf.flags&flagNotNULL != 0 { + if mf.flags&flagUnsigned != 0 { + return scanTypeUint16 + } + return scanTypeInt16 + } + return scanTypeNullInt + + case fieldTypeInt24, fieldTypeLong: + if mf.flags&flagNotNULL != 0 { + if mf.flags&flagUnsigned != 0 { + return scanTypeUint32 + } + return scanTypeInt32 + } + return scanTypeNullInt + + case fieldTypeLongLong: + if mf.flags&flagNotNULL != 0 { + if mf.flags&flagUnsigned != 0 { + return scanTypeUint64 + } + return scanTypeInt64 + } + return scanTypeNullInt + + case fieldTypeFloat: + if mf.flags&flagNotNULL != 0 { + return scanTypeFloat32 + } + return scanTypeNullFloat + + case fieldTypeDouble: + if mf.flags&flagNotNULL != 0 { + return scanTypeFloat64 + } + return scanTypeNullFloat + + case fieldTypeDecimal, fieldTypeNewDecimal, fieldTypeVarChar, + fieldTypeBit, fieldTypeEnum, fieldTypeSet, fieldTypeTinyBLOB, + fieldTypeMediumBLOB, fieldTypeLongBLOB, fieldTypeBLOB, + fieldTypeVarString, fieldTypeString, fieldTypeGeometry, fieldTypeJSON, + fieldTypeTime: + if mf.flags&flagNotNULL != 0 { + // alternatively we could return []byte or even RawBytes + return scanTypeString + } + return scanTypeNullString + + case fieldTypeDate, fieldTypeNewDate, + fieldTypeTimestamp, fieldTypeDateTime: + + // TODO: NULL + // TODO: respect rows.mc.parseTime + return scanTypeTime + + default: + return scanTypeUnknown + } +} diff --git a/rows.go b/rows.go index 18b224f67..0ed5fffc2 100644 --- a/rows.go +++ b/rows.go @@ -9,120 +9,11 @@ package mysql import ( - "database/sql" "database/sql/driver" "io" "reflect" - "time" ) -var ( - scanTypeNil = reflect.TypeOf(nil) - scanTypeNullInt = reflect.TypeOf(sql.NullInt64{}) - scanTypeUint8 = reflect.TypeOf(uint8(0)) - scanTypeInt8 = reflect.TypeOf(int8(0)) - scanTypeUint16 = reflect.TypeOf(uint16(0)) - scanTypeInt16 = reflect.TypeOf(int16(0)) - scanTypeUint32 = reflect.TypeOf(uint32(0)) - scanTypeInt32 = reflect.TypeOf(int32(0)) - scanTypeUint64 = reflect.TypeOf(uint64(0)) - scanTypeInt64 = reflect.TypeOf(int64(0)) - scanTypeNullFloat = reflect.TypeOf(sql.NullFloat64{}) - scanTypeFloat32 = reflect.TypeOf(float32(0)) - scanTypeFloat64 = reflect.TypeOf(float64(0)) - scanTypeNullString = reflect.TypeOf(sql.NullString{}) - scanTypeString = reflect.TypeOf("") - scanTypeBytes = reflect.TypeOf([]byte{}) - scanTypeRawBytes = reflect.TypeOf(sql.RawBytes{}) - scanTypeTime = reflect.TypeOf(time.Time{}) - scanTypeUnknown = reflect.TypeOf(new(interface{})) -) - -type mysqlField struct { - tableName string - name string - flags fieldFlag - fieldType byte - decimals byte -} - -func (mf *mysqlField) scanType() reflect.Type { - switch mf.fieldType { - case fieldTypeNULL: - return scanTypeNil - - case fieldTypeTiny: - if mf.flags&flagNotNULL != 0 { - if mf.flags&flagUnsigned != 0 { - return scanTypeUint8 - } - return scanTypeInt8 - } - return scanTypeNullInt - - case fieldTypeShort, fieldTypeYear: - if mf.flags&flagNotNULL != 0 { - if mf.flags&flagUnsigned != 0 { - return scanTypeUint16 - } - return scanTypeInt16 - } - return scanTypeNullInt - - case fieldTypeInt24, fieldTypeLong: - if mf.flags&flagNotNULL != 0 { - if mf.flags&flagUnsigned != 0 { - return scanTypeUint32 - } - return scanTypeInt32 - } - return scanTypeNullInt - - case fieldTypeLongLong: - if mf.flags&flagNotNULL != 0 { - if mf.flags&flagUnsigned != 0 { - return scanTypeUint64 - } - return scanTypeInt64 - } - return scanTypeNullInt - - case fieldTypeFloat: - if mf.flags&flagNotNULL != 0 { - return scanTypeFloat32 - } - return scanTypeNullFloat - - case fieldTypeDouble: - if mf.flags&flagNotNULL != 0 { - return scanTypeFloat64 - } - return scanTypeNullFloat - - case fieldTypeDecimal, fieldTypeNewDecimal, fieldTypeVarChar, - fieldTypeBit, fieldTypeEnum, fieldTypeSet, fieldTypeTinyBLOB, - fieldTypeMediumBLOB, fieldTypeLongBLOB, fieldTypeBLOB, - fieldTypeVarString, fieldTypeString, fieldTypeGeometry, fieldTypeJSON, - fieldTypeTime: - if mf.flags&flagNotNULL != 0 { - // alternatively we could return []byte or even RawBytes - return scanTypeString - } - return scanTypeNullString - - case - fieldTypeDate, fieldTypeNewDate, - fieldTypeTimestamp, fieldTypeDateTime: - - // TODO: NULL - // TODO: respect rows.mc.parseTime - return scanTypeTime - - default: - return scanTypeUnknown - } -} - type resultSet struct { columns []mysqlField columnNames []string From 1b786bd4ca4381211baab828a5ff6736ffaec234 Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Sat, 6 May 2017 16:11:03 +0800 Subject: [PATCH 04/25] fields: use NullTime for nullable datetime fields --- fields.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/fields.go b/fields.go index f6cc9e8fc..37cdf563b 100644 --- a/fields.go +++ b/fields.go @@ -33,6 +33,7 @@ var ( scanTypeBytes = reflect.TypeOf([]byte{}) scanTypeRawBytes = reflect.TypeOf(sql.RawBytes{}) scanTypeTime = reflect.TypeOf(time.Time{}) + scanTypeNullTime = reflect.TypeOf(NullTime{}) scanTypeUnknown = reflect.TypeOf(new(interface{})) ) @@ -111,9 +112,12 @@ func (mf *mysqlField) scanType() reflect.Type { case fieldTypeDate, fieldTypeNewDate, fieldTypeTimestamp, fieldTypeDateTime: - // TODO: NULL // TODO: respect rows.mc.parseTime - return scanTypeTime + + if mf.flags&flagNotNULL != 0 { + return scanTypeTime + } + return scanTypeNullTime default: return scanTypeUnknown From 571f08219017366e13a9e7217e7f8edcdabc4f2f Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Sat, 6 May 2017 16:25:48 +0800 Subject: [PATCH 05/25] fields: make fieldType its own type --- const.go | 6 ++++-- fields.go | 2 +- packets.go | 18 +++++++++--------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/const.go b/const.go index 88cfff3fd..3f793f513 100644 --- a/const.go +++ b/const.go @@ -87,8 +87,10 @@ const ( ) // https://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnType +type fieldType byte + const ( - fieldTypeDecimal byte = iota + fieldTypeDecimal fieldType = iota fieldTypeTiny fieldTypeShort fieldTypeLong @@ -107,7 +109,7 @@ const ( fieldTypeBit ) const ( - fieldTypeJSON byte = iota + 0xf5 + fieldTypeJSON fieldType = iota + 0xf5 fieldTypeNewDecimal fieldTypeEnum fieldTypeSet diff --git a/fields.go b/fields.go index 37cdf563b..a9e362dab 100644 --- a/fields.go +++ b/fields.go @@ -41,7 +41,7 @@ type mysqlField struct { tableName string name string flags fieldFlag - fieldType byte + fieldType fieldType decimals byte } diff --git a/packets.go b/packets.go index 1887467df..1bd2d7225 100644 --- a/packets.go +++ b/packets.go @@ -703,7 +703,7 @@ func (mc *mysqlConn) readColumns(count int) ([]mysqlField, error) { pos += n + 1 + 2 + 4 // Field type [uint8] - columns[i].fieldType = data[pos] + columns[i].fieldType = fieldType(data[pos]) pos++ // Flags [uint16] @@ -980,7 +980,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error { // build NULL-bitmap if arg == nil { nullMask[i/8] |= 1 << (uint(i) & 7) - paramTypes[i+i] = fieldTypeNULL + paramTypes[i+i] = byte(fieldTypeNULL) paramTypes[i+i+1] = 0x00 continue } @@ -988,7 +988,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error { // cache types and values switch v := arg.(type) { case int64: - paramTypes[i+i] = fieldTypeLongLong + paramTypes[i+i] = byte(fieldTypeLongLong) paramTypes[i+i+1] = 0x00 if cap(paramValues)-len(paramValues)-8 >= 0 { @@ -1004,7 +1004,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error { } case float64: - paramTypes[i+i] = fieldTypeDouble + paramTypes[i+i] = byte(fieldTypeDouble) paramTypes[i+i+1] = 0x00 if cap(paramValues)-len(paramValues)-8 >= 0 { @@ -1020,7 +1020,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error { } case bool: - paramTypes[i+i] = fieldTypeTiny + paramTypes[i+i] = byte(fieldTypeTiny) paramTypes[i+i+1] = 0x00 if v { @@ -1032,7 +1032,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error { case []byte: // Common case (non-nil value) first if v != nil { - paramTypes[i+i] = fieldTypeString + paramTypes[i+i] = byte(fieldTypeString) paramTypes[i+i+1] = 0x00 if len(v) < mc.maxAllowedPacket-pos-len(paramValues)-(len(args)-(i+1))*64 { @@ -1050,11 +1050,11 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error { // Handle []byte(nil) as a NULL value nullMask[i/8] |= 1 << (uint(i) & 7) - paramTypes[i+i] = fieldTypeNULL + paramTypes[i+i] = byte(fieldTypeNULL) paramTypes[i+i+1] = 0x00 case string: - paramTypes[i+i] = fieldTypeString + paramTypes[i+i] = byte(fieldTypeString) paramTypes[i+i+1] = 0x00 if len(v) < mc.maxAllowedPacket-pos-len(paramValues)-(len(args)-(i+1))*64 { @@ -1069,7 +1069,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error { } case time.Time: - paramTypes[i+i] = fieldTypeString + paramTypes[i+i] = byte(fieldTypeString) paramTypes[i+i+1] = 0x00 var a [64]byte From b6124b534a6ab1e596af666e9f4f77f9b3b615d8 Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Sat, 6 May 2017 17:40:06 +0800 Subject: [PATCH 06/25] rows: implement driver.RowsColumnTypeDatabaseTypeName --- fields.go | 31 +++++++++++++++++++++++++++++++ rows.go | 7 +++++++ 2 files changed, 38 insertions(+) diff --git a/fields.go b/fields.go index a9e362dab..fc2378e24 100644 --- a/fields.go +++ b/fields.go @@ -14,6 +14,37 @@ import ( "time" ) +var typeDatabaseName = map[fieldType]string{ + fieldTypeDecimal: "DECIMAL", + fieldTypeTiny: "TINYINT", + fieldTypeShort: "SMALLINT", + fieldTypeLong: "INT", + fieldTypeFloat: "FLOAT", + fieldTypeDouble: "DOUBLE", + fieldTypeNULL: "NULL", + fieldTypeTimestamp: "TIMESTAMP", + fieldTypeLongLong: "BIGINT", + fieldTypeInt24: "MEDIUMINT", + fieldTypeDate: "DATE", + fieldTypeTime: "TIME", + fieldTypeDateTime: "DATETIME", + fieldTypeYear: "YEAR", + fieldTypeNewDate: "DATE", + fieldTypeVarChar: "VARCHAR", + fieldTypeBit: "BIT", + fieldTypeJSON: "JSON", + fieldTypeNewDecimal: "DECIMAL", + fieldTypeEnum: "ENUM", + fieldTypeSet: "SET", + fieldTypeTinyBLOB: "TINYBLOB", + fieldTypeMediumBLOB: "MEDIUMBLOB", + fieldTypeLongBLOB: "LONGBLOB", + fieldTypeBLOB: "BLOB", + fieldTypeVarString: "VARSTRING", // correct? + fieldTypeString: "STRING", // correct? + fieldTypeGeometry: "GEOMETRY", +} + var ( scanTypeNil = reflect.TypeOf(nil) scanTypeNullInt = reflect.TypeOf(sql.NullInt64{}) diff --git a/rows.go b/rows.go index 0ed5fffc2..ebc3126aa 100644 --- a/rows.go +++ b/rows.go @@ -58,6 +58,13 @@ func (rows *mysqlRows) Columns() []string { return columns } +func (rows *mysqlRows) ColumnTypeDatabaseTypeName(i int) string { + if name, ok := typeDatabaseName[rows.rs.columns[i].fieldType]; ok { + return name + } + return "" +} + func (rows *mysqlRows) ColumnTypeNullable(i int) (nullable, ok bool) { return rows.rs.columns[i].flags&flagNotNULL != 0, true } From 3ed8bb2c073fc02860182d0a13ade480cdf6d258 Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Sat, 6 May 2017 17:40:42 +0800 Subject: [PATCH 07/25] fields: fix copyright year --- fields.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fields.go b/fields.go index fc2378e24..26467fb2c 100644 --- a/fields.go +++ b/fields.go @@ -1,6 +1,6 @@ // Go MySQL Driver - A MySQL-Driver for Go's database/sql package // -// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. +// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, From 18201488c1f3dc3369079e327b995eb7708790fa Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Sat, 13 May 2017 00:39:55 +0800 Subject: [PATCH 08/25] rows: compile time interface implementation checks --- rows_test.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 rows_test.go diff --git a/rows_test.go b/rows_test.go new file mode 100644 index 000000000..9761ff67f --- /dev/null +++ b/rows_test.go @@ -0,0 +1,27 @@ +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package +// +// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. + +package mysql + +import ( + "database/sql/driver" +) + +// Ensure that all the driver interfaces are implemented +var ( + _ driver.Rows = &binaryRows{} + _ driver.Rows = &textRows{} + _ driver.RowsColumnTypeDatabaseTypeName = &binaryRows{} + _ driver.RowsColumnTypeDatabaseTypeName = &textRows{} + _ driver.RowsColumnTypeNullable = &binaryRows{} + _ driver.RowsColumnTypeNullable = &textRows{} + _ driver.RowsColumnTypeScanType = &binaryRows{} + _ driver.RowsColumnTypeScanType = &textRows{} + _ driver.RowsNextResultSet = &binaryRows{} + _ driver.RowsNextResultSet = &textRows{} +) From 0570286dbc21f8da047a79d152f7dd3c8e0bc420 Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Sat, 13 May 2017 00:51:12 +0800 Subject: [PATCH 09/25] rows: move tests to versioned driver test files --- driver_go18_test.go | 12 ++++++++++++ driver_test.go | 6 ++++++ rows_test.go | 27 --------------------------- 3 files changed, 18 insertions(+), 27 deletions(-) delete mode 100644 rows_test.go diff --git a/driver_go18_test.go b/driver_go18_test.go index 4962838f2..dd4fe0003 100644 --- a/driver_go18_test.go +++ b/driver_go18_test.go @@ -35,6 +35,18 @@ var ( _ driver.StmtQueryContext = &mysqlStmt{} ) +// Ensure that all the driver interfaces are implemented +var ( + _ driver.RowsColumnTypeDatabaseTypeName = &binaryRows{} + _ driver.RowsColumnTypeDatabaseTypeName = &textRows{} + _ driver.RowsColumnTypeNullable = &binaryRows{} + _ driver.RowsColumnTypeNullable = &textRows{} + _ driver.RowsColumnTypeScanType = &binaryRows{} + _ driver.RowsColumnTypeScanType = &textRows{} + _ driver.RowsNextResultSet = &binaryRows{} + _ driver.RowsNextResultSet = &textRows{} +) + func TestMultiResultSet(t *testing.T) { type result struct { values [][]int diff --git a/driver_test.go b/driver_test.go index bc0386a09..79b2b9fd2 100644 --- a/driver_test.go +++ b/driver_test.go @@ -27,6 +27,12 @@ import ( "time" ) +// Ensure that all the driver interfaces are implemented +var ( + _ driver.Rows = &binaryRows{} + _ driver.Rows = &textRows{} +) + var ( user string pass string diff --git a/rows_test.go b/rows_test.go deleted file mode 100644 index 9761ff67f..000000000 --- a/rows_test.go +++ /dev/null @@ -1,27 +0,0 @@ -// Go MySQL Driver - A MySQL-Driver for Go's database/sql package -// -// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this file, -// You can obtain one at http://mozilla.org/MPL/2.0/. - -package mysql - -import ( - "database/sql/driver" -) - -// Ensure that all the driver interfaces are implemented -var ( - _ driver.Rows = &binaryRows{} - _ driver.Rows = &textRows{} - _ driver.RowsColumnTypeDatabaseTypeName = &binaryRows{} - _ driver.RowsColumnTypeDatabaseTypeName = &textRows{} - _ driver.RowsColumnTypeNullable = &binaryRows{} - _ driver.RowsColumnTypeNullable = &textRows{} - _ driver.RowsColumnTypeScanType = &binaryRows{} - _ driver.RowsColumnTypeScanType = &textRows{} - _ driver.RowsNextResultSet = &binaryRows{} - _ driver.RowsNextResultSet = &textRows{} -) From 32406509e8b077afd637ae7544abc1a29a55a6d5 Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Fri, 29 Sep 2017 23:59:55 +0200 Subject: [PATCH 10/25] rows: cache parseTime in resultSet instead of mysqlConn --- connection.go | 4 +++- driver.go | 1 - packets.go | 4 ++-- rows.go | 1 + 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/connection.go b/connection.go index 58ae29988..fae9d1e1d 100644 --- a/connection.go +++ b/connection.go @@ -39,7 +39,6 @@ type mysqlConn struct { flags clientFlag status statusFlag sequence uint8 - parseTime bool // for context support (Go 1.8+) watching bool @@ -403,6 +402,9 @@ func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error) return nil, err } } + + rows.rs.parseTime = mc.cfg.ParseTime + // Columns rows.rs.columns, err = mc.readColumns(resLen) return rows, err diff --git a/driver.go b/driver.go index d42ce7a3d..0236113de 100644 --- a/driver.go +++ b/driver.go @@ -63,7 +63,6 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) { if err != nil { return nil, err } - mc.parseTime = mc.cfg.ParseTime // Connect to Server if dial, ok := dials[mc.cfg.Net]; ok { diff --git a/packets.go b/packets.go index 1bd2d7225..8a3ef4848 100644 --- a/packets.go +++ b/packets.go @@ -761,7 +761,7 @@ func (rows *textRows) readRow(dest []driver.Value) error { pos += n if err == nil { if !isNull { - if !mc.parseTime { + if !rows.rs.parseTime { continue } else { switch rows.rs.columns[i].fieldType { @@ -1265,7 +1265,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error { ) } dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, true) - case rows.mc.parseTime: + case rows.rs.parseTime: dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.Loc) default: var dstlen uint8 diff --git a/rows.go b/rows.go index ebc3126aa..0a4a694e2 100644 --- a/rows.go +++ b/rows.go @@ -18,6 +18,7 @@ type resultSet struct { columns []mysqlField columnNames []string done bool + parseTime bool // cached from cfg } type mysqlRows struct { From 163ddcde6bbdcec28b521f7100a74f5b10f7a6b5 Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Sat, 30 Sep 2017 00:26:24 +0200 Subject: [PATCH 11/25] fields: fix string and time types --- fields.go | 24 ++++++++++-------------- rows.go | 2 +- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/fields.go b/fields.go index 26467fb2c..ea02263af 100644 --- a/fields.go +++ b/fields.go @@ -40,8 +40,8 @@ var typeDatabaseName = map[fieldType]string{ fieldTypeMediumBLOB: "MEDIUMBLOB", fieldTypeLongBLOB: "LONGBLOB", fieldTypeBLOB: "BLOB", - fieldTypeVarString: "VARSTRING", // correct? - fieldTypeString: "STRING", // correct? + fieldTypeVarString: "VARCHAR", + fieldTypeString: "CHAR", fieldTypeGeometry: "GEOMETRY", } @@ -76,7 +76,7 @@ type mysqlField struct { decimals byte } -func (mf *mysqlField) scanType() reflect.Type { +func (mf *mysqlField) scanType(parseTime bool) reflect.Type { switch mf.fieldType { case fieldTypeNULL: return scanTypeNil @@ -134,21 +134,17 @@ func (mf *mysqlField) scanType() reflect.Type { fieldTypeMediumBLOB, fieldTypeLongBLOB, fieldTypeBLOB, fieldTypeVarString, fieldTypeString, fieldTypeGeometry, fieldTypeJSON, fieldTypeTime: - if mf.flags&flagNotNULL != 0 { - // alternatively we could return []byte or even RawBytes - return scanTypeString - } - return scanTypeNullString + return scanTypeRawBytes case fieldTypeDate, fieldTypeNewDate, fieldTypeTimestamp, fieldTypeDateTime: - - // TODO: respect rows.mc.parseTime - - if mf.flags&flagNotNULL != 0 { - return scanTypeTime + if parseTime { + if mf.flags&flagNotNULL != 0 { + return scanTypeTime + } + return scanTypeNullTime } - return scanTypeNullTime + return scanTypeRawBytes default: return scanTypeUnknown diff --git a/rows.go b/rows.go index 0a4a694e2..d4dcfecbd 100644 --- a/rows.go +++ b/rows.go @@ -71,7 +71,7 @@ func (rows *mysqlRows) ColumnTypeNullable(i int) (nullable, ok bool) { } func (rows *mysqlRows) ColumnTypeScanType(i int) reflect.Type { - return rows.rs.columns[i].scanType() + return rows.rs.columns[i].scanType(rows.rs.parseTime) } func (rows *mysqlRows) Close() (err error) { From 91e72b098dc7f2d3a9893c5691b1b00ee7ddad63 Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Wed, 4 Oct 2017 20:20:14 +0200 Subject: [PATCH 12/25] rows: implement ColumnTypeLength --- driver_go18_test.go | 2 ++ fields.go | 1 + packets.go | 5 ++++- rows.go | 4 ++++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/driver_go18_test.go b/driver_go18_test.go index dd4fe0003..906b2dd24 100644 --- a/driver_go18_test.go +++ b/driver_go18_test.go @@ -39,6 +39,8 @@ var ( var ( _ driver.RowsColumnTypeDatabaseTypeName = &binaryRows{} _ driver.RowsColumnTypeDatabaseTypeName = &textRows{} + _ driver.RowsColumnTypeLength = &binaryRows{} + _ driver.RowsColumnTypeLength = &textRows{} _ driver.RowsColumnTypeNullable = &binaryRows{} _ driver.RowsColumnTypeNullable = &textRows{} _ driver.RowsColumnTypeScanType = &binaryRows{} diff --git a/fields.go b/fields.go index ea02263af..20928abbb 100644 --- a/fields.go +++ b/fields.go @@ -71,6 +71,7 @@ var ( type mysqlField struct { tableName string name string + length uint32 flags fieldFlag fieldType fieldType decimals byte diff --git a/packets.go b/packets.go index 8a3ef4848..e07897ffe 100644 --- a/packets.go +++ b/packets.go @@ -699,8 +699,11 @@ func (mc *mysqlConn) readColumns(count int) ([]mysqlField, error) { // Filler [uint8] // Charset [charset, collation uint8] + pos += n + 1 + 2 + // Length [uint32] - pos += n + 1 + 2 + 4 + columns[i].length = binary.LittleEndian.Uint32(data[pos : pos+4]) + pos += 4 // Field type [uint8] columns[i].fieldType = fieldType(data[pos]) diff --git a/rows.go b/rows.go index d4dcfecbd..288c73d51 100644 --- a/rows.go +++ b/rows.go @@ -66,6 +66,10 @@ func (rows *mysqlRows) ColumnTypeDatabaseTypeName(i int) string { return "" } +func (rows *mysqlRows) ColumnTypeLength(i int) (length int64, ok bool) { + return int64(rows.rs.columns[i].length), true +} + func (rows *mysqlRows) ColumnTypeNullable(i int) (nullable, ok bool) { return rows.rs.columns[i].flags&flagNotNULL != 0, true } From 6a18c41a56b09ddbbd8420d6394637724ce8a457 Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Wed, 4 Oct 2017 20:21:00 +0200 Subject: [PATCH 13/25] rows: implement ColumnTypePrecisionScale --- driver_go18_test.go | 2 ++ rows.go | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/driver_go18_test.go b/driver_go18_test.go index 906b2dd24..ea2ab10df 100644 --- a/driver_go18_test.go +++ b/driver_go18_test.go @@ -43,6 +43,8 @@ var ( _ driver.RowsColumnTypeLength = &textRows{} _ driver.RowsColumnTypeNullable = &binaryRows{} _ driver.RowsColumnTypeNullable = &textRows{} + _ driver.RowsColumnTypePrecisionScale = &binaryRows{} + _ driver.RowsColumnTypePrecisionScale = &textRows{} _ driver.RowsColumnTypeScanType = &binaryRows{} _ driver.RowsColumnTypeScanType = &textRows{} _ driver.RowsNextResultSet = &binaryRows{} diff --git a/rows.go b/rows.go index 288c73d51..97c71fec0 100644 --- a/rows.go +++ b/rows.go @@ -72,6 +72,11 @@ func (rows *mysqlRows) ColumnTypeLength(i int) (length int64, ok bool) { func (rows *mysqlRows) ColumnTypeNullable(i int) (nullable, ok bool) { return rows.rs.columns[i].flags&flagNotNULL != 0, true +func (rows *mysqlRows) ColumnTypePrecisionScale(i int) (int64, int64, bool) { + if decimals := rows.rs.columns[i].decimals; decimals > 0 { + return int64(decimals), 0, true + } + return 0, 0, false } func (rows *mysqlRows) ColumnTypeScanType(i int) reflect.Type { From 0a5e4cb9de8e5b2a248c593439f03688dd818293 Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Wed, 4 Oct 2017 20:21:23 +0200 Subject: [PATCH 14/25] rows: fix ColumnTypeNullable --- rows.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rows.go b/rows.go index 97c71fec0..c130c43db 100644 --- a/rows.go +++ b/rows.go @@ -71,7 +71,9 @@ func (rows *mysqlRows) ColumnTypeLength(i int) (length int64, ok bool) { } func (rows *mysqlRows) ColumnTypeNullable(i int) (nullable, ok bool) { - return rows.rs.columns[i].flags&flagNotNULL != 0, true + return rows.rs.columns[i].flags&flagNotNULL == 0, true +} + func (rows *mysqlRows) ColumnTypePrecisionScale(i int) (int64, int64, bool) { if decimals := rows.rs.columns[i].decimals; decimals > 0 { return int64(decimals), 0, true From 2042d7323e2ec1d9d52e235fd23a8e1261123284 Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Wed, 4 Oct 2017 20:43:32 +0200 Subject: [PATCH 15/25] rows: ColumnTypes tests part1 --- driver_go18_test.go | 162 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/driver_go18_test.go b/driver_go18_test.go index ea2ab10df..118104712 100644 --- a/driver_go18_test.go +++ b/driver_go18_test.go @@ -574,3 +574,165 @@ func TestContextBeginReadOnly(t *testing.T) { } }) } + +func TestRowsColumnTypes(t *testing.T) { + niNULL := sql.NullInt64{0, false} + ni0 := sql.NullInt64{0, true} + ni1 := sql.NullInt64{1, true} + ni42 := sql.NullInt64{42, true} + nfNULL := sql.NullFloat64{0.0, false} + nf0 := sql.NullFloat64{0.0, true} + nf1337 := sql.NullFloat64{13.37, true} + rbNULL := sql.RawBytes(nil) + rb0 := sql.RawBytes("0") + rb42 := sql.RawBytes("42") + rbTest := sql.RawBytes("Test") + + var columns = []struct { + name string + fieldType string // type used when creating table schema + databaseTypeName string // actual type used by MySQL + scanType reflect.Type + nullable bool + length int64 // 0 if not ok, BYTE length, not CHAR length + precision int64 // 0 if not ok + valuesIn [3]string + valuesOut [3]interface{} + }{ + {"boolnull", "BOOL", "TINYINT", scanTypeNullInt, true, 1, 0, [3]string{"NULL", "true", "0"}, [3]interface{}{niNULL, ni1, ni0}}, + {"bool", "BOOL NOT NULL", "TINYINT", scanTypeInt8, false, 1, 0, [3]string{"1", "0", "FALSE"}, [3]interface{}{int8(1), int8(0), int8(0)}}, + {"intnull", "INTEGER", "INT", scanTypeNullInt, true, 11, 0, [3]string{"0", "NULL", "42"}, [3]interface{}{ni0, niNULL, ni42}}, + {"int3null", "INT(3)", "INT", scanTypeNullInt, true, 3, 0, [3]string{"0", "NULL", "42"}, [3]interface{}{ni0, niNULL, ni42}}, + {"int7", "INT(7) NOT NULL", "INT", scanTypeInt32, false, 7, 0, [3]string{"0", "-1337", "42"}, [3]interface{}{int32(0), int32(-1337), int32(42)}}, + {"uint13", "INT(13) UNSIGNED NOT NULL", "INT", scanTypeUint32, false, 13, 0, [3]string{"0", "1337", "42"}, [3]interface{}{uint32(0), uint32(1337), uint32(42)}}, + {"doublenull", "DOUBLE", "DOUBLE", scanTypeNullFloat, true, 22, 31, [3]string{"0", "NULL", "13.37"}, [3]interface{}{nf0, nfNULL, nf1337}}, + {"char25null", "CHAR(25)", "CHAR", scanTypeRawBytes, true, 75, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}}, + {"varchar42", "VARCHAR(42) NOT NULL", "VARCHAR", scanTypeRawBytes, false, 126, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}}, + {"textnull", "TEXT", "BLOB", scanTypeRawBytes, true, 196605, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}}, + {"longtext", "LONGTEXT NOT NULL", "BLOB", scanTypeRawBytes, false, 4294967295, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}}, + } + + schema := "" + values1 := "" + values2 := "" + values3 := "" + for _, column := range columns { + schema += fmt.Sprintf("`%s` %s, ", column.name, column.fieldType) + values1 += column.valuesIn[0] + ", " + values2 += column.valuesIn[1] + ", " + values3 += column.valuesIn[2] + ", " + } + schema = schema[:len(schema)-2] + values1 = values1[:len(values1)-2] + values2 = values2[:len(values2)-2] + values3 = values3[:len(values3)-2] + + runTests(t, dsn, func(dbt *DBTest) { + dbt.mustExec("CREATE TABLE test (" + schema + ")") + dbt.mustExec("INSERT INTO test VALUES (" + values1 + "), (" + values2 + "), (" + values3 + ")") + + rows, err := dbt.db.Query("SELECT * FROM test") + if err != nil { + t.Fatalf("Query: %v", err) + } + + tt, err := rows.ColumnTypes() + if err != nil { + t.Fatalf("ColumnTypes: %v", err) + } + + if len(tt) != len(columns) { + t.Fatalf("unexpected number of columns: expected %d, got %d", len(columns), len(tt)) + } + + types := make([]reflect.Type, len(tt)) + for i, tp := range tt { + column := columns[i] + + // Name + name := tp.Name() + if name != column.name { + t.Errorf("column name mismatch %s != %s", name, column.name) + continue + } + + // DatabaseTypeName + databaseTypeName := tp.DatabaseTypeName() + if databaseTypeName != column.databaseTypeName { + t.Errorf("databasetypename name mismatch for column %q: %s != %s", name, databaseTypeName, column.databaseTypeName) + continue + } + + // ScanType + scanType := tp.ScanType() + if scanType != column.scanType { + if scanType == nil { + t.Errorf("scantype is null for column %q", name) + } else { + t.Errorf("scantype mismatch for column %q: %s != %s", name, scanType.Name(), column.scanType.Name()) + } + continue + } + types[i] = scanType + + // Nullable + nullable, ok := tp.Nullable() + if !ok { + t.Errorf("nullable not ok %q", name) + continue + } + if nullable != column.nullable { + t.Errorf("nullable mismatch for column %q: %t != %t", name, nullable, column.nullable) + } + + // Length + length, ok := tp.Length() + if length != column.length { + if !ok { + t.Errorf("length not ok for column %q", name) + } else { + t.Errorf("length mismatch for column %q: %d != %d", name, length, column.length) + } + continue + } + + // Precision + precision, _, ok := tp.DecimalSize() + if precision != column.precision { + if !ok { + t.Errorf("precision not ok for column %q", name) + } else { + t.Errorf("precision mismatch for column %q: %d != %d", name, precision, column.precision) + } + continue + } + } + + values := make([]interface{}, len(tt)) + for i := range values { + values[i] = reflect.New(types[i]).Interface() + } + i := 0 + for rows.Next() { + err = rows.Scan(values...) + if err != nil { + t.Fatalf("failed to scan values in %v", err) + } + for j := range values { + value := reflect.ValueOf(values[j]).Elem().Interface() + if !reflect.DeepEqual(value, columns[j].valuesOut[i]) { + fmt.Println(value, columns[j].valuesOut[i]) + t.Errorf("row %d, column %d: %v != %v", i, j, value, columns[j].valuesOut[i]) + } + } + i++ + } + if i != 3 { + t.Errorf("expected 3 rows, got %d", i) + } + + if err := rows.Close(); err != nil { + t.Errorf("error closing rows: %s", err) + } + }) +} From 5dc4b613fa2e19e36f805cc78064b3a852447e98 Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Wed, 4 Oct 2017 20:58:26 +0200 Subject: [PATCH 16/25] rows: use keyed composite literals in ColumnTypes tests --- driver_go18_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/driver_go18_test.go b/driver_go18_test.go index 118104712..5cb487adf 100644 --- a/driver_go18_test.go +++ b/driver_go18_test.go @@ -576,13 +576,13 @@ func TestContextBeginReadOnly(t *testing.T) { } func TestRowsColumnTypes(t *testing.T) { - niNULL := sql.NullInt64{0, false} - ni0 := sql.NullInt64{0, true} - ni1 := sql.NullInt64{1, true} - ni42 := sql.NullInt64{42, true} - nfNULL := sql.NullFloat64{0.0, false} - nf0 := sql.NullFloat64{0.0, true} - nf1337 := sql.NullFloat64{13.37, true} + niNULL := sql.NullInt64{Int64: 0, Valid: false} + ni0 := sql.NullInt64{Int64: 0, Valid: true} + ni1 := sql.NullInt64{Int64: 1, Valid: true} + ni42 := sql.NullInt64{Int64: 42, Valid: true} + nfNULL := sql.NullFloat64{Float64: 0.0, Valid: false} + nf0 := sql.NullFloat64{Float64: 0.0, Valid: true} + nf1337 := sql.NullFloat64{Float64: 13.37, Valid: true} rbNULL := sql.RawBytes(nil) rb0 := sql.RawBytes("0") rb42 := sql.RawBytes("42") From bb35faa8780e0aade4ccc6713106a6d1a56917cd Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Wed, 4 Oct 2017 22:34:15 +0200 Subject: [PATCH 17/25] rows: ColumnTypes tests part2 --- driver_go18_test.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/driver_go18_test.go b/driver_go18_test.go index 5cb487adf..c9e3d55ca 100644 --- a/driver_go18_test.go +++ b/driver_go18_test.go @@ -602,9 +602,19 @@ func TestRowsColumnTypes(t *testing.T) { {"boolnull", "BOOL", "TINYINT", scanTypeNullInt, true, 1, 0, [3]string{"NULL", "true", "0"}, [3]interface{}{niNULL, ni1, ni0}}, {"bool", "BOOL NOT NULL", "TINYINT", scanTypeInt8, false, 1, 0, [3]string{"1", "0", "FALSE"}, [3]interface{}{int8(1), int8(0), int8(0)}}, {"intnull", "INTEGER", "INT", scanTypeNullInt, true, 11, 0, [3]string{"0", "NULL", "42"}, [3]interface{}{ni0, niNULL, ni42}}, + {"smallint", "SMALLINT NOT NULL", "SMALLINT", scanTypeInt16, false, 6, 0, [3]string{"0", "-32768", "32767"}, [3]interface{}{int16(0), int16(-32768), int16(32767)}}, + {"smallintnull", "SMALLINT", "SMALLINT", scanTypeNullInt, true, 6, 0, [3]string{"0", "NULL", "42"}, [3]interface{}{ni0, niNULL, ni42}}, {"int3null", "INT(3)", "INT", scanTypeNullInt, true, 3, 0, [3]string{"0", "NULL", "42"}, [3]interface{}{ni0, niNULL, ni42}}, {"int7", "INT(7) NOT NULL", "INT", scanTypeInt32, false, 7, 0, [3]string{"0", "-1337", "42"}, [3]interface{}{int32(0), int32(-1337), int32(42)}}, + {"bigint", "BIGINT NOT NULL", "BIGINT", scanTypeInt64, false, 20, 0, [3]string{"0", "65535", "-42"}, [3]interface{}{int64(0), int64(65535), int64(-42)}}, + {"bigintnull", "BIGINT", "BIGINT", scanTypeNullInt, true, 20, 0, [3]string{"NULL", "1", "42"}, [3]interface{}{niNULL, ni1, ni42}}, + {"tinyuint", "TINYINT UNSIGNED NOT NULL", "TINYINT", scanTypeUint8, false, 3, 0, [3]string{"0", "255", "42"}, [3]interface{}{uint8(0), uint8(255), uint8(42)}}, + {"smalluint", "SMALLINT UNSIGNED NOT NULL", "SMALLINT", scanTypeUint16, false, 5, 0, [3]string{"0", "65535", "42"}, [3]interface{}{uint16(0), uint16(65535), uint16(42)}}, + {"biguint", "BIGINT UNSIGNED NOT NULL", "BIGINT", scanTypeUint64, false, 20, 0, [3]string{"0", "65535", "42"}, [3]interface{}{uint64(0), uint64(65535), uint64(42)}}, {"uint13", "INT(13) UNSIGNED NOT NULL", "INT", scanTypeUint32, false, 13, 0, [3]string{"0", "1337", "42"}, [3]interface{}{uint32(0), uint32(1337), uint32(42)}}, + {"float", "FLOAT NOT NULL", "FLOAT", scanTypeFloat32, false, 12, 31, [3]string{"0", "42", "13.37"}, [3]interface{}{float32(0), float32(42), float32(13.37)}}, + {"floatnull", "FLOAT", "FLOAT", scanTypeNullFloat, true, 12, 31, [3]string{"0", "NULL", "13.37"}, [3]interface{}{nf0, nfNULL, nf1337}}, + {"double", "DOUBLE NOT NULL", "DOUBLE", scanTypeFloat64, false, 22, 31, [3]string{"0", "42", "13.37"}, [3]interface{}{float64(0), float64(42), float64(13.37)}}, {"doublenull", "DOUBLE", "DOUBLE", scanTypeNullFloat, true, 22, 31, [3]string{"0", "NULL", "13.37"}, [3]interface{}{nf0, nfNULL, nf1337}}, {"char25null", "CHAR(25)", "CHAR", scanTypeRawBytes, true, 75, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}}, {"varchar42", "VARCHAR(42) NOT NULL", "VARCHAR", scanTypeRawBytes, false, 126, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}}, @@ -721,7 +731,6 @@ func TestRowsColumnTypes(t *testing.T) { for j := range values { value := reflect.ValueOf(values[j]).Elem().Interface() if !reflect.DeepEqual(value, columns[j].valuesOut[i]) { - fmt.Println(value, columns[j].valuesOut[i]) t.Errorf("row %d, column %d: %v != %v", i, j, value, columns[j].valuesOut[i]) } } From b1a9d25d97d8da8f5b15b2929b961645e2860ade Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Wed, 4 Oct 2017 23:53:57 +0200 Subject: [PATCH 18/25] rows: always use NullTime as ScanType for datetime --- driver_go18_test.go | 189 +++++++++++++++++++++++--------------------- fields.go | 10 +-- rows.go | 2 +- 3 files changed, 104 insertions(+), 97 deletions(-) diff --git a/driver_go18_test.go b/driver_go18_test.go index c9e3d55ca..22601bd6b 100644 --- a/driver_go18_test.go +++ b/driver_go18_test.go @@ -583,6 +583,10 @@ func TestRowsColumnTypes(t *testing.T) { nfNULL := sql.NullFloat64{Float64: 0.0, Valid: false} nf0 := sql.NullFloat64{Float64: 0.0, Valid: true} nf1337 := sql.NullFloat64{Float64: 13.37, Valid: true} + nt0 := NullTime{Time: time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC), Valid: true} + nt1 := NullTime{Time: time.Date(2006, 01, 02, 15, 04, 05, 900000000, time.UTC), Valid: true} + nt2 := NullTime{Time: time.Date(2006, 01, 02, 15, 04, 05, 990000000, time.UTC), Valid: true} + nt6 := NullTime{Time: time.Date(2006, 01, 02, 15, 04, 05, 999999000, time.UTC), Valid: true} rbNULL := sql.RawBytes(nil) rb0 := sql.RawBytes("0") rb42 := sql.RawBytes("42") @@ -620,6 +624,9 @@ func TestRowsColumnTypes(t *testing.T) { {"varchar42", "VARCHAR(42) NOT NULL", "VARCHAR", scanTypeRawBytes, false, 126, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}}, {"textnull", "TEXT", "BLOB", scanTypeRawBytes, true, 196605, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}}, {"longtext", "LONGTEXT NOT NULL", "BLOB", scanTypeRawBytes, false, 4294967295, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}}, + {"datetime", "DATETIME", "DATETIME", scanTypeNullTime, true, 19, 0, [3]string{"'2006-01-02 15:04:05'", "'2006-01-02 15:04:05.9'", "'2006-01-02 15:04:05.999999'"}, [3]interface{}{nt0, nt0, nt0}}, + {"datetime2", "DATETIME(2)", "DATETIME", scanTypeNullTime, true, 22, 2, [3]string{"'2006-01-02 15:04:05'", "'2006-01-02 15:04:05.9'", "'2006-01-02 15:04:05.999999'"}, [3]interface{}{nt0, nt1, nt2}}, + {"datetime6", "DATETIME(6)", "DATETIME", scanTypeNullTime, true, 26, 6, [3]string{"'2006-01-02 15:04:05'", "'2006-01-02 15:04:05.9'", "'2006-01-02 15:04:05.999999'"}, [3]interface{}{nt0, nt1, nt6}}, } schema := "" @@ -637,111 +644,117 @@ func TestRowsColumnTypes(t *testing.T) { values2 = values2[:len(values2)-2] values3 = values3[:len(values3)-2] - runTests(t, dsn, func(dbt *DBTest) { - dbt.mustExec("CREATE TABLE test (" + schema + ")") - dbt.mustExec("INSERT INTO test VALUES (" + values1 + "), (" + values2 + "), (" + values3 + ")") - - rows, err := dbt.db.Query("SELECT * FROM test") - if err != nil { - t.Fatalf("Query: %v", err) - } - - tt, err := rows.ColumnTypes() - if err != nil { - t.Fatalf("ColumnTypes: %v", err) - } - - if len(tt) != len(columns) { - t.Fatalf("unexpected number of columns: expected %d, got %d", len(columns), len(tt)) - } + dsns := []string{ + dsn + "&parseTime=true", + dsn + "&parseTime=false", + } + for _, testdsn := range dsns { + runTests(t, testdsn, func(dbt *DBTest) { + dbt.mustExec("CREATE TABLE test (" + schema + ")") + dbt.mustExec("INSERT INTO test VALUES (" + values1 + "), (" + values2 + "), (" + values3 + ")") - types := make([]reflect.Type, len(tt)) - for i, tp := range tt { - column := columns[i] + rows, err := dbt.db.Query("SELECT * FROM test") + if err != nil { + t.Fatalf("Query: %v", err) + } - // Name - name := tp.Name() - if name != column.name { - t.Errorf("column name mismatch %s != %s", name, column.name) - continue + tt, err := rows.ColumnTypes() + if err != nil { + t.Fatalf("ColumnTypes: %v", err) } - // DatabaseTypeName - databaseTypeName := tp.DatabaseTypeName() - if databaseTypeName != column.databaseTypeName { - t.Errorf("databasetypename name mismatch for column %q: %s != %s", name, databaseTypeName, column.databaseTypeName) - continue + if len(tt) != len(columns) { + t.Fatalf("unexpected number of columns: expected %d, got %d", len(columns), len(tt)) } - // ScanType - scanType := tp.ScanType() - if scanType != column.scanType { - if scanType == nil { - t.Errorf("scantype is null for column %q", name) - } else { - t.Errorf("scantype mismatch for column %q: %s != %s", name, scanType.Name(), column.scanType.Name()) + types := make([]reflect.Type, len(tt)) + for i, tp := range tt { + column := columns[i] + + // Name + name := tp.Name() + if name != column.name { + t.Errorf("column name mismatch %s != %s", name, column.name) + continue } - continue - } - types[i] = scanType - // Nullable - nullable, ok := tp.Nullable() - if !ok { - t.Errorf("nullable not ok %q", name) - continue - } - if nullable != column.nullable { - t.Errorf("nullable mismatch for column %q: %t != %t", name, nullable, column.nullable) - } + // DatabaseTypeName + databaseTypeName := tp.DatabaseTypeName() + if databaseTypeName != column.databaseTypeName { + t.Errorf("databasetypename name mismatch for column %q: %s != %s", name, databaseTypeName, column.databaseTypeName) + continue + } - // Length - length, ok := tp.Length() - if length != column.length { - if !ok { - t.Errorf("length not ok for column %q", name) - } else { - t.Errorf("length mismatch for column %q: %d != %d", name, length, column.length) + // ScanType + scanType := tp.ScanType() + if scanType != column.scanType { + if scanType == nil { + t.Errorf("scantype is null for column %q", name) + } else { + t.Errorf("scantype mismatch for column %q: %s != %s", name, scanType.Name(), column.scanType.Name()) + } + continue } - continue - } + types[i] = scanType - // Precision - precision, _, ok := tp.DecimalSize() - if precision != column.precision { + // Nullable + nullable, ok := tp.Nullable() if !ok { - t.Errorf("precision not ok for column %q", name) - } else { - t.Errorf("precision mismatch for column %q: %d != %d", name, precision, column.precision) + t.Errorf("nullable not ok %q", name) + continue + } + if nullable != column.nullable { + t.Errorf("nullable mismatch for column %q: %t != %t", name, nullable, column.nullable) + } + + // Length + length, ok := tp.Length() + if length != column.length { + if !ok { + t.Errorf("length not ok for column %q", name) + } else { + t.Errorf("length mismatch for column %q: %d != %d", name, length, column.length) + } + continue + } + + // Precision + precision, _, ok := tp.DecimalSize() + if precision != column.precision { + if !ok { + t.Errorf("precision not ok for column %q", name) + } else { + t.Errorf("precision mismatch for column %q: %d != %d", name, precision, column.precision) + } + continue } - continue } - } - values := make([]interface{}, len(tt)) - for i := range values { - values[i] = reflect.New(types[i]).Interface() - } - i := 0 - for rows.Next() { - err = rows.Scan(values...) - if err != nil { - t.Fatalf("failed to scan values in %v", err) + values := make([]interface{}, len(tt)) + for i := range values { + values[i] = reflect.New(types[i]).Interface() } - for j := range values { - value := reflect.ValueOf(values[j]).Elem().Interface() - if !reflect.DeepEqual(value, columns[j].valuesOut[i]) { - t.Errorf("row %d, column %d: %v != %v", i, j, value, columns[j].valuesOut[i]) + i := 0 + for rows.Next() { + err = rows.Scan(values...) + if err != nil { + t.Fatalf("failed to scan values in %v", err) } + for j := range values { + value := reflect.ValueOf(values[j]).Elem().Interface() + if !reflect.DeepEqual(value, columns[j].valuesOut[i]) { + t.Errorf("row %d, column %d: %v != %v", i, j, value, columns[j].valuesOut[i]) + } + } + i++ + } + if i != 3 { + t.Errorf("expected 3 rows, got %d", i) } - i++ - } - if i != 3 { - t.Errorf("expected 3 rows, got %d", i) - } - if err := rows.Close(); err != nil { - t.Errorf("error closing rows: %s", err) - } - }) + if err := rows.Close(); err != nil { + t.Errorf("error closing rows: %s", err) + } + }) + } } diff --git a/fields.go b/fields.go index 20928abbb..24f272e71 100644 --- a/fields.go +++ b/fields.go @@ -77,7 +77,7 @@ type mysqlField struct { decimals byte } -func (mf *mysqlField) scanType(parseTime bool) reflect.Type { +func (mf *mysqlField) scanType() reflect.Type { switch mf.fieldType { case fieldTypeNULL: return scanTypeNil @@ -139,13 +139,7 @@ func (mf *mysqlField) scanType(parseTime bool) reflect.Type { case fieldTypeDate, fieldTypeNewDate, fieldTypeTimestamp, fieldTypeDateTime: - if parseTime { - if mf.flags&flagNotNULL != 0 { - return scanTypeTime - } - return scanTypeNullTime - } - return scanTypeRawBytes + return scanTypeNullTime default: return scanTypeUnknown diff --git a/rows.go b/rows.go index c130c43db..b8f7d6767 100644 --- a/rows.go +++ b/rows.go @@ -82,7 +82,7 @@ func (rows *mysqlRows) ColumnTypePrecisionScale(i int) (int64, int64, bool) { } func (rows *mysqlRows) ColumnTypeScanType(i int) reflect.Type { - return rows.rs.columns[i].scanType(rows.rs.parseTime) + return rows.rs.columns[i].scanType() } func (rows *mysqlRows) Close() (err error) { From d03077ccaddcfd3862a9977f69719a4585c71869 Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Thu, 5 Oct 2017 00:00:12 +0200 Subject: [PATCH 19/25] rows: avoid errors through rounding of time values --- driver_go18_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/driver_go18_test.go b/driver_go18_test.go index 22601bd6b..f479c56e5 100644 --- a/driver_go18_test.go +++ b/driver_go18_test.go @@ -584,9 +584,9 @@ func TestRowsColumnTypes(t *testing.T) { nf0 := sql.NullFloat64{Float64: 0.0, Valid: true} nf1337 := sql.NullFloat64{Float64: 13.37, Valid: true} nt0 := NullTime{Time: time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC), Valid: true} - nt1 := NullTime{Time: time.Date(2006, 01, 02, 15, 04, 05, 900000000, time.UTC), Valid: true} - nt2 := NullTime{Time: time.Date(2006, 01, 02, 15, 04, 05, 990000000, time.UTC), Valid: true} - nt6 := NullTime{Time: time.Date(2006, 01, 02, 15, 04, 05, 999999000, time.UTC), Valid: true} + nt1 := NullTime{Time: time.Date(2006, 01, 02, 15, 04, 05, 100000000, time.UTC), Valid: true} + nt2 := NullTime{Time: time.Date(2006, 01, 02, 15, 04, 05, 110000000, time.UTC), Valid: true} + nt6 := NullTime{Time: time.Date(2006, 01, 02, 15, 04, 05, 111111000, time.UTC), Valid: true} rbNULL := sql.RawBytes(nil) rb0 := sql.RawBytes("0") rb42 := sql.RawBytes("42") @@ -624,9 +624,9 @@ func TestRowsColumnTypes(t *testing.T) { {"varchar42", "VARCHAR(42) NOT NULL", "VARCHAR", scanTypeRawBytes, false, 126, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}}, {"textnull", "TEXT", "BLOB", scanTypeRawBytes, true, 196605, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}}, {"longtext", "LONGTEXT NOT NULL", "BLOB", scanTypeRawBytes, false, 4294967295, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}}, - {"datetime", "DATETIME", "DATETIME", scanTypeNullTime, true, 19, 0, [3]string{"'2006-01-02 15:04:05'", "'2006-01-02 15:04:05.9'", "'2006-01-02 15:04:05.999999'"}, [3]interface{}{nt0, nt0, nt0}}, - {"datetime2", "DATETIME(2)", "DATETIME", scanTypeNullTime, true, 22, 2, [3]string{"'2006-01-02 15:04:05'", "'2006-01-02 15:04:05.9'", "'2006-01-02 15:04:05.999999'"}, [3]interface{}{nt0, nt1, nt2}}, - {"datetime6", "DATETIME(6)", "DATETIME", scanTypeNullTime, true, 26, 6, [3]string{"'2006-01-02 15:04:05'", "'2006-01-02 15:04:05.9'", "'2006-01-02 15:04:05.999999'"}, [3]interface{}{nt0, nt1, nt6}}, + {"datetime", "DATETIME", "DATETIME", scanTypeNullTime, true, 19, 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}}, + {"datetime2", "DATETIME(2)", "DATETIME", scanTypeNullTime, true, 22, 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}}, + {"datetime6", "DATETIME(6)", "DATETIME", scanTypeNullTime, true, 26, 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}}, } schema := "" From 65f1dfba1ed63f88e37411fdc7d030ba549be60b Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Thu, 5 Oct 2017 12:05:31 +0200 Subject: [PATCH 20/25] rows: remove parseTime cache --- connection.go | 3 +-- driver.go | 1 + packets.go | 4 ++-- rows.go | 1 - 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/connection.go b/connection.go index fae9d1e1d..e57061412 100644 --- a/connection.go +++ b/connection.go @@ -39,6 +39,7 @@ type mysqlConn struct { flags clientFlag status statusFlag sequence uint8 + parseTime bool // for context support (Go 1.8+) watching bool @@ -403,8 +404,6 @@ func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error) } } - rows.rs.parseTime = mc.cfg.ParseTime - // Columns rows.rs.columns, err = mc.readColumns(resLen) return rows, err diff --git a/driver.go b/driver.go index 0236113de..d42ce7a3d 100644 --- a/driver.go +++ b/driver.go @@ -63,6 +63,7 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) { if err != nil { return nil, err } + mc.parseTime = mc.cfg.ParseTime // Connect to Server if dial, ok := dials[mc.cfg.Net]; ok { diff --git a/packets.go b/packets.go index e07897ffe..d833ae6b6 100644 --- a/packets.go +++ b/packets.go @@ -764,7 +764,7 @@ func (rows *textRows) readRow(dest []driver.Value) error { pos += n if err == nil { if !isNull { - if !rows.rs.parseTime { + if !mc.parseTime { continue } else { switch rows.rs.columns[i].fieldType { @@ -1268,7 +1268,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error { ) } dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, true) - case rows.rs.parseTime: + case rows.mc.parseTime: dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.Loc) default: var dstlen uint8 diff --git a/rows.go b/rows.go index b8f7d6767..eef37ae94 100644 --- a/rows.go +++ b/rows.go @@ -18,7 +18,6 @@ type resultSet struct { columns []mysqlField columnNames []string done bool - parseTime bool // cached from cfg } type mysqlRows struct { From 4023d9ad21710f312f4a67c6465ee97fd9ef6dbf Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Thu, 5 Oct 2017 19:38:11 +0200 Subject: [PATCH 21/25] fields: remove unused scanTypes --- fields.go | 41 +++++++++++++++++------------------------ 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/fields.go b/fields.go index 24f272e71..222f5af14 100644 --- a/fields.go +++ b/fields.go @@ -11,7 +11,6 @@ package mysql import ( "database/sql" "reflect" - "time" ) var typeDatabaseName = map[fieldType]string{ @@ -46,26 +45,21 @@ var typeDatabaseName = map[fieldType]string{ } var ( - scanTypeNil = reflect.TypeOf(nil) - scanTypeNullInt = reflect.TypeOf(sql.NullInt64{}) - scanTypeUint8 = reflect.TypeOf(uint8(0)) - scanTypeInt8 = reflect.TypeOf(int8(0)) - scanTypeUint16 = reflect.TypeOf(uint16(0)) - scanTypeInt16 = reflect.TypeOf(int16(0)) - scanTypeUint32 = reflect.TypeOf(uint32(0)) - scanTypeInt32 = reflect.TypeOf(int32(0)) - scanTypeUint64 = reflect.TypeOf(uint64(0)) - scanTypeInt64 = reflect.TypeOf(int64(0)) - scanTypeNullFloat = reflect.TypeOf(sql.NullFloat64{}) - scanTypeFloat32 = reflect.TypeOf(float32(0)) - scanTypeFloat64 = reflect.TypeOf(float64(0)) - scanTypeNullString = reflect.TypeOf(sql.NullString{}) - scanTypeString = reflect.TypeOf("") - scanTypeBytes = reflect.TypeOf([]byte{}) - scanTypeRawBytes = reflect.TypeOf(sql.RawBytes{}) - scanTypeTime = reflect.TypeOf(time.Time{}) - scanTypeNullTime = reflect.TypeOf(NullTime{}) - scanTypeUnknown = reflect.TypeOf(new(interface{})) + scanTypeNullInt = reflect.TypeOf(sql.NullInt64{}) + scanTypeUint8 = reflect.TypeOf(uint8(0)) + scanTypeInt8 = reflect.TypeOf(int8(0)) + scanTypeUint16 = reflect.TypeOf(uint16(0)) + scanTypeInt16 = reflect.TypeOf(int16(0)) + scanTypeUint32 = reflect.TypeOf(uint32(0)) + scanTypeInt32 = reflect.TypeOf(int32(0)) + scanTypeUint64 = reflect.TypeOf(uint64(0)) + scanTypeInt64 = reflect.TypeOf(int64(0)) + scanTypeNullFloat = reflect.TypeOf(sql.NullFloat64{}) + scanTypeFloat32 = reflect.TypeOf(float32(0)) + scanTypeFloat64 = reflect.TypeOf(float64(0)) + scanTypeRawBytes = reflect.TypeOf(sql.RawBytes{}) + scanTypeNullTime = reflect.TypeOf(NullTime{}) + scanTypeUnknown = reflect.TypeOf(new(interface{})) ) type mysqlField struct { @@ -79,9 +73,6 @@ type mysqlField struct { func (mf *mysqlField) scanType() reflect.Type { switch mf.fieldType { - case fieldTypeNULL: - return scanTypeNil - case fieldTypeTiny: if mf.flags&flagNotNULL != 0 { if mf.flags&flagUnsigned != 0 { @@ -139,6 +130,8 @@ func (mf *mysqlField) scanType() reflect.Type { case fieldTypeDate, fieldTypeNewDate, fieldTypeTimestamp, fieldTypeDateTime: + // NullTime is always returned for more consistent behavior as it can + // handle both cases of parseTime regardless if the field is nullable. return scanTypeNullTime default: From e8324ffa6b9b5cbe96c7f47bb164f6d00b47012d Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Fri, 6 Oct 2017 21:11:37 +0200 Subject: [PATCH 22/25] rows: fix ColumnTypePrecisionScale implementation --- driver_go18_test.go | 75 +++++++++++++++++++++++++++++---------------- rows.go | 20 ++++++++++-- 2 files changed, 66 insertions(+), 29 deletions(-) diff --git a/driver_go18_test.go b/driver_go18_test.go index f479c56e5..f117e2291 100644 --- a/driver_go18_test.go +++ b/driver_go18_test.go @@ -15,6 +15,7 @@ import ( "database/sql" "database/sql/driver" "fmt" + "math" "reflect" "testing" "time" @@ -600,33 +601,41 @@ func TestRowsColumnTypes(t *testing.T) { nullable bool length int64 // 0 if not ok, BYTE length, not CHAR length precision int64 // 0 if not ok + scale int64 valuesIn [3]string valuesOut [3]interface{} }{ - {"boolnull", "BOOL", "TINYINT", scanTypeNullInt, true, 1, 0, [3]string{"NULL", "true", "0"}, [3]interface{}{niNULL, ni1, ni0}}, - {"bool", "BOOL NOT NULL", "TINYINT", scanTypeInt8, false, 1, 0, [3]string{"1", "0", "FALSE"}, [3]interface{}{int8(1), int8(0), int8(0)}}, - {"intnull", "INTEGER", "INT", scanTypeNullInt, true, 11, 0, [3]string{"0", "NULL", "42"}, [3]interface{}{ni0, niNULL, ni42}}, - {"smallint", "SMALLINT NOT NULL", "SMALLINT", scanTypeInt16, false, 6, 0, [3]string{"0", "-32768", "32767"}, [3]interface{}{int16(0), int16(-32768), int16(32767)}}, - {"smallintnull", "SMALLINT", "SMALLINT", scanTypeNullInt, true, 6, 0, [3]string{"0", "NULL", "42"}, [3]interface{}{ni0, niNULL, ni42}}, - {"int3null", "INT(3)", "INT", scanTypeNullInt, true, 3, 0, [3]string{"0", "NULL", "42"}, [3]interface{}{ni0, niNULL, ni42}}, - {"int7", "INT(7) NOT NULL", "INT", scanTypeInt32, false, 7, 0, [3]string{"0", "-1337", "42"}, [3]interface{}{int32(0), int32(-1337), int32(42)}}, - {"bigint", "BIGINT NOT NULL", "BIGINT", scanTypeInt64, false, 20, 0, [3]string{"0", "65535", "-42"}, [3]interface{}{int64(0), int64(65535), int64(-42)}}, - {"bigintnull", "BIGINT", "BIGINT", scanTypeNullInt, true, 20, 0, [3]string{"NULL", "1", "42"}, [3]interface{}{niNULL, ni1, ni42}}, - {"tinyuint", "TINYINT UNSIGNED NOT NULL", "TINYINT", scanTypeUint8, false, 3, 0, [3]string{"0", "255", "42"}, [3]interface{}{uint8(0), uint8(255), uint8(42)}}, - {"smalluint", "SMALLINT UNSIGNED NOT NULL", "SMALLINT", scanTypeUint16, false, 5, 0, [3]string{"0", "65535", "42"}, [3]interface{}{uint16(0), uint16(65535), uint16(42)}}, - {"biguint", "BIGINT UNSIGNED NOT NULL", "BIGINT", scanTypeUint64, false, 20, 0, [3]string{"0", "65535", "42"}, [3]interface{}{uint64(0), uint64(65535), uint64(42)}}, - {"uint13", "INT(13) UNSIGNED NOT NULL", "INT", scanTypeUint32, false, 13, 0, [3]string{"0", "1337", "42"}, [3]interface{}{uint32(0), uint32(1337), uint32(42)}}, - {"float", "FLOAT NOT NULL", "FLOAT", scanTypeFloat32, false, 12, 31, [3]string{"0", "42", "13.37"}, [3]interface{}{float32(0), float32(42), float32(13.37)}}, - {"floatnull", "FLOAT", "FLOAT", scanTypeNullFloat, true, 12, 31, [3]string{"0", "NULL", "13.37"}, [3]interface{}{nf0, nfNULL, nf1337}}, - {"double", "DOUBLE NOT NULL", "DOUBLE", scanTypeFloat64, false, 22, 31, [3]string{"0", "42", "13.37"}, [3]interface{}{float64(0), float64(42), float64(13.37)}}, - {"doublenull", "DOUBLE", "DOUBLE", scanTypeNullFloat, true, 22, 31, [3]string{"0", "NULL", "13.37"}, [3]interface{}{nf0, nfNULL, nf1337}}, - {"char25null", "CHAR(25)", "CHAR", scanTypeRawBytes, true, 75, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}}, - {"varchar42", "VARCHAR(42) NOT NULL", "VARCHAR", scanTypeRawBytes, false, 126, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}}, - {"textnull", "TEXT", "BLOB", scanTypeRawBytes, true, 196605, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}}, - {"longtext", "LONGTEXT NOT NULL", "BLOB", scanTypeRawBytes, false, 4294967295, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}}, - {"datetime", "DATETIME", "DATETIME", scanTypeNullTime, true, 19, 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}}, - {"datetime2", "DATETIME(2)", "DATETIME", scanTypeNullTime, true, 22, 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}}, - {"datetime6", "DATETIME(6)", "DATETIME", scanTypeNullTime, true, 26, 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}}, + {"boolnull", "BOOL", "TINYINT", scanTypeNullInt, true, 1, 0, 0, [3]string{"NULL", "true", "0"}, [3]interface{}{niNULL, ni1, ni0}}, + {"bool", "BOOL NOT NULL", "TINYINT", scanTypeInt8, false, 1, 0, 0, [3]string{"1", "0", "FALSE"}, [3]interface{}{int8(1), int8(0), int8(0)}}, + {"intnull", "INTEGER", "INT", scanTypeNullInt, true, 11, 0, 0, [3]string{"0", "NULL", "42"}, [3]interface{}{ni0, niNULL, ni42}}, + {"smallint", "SMALLINT NOT NULL", "SMALLINT", scanTypeInt16, false, 6, 0, 0, [3]string{"0", "-32768", "32767"}, [3]interface{}{int16(0), int16(-32768), int16(32767)}}, + {"smallintnull", "SMALLINT", "SMALLINT", scanTypeNullInt, true, 6, 0, 0, [3]string{"0", "NULL", "42"}, [3]interface{}{ni0, niNULL, ni42}}, + {"int3null", "INT(3)", "INT", scanTypeNullInt, true, 3, 0, 0, [3]string{"0", "NULL", "42"}, [3]interface{}{ni0, niNULL, ni42}}, + {"int7", "INT(7) NOT NULL", "INT", scanTypeInt32, false, 7, 0, 0, [3]string{"0", "-1337", "42"}, [3]interface{}{int32(0), int32(-1337), int32(42)}}, + {"bigint", "BIGINT NOT NULL", "BIGINT", scanTypeInt64, false, 20, 0, 0, [3]string{"0", "65535", "-42"}, [3]interface{}{int64(0), int64(65535), int64(-42)}}, + {"bigintnull", "BIGINT", "BIGINT", scanTypeNullInt, true, 20, 0, 0, [3]string{"NULL", "1", "42"}, [3]interface{}{niNULL, ni1, ni42}}, + {"tinyuint", "TINYINT UNSIGNED NOT NULL", "TINYINT", scanTypeUint8, false, 3, 0, 0, [3]string{"0", "255", "42"}, [3]interface{}{uint8(0), uint8(255), uint8(42)}}, + {"smalluint", "SMALLINT UNSIGNED NOT NULL", "SMALLINT", scanTypeUint16, false, 5, 0, 0, [3]string{"0", "65535", "42"}, [3]interface{}{uint16(0), uint16(65535), uint16(42)}}, + {"biguint", "BIGINT UNSIGNED NOT NULL", "BIGINT", scanTypeUint64, false, 20, 0, 0, [3]string{"0", "65535", "42"}, [3]interface{}{uint64(0), uint64(65535), uint64(42)}}, + {"uint13", "INT(13) UNSIGNED NOT NULL", "INT", scanTypeUint32, false, 13, 0, 0, [3]string{"0", "1337", "42"}, [3]interface{}{uint32(0), uint32(1337), uint32(42)}}, + {"float", "FLOAT NOT NULL", "FLOAT", scanTypeFloat32, false, 12, math.MaxInt64, math.MaxInt64, [3]string{"0", "42", "13.37"}, [3]interface{}{float32(0), float32(42), float32(13.37)}}, + {"floatnull", "FLOAT", "FLOAT", scanTypeNullFloat, true, 12, math.MaxInt64, math.MaxInt64, [3]string{"0", "NULL", "13.37"}, [3]interface{}{nf0, nfNULL, nf1337}}, + {"float74null", "FLOAT(7,4)", "FLOAT", scanTypeNullFloat, true, 7, math.MaxInt64, 4, [3]string{"0", "NULL", "13.37"}, [3]interface{}{nf0, nfNULL, nf1337}}, + {"double", "DOUBLE NOT NULL", "DOUBLE", scanTypeFloat64, false, 22, math.MaxInt64, math.MaxInt64, [3]string{"0", "42", "13.37"}, [3]interface{}{float64(0), float64(42), float64(13.37)}}, + {"doublenull", "DOUBLE", "DOUBLE", scanTypeNullFloat, true, 22, math.MaxInt64, math.MaxInt64, [3]string{"0", "NULL", "13.37"}, [3]interface{}{nf0, nfNULL, nf1337}}, + {"decimal1", "DECIMAL(10,6) NOT NULL", "DECIMAL", scanTypeRawBytes, false, 12, 10, 6, [3]string{"0", "13.37", "1234.123456"}, [3]interface{}{sql.RawBytes("0.000000"), sql.RawBytes("13.370000"), sql.RawBytes("1234.123456")}}, + {"decimal1null", "DECIMAL(10,6)", "DECIMAL", scanTypeRawBytes, true, 12, 10, 6, [3]string{"0", "NULL", "1234.123456"}, [3]interface{}{sql.RawBytes("0.000000"), rbNULL, sql.RawBytes("1234.123456")}}, + {"decimal2", "DECIMAL(8,4) NOT NULL", "DECIMAL", scanTypeRawBytes, false, 10, 8, 4, [3]string{"0", "13.37", "1234.123456"}, [3]interface{}{sql.RawBytes("0.0000"), sql.RawBytes("13.3700"), sql.RawBytes("1234.1235")}}, + {"decimal2null", "DECIMAL(8,4)", "DECIMAL", scanTypeRawBytes, true, 10, 8, 4, [3]string{"0", "NULL", "1234.123456"}, [3]interface{}{sql.RawBytes("0.0000"), rbNULL, sql.RawBytes("1234.1235")}}, + {"decimal3", "DECIMAL(5,0) NOT NULL", "DECIMAL", scanTypeRawBytes, false, 6, 5, 0, [3]string{"0", "13.37", "-12345.123456"}, [3]interface{}{rb0, sql.RawBytes("13"), sql.RawBytes("-12345")}}, + {"decimal3null", "DECIMAL(5,0)", "DECIMAL", scanTypeRawBytes, true, 6, 5, 0, [3]string{"0", "NULL", "-12345.123456"}, [3]interface{}{rb0, rbNULL, sql.RawBytes("-12345")}}, + {"char25null", "CHAR(25)", "CHAR", scanTypeRawBytes, true, 75, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}}, + {"varchar42", "VARCHAR(42) NOT NULL", "VARCHAR", scanTypeRawBytes, false, 126, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}}, + {"textnull", "TEXT", "BLOB", scanTypeRawBytes, true, 196605, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}}, + {"longtext", "LONGTEXT NOT NULL", "BLOB", scanTypeRawBytes, false, 4294967295, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}}, + {"datetime", "DATETIME", "DATETIME", scanTypeNullTime, true, 19, 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}}, + {"datetime2", "DATETIME(2)", "DATETIME", scanTypeNullTime, true, 22, 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}}, + {"datetime6", "DATETIME(6)", "DATETIME", scanTypeNullTime, true, 26, 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}}, } schema := "" @@ -718,8 +727,8 @@ func TestRowsColumnTypes(t *testing.T) { continue } - // Precision - precision, _, ok := tp.DecimalSize() + // Precision and Scale + precision, scale, ok := tp.DecimalSize() if precision != column.precision { if !ok { t.Errorf("precision not ok for column %q", name) @@ -728,6 +737,14 @@ func TestRowsColumnTypes(t *testing.T) { } continue } + if scale != column.scale { + if !ok { + t.Errorf("scale not ok for column %q", name) + } else { + t.Errorf("scale mismatch for column %q: %d != %d", name, scale, column.scale) + } + continue + } } values := make([]interface{}, len(tt)) @@ -743,7 +760,11 @@ func TestRowsColumnTypes(t *testing.T) { for j := range values { value := reflect.ValueOf(values[j]).Elem().Interface() if !reflect.DeepEqual(value, columns[j].valuesOut[i]) { - t.Errorf("row %d, column %d: %v != %v", i, j, value, columns[j].valuesOut[i]) + if columns[j].scanType == scanTypeRawBytes { + t.Errorf("row %d, column %d: %v != %v", i, j, string(value.(sql.RawBytes)), string(columns[j].valuesOut[i].(sql.RawBytes))) + } else { + t.Errorf("row %d, column %d: %v != %v", i, j, value, columns[j].valuesOut[i]) + } } } i++ diff --git a/rows.go b/rows.go index eef37ae94..a63fd039f 100644 --- a/rows.go +++ b/rows.go @@ -11,6 +11,7 @@ package mysql import ( "database/sql/driver" "io" + "math" "reflect" ) @@ -74,9 +75,24 @@ func (rows *mysqlRows) ColumnTypeNullable(i int) (nullable, ok bool) { } func (rows *mysqlRows) ColumnTypePrecisionScale(i int) (int64, int64, bool) { - if decimals := rows.rs.columns[i].decimals; decimals > 0 { - return int64(decimals), 0, true + column := rows.rs.columns[i] + decimals := int64(column.decimals) + + switch column.fieldType { + case fieldTypeDecimal, fieldTypeNewDecimal: + if decimals > 0 { + return int64(column.length) - 2, decimals, true + } + return int64(column.length) - 1, decimals, true + case fieldTypeTimestamp, fieldTypeDateTime, fieldTypeTime: + return decimals, decimals, true + case fieldTypeFloat, fieldTypeDouble: + if decimals == 0x1f { + return math.MaxInt64, math.MaxInt64, true + } + return math.MaxInt64, decimals, true } + return 0, 0, false } From 4d657f6465ff3d7e8e6a51969f358eff2261b0f8 Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Fri, 6 Oct 2017 21:30:20 +0200 Subject: [PATCH 23/25] fields: sort types alphabetical --- fields.go | 56 +++++++++++++++++++++++++++---------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/fields.go b/fields.go index 222f5af14..cded986d2 100644 --- a/fields.go +++ b/fields.go @@ -14,51 +14,51 @@ import ( ) var typeDatabaseName = map[fieldType]string{ + fieldTypeBit: "BIT", + fieldTypeBLOB: "BLOB", + fieldTypeDate: "DATE", + fieldTypeDateTime: "DATETIME", fieldTypeDecimal: "DECIMAL", - fieldTypeTiny: "TINYINT", - fieldTypeShort: "SMALLINT", - fieldTypeLong: "INT", - fieldTypeFloat: "FLOAT", fieldTypeDouble: "DOUBLE", - fieldTypeNULL: "NULL", - fieldTypeTimestamp: "TIMESTAMP", - fieldTypeLongLong: "BIGINT", + fieldTypeEnum: "ENUM", + fieldTypeFloat: "FLOAT", + fieldTypeGeometry: "GEOMETRY", fieldTypeInt24: "MEDIUMINT", - fieldTypeDate: "DATE", - fieldTypeTime: "TIME", - fieldTypeDateTime: "DATETIME", - fieldTypeYear: "YEAR", - fieldTypeNewDate: "DATE", - fieldTypeVarChar: "VARCHAR", - fieldTypeBit: "BIT", fieldTypeJSON: "JSON", + fieldTypeLong: "INT", + fieldTypeLongBLOB: "LONGBLOB", + fieldTypeLongLong: "BIGINT", + fieldTypeMediumBLOB: "MEDIUMBLOB", + fieldTypeNewDate: "DATE", fieldTypeNewDecimal: "DECIMAL", - fieldTypeEnum: "ENUM", + fieldTypeNULL: "NULL", fieldTypeSet: "SET", + fieldTypeShort: "SMALLINT", + fieldTypeString: "CHAR", + fieldTypeTime: "TIME", + fieldTypeTimestamp: "TIMESTAMP", + fieldTypeTiny: "TINYINT", fieldTypeTinyBLOB: "TINYBLOB", - fieldTypeMediumBLOB: "MEDIUMBLOB", - fieldTypeLongBLOB: "LONGBLOB", - fieldTypeBLOB: "BLOB", + fieldTypeVarChar: "VARCHAR", fieldTypeVarString: "VARCHAR", - fieldTypeString: "CHAR", - fieldTypeGeometry: "GEOMETRY", + fieldTypeYear: "YEAR", } var ( - scanTypeNullInt = reflect.TypeOf(sql.NullInt64{}) - scanTypeUint8 = reflect.TypeOf(uint8(0)) + scanTypeFloat32 = reflect.TypeOf(float32(0)) + scanTypeFloat64 = reflect.TypeOf(float64(0)) scanTypeInt8 = reflect.TypeOf(int8(0)) - scanTypeUint16 = reflect.TypeOf(uint16(0)) scanTypeInt16 = reflect.TypeOf(int16(0)) - scanTypeUint32 = reflect.TypeOf(uint32(0)) scanTypeInt32 = reflect.TypeOf(int32(0)) - scanTypeUint64 = reflect.TypeOf(uint64(0)) scanTypeInt64 = reflect.TypeOf(int64(0)) scanTypeNullFloat = reflect.TypeOf(sql.NullFloat64{}) - scanTypeFloat32 = reflect.TypeOf(float32(0)) - scanTypeFloat64 = reflect.TypeOf(float64(0)) - scanTypeRawBytes = reflect.TypeOf(sql.RawBytes{}) + scanTypeNullInt = reflect.TypeOf(sql.NullInt64{}) scanTypeNullTime = reflect.TypeOf(NullTime{}) + scanTypeUint8 = reflect.TypeOf(uint8(0)) + scanTypeUint16 = reflect.TypeOf(uint16(0)) + scanTypeUint32 = reflect.TypeOf(uint32(0)) + scanTypeUint64 = reflect.TypeOf(uint64(0)) + scanTypeRawBytes = reflect.TypeOf(sql.RawBytes{}) scanTypeUnknown = reflect.TypeOf(new(interface{})) ) From c60820c3c9aeb3a001571daa4e92894e760ae33e Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Sat, 7 Oct 2017 17:33:52 +0200 Subject: [PATCH 24/25] rows: remove ColumnTypeLength implementation for now --- driver_go18_test.go | 85 ++++++++++++++++++++++----------------------- rows.go | 6 ++-- 2 files changed, 45 insertions(+), 46 deletions(-) diff --git a/driver_go18_test.go b/driver_go18_test.go index f117e2291..953adeb8a 100644 --- a/driver_go18_test.go +++ b/driver_go18_test.go @@ -38,10 +38,10 @@ var ( // Ensure that all the driver interfaces are implemented var ( + // _ driver.RowsColumnTypeLength = &binaryRows{} + // _ driver.RowsColumnTypeLength = &textRows{} _ driver.RowsColumnTypeDatabaseTypeName = &binaryRows{} _ driver.RowsColumnTypeDatabaseTypeName = &textRows{} - _ driver.RowsColumnTypeLength = &binaryRows{} - _ driver.RowsColumnTypeLength = &textRows{} _ driver.RowsColumnTypeNullable = &binaryRows{} _ driver.RowsColumnTypeNullable = &textRows{} _ driver.RowsColumnTypePrecisionScale = &binaryRows{} @@ -599,43 +599,42 @@ func TestRowsColumnTypes(t *testing.T) { databaseTypeName string // actual type used by MySQL scanType reflect.Type nullable bool - length int64 // 0 if not ok, BYTE length, not CHAR length precision int64 // 0 if not ok scale int64 valuesIn [3]string valuesOut [3]interface{} }{ - {"boolnull", "BOOL", "TINYINT", scanTypeNullInt, true, 1, 0, 0, [3]string{"NULL", "true", "0"}, [3]interface{}{niNULL, ni1, ni0}}, - {"bool", "BOOL NOT NULL", "TINYINT", scanTypeInt8, false, 1, 0, 0, [3]string{"1", "0", "FALSE"}, [3]interface{}{int8(1), int8(0), int8(0)}}, - {"intnull", "INTEGER", "INT", scanTypeNullInt, true, 11, 0, 0, [3]string{"0", "NULL", "42"}, [3]interface{}{ni0, niNULL, ni42}}, - {"smallint", "SMALLINT NOT NULL", "SMALLINT", scanTypeInt16, false, 6, 0, 0, [3]string{"0", "-32768", "32767"}, [3]interface{}{int16(0), int16(-32768), int16(32767)}}, - {"smallintnull", "SMALLINT", "SMALLINT", scanTypeNullInt, true, 6, 0, 0, [3]string{"0", "NULL", "42"}, [3]interface{}{ni0, niNULL, ni42}}, - {"int3null", "INT(3)", "INT", scanTypeNullInt, true, 3, 0, 0, [3]string{"0", "NULL", "42"}, [3]interface{}{ni0, niNULL, ni42}}, - {"int7", "INT(7) NOT NULL", "INT", scanTypeInt32, false, 7, 0, 0, [3]string{"0", "-1337", "42"}, [3]interface{}{int32(0), int32(-1337), int32(42)}}, - {"bigint", "BIGINT NOT NULL", "BIGINT", scanTypeInt64, false, 20, 0, 0, [3]string{"0", "65535", "-42"}, [3]interface{}{int64(0), int64(65535), int64(-42)}}, - {"bigintnull", "BIGINT", "BIGINT", scanTypeNullInt, true, 20, 0, 0, [3]string{"NULL", "1", "42"}, [3]interface{}{niNULL, ni1, ni42}}, - {"tinyuint", "TINYINT UNSIGNED NOT NULL", "TINYINT", scanTypeUint8, false, 3, 0, 0, [3]string{"0", "255", "42"}, [3]interface{}{uint8(0), uint8(255), uint8(42)}}, - {"smalluint", "SMALLINT UNSIGNED NOT NULL", "SMALLINT", scanTypeUint16, false, 5, 0, 0, [3]string{"0", "65535", "42"}, [3]interface{}{uint16(0), uint16(65535), uint16(42)}}, - {"biguint", "BIGINT UNSIGNED NOT NULL", "BIGINT", scanTypeUint64, false, 20, 0, 0, [3]string{"0", "65535", "42"}, [3]interface{}{uint64(0), uint64(65535), uint64(42)}}, - {"uint13", "INT(13) UNSIGNED NOT NULL", "INT", scanTypeUint32, false, 13, 0, 0, [3]string{"0", "1337", "42"}, [3]interface{}{uint32(0), uint32(1337), uint32(42)}}, - {"float", "FLOAT NOT NULL", "FLOAT", scanTypeFloat32, false, 12, math.MaxInt64, math.MaxInt64, [3]string{"0", "42", "13.37"}, [3]interface{}{float32(0), float32(42), float32(13.37)}}, - {"floatnull", "FLOAT", "FLOAT", scanTypeNullFloat, true, 12, math.MaxInt64, math.MaxInt64, [3]string{"0", "NULL", "13.37"}, [3]interface{}{nf0, nfNULL, nf1337}}, - {"float74null", "FLOAT(7,4)", "FLOAT", scanTypeNullFloat, true, 7, math.MaxInt64, 4, [3]string{"0", "NULL", "13.37"}, [3]interface{}{nf0, nfNULL, nf1337}}, - {"double", "DOUBLE NOT NULL", "DOUBLE", scanTypeFloat64, false, 22, math.MaxInt64, math.MaxInt64, [3]string{"0", "42", "13.37"}, [3]interface{}{float64(0), float64(42), float64(13.37)}}, - {"doublenull", "DOUBLE", "DOUBLE", scanTypeNullFloat, true, 22, math.MaxInt64, math.MaxInt64, [3]string{"0", "NULL", "13.37"}, [3]interface{}{nf0, nfNULL, nf1337}}, - {"decimal1", "DECIMAL(10,6) NOT NULL", "DECIMAL", scanTypeRawBytes, false, 12, 10, 6, [3]string{"0", "13.37", "1234.123456"}, [3]interface{}{sql.RawBytes("0.000000"), sql.RawBytes("13.370000"), sql.RawBytes("1234.123456")}}, - {"decimal1null", "DECIMAL(10,6)", "DECIMAL", scanTypeRawBytes, true, 12, 10, 6, [3]string{"0", "NULL", "1234.123456"}, [3]interface{}{sql.RawBytes("0.000000"), rbNULL, sql.RawBytes("1234.123456")}}, - {"decimal2", "DECIMAL(8,4) NOT NULL", "DECIMAL", scanTypeRawBytes, false, 10, 8, 4, [3]string{"0", "13.37", "1234.123456"}, [3]interface{}{sql.RawBytes("0.0000"), sql.RawBytes("13.3700"), sql.RawBytes("1234.1235")}}, - {"decimal2null", "DECIMAL(8,4)", "DECIMAL", scanTypeRawBytes, true, 10, 8, 4, [3]string{"0", "NULL", "1234.123456"}, [3]interface{}{sql.RawBytes("0.0000"), rbNULL, sql.RawBytes("1234.1235")}}, - {"decimal3", "DECIMAL(5,0) NOT NULL", "DECIMAL", scanTypeRawBytes, false, 6, 5, 0, [3]string{"0", "13.37", "-12345.123456"}, [3]interface{}{rb0, sql.RawBytes("13"), sql.RawBytes("-12345")}}, - {"decimal3null", "DECIMAL(5,0)", "DECIMAL", scanTypeRawBytes, true, 6, 5, 0, [3]string{"0", "NULL", "-12345.123456"}, [3]interface{}{rb0, rbNULL, sql.RawBytes("-12345")}}, - {"char25null", "CHAR(25)", "CHAR", scanTypeRawBytes, true, 75, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}}, - {"varchar42", "VARCHAR(42) NOT NULL", "VARCHAR", scanTypeRawBytes, false, 126, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}}, - {"textnull", "TEXT", "BLOB", scanTypeRawBytes, true, 196605, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}}, - {"longtext", "LONGTEXT NOT NULL", "BLOB", scanTypeRawBytes, false, 4294967295, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}}, - {"datetime", "DATETIME", "DATETIME", scanTypeNullTime, true, 19, 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}}, - {"datetime2", "DATETIME(2)", "DATETIME", scanTypeNullTime, true, 22, 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}}, - {"datetime6", "DATETIME(6)", "DATETIME", scanTypeNullTime, true, 26, 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}}, + {"boolnull", "BOOL", "TINYINT", scanTypeNullInt, true, 0, 0, [3]string{"NULL", "true", "0"}, [3]interface{}{niNULL, ni1, ni0}}, + {"bool", "BOOL NOT NULL", "TINYINT", scanTypeInt8, false, 0, 0, [3]string{"1", "0", "FALSE"}, [3]interface{}{int8(1), int8(0), int8(0)}}, + {"intnull", "INTEGER", "INT", scanTypeNullInt, true, 0, 0, [3]string{"0", "NULL", "42"}, [3]interface{}{ni0, niNULL, ni42}}, + {"smallint", "SMALLINT NOT NULL", "SMALLINT", scanTypeInt16, false, 0, 0, [3]string{"0", "-32768", "32767"}, [3]interface{}{int16(0), int16(-32768), int16(32767)}}, + {"smallintnull", "SMALLINT", "SMALLINT", scanTypeNullInt, true, 0, 0, [3]string{"0", "NULL", "42"}, [3]interface{}{ni0, niNULL, ni42}}, + {"int3null", "INT(3)", "INT", scanTypeNullInt, true, 0, 0, [3]string{"0", "NULL", "42"}, [3]interface{}{ni0, niNULL, ni42}}, + {"int7", "INT(7) NOT NULL", "INT", scanTypeInt32, false, 0, 0, [3]string{"0", "-1337", "42"}, [3]interface{}{int32(0), int32(-1337), int32(42)}}, + {"bigint", "BIGINT NOT NULL", "BIGINT", scanTypeInt64, false, 0, 0, [3]string{"0", "65535", "-42"}, [3]interface{}{int64(0), int64(65535), int64(-42)}}, + {"bigintnull", "BIGINT", "BIGINT", scanTypeNullInt, true, 0, 0, [3]string{"NULL", "1", "42"}, [3]interface{}{niNULL, ni1, ni42}}, + {"tinyuint", "TINYINT UNSIGNED NOT NULL", "TINYINT", scanTypeUint8, false, 0, 0, [3]string{"0", "255", "42"}, [3]interface{}{uint8(0), uint8(255), uint8(42)}}, + {"smalluint", "SMALLINT UNSIGNED NOT NULL", "SMALLINT", scanTypeUint16, false, 0, 0, [3]string{"0", "65535", "42"}, [3]interface{}{uint16(0), uint16(65535), uint16(42)}}, + {"biguint", "BIGINT UNSIGNED NOT NULL", "BIGINT", scanTypeUint64, false, 0, 0, [3]string{"0", "65535", "42"}, [3]interface{}{uint64(0), uint64(65535), uint64(42)}}, + {"uint13", "INT(13) UNSIGNED NOT NULL", "INT", scanTypeUint32, false, 0, 0, [3]string{"0", "1337", "42"}, [3]interface{}{uint32(0), uint32(1337), uint32(42)}}, + {"float", "FLOAT NOT NULL", "FLOAT", scanTypeFloat32, false, math.MaxInt64, math.MaxInt64, [3]string{"0", "42", "13.37"}, [3]interface{}{float32(0), float32(42), float32(13.37)}}, + {"floatnull", "FLOAT", "FLOAT", scanTypeNullFloat, true, math.MaxInt64, math.MaxInt64, [3]string{"0", "NULL", "13.37"}, [3]interface{}{nf0, nfNULL, nf1337}}, + {"float74null", "FLOAT(7,4)", "FLOAT", scanTypeNullFloat, true, math.MaxInt64, 4, [3]string{"0", "NULL", "13.37"}, [3]interface{}{nf0, nfNULL, nf1337}}, + {"double", "DOUBLE NOT NULL", "DOUBLE", scanTypeFloat64, false, math.MaxInt64, math.MaxInt64, [3]string{"0", "42", "13.37"}, [3]interface{}{float64(0), float64(42), float64(13.37)}}, + {"doublenull", "DOUBLE", "DOUBLE", scanTypeNullFloat, true, math.MaxInt64, math.MaxInt64, [3]string{"0", "NULL", "13.37"}, [3]interface{}{nf0, nfNULL, nf1337}}, + {"decimal1", "DECIMAL(10,6) NOT NULL", "DECIMAL", scanTypeRawBytes, false, 10, 6, [3]string{"0", "13.37", "1234.123456"}, [3]interface{}{sql.RawBytes("0.000000"), sql.RawBytes("13.370000"), sql.RawBytes("1234.123456")}}, + {"decimal1null", "DECIMAL(10,6)", "DECIMAL", scanTypeRawBytes, true, 10, 6, [3]string{"0", "NULL", "1234.123456"}, [3]interface{}{sql.RawBytes("0.000000"), rbNULL, sql.RawBytes("1234.123456")}}, + {"decimal2", "DECIMAL(8,4) NOT NULL", "DECIMAL", scanTypeRawBytes, false, 8, 4, [3]string{"0", "13.37", "1234.123456"}, [3]interface{}{sql.RawBytes("0.0000"), sql.RawBytes("13.3700"), sql.RawBytes("1234.1235")}}, + {"decimal2null", "DECIMAL(8,4)", "DECIMAL", scanTypeRawBytes, true, 8, 4, [3]string{"0", "NULL", "1234.123456"}, [3]interface{}{sql.RawBytes("0.0000"), rbNULL, sql.RawBytes("1234.1235")}}, + {"decimal3", "DECIMAL(5,0) NOT NULL", "DECIMAL", scanTypeRawBytes, false, 5, 0, [3]string{"0", "13.37", "-12345.123456"}, [3]interface{}{rb0, sql.RawBytes("13"), sql.RawBytes("-12345")}}, + {"decimal3null", "DECIMAL(5,0)", "DECIMAL", scanTypeRawBytes, true, 5, 0, [3]string{"0", "NULL", "-12345.123456"}, [3]interface{}{rb0, rbNULL, sql.RawBytes("-12345")}}, + {"char25null", "CHAR(25)", "CHAR", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}}, + {"varchar42", "VARCHAR(42) NOT NULL", "VARCHAR", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}}, + {"textnull", "TEXT", "BLOB", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}}, + {"longtext", "LONGTEXT NOT NULL", "BLOB", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}}, + {"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}}, + {"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}}, + {"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}}, } schema := "" @@ -717,15 +716,15 @@ func TestRowsColumnTypes(t *testing.T) { } // Length - length, ok := tp.Length() - if length != column.length { - if !ok { - t.Errorf("length not ok for column %q", name) - } else { - t.Errorf("length mismatch for column %q: %d != %d", name, length, column.length) - } - continue - } + // length, ok := tp.Length() + // if length != column.length { + // if !ok { + // t.Errorf("length not ok for column %q", name) + // } else { + // t.Errorf("length mismatch for column %q: %d != %d", name, length, column.length) + // } + // continue + // } // Precision and Scale precision, scale, ok := tp.DecimalSize() diff --git a/rows.go b/rows.go index a63fd039f..18f41693e 100644 --- a/rows.go +++ b/rows.go @@ -66,9 +66,9 @@ func (rows *mysqlRows) ColumnTypeDatabaseTypeName(i int) string { return "" } -func (rows *mysqlRows) ColumnTypeLength(i int) (length int64, ok bool) { - return int64(rows.rs.columns[i].length), true -} +// func (rows *mysqlRows) ColumnTypeLength(i int) (length int64, ok bool) { +// return int64(rows.rs.columns[i].length), true +// } func (rows *mysqlRows) ColumnTypeNullable(i int) (nullable, ok bool) { return rows.rs.columns[i].flags&flagNotNULL == 0, true From 6416689859a2ed491704e43a958b840e5e505145 Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Sat, 7 Oct 2017 17:41:39 +0200 Subject: [PATCH 25/25] README: document ColumnType Support --- README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b5882e6c8..72f54907b 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,11 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac * [Parameters](#parameters) * [Examples](#examples) * [Connection pool and timeouts](#connection-pool-and-timeouts) + * [context.Context Support](#contextcontext-support) + * [ColumnType Support](#columntype-support) * [LOAD DATA LOCAL INFILE support](#load-data-local-infile-support) * [time.Time support](#timetime-support) * [Unicode support](#unicode-support) - * [context.Context Support](#contextcontext-support) * [Testing / Development](#testing--development) * [License](#license) @@ -400,6 +401,13 @@ user:password@/ ### Connection pool and timeouts The connection pool is managed by Go's database/sql package. For details on how to configure the size of the pool and how long connections stay in the pool see `*DB.SetMaxOpenConns`, `*DB.SetMaxIdleConns`, and `*DB.SetConnMaxLifetime` in the [database/sql documentation](https://golang.org/pkg/database/sql/). The read, write, and dial timeouts for each individual connection are configured with the DSN parameters [`readTimeout`](#readtimeout), [`writeTimeout`](#writetimeout), and [`timeout`](#timeout), respectively. +## `ColumnType` Support +This driver supports the [`ColumnType` interface](https://golang.org/pkg/database/sql/#ColumnType) introduced in Go 1.8, with the exception of [`ColumnType.Length()`](https://golang.org/pkg/database/sql/#ColumnType.Length), which is currently not supported. + +## `context.Context` Support +Go 1.8 added `database/sql` support for `context.Context`. This driver supports query timeouts and cancellation via contexts. +See [context support in the database/sql package](https://golang.org/doc/go1.8#database_sql) for more details. + ### `LOAD DATA LOCAL INFILE` support For this feature you need direct access to the package. Therefore you must change the import path (no `_`): @@ -433,10 +441,6 @@ Version 1.0 of the driver recommended adding `&charset=utf8` (alias for `SET NAM See http://dev.mysql.com/doc/refman/5.7/en/charset-unicode.html for more details on MySQL's Unicode support. -## `context.Context` Support -Go 1.8 added `database/sql` support for `context.Context`. This driver supports query timeouts and cancellation via contexts. -See [context support in the database/sql package](https://golang.org/doc/go1.8#database_sql) for more details. - ## Testing / Development To run the driver tests you may need to adjust the configuration. See the [Testing Wiki-Page](https://github.com/go-sql-driver/mysql/wiki/Testing "Testing") for details.