From ffa70d4981de905bced351b5703851f8e8755225 Mon Sep 17 00:00:00 2001
From: Jacek Szwec <jszwec@users.noreply.github.com>
Date: Thu, 23 Mar 2017 06:26:10 -0400
Subject: [PATCH 001/282] Add Multi-Results support (#537)

---
 AUTHORS             |   1 +
 connection.go       |  34 +++++---
 driver_go18_test.go | 190 ++++++++++++++++++++++++++++++++++++++++++++
 packets.go          |  60 +++++++-------
 rows.go             | 122 ++++++++++++++++++++++------
 statement.go        |  56 ++++++++-----
 6 files changed, 377 insertions(+), 86 deletions(-)
 create mode 100644 driver_go18_test.go

diff --git a/AUTHORS b/AUTHORS
index 100370758..f47d35d96 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -25,6 +25,7 @@ Hanno Braun <mail at hannobraun.com>
 Henri Yandell <flamefew at gmail.com>
 Hirotaka Yamamoto <ymmt2005 at gmail.com>
 INADA Naoki <songofacandy at gmail.com>
+Jacek Szwec <szwec.jacek at gmail.com>
 James Harr <james.harr at gmail.com>
 Jian Zhen <zhenjl at gmail.com>
 Joshua Prunier <joshua.prunier at gmail.com>
diff --git a/connection.go b/connection.go
index d82c728f3..08e5fadeb 100644
--- a/connection.go
+++ b/connection.go
@@ -10,6 +10,7 @@ package mysql
 
 import (
 	"database/sql/driver"
+	"io"
 	"net"
 	"strconv"
 	"strings"
@@ -289,22 +290,29 @@ func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, err
 // Internal function to execute commands
 func (mc *mysqlConn) exec(query string) error {
 	// Send command
-	err := mc.writeCommandPacketStr(comQuery, query)
-	if err != nil {
+	if err := mc.writeCommandPacketStr(comQuery, query); err != nil {
 		return err
 	}
 
 	// Read Result
 	resLen, err := mc.readResultSetHeaderPacket()
-	if err == nil && resLen > 0 {
-		if err = mc.readUntilEOF(); err != nil {
+	if err != nil {
+		return err
+	}
+
+	if resLen > 0 {
+		// columns
+		if err := mc.readUntilEOF(); err != nil {
 			return err
 		}
 
-		err = mc.readUntilEOF()
+		// rows
+		if err := mc.readUntilEOF(); err != nil {
+			return err
+		}
 	}
 
-	return err
+	return mc.discardResults()
 }
 
 func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) {
@@ -335,11 +343,17 @@ func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, erro
 			rows.mc = mc
 
 			if resLen == 0 {
-				// no columns, no more data
-				return emptyRows{}, nil
+				rows.rs.done = true
+
+				switch err := rows.NextResultSet(); err {
+				case nil, io.EOF:
+					return rows, nil
+				default:
+					return nil, err
+				}
 			}
 			// Columns
-			rows.columns, err = mc.readColumns(resLen)
+			rows.rs.columns, err = mc.readColumns(resLen)
 			return rows, err
 		}
 	}
@@ -359,7 +373,7 @@ func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
 	if err == nil {
 		rows := new(textRows)
 		rows.mc = mc
-		rows.columns = []mysqlField{{fieldType: fieldTypeVarChar}}
+		rows.rs.columns = []mysqlField{{fieldType: fieldTypeVarChar}}
 
 		if resLen > 0 {
 			// Columns
diff --git a/driver_go18_test.go b/driver_go18_test.go
new file mode 100644
index 000000000..93918ad46
--- /dev/null
+++ b/driver_go18_test.go
@@ -0,0 +1,190 @@
+// +build go1.8
+
+package mysql
+
+import (
+	"database/sql"
+	"fmt"
+	"reflect"
+	"testing"
+)
+
+func TestMultiResultSet(t *testing.T) {
+	type result struct {
+		values  [][]int
+		columns []string
+	}
+
+	// checkRows is a helper test function to validate rows containing 3 result
+	// sets with specific values and columns. The basic query would look like this:
+	//
+	// SELECT 1 AS col1, 2 AS col2 UNION SELECT 3, 4;
+	// SELECT 0 UNION SELECT 1;
+	// SELECT 1 AS col1, 2 AS col2, 3 AS col3 UNION SELECT 4, 5, 6;
+	//
+	// to distinguish test cases the first string argument is put in front of
+	// every error or fatal message.
+	checkRows := func(desc string, rows *sql.Rows, dbt *DBTest) {
+		expected := []result{
+			{
+				values:  [][]int{{1, 2}, {3, 4}},
+				columns: []string{"col1", "col2"},
+			},
+			{
+				values:  [][]int{{1, 2, 3}, {4, 5, 6}},
+				columns: []string{"col1", "col2", "col3"},
+			},
+		}
+
+		var res1 result
+		for rows.Next() {
+			var res [2]int
+			if err := rows.Scan(&res[0], &res[1]); err != nil {
+				dbt.Fatal(err)
+			}
+			res1.values = append(res1.values, res[:])
+		}
+
+		cols, err := rows.Columns()
+		if err != nil {
+			dbt.Fatal(desc, err)
+		}
+		res1.columns = cols
+
+		if !reflect.DeepEqual(expected[0], res1) {
+			dbt.Error(desc, "want =", expected[0], "got =", res1)
+		}
+
+		if !rows.NextResultSet() {
+			dbt.Fatal(desc, "expected next result set")
+		}
+
+		// ignoring one result set
+
+		if !rows.NextResultSet() {
+			dbt.Fatal(desc, "expected next result set")
+		}
+
+		var res2 result
+		cols, err = rows.Columns()
+		if err != nil {
+			dbt.Fatal(desc, err)
+		}
+		res2.columns = cols
+
+		for rows.Next() {
+			var res [3]int
+			if err := rows.Scan(&res[0], &res[1], &res[2]); err != nil {
+				dbt.Fatal(desc, err)
+			}
+			res2.values = append(res2.values, res[:])
+		}
+
+		if !reflect.DeepEqual(expected[1], res2) {
+			dbt.Error(desc, "want =", expected[1], "got =", res2)
+		}
+
+		if rows.NextResultSet() {
+			dbt.Error(desc, "unexpected next result set")
+		}
+
+		if err := rows.Err(); err != nil {
+			dbt.Error(desc, err)
+		}
+	}
+
+	runTestsWithMultiStatement(t, dsn, func(dbt *DBTest) {
+		rows := dbt.mustQuery(`DO 1;
+		SELECT 1 AS col1, 2 AS col2 UNION SELECT 3, 4;
+		DO 1;
+		SELECT 0 UNION SELECT 1;
+		SELECT 1 AS col1, 2 AS col2, 3 AS col3 UNION SELECT 4, 5, 6;`)
+		defer rows.Close()
+		checkRows("query: ", rows, dbt)
+	})
+
+	runTestsWithMultiStatement(t, dsn, func(dbt *DBTest) {
+		queries := []string{
+			`
+			DROP PROCEDURE IF EXISTS test_mrss;
+			CREATE PROCEDURE test_mrss()
+			BEGIN
+				DO 1;
+				SELECT 1 AS col1, 2 AS col2 UNION SELECT 3, 4;
+				DO 1;
+				SELECT 0 UNION SELECT 1;
+				SELECT 1 AS col1, 2 AS col2, 3 AS col3 UNION SELECT 4, 5, 6;
+			END
+		`,
+			`
+			DROP PROCEDURE IF EXISTS test_mrss;
+			CREATE PROCEDURE test_mrss()
+			BEGIN
+				SELECT 1 AS col1, 2 AS col2 UNION SELECT 3, 4;
+				SELECT 0 UNION SELECT 1;
+				SELECT 1 AS col1, 2 AS col2, 3 AS col3 UNION SELECT 4, 5, 6;
+			END
+		`,
+		}
+
+		defer dbt.mustExec("DROP PROCEDURE IF EXISTS test_mrss")
+
+		for i, query := range queries {
+			dbt.mustExec(query)
+
+			stmt, err := dbt.db.Prepare("CALL test_mrss()")
+			if err != nil {
+				dbt.Fatalf("%v (i=%d)", err, i)
+			}
+			defer stmt.Close()
+
+			for j := 0; j < 2; j++ {
+				rows, err := stmt.Query()
+				if err != nil {
+					dbt.Fatalf("%v (i=%d) (j=%d)", err, i, j)
+				}
+				checkRows(fmt.Sprintf("prepared stmt query (i=%d) (j=%d): ", i, j), rows, dbt)
+			}
+		}
+	})
+}
+
+func TestMultiResultSetNoSelect(t *testing.T) {
+	runTestsWithMultiStatement(t, dsn, func(dbt *DBTest) {
+		rows := dbt.mustQuery("DO 1; DO 2;")
+		defer rows.Close()
+
+		if rows.Next() {
+			dbt.Error("unexpected row")
+		}
+
+		if rows.NextResultSet() {
+			dbt.Error("unexpected next result set")
+		}
+
+		if err := rows.Err(); err != nil {
+			dbt.Error("expected nil; got ", err)
+		}
+	})
+}
+
+// tests if rows are set in a proper state if some results were ignored before
+// calling rows.NextResultSet.
+func TestSkipResults(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		rows := dbt.mustQuery("SELECT 1, 2")
+		defer rows.Close()
+
+		if !rows.Next() {
+			dbt.Error("expected row")
+		}
+
+		if rows.NextResultSet() {
+			dbt.Error("unexpected next result set")
+		}
+
+		if err := rows.Err(); err != nil {
+			dbt.Error("expected nil; got ", err)
+		}
+	})
+}
diff --git a/packets.go b/packets.go
index aafe9793e..41b4d3d55 100644
--- a/packets.go
+++ b/packets.go
@@ -584,8 +584,8 @@ func (mc *mysqlConn) handleOkPacket(data []byte) error {
 
 	// server_status [2 bytes]
 	mc.status = readStatus(data[1+n+m : 1+n+m+2])
-	if err := mc.discardResults(); err != nil {
-		return err
+	if mc.status&statusMoreResultsExists != 0 {
+		return nil
 	}
 
 	// warning count [2 bytes]
@@ -698,6 +698,10 @@ func (mc *mysqlConn) readColumns(count int) ([]mysqlField, error) {
 func (rows *textRows) readRow(dest []driver.Value) error {
 	mc := rows.mc
 
+	if rows.rs.done {
+		return io.EOF
+	}
+
 	data, err := mc.readPacket()
 	if err != nil {
 		return err
@@ -707,15 +711,11 @@ func (rows *textRows) readRow(dest []driver.Value) error {
 	if data[0] == iEOF && len(data) == 5 {
 		// server_status [2 bytes]
 		rows.mc.status = readStatus(data[3:])
-		err = rows.mc.discardResults()
-		if err == nil {
-			err = io.EOF
-		} else {
-			// connection unusable
-			rows.mc.Close()
+		rows.rs.done = true
+		if !rows.HasNextResultSet() {
+			rows.mc = nil
 		}
-		rows.mc = nil
-		return err
+		return io.EOF
 	}
 	if data[0] == iERR {
 		rows.mc = nil
@@ -736,7 +736,7 @@ func (rows *textRows) readRow(dest []driver.Value) error {
 				if !mc.parseTime {
 					continue
 				} else {
-					switch rows.columns[i].fieldType {
+					switch rows.rs.columns[i].fieldType {
 					case fieldTypeTimestamp, fieldTypeDateTime,
 						fieldTypeDate, fieldTypeNewDate:
 						dest[i], err = parseDateTime(
@@ -1097,8 +1097,6 @@ func (mc *mysqlConn) discardResults() error {
 			if err := mc.readUntilEOF(); err != nil {
 				return err
 			}
-		} else {
-			mc.status &^= statusMoreResultsExists
 		}
 	}
 	return nil
@@ -1116,15 +1114,11 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
 		// EOF Packet
 		if data[0] == iEOF && len(data) == 5 {
 			rows.mc.status = readStatus(data[3:])
-			err = rows.mc.discardResults()
-			if err == nil {
-				err = io.EOF
-			} else {
-				// connection unusable
-				rows.mc.Close()
+			rows.rs.done = true
+			if !rows.HasNextResultSet() {
+				rows.mc = nil
 			}
-			rows.mc = nil
-			return err
+			return io.EOF
 		}
 		rows.mc = nil
 
@@ -1145,14 +1139,14 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
 		}
 
 		// Convert to byte-coded string
-		switch rows.columns[i].fieldType {
+		switch rows.rs.columns[i].fieldType {
 		case fieldTypeNULL:
 			dest[i] = nil
 			continue
 
 		// Numeric Types
 		case fieldTypeTiny:
-			if rows.columns[i].flags&flagUnsigned != 0 {
+			if rows.rs.columns[i].flags&flagUnsigned != 0 {
 				dest[i] = int64(data[pos])
 			} else {
 				dest[i] = int64(int8(data[pos]))
@@ -1161,7 +1155,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
 			continue
 
 		case fieldTypeShort, fieldTypeYear:
-			if rows.columns[i].flags&flagUnsigned != 0 {
+			if rows.rs.columns[i].flags&flagUnsigned != 0 {
 				dest[i] = int64(binary.LittleEndian.Uint16(data[pos : pos+2]))
 			} else {
 				dest[i] = int64(int16(binary.LittleEndian.Uint16(data[pos : pos+2])))
@@ -1170,7 +1164,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
 			continue
 
 		case fieldTypeInt24, fieldTypeLong:
-			if rows.columns[i].flags&flagUnsigned != 0 {
+			if rows.rs.columns[i].flags&flagUnsigned != 0 {
 				dest[i] = int64(binary.LittleEndian.Uint32(data[pos : pos+4]))
 			} else {
 				dest[i] = int64(int32(binary.LittleEndian.Uint32(data[pos : pos+4])))
@@ -1179,7 +1173,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
 			continue
 
 		case fieldTypeLongLong:
-			if rows.columns[i].flags&flagUnsigned != 0 {
+			if rows.rs.columns[i].flags&flagUnsigned != 0 {
 				val := binary.LittleEndian.Uint64(data[pos : pos+8])
 				if val > math.MaxInt64 {
 					dest[i] = uint64ToString(val)
@@ -1233,10 +1227,10 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
 			case isNull:
 				dest[i] = nil
 				continue
-			case rows.columns[i].fieldType == fieldTypeTime:
+			case rows.rs.columns[i].fieldType == fieldTypeTime:
 				// database/sql does not support an equivalent to TIME, return a string
 				var dstlen uint8
-				switch decimals := rows.columns[i].decimals; decimals {
+				switch decimals := rows.rs.columns[i].decimals; decimals {
 				case 0x00, 0x1f:
 					dstlen = 8
 				case 1, 2, 3, 4, 5, 6:
@@ -1244,7 +1238,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
 				default:
 					return fmt.Errorf(
 						"protocol error, illegal decimals value %d",
-						rows.columns[i].decimals,
+						rows.rs.columns[i].decimals,
 					)
 				}
 				dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, true)
@@ -1252,10 +1246,10 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
 				dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.Loc)
 			default:
 				var dstlen uint8
-				if rows.columns[i].fieldType == fieldTypeDate {
+				if rows.rs.columns[i].fieldType == fieldTypeDate {
 					dstlen = 10
 				} else {
-					switch decimals := rows.columns[i].decimals; decimals {
+					switch decimals := rows.rs.columns[i].decimals; decimals {
 					case 0x00, 0x1f:
 						dstlen = 19
 					case 1, 2, 3, 4, 5, 6:
@@ -1263,7 +1257,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
 					default:
 						return fmt.Errorf(
 							"protocol error, illegal decimals value %d",
-							rows.columns[i].decimals,
+							rows.rs.columns[i].decimals,
 						)
 					}
 				}
@@ -1279,7 +1273,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
 
 		// Please report if this happens!
 		default:
-			return fmt.Errorf("unknown field type %d", rows.columns[i].fieldType)
+			return fmt.Errorf("unknown field type %d", rows.rs.columns[i].fieldType)
 		}
 	}
 
diff --git a/rows.go b/rows.go
index c08255eee..900f548ae 100644
--- a/rows.go
+++ b/rows.go
@@ -21,40 +21,49 @@ type mysqlField struct {
 	decimals  byte
 }
 
-type mysqlRows struct {
-	mc      *mysqlConn
+type resultSet struct {
 	columns []mysqlField
+	done    bool
+}
+
+type mysqlRows struct {
+	mc *mysqlConn
+	rs resultSet
 }
 
 type binaryRows struct {
 	mysqlRows
+	// stmtCols is a pointer to the statement's cached columns for different
+	// result sets.
+	stmtCols *[][]mysqlField
+	// i is a number of the current result set. It is used to fetch proper
+	// columns from stmtCols.
+	i int
 }
 
 type textRows struct {
 	mysqlRows
 }
 
-type emptyRows struct{}
-
 func (rows *mysqlRows) Columns() []string {
-	columns := make([]string, len(rows.columns))
+	columns := make([]string, len(rows.rs.columns))
 	if rows.mc != nil && rows.mc.cfg.ColumnsWithAlias {
 		for i := range columns {
-			if tableName := rows.columns[i].tableName; len(tableName) > 0 {
-				columns[i] = tableName + "." + rows.columns[i].name
+			if tableName := rows.rs.columns[i].tableName; len(tableName) > 0 {
+				columns[i] = tableName + "." + rows.rs.columns[i].name
 			} else {
-				columns[i] = rows.columns[i].name
+				columns[i] = rows.rs.columns[i].name
 			}
 		}
 	} else {
 		for i := range columns {
-			columns[i] = rows.columns[i].name
+			columns[i] = rows.rs.columns[i].name
 		}
 	}
 	return columns
 }
 
-func (rows *mysqlRows) Close() error {
+func (rows *mysqlRows) Close() (err error) {
 	mc := rows.mc
 	if mc == nil {
 		return nil
@@ -64,7 +73,9 @@ func (rows *mysqlRows) Close() error {
 	}
 
 	// Remove unread packets from stream
-	err := mc.readUntilEOF()
+	if !rows.rs.done {
+		err = mc.readUntilEOF()
+	}
 	if err == nil {
 		if err = mc.discardResults(); err != nil {
 			return err
@@ -75,6 +86,73 @@ func (rows *mysqlRows) Close() error {
 	return err
 }
 
+func (rows *mysqlRows) HasNextResultSet() (b bool) {
+	if rows.mc == nil {
+		return false
+	}
+	return rows.mc.status&statusMoreResultsExists != 0
+}
+
+func (rows *mysqlRows) nextResultSet() (int, error) {
+	if rows.mc == nil {
+		return 0, io.EOF
+	}
+	if rows.mc.netConn == nil {
+		return 0, ErrInvalidConn
+	}
+
+	// Remove unread packets from stream
+	if !rows.rs.done {
+		if err := rows.mc.readUntilEOF(); err != nil {
+			return 0, err
+		}
+		rows.rs.done = true
+	}
+
+	if !rows.HasNextResultSet() {
+		rows.mc = nil
+		return 0, io.EOF
+	}
+	rows.rs = resultSet{}
+	return rows.mc.readResultSetHeaderPacket()
+}
+
+func (rows *mysqlRows) nextNotEmptyResultSet() (int, error) {
+	for {
+		resLen, err := rows.nextResultSet()
+		if err != nil {
+			return 0, err
+		}
+
+		if resLen > 0 {
+			return resLen, nil
+		}
+
+		rows.rs.done = true
+	}
+}
+
+func (rows *binaryRows) NextResultSet() (err error) {
+	resLen, err := rows.nextNotEmptyResultSet()
+	if err != nil {
+		return err
+	}
+
+	// get columns, if not cached, read them and cache them.
+	if rows.i >= len(*rows.stmtCols) {
+		rows.rs.columns, err = rows.mc.readColumns(resLen)
+		*rows.stmtCols = append(*rows.stmtCols, rows.rs.columns)
+	} else {
+		rows.rs.columns = (*rows.stmtCols)[rows.i]
+		if err := rows.mc.readUntilEOF(); err != nil {
+			return err
+		}
+	}
+
+	rows.i++
+	return nil
+}
+
 func (rows *binaryRows) Next(dest []driver.Value) error {
 	if mc := rows.mc; mc != nil {
 		if mc.netConn == nil {
@@ -87,6 +165,16 @@ func (rows *binaryRows) Next(dest []driver.Value) error {
 	return io.EOF
 }
 
+func (rows *textRows) NextResultSet() (err error) {
+	resLen, err := rows.nextNotEmptyResultSet()
+	if err != nil {
+		return err
+	}
+
+	rows.rs.columns, err = rows.mc.readColumns(resLen)
+	return err
+}
+
 func (rows *textRows) Next(dest []driver.Value) error {
 	if mc := rows.mc; mc != nil {
 		if mc.netConn == nil {
@@ -98,15 +186,3 @@ func (rows *textRows) Next(dest []driver.Value) error {
 	}
 	return io.EOF
 }
-
-func (rows emptyRows) Columns() []string {
-	return nil
-}
-
-func (rows emptyRows) Close() error {
-	return nil
-}
-
-func (rows emptyRows) Next(dest []driver.Value) error {
-	return io.EOF
-}
diff --git a/statement.go b/statement.go
index 7f9b04585..b88771674 100644
--- a/statement.go
+++ b/statement.go
@@ -11,6 +11,7 @@ package mysql
 import (
 	"database/sql/driver"
 	"fmt"
+	"io"
 	"reflect"
 	"strconv"
 )
@@ -19,7 +20,7 @@ type mysqlStmt struct {
 	mc         *mysqlConn
 	id         uint32
 	paramCount int
-	columns    []mysqlField // cached from the first query
+	columns    [][]mysqlField // cached from the first query
 }
 
 func (stmt *mysqlStmt) Close() error {
@@ -62,26 +63,30 @@ func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
 
 	// Read Result
 	resLen, err := mc.readResultSetHeaderPacket()
-	if err == nil {
-		if resLen > 0 {
-			// Columns
-			err = mc.readUntilEOF()
-			if err != nil {
-				return nil, err
-			}
+	if err != nil {
+		return nil, err
+	}
 
-			// Rows
-			err = mc.readUntilEOF()
+	if resLen > 0 {
+		// Columns
+		if err = mc.readUntilEOF(); err != nil {
+			return nil, err
 		}
-		if err == nil {
-			return &mysqlResult{
-				affectedRows: int64(mc.affectedRows),
-				insertId:     int64(mc.insertId),
-			}, nil
+
+		// Rows
+		if err := mc.readUntilEOF(); err != nil {
+			return nil, err
 		}
 	}
 
-	return nil, err
+	if err := mc.discardResults(); err != nil {
+		return nil, err
+	}
+
+	return &mysqlResult{
+		affectedRows: int64(mc.affectedRows),
+		insertId:     int64(mc.insertId),
+	}, nil
 }
 
 func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
@@ -104,18 +109,29 @@ func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
 	}
 
 	rows := new(binaryRows)
+	rows.stmtCols = &stmt.columns
 
 	if resLen > 0 {
 		rows.mc = mc
+		rows.i++
 		// Columns
 		// If not cached, read them and cache them
-		if stmt.columns == nil {
-			rows.columns, err = mc.readColumns(resLen)
-			stmt.columns = rows.columns
+		if len(stmt.columns) == 0 {
+			rows.rs.columns, err = mc.readColumns(resLen)
+			stmt.columns = append(stmt.columns, rows.rs.columns)
 		} else {
-			rows.columns = stmt.columns
+			rows.rs.columns = stmt.columns[0]
 			err = mc.readUntilEOF()
 		}
+	} else {
+		rows.rs.done = true
+
+		switch err := rows.NextResultSet(); err {
+		case nil, io.EOF:
+			return rows, nil
+		default:
+			return nil, err
+		}
 	}
 
 	return rows, err

From 53e2a9bf25b20e8f597896666f0663d84e22c68e Mon Sep 17 00:00:00 2001
From: INADA Naoki <methane@users.noreply.github.com>
Date: Fri, 24 Mar 2017 20:09:48 +0900
Subject: [PATCH 002/282] travis: add Go 1.8 test (#557)

---
 .travis.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.travis.yml b/.travis.yml
index c1cc10aaf..b89d7e11b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,6 +7,7 @@ go:
   - 1.5
   - 1.6
   - 1.7
+  - 1.8
   - tip
 
 before_script:

From c1601d438e5de4da722976a6ce639be73629ed0c Mon Sep 17 00:00:00 2001
From: INADA Naoki <methane@users.noreply.github.com>
Date: Mon, 27 Mar 2017 19:58:37 +0900
Subject: [PATCH 003/282] travis: Add MySQL 5.7 and MariaDB tests. (#560)

* travis: Add MySQL 5.7 test

* travis: Add MariaDB 5.5 and 10.1 test
---
 .travis.yml           | 63 +++++++++++++++++++++++++++++++++++++++++++
 .travis/docker.cnf    |  5 ++++
 .travis/wait_mysql.sh |  8 ++++++
 3 files changed, 76 insertions(+)
 create mode 100644 .travis/docker.cnf
 create mode 100755 .travis/wait_mysql.sh

diff --git a/.travis.yml b/.travis.yml
index b89d7e11b..2fe0f6d4c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,5 +10,68 @@ go:
   - 1.8
   - tip
 
+matrix:
+  include:
+    - env: DB=MYSQL57
+      sudo: required
+      dist: trusty
+      go: 1.8
+      services:
+        - docker
+      before_install:
+        - docker pull mysql:5.7
+        - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
+          mysql:5.7 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB
+        - sleep 30
+        - cp .travis/docker.cnf ~/.my.cnf
+        - mysql --print-defaults
+        - .travis/wait_mysql.sh
+      before_script:
+        - export MYSQL_TEST_USER=gotest
+        - export MYSQL_TEST_PASS=secret
+        - export MYSQL_TEST_ADDR=127.0.0.1:3307
+        - export MYSQL_TEST_CONCURRENT=1
+
+    - env: DB=MARIA55
+      sudo: required
+      dist: trusty
+      go: 1.8
+      services:
+        - docker
+      before_install:
+        - docker pull mariadb:5.5
+        - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
+          mariadb:5.5 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB
+        - sleep 30
+        - cp .travis/docker.cnf ~/.my.cnf
+        - mysql --print-defaults
+        - .travis/wait_mysql.sh
+      before_script:
+        - export MYSQL_TEST_USER=gotest
+        - export MYSQL_TEST_PASS=secret
+        - export MYSQL_TEST_ADDR=127.0.0.1:3307
+        - export MYSQL_TEST_CONCURRENT=1
+
+    - env: DB=MARIA10_1
+      sudo: required
+      dist: trusty
+      go: 1.8
+      services:
+        - docker
+      before_install:
+        - docker pull mariadb:10.1
+        - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
+          mariadb:10.1 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB
+        - sleep 30
+        - cp .travis/docker.cnf ~/.my.cnf
+        - mysql --print-defaults
+        - .travis/wait_mysql.sh
+      before_script:
+        - export MYSQL_TEST_USER=gotest
+        - export MYSQL_TEST_PASS=secret
+        - export MYSQL_TEST_ADDR=127.0.0.1:3307
+        - export MYSQL_TEST_CONCURRENT=1
+
+
 before_script:
   - mysql -e 'create database gotest;'
diff --git a/.travis/docker.cnf b/.travis/docker.cnf
new file mode 100644
index 000000000..e57754e5a
--- /dev/null
+++ b/.travis/docker.cnf
@@ -0,0 +1,5 @@
+[client]
+user = gotest
+password = secret
+host = 127.0.0.1
+port = 3307
diff --git a/.travis/wait_mysql.sh b/.travis/wait_mysql.sh
new file mode 100755
index 000000000..abcf5f0ae
--- /dev/null
+++ b/.travis/wait_mysql.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+while :
+do
+    sleep 3
+    if mysql -e 'select version()'; then
+        break
+    fi
+done

From 9dee4ca50b83acdf57a35fb9e6fb4be640afa2f3 Mon Sep 17 00:00:00 2001
From: Peter Schultz <schultz.peter@hotmail.com>
Date: Mon, 27 Mar 2017 13:30:21 +0200
Subject: [PATCH 004/282] Fix documentation for timeout parameters (#535)

* Fix documentation for timeout parameters

* Add readme section about connection pool

* Add myself to AUTHORS
---
 AUTHORS   |  1 +
 README.md | 18 ++++++++++++------
 2 files changed, 13 insertions(+), 6 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index f47d35d96..e47e5425c 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -42,6 +42,7 @@ Michael Woolnough <michael.woolnough at gmail.com>
 Nicola Peduzzi <thenikso at gmail.com>
 Olivier Mengué <dolmen at cpan.org>
 Paul Bonser <misterpib at gmail.com>
+Peter Schultz <peter.schultz at classmarkets.com>
 Runrioter Wung <runrioter at gmail.com>
 Soroush Pour <me at soroushjp.com>
 Stan Putrya <root.vagner at gmail.com>
diff --git a/README.md b/README.md
index 645203850..a060e3cfd 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,7 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
       * [Address](#address)
       * [Parameters](#parameters)
       * [Examples](#examples)
+    * [Connection pool and timeouts](#connection-pool-and-timeouts)
     * [LOAD DATA LOCAL INFILE support](#load-data-local-infile-support)
     * [time.Time support](#timetime-support)
     * [Unicode support](#unicode-support)
@@ -260,11 +261,11 @@ Default:        false
 ##### `readTimeout`
 
 ```
-Type:           decimal number
+Type:           duration
 Default:        0
 ```
 
-I/O read timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*.
+I/O read timeout. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*.
 
 ##### `strict`
 
@@ -283,11 +284,11 @@ By default MySQL also treats notes as warnings. Use [`sql_notes=false`](http://d
 ##### `timeout`
 
 ```
-Type:           decimal number
+Type:           duration
 Default:        OS default
 ```
 
-*Driver* side connection timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*. To set a server side timeout, use the parameter [`wait_timeout`](http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_wait_timeout).
+Timeout for establishing connections, aka dial timeout. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*.
 
 ##### `tls`
 
@@ -302,11 +303,11 @@ Default:        false
 ##### `writeTimeout`
 
 ```
-Type:           decimal number
+Type:           duration
 Default:        0
 ```
 
-I/O write timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*.
+I/O write timeout. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*.
 
 
 ##### System Variables
@@ -380,6 +381,11 @@ No Database preselected:
 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.
+
+
 ### `LOAD DATA LOCAL INFILE` support
 For this feature you need direct access to the package. Therefore you must change the import path (no `_`):
 ```go

From 15dbaaedd46b79251eaa9c8ed5f280d28978bb3f Mon Sep 17 00:00:00 2001
From: Lion Yang <lion@aosc.xyz>
Date: Wed, 26 Apr 2017 20:08:28 +0800
Subject: [PATCH 005/282] doc: fix synopsis for godoc (#567)

---
 AUTHORS   | 1 +
 driver.go | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/AUTHORS b/AUTHORS
index e47e5425c..48d04eb52 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -35,6 +35,7 @@ Kamil Dziedzic <kamil at klecza.pl>
 Kevin Malachowski <kevin at chowski.com>
 Lennart Rudolph <lrudolph at hmc.edu>
 Leonardo YongUk Kim <dalinaum at gmail.com>
+Lion Yang <lion@aosc.xyz>
 Luca Looz <luca.looz92 at gmail.com>
 Lucas Liu <extrafliu at gmail.com>
 Luke Scott <luke at webconnex.com>
diff --git a/driver.go b/driver.go
index 0022d1f1e..e51d98a3c 100644
--- a/driver.go
+++ b/driver.go
@@ -4,7 +4,7 @@
 // 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 provides a MySQL driver for Go's database/sql package
+// Package mysql provides a MySQL driver for Go's database/sql package.
 //
 // The driver should be used via the database/sql package:
 //

From 7df1717c1242916d5330d547e327323a16253f39 Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Fri, 28 Apr 2017 21:37:05 +0800
Subject: [PATCH 006/282] Format AUTHORS (#574)

---
 AUTHORS | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/AUTHORS b/AUTHORS
index 48d04eb52..97ae23d51 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -35,7 +35,7 @@ Kamil Dziedzic <kamil at klecza.pl>
 Kevin Malachowski <kevin at chowski.com>
 Lennart Rudolph <lrudolph at hmc.edu>
 Leonardo YongUk Kim <dalinaum at gmail.com>
-Lion Yang <lion@aosc.xyz>
+Lion Yang <lion at aosc.xyz>
 Luca Looz <luca.looz92 at gmail.com>
 Lucas Liu <extrafliu at gmail.com>
 Luke Scott <luke at webconnex.com>

From c775fbc0c7ce5256db87cc1645d60147dbb7ca1c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dani=C3=ABl=20van=20Eeden?= <github@myname.nl>
Date: Fri, 28 Apr 2017 15:41:18 +0200
Subject: [PATCH 007/282] Use hostname from DSN as default for TLS if tls=true
 (#564)

---
 dsn.go | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/dsn.go b/dsn.go
index ac00dcedd..5c828bf90 100644
--- a/dsn.go
+++ b/dsn.go
@@ -494,6 +494,10 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 				if boolValue {
 					cfg.TLSConfig = "true"
 					cfg.tls = &tls.Config{}
+					host, _, err := net.SplitHostPort(cfg.Addr)
+					if err == nil {
+						cfg.tls.ServerName = host
+					}
 				} else {
 					cfg.TLSConfig = "false"
 				}

From 147bd02c2c516cf9a8878cb75898ee8a9eea0228 Mon Sep 17 00:00:00 2001
From: astaxie <xiemengjun@gmail.com>
Date: Sat, 29 Apr 2017 00:02:31 +0800
Subject: [PATCH 008/282] args assigned and not used (#575)

* args assigned and not used

* add to AUTHORS
---
 AUTHORS       | 1 +
 connection.go | 2 --
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 97ae23d51..a6b845f72 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -13,6 +13,7 @@
 
 Aaron Hopkins <go-sql-driver at die.net>
 Arne Hormann <arnehormann at gmail.com>
+Asta Xie <xiemengjun at gmail.com>
 Carlos Nieto <jose.carlos at menteslibres.net>
 Chris Moos <chris at tech9computers.com>
 Daniel Nichter <nil at codenode.com>
diff --git a/connection.go b/connection.go
index 08e5fadeb..cdce3e30f 100644
--- a/connection.go
+++ b/connection.go
@@ -272,7 +272,6 @@ func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, err
 			return nil, err
 		}
 		query = prepared
-		args = nil
 	}
 	mc.affectedRows = 0
 	mc.insertId = 0
@@ -330,7 +329,6 @@ func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, erro
 			return nil, err
 		}
 		query = prepared
-		args = nil
 	}
 	// Send command
 	err := mc.writeCommandPacketStr(comQuery, query)

From 20e36f9a8c12673b2518c3dc214dc976d8731a9e Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Wed, 3 May 2017 02:07:42 +0800
Subject: [PATCH 009/282] travis: check go vet and gofmt (#577)

* travis: check go vet and gofmt

This makes the tests fail if go vet or gofmt find something

* travis: use gofmt simplify flag
---
 .travis.yml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/.travis.yml b/.travis.yml
index 2fe0f6d4c..c28bccab7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -75,3 +75,7 @@ matrix:
 
 before_script:
   - mysql -e 'create database gotest;'
+script:
+  - go test -v
+  - go vet ./...
+  - test -z "$(gofmt -d -s . | tee /dev/stderr)"

From 46a82062e1f56d87c7a9244d665fac55df25522f Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Wed, 3 May 2017 02:08:05 +0800
Subject: [PATCH 010/282] fix typos (#579)

---
 driver_test.go  | 6 +++---
 packets_test.go | 8 ++++----
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/driver_test.go b/driver_test.go
index 78e68f5d0..6cd9675d9 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -684,7 +684,7 @@ func TestDateTime(t *testing.T) {
 				for _, setup := range setups.tests {
 					allowBinTime := true
 					if setup.s == "" {
-						// fill time string whereever Go can reliable produce it
+						// fill time string wherever Go can reliable produce it
 						setup.s = setup.t.Format(setups.tlayout)
 					} else if setup.s[0] == '!' {
 						// skip tests using setup.t as source in queries
@@ -856,14 +856,14 @@ func TestNULL(t *testing.T) {
 			dbt.Fatal(err)
 		}
 		if b != nil {
-			dbt.Error("non-nil []byte wich should be nil")
+			dbt.Error("non-nil []byte which should be nil")
 		}
 		// Read non-nil
 		if err = nonNullStmt.QueryRow().Scan(&b); err != nil {
 			dbt.Fatal(err)
 		}
 		if b == nil {
-			dbt.Error("nil []byte wich should be non-nil")
+			dbt.Error("nil []byte which should be non-nil")
 		}
 		// Insert nil
 		b = nil
diff --git a/packets_test.go b/packets_test.go
index 98404586a..b1d64f5c7 100644
--- a/packets_test.go
+++ b/packets_test.go
@@ -101,7 +101,7 @@ func TestReadPacketSingleByte(t *testing.T) {
 		t.Fatal(err)
 	}
 	if len(packet) != 1 {
-		t.Fatalf("unexpected packet lenght: expected %d, got %d", 1, len(packet))
+		t.Fatalf("unexpected packet length: expected %d, got %d", 1, len(packet))
 	}
 	if packet[0] != 0xff {
 		t.Fatalf("unexpected packet content: expected %x, got %x", 0xff, packet[0])
@@ -171,7 +171,7 @@ func TestReadPacketSplit(t *testing.T) {
 		t.Fatal(err)
 	}
 	if len(packet) != maxPacketSize {
-		t.Fatalf("unexpected packet lenght: expected %d, got %d", maxPacketSize, len(packet))
+		t.Fatalf("unexpected packet length: expected %d, got %d", maxPacketSize, len(packet))
 	}
 	if packet[0] != 0x11 {
 		t.Fatalf("unexpected payload start: expected %x, got %x", 0x11, packet[0])
@@ -205,7 +205,7 @@ func TestReadPacketSplit(t *testing.T) {
 		t.Fatal(err)
 	}
 	if len(packet) != 2*maxPacketSize {
-		t.Fatalf("unexpected packet lenght: expected %d, got %d", 2*maxPacketSize, len(packet))
+		t.Fatalf("unexpected packet length: expected %d, got %d", 2*maxPacketSize, len(packet))
 	}
 	if packet[0] != 0x11 {
 		t.Fatalf("unexpected payload start: expected %x, got %x", 0x11, packet[0])
@@ -231,7 +231,7 @@ func TestReadPacketSplit(t *testing.T) {
 		t.Fatal(err)
 	}
 	if len(packet) != maxPacketSize+42 {
-		t.Fatalf("unexpected packet lenght: expected %d, got %d", maxPacketSize+42, len(packet))
+		t.Fatalf("unexpected packet length: expected %d, got %d", maxPacketSize+42, len(packet))
 	}
 	if packet[0] != 0x11 {
 		t.Fatalf("unexpected payload start: expected %x, got %x", 0x11, packet[0])

From af474b6efd526c7a5a56805160b186b56d4b08a6 Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Wed, 3 May 2017 02:08:27 +0800
Subject: [PATCH 011/282] packets: replace repeated if/else if by switch (#580)

---
 packets.go | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/packets.go b/packets.go
index 41b4d3d55..ae1333000 100644
--- a/packets.go
+++ b/packets.go
@@ -486,23 +486,24 @@ func (mc *mysqlConn) readResultOK() ([]byte, error) {
 				plugin := string(data[1:pluginEndIndex])
 				cipher := data[pluginEndIndex+1 : len(data)-1]
 
-				if plugin == "mysql_old_password" {
+				switch plugin {
+				case "mysql_old_password":
 					// using old_passwords
 					return cipher, ErrOldPassword
-				} else if plugin == "mysql_clear_password" {
+				case "mysql_clear_password":
 					// using clear text password
 					return cipher, ErrCleartextPassword
-				} else if plugin == "mysql_native_password" {
+				case "mysql_native_password":
 					// using mysql default authentication method
 					return cipher, ErrNativePassword
-				} else {
+				default:
 					return cipher, ErrUnknownPlugin
 				}
-			} else {
-				// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest
-				return nil, ErrOldPassword
 			}
 
+			// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest
+			return nil, ErrOldPassword
+
 		default: // Error otherwise
 			return nil, mc.handleErrorPacket(data)
 		}

From ae5a66df4d33ba404d639bd0fc9189cd5ee79bcf Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Wed, 3 May 2017 02:08:56 +0800
Subject: [PATCH 012/282] packets: remove unnecessary conversion from float32
 to float32 (#581)

---
 packets.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packets.go b/packets.go
index ae1333000..cb21397a2 100644
--- a/packets.go
+++ b/packets.go
@@ -1188,7 +1188,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
 			continue
 
 		case fieldTypeFloat:
-			dest[i] = float32(math.Float32frombits(binary.LittleEndian.Uint32(data[pos : pos+4])))
+			dest[i] = math.Float32frombits(binary.LittleEndian.Uint32(data[pos : pos+4]))
 			pos += 4
 			continue
 

From e5cc7f33ef2ae4a90bf16d4237c94e3accfd0706 Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Wed, 10 May 2017 18:51:12 +0800
Subject: [PATCH 013/282] travis: submit coverage to coveralls (#589)

* travis: submit coverage to coveralls

* travis: fix install of coverage tools in matrix tests
---
 .travis.yml | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/.travis.yml b/.travis.yml
index c28bccab7..a551785b8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,6 +10,10 @@ go:
   - 1.8
   - tip
 
+before_install:
+  - go get golang.org/x/tools/cmd/cover
+  - go get github.com/mattn/goveralls
+
 matrix:
   include:
     - env: DB=MYSQL57
@@ -19,6 +23,8 @@ matrix:
       services:
         - docker
       before_install:
+        - go get golang.org/x/tools/cmd/cover
+        - go get github.com/mattn/goveralls
         - docker pull mysql:5.7
         - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
           mysql:5.7 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB
@@ -39,6 +45,8 @@ matrix:
       services:
         - docker
       before_install:
+        - go get golang.org/x/tools/cmd/cover
+        - go get github.com/mattn/goveralls
         - docker pull mariadb:5.5
         - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
           mariadb:5.5 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB
@@ -59,6 +67,8 @@ matrix:
       services:
         - docker
       before_install:
+        - go get golang.org/x/tools/cmd/cover
+        - go get github.com/mattn/goveralls
         - docker pull mariadb:10.1
         - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
           mariadb:10.1 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB
@@ -76,6 +86,7 @@ matrix:
 before_script:
   - mysql -e 'create database gotest;'
 script:
-  - go test -v
+  - go test -v -covermode=count -coverprofile=coverage.out
   - go vet ./...
   - test -z "$(gofmt -d -s . | tee /dev/stderr)"
+  - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci

From 336127359b09c9c8164e39c4c44f4d36ef081dc8 Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Wed, 10 May 2017 20:46:09 +0800
Subject: [PATCH 014/282] infile: fix reading from empty file (#590)

---
 driver_test.go | 28 +++++++++++++++++++++-------
 infile.go      |  3 ++-
 2 files changed, 23 insertions(+), 8 deletions(-)

diff --git a/driver_test.go b/driver_test.go
index 6cd9675d9..46b8ca698 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -1054,22 +1054,36 @@ func TestLoadData(t *testing.T) {
 				dbt.Fatalf("rows count mismatch. Got %d, want 4", i)
 			}
 		}
+
+		dbt.db.Exec("DROP TABLE IF EXISTS test")
+		dbt.mustExec("CREATE TABLE test (id INT NOT NULL PRIMARY KEY, value TEXT NOT NULL) CHARACTER SET utf8")
+
+		// Local File
 		file, err := ioutil.TempFile("", "gotest")
 		defer os.Remove(file.Name())
 		if err != nil {
 			dbt.Fatal(err)
 		}
-		file.WriteString("1\ta string\n2\ta string containing a \\t\n3\ta string containing a \\n\n4\ta string containing both \\t\\n\n")
-		file.Close()
+		RegisterLocalFile(file.Name())
 
-		dbt.db.Exec("DROP TABLE IF EXISTS test")
-		dbt.mustExec("CREATE TABLE test (id INT NOT NULL PRIMARY KEY, value TEXT NOT NULL) CHARACTER SET utf8")
+		// Try first with empty file
+		dbt.mustExec(fmt.Sprintf("LOAD DATA LOCAL INFILE %q INTO TABLE test", file.Name()))
+		var count int
+		err = dbt.db.QueryRow("SELECT COUNT(*) FROM test").Scan(&count)
+		if err != nil {
+			dbt.Fatal(err.Error())
+		}
+		if count != 0 {
+			dbt.Fatalf("unexpected row count: got %d, want 0", count)
+		}
 
-		// Local File
-		RegisterLocalFile(file.Name())
+		// Then fille File with data and try to load it
+		file.WriteString("1\ta string\n2\ta string containing a \\t\n3\ta string containing a \\n\n4\ta string containing both \\t\\n\n")
+		file.Close()
 		dbt.mustExec(fmt.Sprintf("LOAD DATA LOCAL INFILE %q INTO TABLE test", file.Name()))
 		verifyLoadDataResult()
-		// negative test
+
+		// Try with non-existing file
 		_, err = dbt.db.Exec("LOAD DATA LOCAL INFILE 'doesnotexist' INTO TABLE test")
 		if err == nil {
 			dbt.Fatal("load non-existent file didn't fail")
diff --git a/infile.go b/infile.go
index 547357cfa..4020f9192 100644
--- a/infile.go
+++ b/infile.go
@@ -147,7 +147,8 @@ func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
 	}
 
 	// send content packets
-	if err == nil {
+	// if packetSize == 0, the Reader contains no data
+	if err == nil && packetSize > 0 {
 		data := make([]byte, 4+packetSize)
 		var n int
 		for err == nil {

From aeb7d3c9eef860ca8b457a664ff9ba31e2b836fa Mon Sep 17 00:00:00 2001
From: INADA Naoki <methane@users.noreply.github.com>
Date: Fri, 12 May 2017 15:43:05 +0900
Subject: [PATCH 015/282] remove columns definition cache. (#592)

fixes #587
---
 rows.go      | 23 +++--------------------
 statement.go | 13 +------------
 2 files changed, 4 insertions(+), 32 deletions(-)

diff --git a/rows.go b/rows.go
index 900f548ae..ad66a8e6e 100644
--- a/rows.go
+++ b/rows.go
@@ -33,12 +33,6 @@ type mysqlRows struct {
 
 type binaryRows struct {
 	mysqlRows
-	// stmtCols is a pointer to the statement's cached columns for different
-	// result sets.
-	stmtCols *[][]mysqlField
-	// i is a number of the current result set. It is used to fetch proper
-	// columns from stmtCols.
-	i int
 }
 
 type textRows struct {
@@ -132,25 +126,14 @@ func (rows *mysqlRows) nextNotEmptyResultSet() (int, error) {
 	}
 }
 
-func (rows *binaryRows) NextResultSet() (err error) {
+func (rows *binaryRows) NextResultSet() error {
 	resLen, err := rows.nextNotEmptyResultSet()
 	if err != nil {
 		return err
 	}
 
-	// get columns, if not cached, read them and cache them.
-	if rows.i >= len(*rows.stmtCols) {
-		rows.rs.columns, err = rows.mc.readColumns(resLen)
-		*rows.stmtCols = append(*rows.stmtCols, rows.rs.columns)
-	} else {
-		rows.rs.columns = (*rows.stmtCols)[rows.i]
-		if err := rows.mc.readUntilEOF(); err != nil {
-			return err
-		}
-	}
-
-	rows.i++
-	return nil
+	rows.rs.columns, err = rows.mc.readColumns(resLen)
+	return err
 }
 
 func (rows *binaryRows) Next(dest []driver.Value) error {
diff --git a/statement.go b/statement.go
index b88771674..e5071276a 100644
--- a/statement.go
+++ b/statement.go
@@ -20,7 +20,6 @@ type mysqlStmt struct {
 	mc         *mysqlConn
 	id         uint32
 	paramCount int
-	columns    [][]mysqlField // cached from the first query
 }
 
 func (stmt *mysqlStmt) Close() error {
@@ -109,20 +108,10 @@ func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
 	}
 
 	rows := new(binaryRows)
-	rows.stmtCols = &stmt.columns
 
 	if resLen > 0 {
 		rows.mc = mc
-		rows.i++
-		// Columns
-		// If not cached, read them and cache them
-		if len(stmt.columns) == 0 {
-			rows.rs.columns, err = mc.readColumns(resLen)
-			stmt.columns = append(stmt.columns, rows.rs.columns)
-		} else {
-			rows.rs.columns = stmt.columns[0]
-			err = mc.readUntilEOF()
-		}
+		rows.rs.columns, err = mc.readColumns(resLen)
 	} else {
 		rows.rs.done = true
 

From 382e13d099fcf5f5994290892ab258fbebbdc5e3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Justin=20Nu=C3=9F?= <nuss.justin@gmail.com>
Date: Fri, 12 May 2017 17:29:33 +0200
Subject: [PATCH 016/282] Avoid allocating on each call to rows.Columns (#444)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Cache column names for result sets

* Test that Columns() avoids allocating on second call

* Add Justin Nuß to the AUTHORS file
---
 AUTHORS        |  1 +
 driver_test.go | 33 +++++++++++++++++++++++++++++++++
 rows.go        | 11 +++++++++--
 3 files changed, 43 insertions(+), 2 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index a6b845f72..1928dac89 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -32,6 +32,7 @@ Jian Zhen <zhenjl at gmail.com>
 Joshua Prunier <joshua.prunier at gmail.com>
 Julien Lefevre <julien.lefevr at gmail.com>
 Julien Schmidt <go-sql-driver at julienschmidt.com>
+Justin Nuß <nuss.justin at gmail.com>
 Kamil Dziedzic <kamil at klecza.pl>
 Kevin Malachowski <kevin at chowski.com>
 Lennart Rudolph <lrudolph at hmc.edu>
diff --git a/driver_test.go b/driver_test.go
index 46b8ca698..07f7e79b9 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -1916,3 +1916,36 @@ func TestInterruptBySignal(t *testing.T) {
 		}
 	})
 }
+
+func TestColumnsReusesSlice(t *testing.T) {
+	rows := mysqlRows{
+		rs: resultSet{
+			columns: []mysqlField{
+				{
+					tableName: "test",
+					name:      "A",
+				},
+				{
+					tableName: "test",
+					name:      "B",
+				},
+			},
+		},
+	}
+
+	allocs := testing.AllocsPerRun(1, func() {
+		cols := rows.Columns()
+
+		if len(cols) != 2 {
+			t.Fatalf("expected 2 columns, got %d", len(cols))
+		}
+	})
+
+	if allocs != 0 {
+		t.Fatalf("expected 0 allocations, got %d", int(allocs))
+	}
+
+	if rows.rs.columnNames == nil {
+		t.Fatalf("expected columnNames to be set, got nil")
+	}
+}
diff --git a/rows.go b/rows.go
index ad66a8e6e..13905e216 100644
--- a/rows.go
+++ b/rows.go
@@ -22,8 +22,9 @@ type mysqlField struct {
 }
 
 type resultSet struct {
-	columns []mysqlField
-	done    bool
+	columns     []mysqlField
+	columnNames []string
+	done        bool
 }
 
 type mysqlRows struct {
@@ -40,6 +41,10 @@ type textRows struct {
 }
 
 func (rows *mysqlRows) Columns() []string {
+	if rows.rs.columnNames != nil {
+		return rows.rs.columnNames
+	}
+
 	columns := make([]string, len(rows.rs.columns))
 	if rows.mc != nil && rows.mc.cfg.ColumnsWithAlias {
 		for i := range columns {
@@ -54,6 +59,8 @@ func (rows *mysqlRows) Columns() []string {
 			columns[i] = rows.rs.columns[i].name
 		}
 	}
+
+	rows.rs.columnNames = columns
 	return columns
 }
 

From 44fa292b235f77480ea43a632d4d6316d82dd64a Mon Sep 17 00:00:00 2001
From: pivotal-rebecca-chin <rchin@pivotal.io>
Date: Tue, 30 May 2017 10:49:38 -0400
Subject: [PATCH 017/282] utils: don't mutate registered tls configs (#600)

Fixes #536
---
 AUTHORS         |  3 +++
 dsn.go          |  2 ++
 dsn_test.go     |  2 ++
 utils.go        |  2 ++
 utils_go17.go   | 40 ++++++++++++++++++++++++++++++++++++++++
 utils_go18.go   | 17 +++++++++++++++++
 utils_legacy.go | 18 ++++++++++++++++++
 7 files changed, 84 insertions(+)
 create mode 100644 utils_go17.go
 create mode 100644 utils_go18.go
 create mode 100644 utils_legacy.go

diff --git a/AUTHORS b/AUTHORS
index 1928dac89..4601d08f2 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -18,6 +18,7 @@ Carlos Nieto <jose.carlos at menteslibres.net>
 Chris Moos <chris at tech9computers.com>
 Daniel Nichter <nil at codenode.com>
 Daniël van Eeden <git at myname.nl>
+Dave Protasowski <dprotaso at gmail.com>
 DisposaBoy <disposaboy at dby.me>
 Egor Smolyakov <egorsmkv at gmail.com>
 Frederick Mayle <frederickmayle at gmail.com>
@@ -46,6 +47,7 @@ Nicola Peduzzi <thenikso at gmail.com>
 Olivier Mengué <dolmen at cpan.org>
 Paul Bonser <misterpib at gmail.com>
 Peter Schultz <peter.schultz at classmarkets.com>
+Rebecca Chin <rchin at pivotal.io>
 Runrioter Wung <runrioter at gmail.com>
 Soroush Pour <me at soroushjp.com>
 Stan Putrya <root.vagner at gmail.com>
@@ -59,4 +61,5 @@ Zhenye Xie <xiezhenye at gmail.com>
 
 Barracuda Networks, Inc.
 Google Inc.
+Pivotal Inc.
 Stripe Inc.
diff --git a/dsn.go b/dsn.go
index 5c828bf90..c49827f5d 100644
--- a/dsn.go
+++ b/dsn.go
@@ -511,6 +511,8 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 				}
 
 				if tlsConfig, ok := tlsConfigRegister[name]; ok {
+					tlsConfig = cloneTLSConfig(tlsConfig)
+
 					if len(tlsConfig.ServerName) == 0 && !tlsConfig.InsecureSkipVerify {
 						host, _, err := net.SplitHostPort(cfg.Addr)
 						if err == nil {
diff --git a/dsn_test.go b/dsn_test.go
index 0693192ad..4fd76b813 100644
--- a/dsn_test.go
+++ b/dsn_test.go
@@ -159,6 +159,8 @@ func TestDSNWithCustomTLS(t *testing.T) {
 		t.Error(err.Error())
 	} else if cfg.tls.ServerName != name {
 		t.Errorf("did not get the correct ServerName (%s) parsing DSN (%s).", name, tst)
+	} else if tlsCfg.ServerName != "" {
+		t.Errorf("tlsCfg was mutated ServerName (%s) should be empty parsing DSN (%s).", name, tst)
 	}
 
 	DeregisterTLSConfig("utils_test")
diff --git a/utils.go b/utils.go
index d523b7ffd..bd11c6975 100644
--- a/utils.go
+++ b/utils.go
@@ -26,6 +26,8 @@ var (
 // RegisterTLSConfig registers a custom tls.Config to be used with sql.Open.
 // Use the key as a value in the DSN where tls=value.
 //
+// Note: The tls.Config provided to needs to be exclusively owned by the driver after registering.
+//
 //  rootCertPool := x509.NewCertPool()
 //  pem, err := ioutil.ReadFile("/path/ca-cert.pem")
 //  if err != nil {
diff --git a/utils_go17.go b/utils_go17.go
new file mode 100644
index 000000000..f59563456
--- /dev/null
+++ b/utils_go17.go
@@ -0,0 +1,40 @@
+// 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/.
+
+// +build go1.7
+// +build !go1.8
+
+package mysql
+
+import "crypto/tls"
+
+func cloneTLSConfig(c *tls.Config) *tls.Config {
+	return &tls.Config{
+		Rand:                        c.Rand,
+		Time:                        c.Time,
+		Certificates:                c.Certificates,
+		NameToCertificate:           c.NameToCertificate,
+		GetCertificate:              c.GetCertificate,
+		RootCAs:                     c.RootCAs,
+		NextProtos:                  c.NextProtos,
+		ServerName:                  c.ServerName,
+		ClientAuth:                  c.ClientAuth,
+		ClientCAs:                   c.ClientCAs,
+		InsecureSkipVerify:          c.InsecureSkipVerify,
+		CipherSuites:                c.CipherSuites,
+		PreferServerCipherSuites:    c.PreferServerCipherSuites,
+		SessionTicketsDisabled:      c.SessionTicketsDisabled,
+		SessionTicketKey:            c.SessionTicketKey,
+		ClientSessionCache:          c.ClientSessionCache,
+		MinVersion:                  c.MinVersion,
+		MaxVersion:                  c.MaxVersion,
+		CurvePreferences:            c.CurvePreferences,
+		DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled,
+		Renegotiation:               c.Renegotiation,
+	}
+}
diff --git a/utils_go18.go b/utils_go18.go
new file mode 100644
index 000000000..2aa9d0f18
--- /dev/null
+++ b/utils_go18.go
@@ -0,0 +1,17 @@
+// 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/.
+
+// +build go1.8
+
+package mysql
+
+import "crypto/tls"
+
+func cloneTLSConfig(c *tls.Config) *tls.Config {
+	return c.Clone()
+}
diff --git a/utils_legacy.go b/utils_legacy.go
new file mode 100644
index 000000000..a03b10de2
--- /dev/null
+++ b/utils_legacy.go
@@ -0,0 +1,18 @@
+// 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/.
+
+// +build !go1.7
+
+package mysql
+
+import "crypto/tls"
+
+func cloneTLSConfig(c *tls.Config) *tls.Config {
+	clone := *c
+	return &clone
+}

From b64477099cc2bb2aaf583b4342d04e7d9966ff4c Mon Sep 17 00:00:00 2001
From: Song Gao <song@gao.io>
Date: Tue, 30 May 2017 22:52:20 -0700
Subject: [PATCH 018/282] add rejectReadOnly option (to fix AWS Aurora
 failover) (#604)

* add RejectReadOnly

* update README.md

* close connection explicitly before returning ErrBadConn for 1792 (#2)

* add test and improve doc

* doc/comment changes
---
 AUTHORS        |  1 +
 README.md      | 25 ++++++++++++++++++++++++
 driver_test.go | 52 +++++++++++++++++++++++++++++++++++++++++++++-----
 dsn.go         | 18 +++++++++++++++++
 packets.go     | 15 +++++++++++++++
 5 files changed, 106 insertions(+), 5 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 4601d08f2..0a22d46e7 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -61,5 +61,6 @@ Zhenye Xie <xiezhenye at gmail.com>
 
 Barracuda Networks, Inc.
 Google Inc.
+Keybase Inc.
 Pivotal Inc.
 Stripe Inc.
diff --git a/README.md b/README.md
index a060e3cfd..76a35d32e 100644
--- a/README.md
+++ b/README.md
@@ -267,6 +267,31 @@ Default:        0
 
 I/O read timeout. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*.
 
+##### `rejectReadOnly`
+
+```
+Type:           bool
+Valid Values:   true, false
+Default:        false
+```
+
+
+`rejectreadOnly=true` causes the driver to reject read-only connections. This
+is for a possible race condition during an automatic failover, where the mysql
+client gets connected to a read-only replica after the failover. 
+
+Note that this should be a fairly rare case, as an automatic failover normally
+happens when the primary is down, and the race condition shouldn't happen
+unless it comes back up online as soon as the failover is kicked off. On the
+other hand, when this happens, a MySQL application can get stuck on a
+read-only connection until restarted. It is however fairly easy to reproduce,
+for example, using a manual failover on AWS Aurora's MySQL-compatible cluster.
+
+If you are not relying on read-only transactions to reject writes that aren't
+supposed to happen, setting this on some MySQL providers (such as AWS Aurora)
+is safer for failovers.
+
+
 ##### `strict`
 
 ```
diff --git a/driver_test.go b/driver_test.go
index 07f7e79b9..6ca5434a9 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -171,6 +171,17 @@ func (dbt *DBTest) mustQuery(query string, args ...interface{}) (rows *sql.Rows)
 	return rows
 }
 
+func maybeSkip(t *testing.T, err error, skipErrno uint16) {
+	mySQLErr, ok := err.(*MySQLError)
+	if !ok {
+		return
+	}
+
+	if mySQLErr.Number == skipErrno {
+		t.Skipf("skipping test for error: %v", err)
+	}
+}
+
 func TestEmptyQuery(t *testing.T) {
 	runTests(t, dsn, func(dbt *DBTest) {
 		// just a comment, no query
@@ -1168,11 +1179,9 @@ func TestStrict(t *testing.T) {
 	if conn != nil {
 		conn.Close()
 	}
-	if me, ok := err.(*MySQLError); ok && me.Number == 1231 {
-		// Error 1231: Variable 'sql_mode' can't be set to the value of 'ALLOW_INVALID_DATES'
-		// => skip test, MySQL server version is too old
-		return
-	}
+	// Error 1231: Variable 'sql_mode' can't be set to the value of
+	// 'ALLOW_INVALID_DATES' => skip test, MySQL server version is too old
+	maybeSkip(t, err, 1231)
 	runTests(t, relaxedDsn, func(dbt *DBTest) {
 		dbt.mustExec("CREATE TABLE test (a TINYINT NOT NULL, b CHAR(4))")
 
@@ -1949,3 +1958,36 @@ func TestColumnsReusesSlice(t *testing.T) {
 		t.Fatalf("expected columnNames to be set, got nil")
 	}
 }
+
+func TestRejectReadOnly(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		// Create Table
+		dbt.mustExec("CREATE TABLE test (value BOOL)")
+		// Set the session to read-only. We didn't set the `rejectReadOnly`
+		// option, so any writes after this should fail.
+		_, err := dbt.db.Exec("SET SESSION TRANSACTION READ ONLY")
+		// Error 1193: Unknown system variable 'TRANSACTION' => skip test,
+		// MySQL server version is too old
+		maybeSkip(t, err, 1193)
+		if _, err := dbt.db.Exec("DROP TABLE test"); err == nil {
+			t.Fatalf("writing to DB in read-only session without " +
+				"rejectReadOnly did not error")
+		}
+		// Set the session back to read-write so runTests() can properly clean
+		// up the table `test`.
+		dbt.mustExec("SET SESSION TRANSACTION READ WRITE")
+	})
+
+	// Enable the `rejectReadOnly` option.
+	runTests(t, dsn+"&rejectReadOnly=true", func(dbt *DBTest) {
+		// Create Table
+		dbt.mustExec("CREATE TABLE test (value BOOL)")
+		// Set the session to read only. Any writes after this should error on
+		// a driver.ErrBadConn, and cause `database/sql` to initiate a new
+		// connection.
+		dbt.mustExec("SET SESSION TRANSACTION READ ONLY")
+		// This would error, but `database/sql` should automatically retry on a
+		// new connection which is not read-only, and eventually succeed.
+		dbt.mustExec("DROP TABLE test")
+	})
+}
diff --git a/dsn.go b/dsn.go
index c49827f5d..199c9b177 100644
--- a/dsn.go
+++ b/dsn.go
@@ -53,6 +53,7 @@ type Config struct {
 	InterpolateParams       bool // Interpolate placeholders into query string
 	MultiStatements         bool // Allow multiple statements in one query
 	ParseTime               bool // Parse time values to time.Time
+	RejectReadOnly          bool // Reject read-only connections
 	Strict                  bool // Return warnings as errors
 }
 
@@ -195,6 +196,15 @@ func (cfg *Config) FormatDSN() string {
 		buf.WriteString(cfg.ReadTimeout.String())
 	}
 
+	if cfg.RejectReadOnly {
+		if hasParam {
+			buf.WriteString("&rejectReadOnly=true")
+		} else {
+			hasParam = true
+			buf.WriteString("?rejectReadOnly=true")
+		}
+	}
+
 	if cfg.Strict {
 		if hasParam {
 			buf.WriteString("&strict=true")
@@ -472,6 +482,14 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 				return
 			}
 
+		// Reject read-only connections
+		case "rejectReadOnly":
+			var isBool bool
+			cfg.RejectReadOnly, isBool = readBool(value)
+			if !isBool {
+				return errors.New("invalid bool value: " + value)
+			}
+
 		// Strict mode
 		case "strict":
 			var isBool bool
diff --git a/packets.go b/packets.go
index cb21397a2..303405a17 100644
--- a/packets.go
+++ b/packets.go
@@ -551,6 +551,21 @@ func (mc *mysqlConn) handleErrorPacket(data []byte) error {
 	// Error Number [16 bit uint]
 	errno := binary.LittleEndian.Uint16(data[1:3])
 
+	// 1792: ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION
+	if errno == 1792 && mc.cfg.RejectReadOnly {
+		// Oops; we are connected to a read-only connection, and won't be able
+		// to issue any write statements. Since RejectReadOnly is configured,
+		// we throw away this connection hoping this one would have write
+		// permission. This is specifically for a possible race condition
+		// during failover (e.g. on AWS Aurora). See README.md for more.
+		//
+		// We explicitly close the connection before returning
+		// driver.ErrBadConn to ensure that `database/sql` purges this
+		// connection and initiates a new one for next statement next time.
+		mc.Close()
+		return driver.ErrBadConn
+	}
+
 	pos := 3
 
 	// SQL State [optional: # + 5bytes string]

From e3f0fdcf52770a162d5a4540ddb8d9957ff456d1 Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Thu, 1 Jun 2017 14:26:16 +0800
Subject: [PATCH 019/282] Add missing copyright header (#603)

---
 driver_go18_test.go | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/driver_go18_test.go b/driver_go18_test.go
index 93918ad46..5a5fa10ff 100644
--- a/driver_go18_test.go
+++ b/driver_go18_test.go
@@ -1,3 +1,11 @@
+// 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/.
+
 // +build go1.8
 
 package mysql

From 56226343bd543f91a3930ed73ebdd03cfd633e85 Mon Sep 17 00:00:00 2001
From: Ichinose Shogo <shogo82148@gmail.com>
Date: Fri, 9 Jun 2017 15:43:30 +0900
Subject: [PATCH 020/282] Add support for context.Context (#608)

* Add supports to context.Context

* add authors related context.Context support

* fix comment of mysqlContext.

- s/from/for/
- and start the comment with a space please

* closed is now just bool flag.

* drop read-only transactions support

* remove unused methods from mysqlContext interface.

* moved checking canceled logic into method of connection.

* add a section about context.Context to the README.

* use atomic variable for closed.

* short circuit for context.Background()

* fix illegal watching state

* set rows.finish directly.

* move namedValueToValue to utils_go18.go

* move static interface implementation checks to the top of the file

* add the new section about `context.Context` to the table of contents, and fix the section.

* mark unsupported features with TODO comments

* rename watcher to starter.

* use mc.error() instead of duplicated logics.
---
 AUTHORS                |   4 +
 README.md              |   4 +
 benchmark_go18_test.go |  93 ++++++++++++++
 connection.go          |  95 ++++++++++++--
 connection_go18.go     | 207 +++++++++++++++++++++++++++++++
 driver.go              |  11 ++
 driver_go18_test.go    | 272 +++++++++++++++++++++++++++++++++++++++++
 driver_test.go         |   8 ++
 packets.go             |  11 ++
 packets_test.go        |   3 +-
 rows.go                |  26 ++--
 statement.go           |  10 +-
 transaction.go         |   4 +-
 utils_go18.go          |  18 ++-
 14 files changed, 738 insertions(+), 28 deletions(-)
 create mode 100644 benchmark_go18_test.go
 create mode 100644 connection_go18.go

diff --git a/AUTHORS b/AUTHORS
index 0a22d46e7..5f27aa457 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -14,6 +14,7 @@
 Aaron Hopkins <go-sql-driver at die.net>
 Arne Hormann <arnehormann at gmail.com>
 Asta Xie <xiemengjun at gmail.com>
+Bulat Gaifullin <gaifullinbf at gmail.com>
 Carlos Nieto <jose.carlos at menteslibres.net>
 Chris Moos <chris at tech9computers.com>
 Daniel Nichter <nil at codenode.com>
@@ -21,11 +22,13 @@ Daniël van Eeden <git at myname.nl>
 Dave Protasowski <dprotaso at gmail.com>
 DisposaBoy <disposaboy at dby.me>
 Egor Smolyakov <egorsmkv at gmail.com>
+Evan Shaw <evan at vendhq.com>
 Frederick Mayle <frederickmayle at gmail.com>
 Gustavo Kristic <gkristic at gmail.com>
 Hanno Braun <mail at hannobraun.com>
 Henri Yandell <flamefew at gmail.com>
 Hirotaka Yamamoto <ymmt2005 at gmail.com>
+ICHINOSE Shogo <shogo82148 at gmail.com>
 INADA Naoki <songofacandy at gmail.com>
 Jacek Szwec <szwec.jacek at gmail.com>
 James Harr <james.harr at gmail.com>
@@ -45,6 +48,7 @@ Luke Scott <luke at webconnex.com>
 Michael Woolnough <michael.woolnough at gmail.com>
 Nicola Peduzzi <thenikso at gmail.com>
 Olivier Mengué <dolmen at cpan.org>
+oscarzhao <oscarzhaosl at gmail.com>
 Paul Bonser <misterpib at gmail.com>
 Peter Schultz <peter.schultz at classmarkets.com>
 Rebecca Chin <rchin at pivotal.io>
diff --git a/README.md b/README.md
index 76a35d32e..d39b29c90 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,7 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
     * [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)
 
@@ -443,6 +444,9 @@ 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.
diff --git a/benchmark_go18_test.go b/benchmark_go18_test.go
new file mode 100644
index 000000000..d6a7e9d6e
--- /dev/null
+++ b/benchmark_go18_test.go
@@ -0,0 +1,93 @@
+// 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/.
+
+// +build go1.8
+
+package mysql
+
+import (
+	"context"
+	"database/sql"
+	"fmt"
+	"runtime"
+	"testing"
+)
+
+func benchmarkQueryContext(b *testing.B, db *sql.DB, p int) {
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+	db.SetMaxIdleConns(p * runtime.GOMAXPROCS(0))
+
+	tb := (*TB)(b)
+	stmt := tb.checkStmt(db.PrepareContext(ctx, "SELECT val FROM foo WHERE id=?"))
+	defer stmt.Close()
+
+	b.SetParallelism(p)
+	b.ReportAllocs()
+	b.ResetTimer()
+	b.RunParallel(func(pb *testing.PB) {
+		var got string
+		for pb.Next() {
+			tb.check(stmt.QueryRow(1).Scan(&got))
+			if got != "one" {
+				b.Fatalf("query = %q; want one", got)
+			}
+		}
+	})
+}
+
+func BenchmarkQueryContext(b *testing.B) {
+	db := initDB(b,
+		"DROP TABLE IF EXISTS foo",
+		"CREATE TABLE foo (id INT PRIMARY KEY, val CHAR(50))",
+		`INSERT INTO foo VALUES (1, "one")`,
+		`INSERT INTO foo VALUES (2, "two")`,
+	)
+	defer db.Close()
+	for _, p := range []int{1, 2, 3, 4} {
+		b.Run(fmt.Sprintf("%d", p), func(b *testing.B) {
+			benchmarkQueryContext(b, db, p)
+		})
+	}
+}
+
+func benchmarkExecContext(b *testing.B, db *sql.DB, p int) {
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+	db.SetMaxIdleConns(p * runtime.GOMAXPROCS(0))
+
+	tb := (*TB)(b)
+	stmt := tb.checkStmt(db.PrepareContext(ctx, "DO 1"))
+	defer stmt.Close()
+
+	b.SetParallelism(p)
+	b.ReportAllocs()
+	b.ResetTimer()
+	b.RunParallel(func(pb *testing.PB) {
+		for pb.Next() {
+			if _, err := stmt.ExecContext(ctx); err != nil {
+				b.Fatal(err)
+			}
+		}
+	})
+}
+
+func BenchmarkExecContext(b *testing.B) {
+	db := initDB(b,
+		"DROP TABLE IF EXISTS foo",
+		"CREATE TABLE foo (id INT PRIMARY KEY, val CHAR(50))",
+		`INSERT INTO foo VALUES (1, "one")`,
+		`INSERT INTO foo VALUES (2, "two")`,
+	)
+	defer db.Close()
+	for _, p := range []int{1, 2, 3, 4} {
+		b.Run(fmt.Sprintf("%d", p), func(b *testing.B) {
+			benchmarkQueryContext(b, db, p)
+		})
+	}
+}
diff --git a/connection.go b/connection.go
index cdce3e30f..89a4f464a 100644
--- a/connection.go
+++ b/connection.go
@@ -14,9 +14,21 @@ import (
 	"net"
 	"strconv"
 	"strings"
+	"sync"
+	"sync/atomic"
 	"time"
 )
 
+// a copy of context.Context for Go 1.7 and later.
+type mysqlContext interface {
+	Done() <-chan struct{}
+	Err() error
+
+	// They are defined in context.Context, but go-mysql-driver does not use them.
+	// Deadline() (deadline time.Time, ok bool)
+	// Value(key interface{}) interface{}
+}
+
 type mysqlConn struct {
 	buf              buffer
 	netConn          net.Conn
@@ -31,6 +43,19 @@ type mysqlConn struct {
 	sequence         uint8
 	parseTime        bool
 	strict           bool
+
+	// for context support (From Go 1.8)
+	watching bool
+	watcher  chan<- mysqlContext
+	closech  chan struct{}
+	finished chan<- struct{}
+
+	// set non-zero when conn is closed, before closech is closed.
+	// accessed atomically.
+	closed int32
+
+	mu          sync.Mutex // guards following fields
+	canceledErr error      // set non-nil if conn is canceled
 }
 
 // Handles parameters set in DSN after the connection is established
@@ -64,7 +89,7 @@ func (mc *mysqlConn) handleParams() (err error) {
 }
 
 func (mc *mysqlConn) Begin() (driver.Tx, error) {
-	if mc.netConn == nil {
+	if mc.isBroken() {
 		errLog.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
@@ -78,7 +103,7 @@ func (mc *mysqlConn) Begin() (driver.Tx, error) {
 
 func (mc *mysqlConn) Close() (err error) {
 	// Makes Close idempotent
-	if mc.netConn != nil {
+	if !mc.isBroken() {
 		err = mc.writeCommandPacket(comQuit)
 	}
 
@@ -92,19 +117,36 @@ func (mc *mysqlConn) Close() (err error) {
 // is called before auth or on auth failure because MySQL will have already
 // closed the network connection.
 func (mc *mysqlConn) cleanup() {
+	if atomic.SwapInt32(&mc.closed, 1) != 0 {
+		return
+	}
+
 	// Makes cleanup idempotent
-	if mc.netConn != nil {
-		if err := mc.netConn.Close(); err != nil {
-			errLog.Print(err)
+	close(mc.closech)
+	if mc.netConn == nil {
+		return
+	}
+	if err := mc.netConn.Close(); err != nil {
+		errLog.Print(err)
+	}
+}
+
+func (mc *mysqlConn) isBroken() bool {
+	return atomic.LoadInt32(&mc.closed) != 0
+}
+
+func (mc *mysqlConn) error() error {
+	if mc.isBroken() {
+		if err := mc.canceled(); err != nil {
+			return err
 		}
-		mc.netConn = nil
+		return ErrInvalidConn
 	}
-	mc.cfg = nil
-	mc.buf.nc = nil
+	return nil
 }
 
 func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
-	if mc.netConn == nil {
+	if mc.isBroken() {
 		errLog.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
@@ -258,7 +300,7 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
 }
 
 func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
-	if mc.netConn == nil {
+	if mc.isBroken() {
 		errLog.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
@@ -315,7 +357,11 @@ func (mc *mysqlConn) exec(query string) error {
 }
 
 func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) {
-	if mc.netConn == nil {
+	return mc.query(query, args)
+}
+
+func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error) {
+	if mc.isBroken() {
 		errLog.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
@@ -387,3 +433,30 @@ func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
 	}
 	return nil, err
 }
+
+// finish is called when the query has canceled.
+func (mc *mysqlConn) cancel(err error) {
+	mc.mu.Lock()
+	mc.canceledErr = err
+	mc.mu.Unlock()
+	mc.cleanup()
+}
+
+// canceled returns non-nil if the connection was closed due to context cancelation.
+func (mc *mysqlConn) canceled() error {
+	mc.mu.Lock()
+	defer mc.mu.Unlock()
+	return mc.canceledErr
+}
+
+// finish is called when the query has succeeded.
+func (mc *mysqlConn) finish() {
+	if !mc.watching || mc.finished == nil {
+		return
+	}
+	select {
+	case mc.finished <- struct{}{}:
+		mc.watching = false
+	case <-mc.closech:
+	}
+}
diff --git a/connection_go18.go b/connection_go18.go
new file mode 100644
index 000000000..384603a9e
--- /dev/null
+++ b/connection_go18.go
@@ -0,0 +1,207 @@
+// 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/.
+
+// +build go1.8
+
+package mysql
+
+import (
+	"context"
+	"database/sql"
+	"database/sql/driver"
+	"errors"
+)
+
+// Ping implements driver.Pinger interface
+func (mc *mysqlConn) Ping(ctx context.Context) error {
+	if mc.isBroken() {
+		errLog.Print(ErrInvalidConn)
+		return driver.ErrBadConn
+	}
+
+	if err := mc.watchCancel(ctx); err != nil {
+		return err
+	}
+	defer mc.finish()
+
+	if err := mc.writeCommandPacket(comPing); err != nil {
+		return err
+	}
+	if _, err := mc.readResultOK(); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// BeginTx implements driver.ConnBeginTx interface
+func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
+	if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault {
+		// TODO: support isolation levels
+		return nil, errors.New("mysql: isolation levels not supported")
+	}
+	if opts.ReadOnly {
+		// TODO: support read-only transactions
+		return nil, errors.New("mysql: read-only transactions not supported")
+	}
+
+	if err := mc.watchCancel(ctx); err != nil {
+		return nil, err
+	}
+
+	tx, err := mc.Begin()
+	mc.finish()
+	if err != nil {
+		return nil, err
+	}
+
+	select {
+	default:
+	case <-ctx.Done():
+		tx.Rollback()
+		return nil, ctx.Err()
+	}
+	return tx, err
+}
+
+func (mc *mysqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
+	dargs, err := namedValueToValue(args)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := mc.watchCancel(ctx); err != nil {
+		return nil, err
+	}
+
+	rows, err := mc.query(query, dargs)
+	if err != nil {
+		mc.finish()
+		return nil, err
+	}
+	rows.finish = mc.finish
+	return rows, err
+}
+
+func (mc *mysqlConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
+	dargs, err := namedValueToValue(args)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := mc.watchCancel(ctx); err != nil {
+		return nil, err
+	}
+	defer mc.finish()
+
+	return mc.Exec(query, dargs)
+}
+
+func (mc *mysqlConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
+	if err := mc.watchCancel(ctx); err != nil {
+		return nil, err
+	}
+
+	stmt, err := mc.Prepare(query)
+	mc.finish()
+	if err != nil {
+		return nil, err
+	}
+
+	select {
+	default:
+	case <-ctx.Done():
+		stmt.Close()
+		return nil, ctx.Err()
+	}
+	return stmt, nil
+}
+
+func (stmt *mysqlStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
+	dargs, err := namedValueToValue(args)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := stmt.mc.watchCancel(ctx); err != nil {
+		return nil, err
+	}
+
+	rows, err := stmt.query(dargs)
+	if err != nil {
+		stmt.mc.finish()
+		return nil, err
+	}
+	rows.finish = stmt.mc.finish
+	return rows, err
+}
+
+func (stmt *mysqlStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
+	dargs, err := namedValueToValue(args)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := stmt.mc.watchCancel(ctx); err != nil {
+		return nil, err
+	}
+	defer stmt.mc.finish()
+
+	return stmt.Exec(dargs)
+}
+
+func (mc *mysqlConn) watchCancel(ctx context.Context) error {
+	if mc.watching {
+		// Reach here if canceled,
+		// so the connection is already invalid
+		mc.cleanup()
+		return nil
+	}
+	if ctx.Done() == nil {
+		return nil
+	}
+
+	mc.watching = true
+	select {
+	default:
+	case <-ctx.Done():
+		return ctx.Err()
+	}
+	if mc.watcher == nil {
+		return nil
+	}
+
+	mc.watcher <- ctx
+
+	return nil
+}
+
+func (mc *mysqlConn) startWatcher() {
+	watcher := make(chan mysqlContext, 1)
+	mc.watcher = watcher
+	finished := make(chan struct{})
+	mc.finished = finished
+	go func() {
+		for {
+			var ctx mysqlContext
+			select {
+			case ctx = <-watcher:
+			case <-mc.closech:
+				return
+			}
+
+			select {
+			case <-ctx.Done():
+				mc.cancel(ctx.Err())
+			case <-finished:
+			case <-mc.closech:
+				return
+			}
+		}
+	}()
+}
diff --git a/driver.go b/driver.go
index e51d98a3c..f11e14462 100644
--- a/driver.go
+++ b/driver.go
@@ -22,6 +22,11 @@ import (
 	"net"
 )
 
+// watcher interface is used for context support (From Go 1.8)
+type watcher interface {
+	startWatcher()
+}
+
 // MySQLDriver is exported to make the driver directly accessible.
 // In general the driver is used via the database/sql package.
 type MySQLDriver struct{}
@@ -52,6 +57,7 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
 	mc := &mysqlConn{
 		maxAllowedPacket: maxPacketSize,
 		maxWriteSize:     maxPacketSize - 1,
+		closech:          make(chan struct{}),
 	}
 	mc.cfg, err = ParseDSN(dsn)
 	if err != nil {
@@ -60,6 +66,11 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
 	mc.parseTime = mc.cfg.ParseTime
 	mc.strict = mc.cfg.Strict
 
+	// Call startWatcher for context support (From Go 1.8)
+	if s, ok := interface{}(mc).(watcher); ok {
+		s.startWatcher()
+	}
+
 	// Connect to Server
 	if dial, ok := dials[mc.cfg.Net]; ok {
 		mc.netConn, err = dial(mc.cfg.Addr)
diff --git a/driver_go18_test.go b/driver_go18_test.go
index 5a5fa10ff..69d0a2b7d 100644
--- a/driver_go18_test.go
+++ b/driver_go18_test.go
@@ -11,10 +11,28 @@
 package mysql
 
 import (
+	"context"
 	"database/sql"
+	"database/sql/driver"
 	"fmt"
 	"reflect"
 	"testing"
+	"time"
+)
+
+// static interface implementation checks of mysqlConn
+var (
+	_ driver.ConnBeginTx        = &mysqlConn{}
+	_ driver.ConnPrepareContext = &mysqlConn{}
+	_ driver.ExecerContext      = &mysqlConn{}
+	_ driver.Pinger             = &mysqlConn{}
+	_ driver.QueryerContext     = &mysqlConn{}
+)
+
+// static interface implementation checks of mysqlStmt
+var (
+	_ driver.StmtExecContext  = &mysqlStmt{}
+	_ driver.StmtQueryContext = &mysqlStmt{}
 )
 
 func TestMultiResultSet(t *testing.T) {
@@ -196,3 +214,257 @@ func TestSkipResults(t *testing.T) {
 		}
 	})
 }
+
+func TestPingContext(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		ctx, cancel := context.WithCancel(context.Background())
+		cancel()
+		if err := dbt.db.PingContext(ctx); err != context.Canceled {
+			dbt.Errorf("expected context.Canceled, got %v", err)
+		}
+	})
+}
+
+func TestContextCancelExec(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec("CREATE TABLE test (v INTEGER)")
+		ctx, cancel := context.WithCancel(context.Background())
+
+		// Delay execution for just a bit until db.ExecContext has begun.
+		defer time.AfterFunc(100*time.Millisecond, cancel).Stop()
+
+		// This query will be canceled.
+		startTime := time.Now()
+		if _, err := dbt.db.ExecContext(ctx, "INSERT INTO test VALUES (SLEEP(1))"); err != context.Canceled {
+			dbt.Errorf("expected context.Canceled, got %v", err)
+		}
+		if d := time.Since(startTime); d > 500*time.Millisecond {
+			dbt.Errorf("too long execution time: %s", d)
+		}
+
+		// Wait for the INSERT query has done.
+		time.Sleep(time.Second)
+
+		// Check how many times the query is executed.
+		var v int
+		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM test").Scan(&v); err != nil {
+			dbt.Fatalf("%s", err.Error())
+		}
+		if v != 1 { // TODO: need to kill the query, and v should be 0.
+			dbt.Errorf("expected val to be 1, got %d", v)
+		}
+
+		// Context is already canceled, so error should come before execution.
+		if _, err := dbt.db.ExecContext(ctx, "INSERT INTO test VALUES (1)"); err == nil {
+			dbt.Error("expected error")
+		} else if err.Error() != "context canceled" {
+			dbt.Fatalf("unexpected error: %s", err)
+		}
+
+		// The second insert query will fail, so the table has no changes.
+		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM test").Scan(&v); err != nil {
+			dbt.Fatalf("%s", err.Error())
+		}
+		if v != 1 {
+			dbt.Errorf("expected val to be 1, got %d", v)
+		}
+	})
+}
+
+func TestContextCancelQuery(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec("CREATE TABLE test (v INTEGER)")
+		ctx, cancel := context.WithCancel(context.Background())
+
+		// Delay execution for just a bit until db.ExecContext has begun.
+		defer time.AfterFunc(100*time.Millisecond, cancel).Stop()
+
+		// This query will be canceled.
+		startTime := time.Now()
+		if _, err := dbt.db.QueryContext(ctx, "INSERT INTO test VALUES (SLEEP(1))"); err != context.Canceled {
+			dbt.Errorf("expected context.Canceled, got %v", err)
+		}
+		if d := time.Since(startTime); d > 500*time.Millisecond {
+			dbt.Errorf("too long execution time: %s", d)
+		}
+
+		// Wait for the INSERT query has done.
+		time.Sleep(time.Second)
+
+		// Check how many times the query is executed.
+		var v int
+		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM test").Scan(&v); err != nil {
+			dbt.Fatalf("%s", err.Error())
+		}
+		if v != 1 { // TODO: need to kill the query, and v should be 0.
+			dbt.Errorf("expected val to be 1, got %d", v)
+		}
+
+		// Context is already canceled, so error should come before execution.
+		if _, err := dbt.db.QueryContext(ctx, "INSERT INTO test VALUES (1)"); err != context.Canceled {
+			dbt.Errorf("expected context.Canceled, got %v", err)
+		}
+
+		// The second insert query will fail, so the table has no changes.
+		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM test").Scan(&v); err != nil {
+			dbt.Fatalf("%s", err.Error())
+		}
+		if v != 1 {
+			dbt.Errorf("expected val to be 1, got %d", v)
+		}
+	})
+}
+
+func TestContextCancelQueryRow(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec("CREATE TABLE test (v INTEGER)")
+		dbt.mustExec("INSERT INTO test VALUES (1), (2), (3)")
+		ctx, cancel := context.WithCancel(context.Background())
+
+		rows, err := dbt.db.QueryContext(ctx, "SELECT v FROM test")
+		if err != nil {
+			dbt.Fatalf("%s", err.Error())
+		}
+
+		// the first row will be succeed.
+		var v int
+		if !rows.Next() {
+			dbt.Fatalf("unexpected end")
+		}
+		if err := rows.Scan(&v); err != nil {
+			dbt.Fatalf("%s", err.Error())
+		}
+
+		cancel()
+		// make sure the driver recieve cancel request.
+		time.Sleep(100 * time.Millisecond)
+
+		if rows.Next() {
+			dbt.Errorf("expected end, but not")
+		}
+		if err := rows.Err(); err != context.Canceled {
+			dbt.Errorf("expected context.Canceled, got %v", err)
+		}
+	})
+}
+
+func TestContextCancelPrepare(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		ctx, cancel := context.WithCancel(context.Background())
+		cancel()
+		if _, err := dbt.db.PrepareContext(ctx, "SELECT 1"); err != context.Canceled {
+			dbt.Errorf("expected context.Canceled, got %v", err)
+		}
+	})
+}
+
+func TestContextCancelStmtExec(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec("CREATE TABLE test (v INTEGER)")
+		ctx, cancel := context.WithCancel(context.Background())
+		stmt, err := dbt.db.PrepareContext(ctx, "INSERT INTO test VALUES (SLEEP(1))")
+		if err != nil {
+			dbt.Fatalf("unexpected error: %v", err)
+		}
+
+		// Delay execution for just a bit until db.ExecContext has begun.
+		defer time.AfterFunc(100*time.Millisecond, cancel).Stop()
+
+		// This query will be canceled.
+		startTime := time.Now()
+		if _, err := stmt.ExecContext(ctx); err != context.Canceled {
+			dbt.Errorf("expected context.Canceled, got %v", err)
+		}
+		if d := time.Since(startTime); d > 500*time.Millisecond {
+			dbt.Errorf("too long execution time: %s", d)
+		}
+
+		// Wait for the INSERT query has done.
+		time.Sleep(time.Second)
+
+		// Check how many times the query is executed.
+		var v int
+		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM test").Scan(&v); err != nil {
+			dbt.Fatalf("%s", err.Error())
+		}
+		if v != 1 { // TODO: need to kill the query, and v should be 0.
+			dbt.Errorf("expected val to be 1, got %d", v)
+		}
+	})
+}
+
+func TestContextCancelStmtQuery(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec("CREATE TABLE test (v INTEGER)")
+		ctx, cancel := context.WithCancel(context.Background())
+		stmt, err := dbt.db.PrepareContext(ctx, "INSERT INTO test VALUES (SLEEP(1))")
+		if err != nil {
+			dbt.Fatalf("unexpected error: %v", err)
+		}
+
+		// Delay execution for just a bit until db.ExecContext has begun.
+		defer time.AfterFunc(100*time.Millisecond, cancel).Stop()
+
+		// This query will be canceled.
+		startTime := time.Now()
+		if _, err := stmt.QueryContext(ctx); err != context.Canceled {
+			dbt.Errorf("expected context.Canceled, got %v", err)
+		}
+		if d := time.Since(startTime); d > 500*time.Millisecond {
+			dbt.Errorf("too long execution time: %s", d)
+		}
+
+		// Wait for the INSERT query has done.
+		time.Sleep(time.Second)
+
+		// Check how many times the query is executed.
+		var v int
+		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM test").Scan(&v); err != nil {
+			dbt.Fatalf("%s", err.Error())
+		}
+		if v != 1 { // TODO: need to kill the query, and v should be 0.
+			dbt.Errorf("expected val to be 1, got %d", v)
+		}
+	})
+}
+
+func TestContextCancelBegin(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec("CREATE TABLE test (v INTEGER)")
+		ctx, cancel := context.WithCancel(context.Background())
+		tx, err := dbt.db.BeginTx(ctx, nil)
+		if err != nil {
+			dbt.Fatal(err)
+		}
+
+		// Delay execution for just a bit until db.ExecContext has begun.
+		defer time.AfterFunc(100*time.Millisecond, cancel).Stop()
+
+		// This query will be canceled.
+		startTime := time.Now()
+		if _, err := tx.ExecContext(ctx, "INSERT INTO test VALUES (SLEEP(1))"); err != context.Canceled {
+			dbt.Errorf("expected context.Canceled, got %v", err)
+		}
+		if d := time.Since(startTime); d > 500*time.Millisecond {
+			dbt.Errorf("too long execution time: %s", d)
+		}
+
+		// Transaction is canceled, so expect an error.
+		switch err := tx.Commit(); err {
+		case sql.ErrTxDone:
+			// because the transaction has already been rollbacked.
+			// the database/sql package watches ctx
+			// and rollbacks when ctx is canceled.
+		case context.Canceled:
+			// the database/sql package rollbacks on another goroutine,
+			// so the transaction may not be rollbacked depending on goroutine scheduling.
+		default:
+			dbt.Errorf("expected sql.ErrTxDone or context.Canceled, got %v", err)
+		}
+
+		// Context is canceled, so cannot begin a transaction.
+		if _, err := dbt.db.BeginTx(ctx, nil); err != context.Canceled {
+			dbt.Errorf("expected context.Canceled, got %v", err)
+		}
+	})
+}
diff --git a/driver_test.go b/driver_test.go
index 6ca5434a9..206e07cc9 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -1991,3 +1991,11 @@ func TestRejectReadOnly(t *testing.T) {
 		dbt.mustExec("DROP TABLE test")
 	})
 }
+
+func TestPing(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		if err := dbt.db.Ping(); err != nil {
+			dbt.fail("Ping", "Ping", err)
+		}
+	})
+}
diff --git a/packets.go b/packets.go
index 303405a17..0bc120c0e 100644
--- a/packets.go
+++ b/packets.go
@@ -30,6 +30,9 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
 		// read packet header
 		data, err := mc.buf.readNext(4)
 		if err != nil {
+			if cerr := mc.canceled(); cerr != nil {
+				return nil, cerr
+			}
 			errLog.Print(err)
 			mc.Close()
 			return nil, driver.ErrBadConn
@@ -63,6 +66,9 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
 		// read packet body [pktLen bytes]
 		data, err = mc.buf.readNext(pktLen)
 		if err != nil {
+			if cerr := mc.canceled(); cerr != nil {
+				return nil, cerr
+			}
 			errLog.Print(err)
 			mc.Close()
 			return nil, driver.ErrBadConn
@@ -125,8 +131,13 @@ func (mc *mysqlConn) writePacket(data []byte) error {
 
 		// Handle error
 		if err == nil { // n != len(data)
+			mc.cleanup()
 			errLog.Print(ErrMalformPkt)
 		} else {
+			if cerr := mc.canceled(); cerr != nil {
+				return cerr
+			}
+			mc.cleanup()
 			errLog.Print(err)
 		}
 		return driver.ErrBadConn
diff --git a/packets_test.go b/packets_test.go
index b1d64f5c7..31c892d85 100644
--- a/packets_test.go
+++ b/packets_test.go
@@ -244,7 +244,8 @@ func TestReadPacketSplit(t *testing.T) {
 func TestReadPacketFail(t *testing.T) {
 	conn := new(mockConn)
 	mc := &mysqlConn{
-		buf: newBuffer(conn),
+		buf:     newBuffer(conn),
+		closech: make(chan struct{}),
 	}
 
 	// illegal empty (stand-alone) packet
diff --git a/rows.go b/rows.go
index 13905e216..c7f5ee26c 100644
--- a/rows.go
+++ b/rows.go
@@ -28,8 +28,9 @@ type resultSet struct {
 }
 
 type mysqlRows struct {
-	mc *mysqlConn
-	rs resultSet
+	mc     *mysqlConn
+	rs     resultSet
+	finish func()
 }
 
 type binaryRows struct {
@@ -65,12 +66,17 @@ func (rows *mysqlRows) Columns() []string {
 }
 
 func (rows *mysqlRows) Close() (err error) {
+	if f := rows.finish; f != nil {
+		f()
+		rows.finish = nil
+	}
+
 	mc := rows.mc
 	if mc == nil {
 		return nil
 	}
-	if mc.netConn == nil {
-		return ErrInvalidConn
+	if err := mc.error(); err != nil {
+		return err
 	}
 
 	// Remove unread packets from stream
@@ -98,8 +104,8 @@ func (rows *mysqlRows) nextResultSet() (int, error) {
 	if rows.mc == nil {
 		return 0, io.EOF
 	}
-	if rows.mc.netConn == nil {
-		return 0, ErrInvalidConn
+	if err := rows.mc.error(); err != nil {
+		return 0, err
 	}
 
 	// Remove unread packets from stream
@@ -145,8 +151,8 @@ func (rows *binaryRows) NextResultSet() error {
 
 func (rows *binaryRows) Next(dest []driver.Value) error {
 	if mc := rows.mc; mc != nil {
-		if mc.netConn == nil {
-			return ErrInvalidConn
+		if err := mc.error(); err != nil {
+			return err
 		}
 
 		// Fetch next row from stream
@@ -167,8 +173,8 @@ func (rows *textRows) NextResultSet() (err error) {
 
 func (rows *textRows) Next(dest []driver.Value) error {
 	if mc := rows.mc; mc != nil {
-		if mc.netConn == nil {
-			return ErrInvalidConn
+		if err := mc.error(); err != nil {
+			return err
 		}
 
 		// Fetch next row from stream
diff --git a/statement.go b/statement.go
index e5071276a..5855f943f 100644
--- a/statement.go
+++ b/statement.go
@@ -23,7 +23,7 @@ type mysqlStmt struct {
 }
 
 func (stmt *mysqlStmt) Close() error {
-	if stmt.mc == nil || stmt.mc.netConn == nil {
+	if stmt.mc == nil || stmt.mc.isBroken() {
 		// driver.Stmt.Close can be called more than once, thus this function
 		// has to be idempotent.
 		// See also Issue #450 and golang/go#16019.
@@ -45,7 +45,7 @@ func (stmt *mysqlStmt) ColumnConverter(idx int) driver.ValueConverter {
 }
 
 func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
-	if stmt.mc.netConn == nil {
+	if stmt.mc.isBroken() {
 		errLog.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
@@ -89,7 +89,11 @@ func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
 }
 
 func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
-	if stmt.mc.netConn == nil {
+	return stmt.query(args)
+}
+
+func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) {
+	if stmt.mc.isBroken() {
 		errLog.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
diff --git a/transaction.go b/transaction.go
index 33c749b35..5d88c0399 100644
--- a/transaction.go
+++ b/transaction.go
@@ -13,7 +13,7 @@ type mysqlTx struct {
 }
 
 func (tx *mysqlTx) Commit() (err error) {
-	if tx.mc == nil || tx.mc.netConn == nil {
+	if tx.mc == nil || tx.mc.isBroken() {
 		return ErrInvalidConn
 	}
 	err = tx.mc.exec("COMMIT")
@@ -22,7 +22,7 @@ func (tx *mysqlTx) Commit() (err error) {
 }
 
 func (tx *mysqlTx) Rollback() (err error) {
-	if tx.mc == nil || tx.mc.netConn == nil {
+	if tx.mc == nil || tx.mc.isBroken() {
 		return ErrInvalidConn
 	}
 	err = tx.mc.exec("ROLLBACK")
diff --git a/utils_go18.go b/utils_go18.go
index 2aa9d0f18..eaeac4f84 100644
--- a/utils_go18.go
+++ b/utils_go18.go
@@ -10,8 +10,24 @@
 
 package mysql
 
-import "crypto/tls"
+import (
+	"crypto/tls"
+	"database/sql/driver"
+	"errors"
+)
 
 func cloneTLSConfig(c *tls.Config) *tls.Config {
 	return c.Clone()
 }
+
+func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) {
+	dargs := make([]driver.Value, len(named))
+	for n, param := range named {
+		if len(param.Name) > 0 {
+			// TODO: support the use of Named Parameters #561
+			return nil, errors.New("mysql: driver does not support the use of Named Parameters")
+		}
+		dargs[n] = param.Value
+	}
+	return dargs, nil
+}

From 72e0ac3f5f08a49db42c27233733873f95ba9576 Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Sun, 11 Jun 2017 02:02:31 +0800
Subject: [PATCH 021/282] Add atomic wrappers for bool and error (#612)

* Add atomic wrappers for bool and error

Improves #608

* Drop Go 1.2 and Go 1.3 support

* "test" noCopy.Lock()
---
 .travis.yml        |  2 --
 README.md          |  4 +--
 connection.go      | 48 ++++++++--------------------
 connection_go18.go |  2 +-
 packets.go         |  6 ++--
 statement.go       |  6 ++--
 transaction.go     |  4 +--
 utils.go           | 64 +++++++++++++++++++++++++++++++++++++
 utils_test.go      | 80 ++++++++++++++++++++++++++++++++++++++++++++++
 9 files changed, 169 insertions(+), 47 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index a551785b8..a4236330b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,6 @@
 sudo: false
 language: go
 go:
-  - 1.2
-  - 1.3
   - 1.4
   - 1.5
   - 1.6
diff --git a/README.md b/README.md
index d39b29c90..5a8937aa7 100644
--- a/README.md
+++ b/README.md
@@ -39,7 +39,7 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
   * Optional placeholder interpolation
 
 ## Requirements
-  * Go 1.2 or higher
+  * Go 1.4 or higher
   * MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
 
 ---------------------------------------
@@ -279,7 +279,7 @@ Default:        false
 
 `rejectreadOnly=true` causes the driver to reject read-only connections. This
 is for a possible race condition during an automatic failover, where the mysql
-client gets connected to a read-only replica after the failover. 
+client gets connected to a read-only replica after the failover.
 
 Note that this should be a fairly rare case, as an automatic failover normally
 happens when the primary is down, and the race condition shouldn't happen
diff --git a/connection.go b/connection.go
index 89a4f464a..2630f5211 100644
--- a/connection.go
+++ b/connection.go
@@ -14,17 +14,15 @@ import (
 	"net"
 	"strconv"
 	"strings"
-	"sync"
-	"sync/atomic"
 	"time"
 )
 
-// a copy of context.Context for Go 1.7 and later.
+// a copy of context.Context for Go 1.7 and earlier
 type mysqlContext interface {
 	Done() <-chan struct{}
 	Err() error
 
-	// They are defined in context.Context, but go-mysql-driver does not use them.
+	// defined in context.Context, but not used in this driver:
 	// Deadline() (deadline time.Time, ok bool)
 	// Value(key interface{}) interface{}
 }
@@ -44,18 +42,13 @@ type mysqlConn struct {
 	parseTime        bool
 	strict           bool
 
-	// for context support (From Go 1.8)
+	// for context support (Go 1.8+)
 	watching bool
 	watcher  chan<- mysqlContext
 	closech  chan struct{}
 	finished chan<- struct{}
-
-	// set non-zero when conn is closed, before closech is closed.
-	// accessed atomically.
-	closed int32
-
-	mu          sync.Mutex // guards following fields
-	canceledErr error      // set non-nil if conn is canceled
+	canceled atomicError // set non-nil if conn is canceled
+	closed   atomicBool  // set when conn is closed, before closech is closed
 }
 
 // Handles parameters set in DSN after the connection is established
@@ -89,7 +82,7 @@ func (mc *mysqlConn) handleParams() (err error) {
 }
 
 func (mc *mysqlConn) Begin() (driver.Tx, error) {
-	if mc.isBroken() {
+	if mc.closed.IsSet() {
 		errLog.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
@@ -103,7 +96,7 @@ func (mc *mysqlConn) Begin() (driver.Tx, error) {
 
 func (mc *mysqlConn) Close() (err error) {
 	// Makes Close idempotent
-	if !mc.isBroken() {
+	if !mc.closed.IsSet() {
 		err = mc.writeCommandPacket(comQuit)
 	}
 
@@ -117,7 +110,7 @@ func (mc *mysqlConn) Close() (err error) {
 // is called before auth or on auth failure because MySQL will have already
 // closed the network connection.
 func (mc *mysqlConn) cleanup() {
-	if atomic.SwapInt32(&mc.closed, 1) != 0 {
+	if !mc.closed.TrySet(true) {
 		return
 	}
 
@@ -131,13 +124,9 @@ func (mc *mysqlConn) cleanup() {
 	}
 }
 
-func (mc *mysqlConn) isBroken() bool {
-	return atomic.LoadInt32(&mc.closed) != 0
-}
-
 func (mc *mysqlConn) error() error {
-	if mc.isBroken() {
-		if err := mc.canceled(); err != nil {
+	if mc.closed.IsSet() {
+		if err := mc.canceled.Value(); err != nil {
 			return err
 		}
 		return ErrInvalidConn
@@ -146,7 +135,7 @@ func (mc *mysqlConn) error() error {
 }
 
 func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
-	if mc.isBroken() {
+	if mc.closed.IsSet() {
 		errLog.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
@@ -300,7 +289,7 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
 }
 
 func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
-	if mc.isBroken() {
+	if mc.closed.IsSet() {
 		errLog.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
@@ -361,7 +350,7 @@ func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, erro
 }
 
 func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error) {
-	if mc.isBroken() {
+	if mc.closed.IsSet() {
 		errLog.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
@@ -436,19 +425,10 @@ func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
 
 // finish is called when the query has canceled.
 func (mc *mysqlConn) cancel(err error) {
-	mc.mu.Lock()
-	mc.canceledErr = err
-	mc.mu.Unlock()
+	mc.canceled.Set(err)
 	mc.cleanup()
 }
 
-// canceled returns non-nil if the connection was closed due to context cancelation.
-func (mc *mysqlConn) canceled() error {
-	mc.mu.Lock()
-	defer mc.mu.Unlock()
-	return mc.canceledErr
-}
-
 // finish is called when the query has succeeded.
 func (mc *mysqlConn) finish() {
 	if !mc.watching || mc.finished == nil {
diff --git a/connection_go18.go b/connection_go18.go
index 384603a9e..c4ca4c33f 100644
--- a/connection_go18.go
+++ b/connection_go18.go
@@ -19,7 +19,7 @@ import (
 
 // Ping implements driver.Pinger interface
 func (mc *mysqlConn) Ping(ctx context.Context) error {
-	if mc.isBroken() {
+	if mc.closed.IsSet() {
 		errLog.Print(ErrInvalidConn)
 		return driver.ErrBadConn
 	}
diff --git a/packets.go b/packets.go
index 0bc120c0e..a76292488 100644
--- a/packets.go
+++ b/packets.go
@@ -30,7 +30,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
 		// read packet header
 		data, err := mc.buf.readNext(4)
 		if err != nil {
-			if cerr := mc.canceled(); cerr != nil {
+			if cerr := mc.canceled.Value(); cerr != nil {
 				return nil, cerr
 			}
 			errLog.Print(err)
@@ -66,7 +66,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
 		// read packet body [pktLen bytes]
 		data, err = mc.buf.readNext(pktLen)
 		if err != nil {
-			if cerr := mc.canceled(); cerr != nil {
+			if cerr := mc.canceled.Value(); cerr != nil {
 				return nil, cerr
 			}
 			errLog.Print(err)
@@ -134,7 +134,7 @@ func (mc *mysqlConn) writePacket(data []byte) error {
 			mc.cleanup()
 			errLog.Print(ErrMalformPkt)
 		} else {
-			if cerr := mc.canceled(); cerr != nil {
+			if cerr := mc.canceled.Value(); cerr != nil {
 				return cerr
 			}
 			mc.cleanup()
diff --git a/statement.go b/statement.go
index 5855f943f..ae6d33b72 100644
--- a/statement.go
+++ b/statement.go
@@ -23,7 +23,7 @@ type mysqlStmt struct {
 }
 
 func (stmt *mysqlStmt) Close() error {
-	if stmt.mc == nil || stmt.mc.isBroken() {
+	if stmt.mc == nil || stmt.mc.closed.IsSet() {
 		// driver.Stmt.Close can be called more than once, thus this function
 		// has to be idempotent.
 		// See also Issue #450 and golang/go#16019.
@@ -45,7 +45,7 @@ func (stmt *mysqlStmt) ColumnConverter(idx int) driver.ValueConverter {
 }
 
 func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
-	if stmt.mc.isBroken() {
+	if stmt.mc.closed.IsSet() {
 		errLog.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
@@ -93,7 +93,7 @@ func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
 }
 
 func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) {
-	if stmt.mc.isBroken() {
+	if stmt.mc.closed.IsSet() {
 		errLog.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
diff --git a/transaction.go b/transaction.go
index 5d88c0399..417d72793 100644
--- a/transaction.go
+++ b/transaction.go
@@ -13,7 +13,7 @@ type mysqlTx struct {
 }
 
 func (tx *mysqlTx) Commit() (err error) {
-	if tx.mc == nil || tx.mc.isBroken() {
+	if tx.mc == nil || tx.mc.closed.IsSet() {
 		return ErrInvalidConn
 	}
 	err = tx.mc.exec("COMMIT")
@@ -22,7 +22,7 @@ func (tx *mysqlTx) Commit() (err error) {
 }
 
 func (tx *mysqlTx) Rollback() (err error) {
-	if tx.mc == nil || tx.mc.isBroken() {
+	if tx.mc == nil || tx.mc.closed.IsSet() {
 		return ErrInvalidConn
 	}
 	err = tx.mc.exec("ROLLBACK")
diff --git a/utils.go b/utils.go
index bd11c6975..de117479d 100644
--- a/utils.go
+++ b/utils.go
@@ -16,6 +16,7 @@ import (
 	"fmt"
 	"io"
 	"strings"
+	"sync/atomic"
 	"time"
 )
 
@@ -740,3 +741,66 @@ func escapeStringQuotes(buf []byte, v string) []byte {
 
 	return buf[:pos]
 }
+
+/******************************************************************************
+*                               Sync utils                                    *
+******************************************************************************/
+// noCopy may be embedded into structs which must not be copied
+// after the first use.
+//
+// See https://github.com/golang/go/issues/8005#issuecomment-190753527
+// for details.
+type noCopy struct{}
+
+// Lock is a no-op used by -copylocks checker from `go vet`.
+func (*noCopy) Lock() {}
+
+// atomicBool is a wrapper around uint32 for usage as a boolean value with
+// atomic access.
+type atomicBool struct {
+	_noCopy noCopy
+	value   uint32
+}
+
+// IsSet returns wether the current boolean value is true
+func (ab *atomicBool) IsSet() bool {
+	return atomic.LoadUint32(&ab.value) > 0
+}
+
+// Set sets the value of the bool regardless of the previous value
+func (ab *atomicBool) Set(value bool) {
+	if value {
+		atomic.StoreUint32(&ab.value, 1)
+	} else {
+		atomic.StoreUint32(&ab.value, 0)
+	}
+}
+
+// TrySet sets the value of the bool and returns wether the value changed
+func (ab *atomicBool) TrySet(value bool) bool {
+	if value {
+		return atomic.SwapUint32(&ab.value, 1) == 0
+	}
+	return atomic.SwapUint32(&ab.value, 0) > 0
+}
+
+// atomicBool is a wrapper for atomically accessed error values
+type atomicError struct {
+	_noCopy noCopy
+	value   atomic.Value
+}
+
+// Set sets the error value regardless of the previous value.
+// The value must not be nil
+func (ae *atomicError) Set(value error) {
+	ae.value.Store(value)
+}
+
+// Value returns the current error value
+func (ae *atomicError) Value() error {
+	if v := ae.value.Load(); v != nil {
+		// this will panic if the value doesn't implement the error interface
+		return v.(error)
+	}
+	return nil
+}
diff --git a/utils_test.go b/utils_test.go
index 0d6c6684f..0041892db 100644
--- a/utils_test.go
+++ b/utils_test.go
@@ -195,3 +195,83 @@ func TestEscapeQuotes(t *testing.T) {
 	expect("foo''bar", "foo'bar")      // affected
 	expect("foo\"bar", "foo\"bar")     // not affected
 }
+
+func TestAtomicBool(t *testing.T) {
+	var ab atomicBool
+	if ab.IsSet() {
+		t.Fatal("Expected value to be false")
+	}
+
+	ab.Set(true)
+	if ab.value != 1 {
+		t.Fatal("Set(true) did not set value to 1")
+	}
+	if !ab.IsSet() {
+		t.Fatal("Expected value to be true")
+	}
+
+	ab.Set(true)
+	if !ab.IsSet() {
+		t.Fatal("Expected value to be true")
+	}
+
+	ab.Set(false)
+	if ab.value != 0 {
+		t.Fatal("Set(false) did not set value to 0")
+	}
+	if ab.IsSet() {
+		t.Fatal("Expected value to be false")
+	}
+
+	ab.Set(false)
+	if ab.IsSet() {
+		t.Fatal("Expected value to be false")
+	}
+	if ab.TrySet(false) {
+		t.Fatal("Expected TrySet(false) to fail")
+	}
+	if !ab.TrySet(true) {
+		t.Fatal("Expected TrySet(true) to succeed")
+	}
+	if !ab.IsSet() {
+		t.Fatal("Expected value to be true")
+	}
+
+	ab.Set(true)
+	if !ab.IsSet() {
+		t.Fatal("Expected value to be true")
+	}
+	if ab.TrySet(true) {
+		t.Fatal("Expected TrySet(true) to fail")
+	}
+	if !ab.TrySet(false) {
+		t.Fatal("Expected TrySet(false) to succeed")
+	}
+	if ab.IsSet() {
+		t.Fatal("Expected value to be false")
+	}
+
+	ab._noCopy.Lock() // we've "tested" it ¯\_(ツ)_/¯
+}
+
+func TestAtomicError(t *testing.T) {
+	var ae atomicError
+	if ae.Value() != nil {
+		t.Fatal("Expected value to be nil")
+	}
+
+	ae.Set(ErrMalformPkt)
+	if v := ae.Value(); v != ErrMalformPkt {
+		if v == nil {
+			t.Fatal("Value is still nil")
+		}
+		t.Fatal("Error did not match")
+	}
+	ae.Set(ErrPktSync)
+	if ae.Value() == ErrMalformPkt {
+		t.Fatal("Error still matches old error")
+	}
+	if v := ae.Value(); v != ErrPktSync {
+		t.Fatal("Error did not match")
+	}
+}

From a825be04c652d01442384e9dcdf2cdc3f1eda67f Mon Sep 17 00:00:00 2001
From: Achille <achille.roussel@gmail.com>
Date: Sat, 10 Jun 2017 13:04:23 -0700
Subject: [PATCH 022/282] packets: use AppendFormat to format time values
 (#615)

* use AppendFormat to format time values

* drop support for Go 1.4
---
 .travis.yml |  1 -
 AUTHORS     |  1 +
 README.md   |  2 +-
 packets.go  | 12 +++++++-----
 4 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index a4236330b..0bfd17597 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,6 @@
 sudo: false
 language: go
 go:
-  - 1.4
   - 1.5
   - 1.6
   - 1.7
diff --git a/AUTHORS b/AUTHORS
index 5f27aa457..58c749e29 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -12,6 +12,7 @@
 # Individual Persons
 
 Aaron Hopkins <go-sql-driver at die.net>
+Achille Roussel <achille.roussel at gmail.com>
 Arne Hormann <arnehormann at gmail.com>
 Asta Xie <xiemengjun at gmail.com>
 Bulat Gaifullin <gaifullinbf at gmail.com>
diff --git a/README.md b/README.md
index 5a8937aa7..bfe40e15d 100644
--- a/README.md
+++ b/README.md
@@ -39,7 +39,7 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
   * Optional placeholder interpolation
 
 ## Requirements
-  * Go 1.4 or higher
+  * Go 1.5 or higher
   * MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
 
 ---------------------------------------
diff --git a/packets.go b/packets.go
index a76292488..9715067c4 100644
--- a/packets.go
+++ b/packets.go
@@ -1078,17 +1078,19 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 				paramTypes[i+i] = fieldTypeString
 				paramTypes[i+i+1] = 0x00
 
-				var val []byte
+				var a [64]byte
+				var b = a[:0]
+
 				if v.IsZero() {
-					val = []byte("0000-00-00")
+					b = append(b, "0000-00-00"...)
 				} else {
-					val = []byte(v.In(mc.cfg.Loc).Format(timeFormat))
+					b = v.In(mc.cfg.Loc).AppendFormat(b, timeFormat)
 				}
 
 				paramValues = appendLengthEncodedInteger(paramValues,
-					uint64(len(val)),
+					uint64(len(b)),
 				)
-				paramValues = append(paramValues, val...)
+				paramValues = append(paramValues, b...)
 
 			default:
 				return fmt.Errorf("can not convert type: %T", arg)

From d2a817549028fb5dc309dac04f2e695f56fb4ccf Mon Sep 17 00:00:00 2001
From: Maciej Zimnoch <mzimnoch@hotmail.com>
Date: Thu, 15 Jun 2017 18:19:47 -0700
Subject: [PATCH 023/282] Transaction isolation levels (#619)

* Added Gogland IDE internal directory to .gitignore

* Support transaction isolation level in BeginTx

* Review fixes

* Simplyfied TestContextBeginIsolationLevel test

* Applied more review comments

* Applied review remarks
---
 .gitignore          |  1 +
 AUTHORS             |  1 +
 connection_go18.go  | 27 ++++++++++-------------
 driver_go18_test.go | 52 +++++++++++++++++++++++++++++++++++++++++++
 utils_go18.go       | 16 ++++++++++++++
 utils_go18_test.go  | 54 +++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 136 insertions(+), 15 deletions(-)
 create mode 100644 utils_go18_test.go

diff --git a/.gitignore b/.gitignore
index ba8e0cb3a..2de28da16 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@
 Icon?
 ehthumbs.db
 Thumbs.db
+.idea
diff --git a/AUTHORS b/AUTHORS
index 58c749e29..10e2ebb94 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -46,6 +46,7 @@ Lion Yang <lion at aosc.xyz>
 Luca Looz <luca.looz92 at gmail.com>
 Lucas Liu <extrafliu at gmail.com>
 Luke Scott <luke at webconnex.com>
+Maciej Zimnoch <maciej.zimnoch@codilime.com>
 Michael Woolnough <michael.woolnough at gmail.com>
 Nicola Peduzzi <thenikso at gmail.com>
 Olivier Mengué <dolmen at cpan.org>
diff --git a/connection_go18.go b/connection_go18.go
index c4ca4c33f..3ff6ff24f 100644
--- a/connection_go18.go
+++ b/connection_go18.go
@@ -41,10 +41,6 @@ func (mc *mysqlConn) Ping(ctx context.Context) error {
 
 // BeginTx implements driver.ConnBeginTx interface
 func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
-	if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault {
-		// TODO: support isolation levels
-		return nil, errors.New("mysql: isolation levels not supported")
-	}
 	if opts.ReadOnly {
 		// TODO: support read-only transactions
 		return nil, errors.New("mysql: read-only transactions not supported")
@@ -54,19 +50,20 @@ func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver
 		return nil, err
 	}
 
-	tx, err := mc.Begin()
-	mc.finish()
-	if err != nil {
-		return nil, err
-	}
+	defer mc.finish()
 
-	select {
-	default:
-	case <-ctx.Done():
-		tx.Rollback()
-		return nil, ctx.Err()
+	if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault {
+		level, err := mapIsolationLevel(opts.Isolation)
+		if err != nil {
+			return nil, err
+		}
+		err = mc.exec("SET TRANSACTION ISOLATION LEVEL " + level)
+		if err != nil {
+			return nil, err
+		}
 	}
-	return tx, err
+
+	return mc.Begin()
 }
 
 func (mc *mysqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
diff --git a/driver_go18_test.go b/driver_go18_test.go
index 69d0a2b7d..f2184add0 100644
--- a/driver_go18_test.go
+++ b/driver_go18_test.go
@@ -468,3 +468,55 @@ func TestContextCancelBegin(t *testing.T) {
 		}
 	})
 }
+
+func TestContextBeginIsolationLevel(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec("CREATE TABLE test (v INTEGER)")
+		ctx, cancel := context.WithCancel(context.Background())
+		defer cancel()
+
+		tx1, err := dbt.db.BeginTx(ctx, &sql.TxOptions{
+			Isolation: sql.LevelRepeatableRead,
+		})
+		if err != nil {
+			dbt.Fatal(err)
+		}
+
+		tx2, err := dbt.db.BeginTx(ctx, &sql.TxOptions{
+			Isolation: sql.LevelReadCommitted,
+		})
+		if err != nil {
+			dbt.Fatal(err)
+		}
+
+		_, err = tx1.ExecContext(ctx, "INSERT INTO test VALUES (1)")
+		if err != nil {
+			dbt.Fatal(err)
+		}
+
+		var v int
+		row := tx2.QueryRowContext(ctx, "SELECT COUNT(*) FROM test")
+		if err := row.Scan(&v); err != nil {
+			dbt.Fatal(err)
+		}
+		// Because writer transaction wasn't commited yet, it should be available
+		if v != 0 {
+			dbt.Errorf("expected val to be 0, got %d", v)
+		}
+
+		err = tx1.Commit()
+		if err != nil {
+			dbt.Fatal(err)
+		}
+
+		row = tx2.QueryRowContext(ctx, "SELECT COUNT(*) FROM test")
+		if err := row.Scan(&v); err != nil {
+			dbt.Fatal(err)
+		}
+		// Data written by writer transaction is already commited, it should be selectable
+		if v != 1 {
+			dbt.Errorf("expected val to be 1, got %d", v)
+		}
+		tx2.Commit()
+	})
+}
diff --git a/utils_go18.go b/utils_go18.go
index eaeac4f84..7d8c9b16e 100644
--- a/utils_go18.go
+++ b/utils_go18.go
@@ -12,6 +12,7 @@ package mysql
 
 import (
 	"crypto/tls"
+	"database/sql"
 	"database/sql/driver"
 	"errors"
 )
@@ -31,3 +32,18 @@ func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) {
 	}
 	return dargs, nil
 }
+
+func mapIsolationLevel(level driver.IsolationLevel) (string, error) {
+	switch sql.IsolationLevel(level) {
+	case sql.LevelRepeatableRead:
+		return "REPEATABLE READ", nil
+	case sql.LevelReadCommitted:
+		return "READ COMMITTED", nil
+	case sql.LevelReadUncommitted:
+		return "READ UNCOMMITTED", nil
+	case sql.LevelSerializable:
+		return "SERIALIZABLE", nil
+	default:
+		return "", errors.New("mysql: unsupported isolation level: " + string(level))
+	}
+}
diff --git a/utils_go18_test.go b/utils_go18_test.go
new file mode 100644
index 000000000..856c25f56
--- /dev/null
+++ b/utils_go18_test.go
@@ -0,0 +1,54 @@
+// 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/.
+
+// +build go1.8
+
+package mysql
+
+import (
+	"database/sql"
+	"database/sql/driver"
+	"testing"
+)
+
+func TestIsolationLevelMapping(t *testing.T) {
+
+	data := []struct {
+		level    driver.IsolationLevel
+		expected string
+	}{
+		{
+			level:    driver.IsolationLevel(sql.LevelReadCommitted),
+			expected: "READ COMMITTED",
+		},
+		{
+			level:    driver.IsolationLevel(sql.LevelRepeatableRead),
+			expected: "REPEATABLE READ",
+		},
+		{
+			level:    driver.IsolationLevel(sql.LevelReadUncommitted),
+			expected: "READ UNCOMMITTED",
+		},
+		{
+			level:    driver.IsolationLevel(sql.LevelSerializable),
+			expected: "SERIALIZABLE",
+		},
+	}
+
+	for i, td := range data {
+		if actual, err := mapIsolationLevel(td.level); actual != td.expected || err != nil {
+			t.Fatal(i, td.expected, actual, err)
+		}
+	}
+
+	// check unsupported mapping
+	if actual, err := mapIsolationLevel(driver.IsolationLevel(sql.LevelLinearizable)); actual != "" || err == nil {
+		t.Fatal("Expected error on unsupported isolation level")
+	}
+
+}

From 8fefef06da7707b5a3cda42195a6b409c9c9ab5c Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Fri, 16 Jun 2017 21:16:31 +0800
Subject: [PATCH 024/282] utils: guard tlsConfigRegister by a lock (#613)

* utils: guard tlsConfigRegister by a lock

Fixes #610

* utils: move TLSConfig lock and clone logic to helper func
---
 dsn.go   |  4 +---
 utils.go | 16 ++++++++++++++++
 2 files changed, 17 insertions(+), 3 deletions(-)

diff --git a/dsn.go b/dsn.go
index 199c9b177..ca1f2bf14 100644
--- a/dsn.go
+++ b/dsn.go
@@ -528,9 +528,7 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 					return fmt.Errorf("invalid value for TLS config name: %v", err)
 				}
 
-				if tlsConfig, ok := tlsConfigRegister[name]; ok {
-					tlsConfig = cloneTLSConfig(tlsConfig)
-
+				if tlsConfig := getTLSConfigClone(name); tlsConfig != nil {
 					if len(tlsConfig.ServerName) == 0 && !tlsConfig.InsecureSkipVerify {
 						host, _, err := net.SplitHostPort(cfg.Addr)
 						if err == nil {
diff --git a/utils.go b/utils.go
index de117479d..82da83099 100644
--- a/utils.go
+++ b/utils.go
@@ -16,11 +16,13 @@ import (
 	"fmt"
 	"io"
 	"strings"
+	"sync"
 	"sync/atomic"
 	"time"
 )
 
 var (
+	tlsConfigLock     sync.RWMutex
 	tlsConfigRegister map[string]*tls.Config // Register for custom tls.Configs
 )
 
@@ -54,19 +56,32 @@ func RegisterTLSConfig(key string, config *tls.Config) error {
 		return fmt.Errorf("key '%s' is reserved", key)
 	}
 
+	tlsConfigLock.Lock()
 	if tlsConfigRegister == nil {
 		tlsConfigRegister = make(map[string]*tls.Config)
 	}
 
 	tlsConfigRegister[key] = config
+	tlsConfigLock.Unlock()
 	return nil
 }
 
 // DeregisterTLSConfig removes the tls.Config associated with key.
 func DeregisterTLSConfig(key string) {
+	tlsConfigLock.Lock()
 	if tlsConfigRegister != nil {
 		delete(tlsConfigRegister, key)
 	}
+	tlsConfigLock.Unlock()
+}
+
+func getTLSConfigClone(key string) (config *tls.Config) {
+	tlsConfigLock.RLock()
+	if v, ok := tlsConfigRegister[key]; ok {
+		config = cloneTLSConfig(v)
+	}
+	tlsConfigLock.RUnlock()
+	return
 }
 
 // Returns the bool value of the input.
@@ -745,6 +760,7 @@ func escapeStringQuotes(buf []byte, v string) []byte {
 /******************************************************************************
 *                               Sync utils                                    *
 ******************************************************************************/
+
 // noCopy may be embedded into structs which must not be copied
 // after the first use.
 //

From bf7f34fef176d890dfcc6e94319ac747291bc09a Mon Sep 17 00:00:00 2001
From: Ichinose Shogo <shogo82148@gmail.com>
Date: Mon, 3 Jul 2017 15:14:36 +0900
Subject: [PATCH 025/282] Start the watcher after handshake (#627)

Fixes #626
---
 driver.go | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/driver.go b/driver.go
index f11e14462..c341b6680 100644
--- a/driver.go
+++ b/driver.go
@@ -66,11 +66,6 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
 	mc.parseTime = mc.cfg.ParseTime
 	mc.strict = mc.cfg.Strict
 
-	// Call startWatcher for context support (From Go 1.8)
-	if s, ok := interface{}(mc).(watcher); ok {
-		s.startWatcher()
-	}
-
 	// Connect to Server
 	if dial, ok := dials[mc.cfg.Net]; ok {
 		mc.netConn, err = dial(mc.cfg.Addr)
@@ -92,6 +87,11 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
 		}
 	}
 
+	// Call startWatcher for context support (From Go 1.8)
+	if s, ok := interface{}(mc).(watcher); ok {
+		s.startWatcher()
+	}
+
 	mc.buf = newBuffer(mc.netConn)
 
 	// Set I/O timeouts

From 3955978caca48c1658a4bb7a9c6a0f084e326af3 Mon Sep 17 00:00:00 2001
From: Jeff Hodges <jeff@somethingsimilar.com>
Date: Sat, 15 Jul 2017 12:24:08 -0700
Subject: [PATCH 026/282] sort the generic config.Params in the DSN (#637)

This sorts the config.Params values as they are placed in the DSN.

This makes it easier for other projects to test that they are munging their DSNs
correctly and do other similar tasks.
---
 AUTHORS     |  1 +
 dsn.go      | 10 ++++++++--
 dsn_test.go | 16 ++++++++++++++++
 3 files changed, 25 insertions(+), 2 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 10e2ebb94..5526e3e90 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -33,6 +33,7 @@ ICHINOSE Shogo <shogo82148 at gmail.com>
 INADA Naoki <songofacandy at gmail.com>
 Jacek Szwec <szwec.jacek at gmail.com>
 James Harr <james.harr at gmail.com>
+Jeff Hodges <jeff at somethingsimilar.com>
 Jian Zhen <zhenjl at gmail.com>
 Joshua Prunier <joshua.prunier at gmail.com>
 Julien Lefevre <julien.lefevr at gmail.com>
diff --git a/dsn.go b/dsn.go
index ca1f2bf14..ab2fdfc6a 100644
--- a/dsn.go
+++ b/dsn.go
@@ -15,6 +15,7 @@ import (
 	"fmt"
 	"net"
 	"net/url"
+	"sort"
 	"strconv"
 	"strings"
 	"time"
@@ -257,7 +258,12 @@ func (cfg *Config) FormatDSN() string {
 
 	// other params
 	if cfg.Params != nil {
-		for param, value := range cfg.Params {
+		var params []string
+		for param := range cfg.Params {
+			params = append(params, param)
+		}
+		sort.Strings(params)
+		for _, param := range params {
 			if hasParam {
 				buf.WriteByte('&')
 			} else {
@@ -267,7 +273,7 @@ func (cfg *Config) FormatDSN() string {
 
 			buf.WriteString(param)
 			buf.WriteByte('=')
-			buf.WriteString(url.QueryEscape(value))
+			buf.WriteString(url.QueryEscape(cfg.Params[param]))
 		}
 	}
 
diff --git a/dsn_test.go b/dsn_test.go
index 4fd76b813..d1957f8e8 100644
--- a/dsn_test.go
+++ b/dsn_test.go
@@ -220,6 +220,22 @@ func TestDSNUnsafeCollation(t *testing.T) {
 	}
 }
 
+func TestParamsAreSorted(t *testing.T) {
+	expected := "/dbname?interpolateParams=true&foobar=baz&quux=loo"
+	dsn := &Config{
+		DBName:            "dbname",
+		InterpolateParams: true,
+		Params: map[string]string{
+			"quux":   "loo",
+			"foobar": "baz",
+		},
+	}
+	actual := dsn.FormatDSN()
+	if actual != expected {
+		t.Errorf("generic Config.Params were not sorted: want %#v, got %#v", expected, actual)
+	}
+}
+
 func BenchmarkParseDSN(b *testing.B) {
 	b.ReportAllocs()
 

From 37b91d8876474f11b8e419f2fa65b15824ab9b9c Mon Sep 17 00:00:00 2001
From: Alex Lee <lishuode@outlook.com>
Date: Fri, 18 Aug 2017 04:13:38 +0000
Subject: [PATCH 027/282] Mark `AllowNativePassword` in DSN param string be
 true by default. (#644)

---
 AUTHORS     |  1 +
 README.md   |  4 ++--
 dsn.go      | 11 ++++++-----
 dsn_test.go | 36 ++++++++++++++++++++----------------
 4 files changed, 29 insertions(+), 23 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 5526e3e90..646159940 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -56,6 +56,7 @@ Paul Bonser <misterpib at gmail.com>
 Peter Schultz <peter.schultz at classmarkets.com>
 Rebecca Chin <rchin at pivotal.io>
 Runrioter Wung <runrioter at gmail.com>
+Shuode Li <elemount at qq.com>
 Soroush Pour <me at soroushjp.com>
 Stan Putrya <root.vagner at gmail.com>
 Stanley Gunawan <gunawan.stanley at gmail.com>
diff --git a/README.md b/README.md
index bfe40e15d..925a69cf1 100644
--- a/README.md
+++ b/README.md
@@ -138,9 +138,9 @@ Default:        false
 ```
 Type:           bool
 Valid Values:   true, false
-Default:        false
+Default:        true
 ```
-`allowNativePasswords=true` allows the usage of the mysql native password method.
+`allowNativePasswords=false` disallows the usage of MySQL native password method.
 
 ##### `allowOldPasswords`
 
diff --git a/dsn.go b/dsn.go
index ab2fdfc6a..9cf4bb94a 100644
--- a/dsn.go
+++ b/dsn.go
@@ -104,12 +104,12 @@ func (cfg *Config) FormatDSN() string {
 		}
 	}
 
-	if cfg.AllowNativePasswords {
+	if !cfg.AllowNativePasswords {
 		if hasParam {
-			buf.WriteString("&allowNativePasswords=true")
+			buf.WriteString("&allowNativePasswords=false")
 		} else {
 			hasParam = true
-			buf.WriteString("?allowNativePasswords=true")
+			buf.WriteString("?allowNativePasswords=false")
 		}
 	}
 
@@ -284,8 +284,9 @@ func (cfg *Config) FormatDSN() string {
 func ParseDSN(dsn string) (cfg *Config, err error) {
 	// New config with some default values
 	cfg = &Config{
-		Loc:       time.UTC,
-		Collation: defaultCollation,
+		Loc:                  time.UTC,
+		Collation:            defaultCollation,
+		AllowNativePasswords: true,
 	}
 
 	// [user[:password]@][net[(addr)]]/dbname[?param1=value1&paramN=valueN]
diff --git a/dsn_test.go b/dsn_test.go
index d1957f8e8..671af020b 100644
--- a/dsn_test.go
+++ b/dsn_test.go
@@ -22,46 +22,49 @@ var testDSNs = []struct {
 	out *Config
 }{{
 	"username:password@protocol(address)/dbname?param=value",
-	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8_general_ci", Loc: time.UTC},
+	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true},
 }, {
 	"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true",
-	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8_general_ci", Loc: time.UTC, ColumnsWithAlias: true},
+	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true, ColumnsWithAlias: true},
 }, {
 	"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true&multiStatements=true",
-	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8_general_ci", Loc: time.UTC, ColumnsWithAlias: true, MultiStatements: true},
+	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true, ColumnsWithAlias: true, MultiStatements: true},
 }, {
 	"user@unix(/path/to/socket)/dbname?charset=utf8",
-	&Config{User: "user", Net: "unix", Addr: "/path/to/socket", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8_general_ci", Loc: time.UTC},
+	&Config{User: "user", Net: "unix", Addr: "/path/to/socket", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true},
 }, {
 	"user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true",
-	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, TLSConfig: "true"},
+	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true, TLSConfig: "true"},
 }, {
 	"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify",
-	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8mb4,utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, TLSConfig: "skip-verify"},
+	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8mb4,utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true, TLSConfig: "skip-verify"},
 }, {
 	"user:password@/dbname?loc=UTC&timeout=30s&readTimeout=1s&writeTimeout=1s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci&maxAllowedPacket=16777216",
-	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_unicode_ci", Loc: time.UTC, Timeout: 30 * time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, AllowAllFiles: true, AllowOldPasswords: true, ClientFoundRows: true, MaxAllowedPacket: 16777216},
+	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_unicode_ci", Loc: time.UTC, AllowNativePasswords: true, Timeout: 30 * time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, AllowAllFiles: true, AllowOldPasswords: true, ClientFoundRows: true, MaxAllowedPacket: 16777216},
+}, {
+	"user:password@/dbname?allowNativePasswords=false",
+	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: false},
 }, {
 	"user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local",
-	&Config{User: "user", Passwd: "p@ss(word)", Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:80", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.Local},
+	&Config{User: "user", Passwd: "p@ss(word)", Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:80", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.Local, AllowNativePasswords: true},
 }, {
 	"/dbname",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true},
 }, {
 	"@/",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true},
 }, {
 	"/",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true},
 }, {
 	"",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true},
 }, {
 	"user:p@/ssword@/",
-	&Config{User: "user", Passwd: "p@/ssword", Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC},
+	&Config{User: "user", Passwd: "p@/ssword", Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true},
 }, {
 	"unix/?arg=%2Fsome%2Fpath.ext",
-	&Config{Net: "unix", Addr: "/tmp/mysql.sock", Params: map[string]string{"arg": "/some/path.ext"}, Collation: "utf8_general_ci", Loc: time.UTC},
+	&Config{Net: "unix", Addr: "/tmp/mysql.sock", Params: map[string]string{"arg": "/some/path.ext"}, Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true},
 }}
 
 func TestDSNParser(t *testing.T) {
@@ -223,8 +226,9 @@ func TestDSNUnsafeCollation(t *testing.T) {
 func TestParamsAreSorted(t *testing.T) {
 	expected := "/dbname?interpolateParams=true&foobar=baz&quux=loo"
 	dsn := &Config{
-		DBName:            "dbname",
-		InterpolateParams: true,
+		DBName:               "dbname",
+		InterpolateParams:    true,
+		AllowNativePasswords: true,
 		Params: map[string]string{
 			"quux":   "loo",
 			"foobar": "baz",

From 21d7e97c9f760ca685a01ecea202e1c84276daa1 Mon Sep 17 00:00:00 2001
From: Alex Lee <lishuode@outlook.com>
Date: Mon, 21 Aug 2017 04:58:21 +0000
Subject: [PATCH 028/282] Fix `mysql_clear_password` plugin on auth switch
 panic. (#646)

Fixes #636
---
 packets.go | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/packets.go b/packets.go
index 9715067c4..461fbed9f 100644
--- a/packets.go
+++ b/packets.go
@@ -352,7 +352,9 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
 // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
 func (mc *mysqlConn) writeOldAuthPacket(cipher []byte) error {
 	// User password
-	scrambleBuff := scrambleOldPassword(cipher, []byte(mc.cfg.Passwd))
+	// https://dev.mysql.com/doc/internals/en/old-password-authentication.html
+	// Old password authentication only need and will need 8-byte challenge.
+	scrambleBuff := scrambleOldPassword(cipher[:8], []byte(mc.cfg.Passwd))
 
 	// Calculate the packet length and add a tailing 0
 	pktLen := len(scrambleBuff) + 1
@@ -392,7 +394,9 @@ func (mc *mysqlConn) writeClearAuthPacket() error {
 //  Native password authentication method
 // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
 func (mc *mysqlConn) writeNativeAuthPacket(cipher []byte) error {
-	scrambleBuff := scramblePassword(cipher, []byte(mc.cfg.Passwd))
+	// https://dev.mysql.com/doc/internals/en/secure-password-authentication.html
+	// Native password authentication only need and will need 20-byte challenge.
+	scrambleBuff := scramblePassword(cipher[0:20], []byte(mc.cfg.Passwd))
 
 	// Calculate the packet length and add a tailing 0
 	pktLen := len(scrambleBuff)
@@ -495,7 +499,7 @@ func (mc *mysqlConn) readResultOK() ([]byte, error) {
 			if len(data) > 1 {
 				pluginEndIndex := bytes.IndexByte(data, 0x00)
 				plugin := string(data[1:pluginEndIndex])
-				cipher := data[pluginEndIndex+1 : len(data)-1]
+				cipher := data[pluginEndIndex+1:]
 
 				switch plugin {
 				case "mysql_old_password":

From 26471af196a17ee75a22e6481b5a5897fb16b081 Mon Sep 17 00:00:00 2001
From: Arne Hormann <arnehormann@users.noreply.github.com>
Date: Tue, 22 Aug 2017 23:48:09 +0200
Subject: [PATCH 029/282] fewer driver.ErrBadConn to prevent repeated queries
 (#302)

According to the database/sql/driver documentation, ErrBadConn should only
be used when the database was not affected. The driver restarts the same
query on a different connection, then.
The mysql driver did not follow this advice, so queries were repeated if
ErrBadConn is returned but a query succeeded.

This is fixed by changing most ErrBadConn errors to ErrInvalidConn.

The only valid returns of ErrBadConn are at the beginning of a database
interaction when no data was sent to the database yet.

Those valid cases are located the following funcs before attempting to write
to the network or if 0 bytes were written:

* Begin
* BeginTx
* Exec
* ExecContext
* Prepare
* PrepareContext
* Query
* QueryContext

Commit and Rollback could arguably also be on that list, but are left out as
some engines like MyISAM are not supporting transactions.

Tests in b/packets_test.go were changed because they simulate a read not
preceded by a write to the db. This cannot happen as the client has to send
the query first.
---
 connection.go   | 23 ++++++++++++++++-------
 errors.go       |  6 ++++++
 packets.go      | 28 ++++++++++++++++------------
 packets_test.go | 13 ++++++-------
 statement.go    |  4 ++--
 5 files changed, 46 insertions(+), 28 deletions(-)

diff --git a/connection.go b/connection.go
index 2630f5211..b07528653 100644
--- a/connection.go
+++ b/connection.go
@@ -81,6 +81,16 @@ func (mc *mysqlConn) handleParams() (err error) {
 	return
 }
 
+func (mc *mysqlConn) markBadConn(err error) error {
+	if mc == nil {
+		return err
+	}
+	if err != errBadConnNoWrite {
+		return err
+	}
+	return driver.ErrBadConn
+}
+
 func (mc *mysqlConn) Begin() (driver.Tx, error) {
 	if mc.closed.IsSet() {
 		errLog.Print(ErrInvalidConn)
@@ -90,8 +100,7 @@ func (mc *mysqlConn) Begin() (driver.Tx, error) {
 	if err == nil {
 		return &mysqlTx{mc}, err
 	}
-
-	return nil, err
+	return nil, mc.markBadConn(err)
 }
 
 func (mc *mysqlConn) Close() (err error) {
@@ -142,7 +151,7 @@ func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
 	// Send command
 	err := mc.writeCommandPacketStr(comStmtPrepare, query)
 	if err != nil {
-		return nil, err
+		return nil, mc.markBadConn(err)
 	}
 
 	stmt := &mysqlStmt{
@@ -176,7 +185,7 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
 	if buf == nil {
 		// can not take the buffer. Something must be wrong with the connection
 		errLog.Print(ErrBusyBuffer)
-		return "", driver.ErrBadConn
+		return "", ErrInvalidConn
 	}
 	buf = buf[:0]
 	argPos := 0
@@ -314,14 +323,14 @@ func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, err
 			insertId:     int64(mc.insertId),
 		}, err
 	}
-	return nil, err
+	return nil, mc.markBadConn(err)
 }
 
 // Internal function to execute commands
 func (mc *mysqlConn) exec(query string) error {
 	// Send command
 	if err := mc.writeCommandPacketStr(comQuery, query); err != nil {
-		return err
+		return mc.markBadConn(err)
 	}
 
 	// Read Result
@@ -390,7 +399,7 @@ func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error)
 			return rows, err
 		}
 	}
-	return nil, err
+	return nil, mc.markBadConn(err)
 }
 
 // Gets the value of the given MySQL System Variable
diff --git a/errors.go b/errors.go
index 857854e14..d0d0d2e11 100644
--- a/errors.go
+++ b/errors.go
@@ -31,6 +31,12 @@ var (
 	ErrPktSyncMul        = errors.New("commands out of sync. Did you run multiple statements at once?")
 	ErrPktTooLarge       = errors.New("packet for query is too large. Try adjusting the 'max_allowed_packet' variable on the server")
 	ErrBusyBuffer        = errors.New("busy buffer")
+
+	// errBadConnNoWrite is used for connection errors where nothing was sent to the database yet.
+	// If this happens first in a function starting a database interaction, it should be replaced by driver.ErrBadConn
+	// to trigger a resend.
+	// See https://github.com/go-sql-driver/mysql/pull/302
+	errBadConnNoWrite = errors.New("bad connection")
 )
 
 var errLog = Logger(log.New(os.Stderr, "[mysql] ", log.Ldate|log.Ltime|log.Lshortfile))
diff --git a/packets.go b/packets.go
index 461fbed9f..79648d572 100644
--- a/packets.go
+++ b/packets.go
@@ -35,7 +35,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
 			}
 			errLog.Print(err)
 			mc.Close()
-			return nil, driver.ErrBadConn
+			return nil, ErrInvalidConn
 		}
 
 		// packet length [24 bit]
@@ -57,7 +57,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
 			if prevData == nil {
 				errLog.Print(ErrMalformPkt)
 				mc.Close()
-				return nil, driver.ErrBadConn
+				return nil, ErrInvalidConn
 			}
 
 			return prevData, nil
@@ -71,7 +71,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
 			}
 			errLog.Print(err)
 			mc.Close()
-			return nil, driver.ErrBadConn
+			return nil, ErrInvalidConn
 		}
 
 		// return data if this was the last packet
@@ -137,10 +137,14 @@ func (mc *mysqlConn) writePacket(data []byte) error {
 			if cerr := mc.canceled.Value(); cerr != nil {
 				return cerr
 			}
+			if n == 0 && pktLen == len(data)-4 {
+				// only for the first loop iteration when nothing was written yet
+				return errBadConnNoWrite
+			}
 			mc.cleanup()
 			errLog.Print(err)
 		}
-		return driver.ErrBadConn
+		return ErrInvalidConn
 	}
 }
 
@@ -274,7 +278,7 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
 	if data == nil {
 		// can not take the buffer. Something must be wrong with the connection
 		errLog.Print(ErrBusyBuffer)
-		return driver.ErrBadConn
+		return errBadConnNoWrite
 	}
 
 	// ClientFlags [32 bit]
@@ -362,7 +366,7 @@ func (mc *mysqlConn) writeOldAuthPacket(cipher []byte) error {
 	if data == nil {
 		// can not take the buffer. Something must be wrong with the connection
 		errLog.Print(ErrBusyBuffer)
-		return driver.ErrBadConn
+		return errBadConnNoWrite
 	}
 
 	// Add the scrambled password [null terminated string]
@@ -381,7 +385,7 @@ func (mc *mysqlConn) writeClearAuthPacket() error {
 	if data == nil {
 		// can not take the buffer. Something must be wrong with the connection
 		errLog.Print(ErrBusyBuffer)
-		return driver.ErrBadConn
+		return errBadConnNoWrite
 	}
 
 	// Add the clear password [null terminated string]
@@ -404,7 +408,7 @@ func (mc *mysqlConn) writeNativeAuthPacket(cipher []byte) error {
 	if data == nil {
 		// can not take the buffer. Something must be wrong with the connection
 		errLog.Print(ErrBusyBuffer)
-		return driver.ErrBadConn
+		return errBadConnNoWrite
 	}
 
 	// Add the scramble
@@ -425,7 +429,7 @@ func (mc *mysqlConn) writeCommandPacket(command byte) error {
 	if data == nil {
 		// can not take the buffer. Something must be wrong with the connection
 		errLog.Print(ErrBusyBuffer)
-		return driver.ErrBadConn
+		return errBadConnNoWrite
 	}
 
 	// Add command byte
@@ -444,7 +448,7 @@ func (mc *mysqlConn) writeCommandPacketStr(command byte, arg string) error {
 	if data == nil {
 		// can not take the buffer. Something must be wrong with the connection
 		errLog.Print(ErrBusyBuffer)
-		return driver.ErrBadConn
+		return errBadConnNoWrite
 	}
 
 	// Add command byte
@@ -465,7 +469,7 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
 	if data == nil {
 		// can not take the buffer. Something must be wrong with the connection
 		errLog.Print(ErrBusyBuffer)
-		return driver.ErrBadConn
+		return errBadConnNoWrite
 	}
 
 	// Add command byte
@@ -931,7 +935,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 	if data == nil {
 		// can not take the buffer. Something must be wrong with the connection
 		errLog.Print(ErrBusyBuffer)
-		return driver.ErrBadConn
+		return errBadConnNoWrite
 	}
 
 	// command [1 byte]
diff --git a/packets_test.go b/packets_test.go
index 31c892d85..2f8207511 100644
--- a/packets_test.go
+++ b/packets_test.go
@@ -9,7 +9,6 @@
 package mysql
 
 import (
-	"database/sql/driver"
 	"errors"
 	"net"
 	"testing"
@@ -252,8 +251,8 @@ func TestReadPacketFail(t *testing.T) {
 	conn.data = []byte{0x00, 0x00, 0x00, 0x00}
 	conn.maxReads = 1
 	_, err := mc.readPacket()
-	if err != driver.ErrBadConn {
-		t.Errorf("expected ErrBadConn, got %v", err)
+	if err != ErrInvalidConn {
+		t.Errorf("expected ErrInvalidConn, got %v", err)
 	}
 
 	// reset
@@ -264,8 +263,8 @@ func TestReadPacketFail(t *testing.T) {
 	// fail to read header
 	conn.closed = true
 	_, err = mc.readPacket()
-	if err != driver.ErrBadConn {
-		t.Errorf("expected ErrBadConn, got %v", err)
+	if err != ErrInvalidConn {
+		t.Errorf("expected ErrInvalidConn, got %v", err)
 	}
 
 	// reset
@@ -277,7 +276,7 @@ func TestReadPacketFail(t *testing.T) {
 	// fail to read body
 	conn.maxReads = 1
 	_, err = mc.readPacket()
-	if err != driver.ErrBadConn {
-		t.Errorf("expected ErrBadConn, got %v", err)
+	if err != ErrInvalidConn {
+		t.Errorf("expected ErrInvalidConn, got %v", err)
 	}
 }
diff --git a/statement.go b/statement.go
index ae6d33b72..ae223507f 100644
--- a/statement.go
+++ b/statement.go
@@ -52,7 +52,7 @@ func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
 	// Send command
 	err := stmt.writeExecutePacket(args)
 	if err != nil {
-		return nil, err
+		return nil, stmt.mc.markBadConn(err)
 	}
 
 	mc := stmt.mc
@@ -100,7 +100,7 @@ func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) {
 	// Send command
 	err := stmt.writeExecutePacket(args)
 	if err != nil {
-		return nil, err
+		return nil, stmt.mc.markBadConn(err)
 	}
 
 	mc := stmt.mc

From 14f4292c08c0daf077adddd5824cffc82a797ff4 Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Thu, 14 Sep 2017 09:49:16 +0200
Subject: [PATCH 030/282] travis: fix MySQL config (#661)

* travis: test with Go 1.9

* travis: adjust MySQL config

Fixes #660
---
 .travis.yml | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 0bfd17597..fa0b2c933 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,18 +5,25 @@ go:
   - 1.6
   - 1.7
   - 1.8
+  - 1.9
   - tip
 
 before_install:
   - go get golang.org/x/tools/cmd/cover
   - go get github.com/mattn/goveralls
 
+before_script:
+  - echo -e "[server]\ninnodb_log_file_size=256MB\ninnodb_buffer_pool_size=512MB\nmax_allowed_packet=16MB" | sudo tee -a /etc/mysql/my.cnf
+  - sudo service mysql restart
+  - .travis/wait_mysql.sh
+  - mysql -e 'create database gotest;'
+
 matrix:
   include:
     - env: DB=MYSQL57
       sudo: required
       dist: trusty
-      go: 1.8
+      go: 1.9
       services:
         - docker
       before_install:
@@ -38,7 +45,7 @@ matrix:
     - env: DB=MARIA55
       sudo: required
       dist: trusty
-      go: 1.8
+      go: 1.9
       services:
         - docker
       before_install:
@@ -60,7 +67,7 @@ matrix:
     - env: DB=MARIA10_1
       sudo: required
       dist: trusty
-      go: 1.8
+      go: 1.9
       services:
         - docker
       before_install:
@@ -79,9 +86,6 @@ matrix:
         - export MYSQL_TEST_ADDR=127.0.0.1:3307
         - export MYSQL_TEST_CONCURRENT=1
 
-
-before_script:
-  - mysql -e 'create database gotest;'
 script:
   - go test -v -covermode=count -coverprofile=coverage.out
   - go vet ./...

From 1548d61ac1b48a30add201183a992009bc02af20 Mon Sep 17 00:00:00 2001
From: Ichinose Shogo <shogo82148@gmail.com>
Date: Fri, 15 Sep 2017 14:15:32 +0900
Subject: [PATCH 031/282] support READ-ONLY transactions (#618)

---
 connection.go       | 12 +++++++++++-
 connection_go18.go  |  9 +--------
 driver_go18_test.go | 38 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 50 insertions(+), 9 deletions(-)

diff --git a/connection.go b/connection.go
index b07528653..948a59561 100644
--- a/connection.go
+++ b/connection.go
@@ -92,11 +92,21 @@ func (mc *mysqlConn) markBadConn(err error) error {
 }
 
 func (mc *mysqlConn) Begin() (driver.Tx, error) {
+	return mc.begin(false)
+}
+
+func (mc *mysqlConn) begin(readOnly bool) (driver.Tx, error) {
 	if mc.closed.IsSet() {
 		errLog.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
-	err := mc.exec("START TRANSACTION")
+	var q string
+	if readOnly {
+		q = "START TRANSACTION READ ONLY"
+	} else {
+		q = "START TRANSACTION"
+	}
+	err := mc.exec(q)
 	if err == nil {
 		return &mysqlTx{mc}, err
 	}
diff --git a/connection_go18.go b/connection_go18.go
index 3ff6ff24f..48a9cca64 100644
--- a/connection_go18.go
+++ b/connection_go18.go
@@ -14,7 +14,6 @@ import (
 	"context"
 	"database/sql"
 	"database/sql/driver"
-	"errors"
 )
 
 // Ping implements driver.Pinger interface
@@ -41,15 +40,9 @@ func (mc *mysqlConn) Ping(ctx context.Context) error {
 
 // BeginTx implements driver.ConnBeginTx interface
 func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
-	if opts.ReadOnly {
-		// TODO: support read-only transactions
-		return nil, errors.New("mysql: read-only transactions not supported")
-	}
-
 	if err := mc.watchCancel(ctx); err != nil {
 		return nil, err
 	}
-
 	defer mc.finish()
 
 	if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault {
@@ -63,7 +56,7 @@ func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver
 		}
 	}
 
-	return mc.Begin()
+	return mc.begin(opts.ReadOnly)
 }
 
 func (mc *mysqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
diff --git a/driver_go18_test.go b/driver_go18_test.go
index f2184add0..4962838f2 100644
--- a/driver_go18_test.go
+++ b/driver_go18_test.go
@@ -520,3 +520,41 @@ func TestContextBeginIsolationLevel(t *testing.T) {
 		tx2.Commit()
 	})
 }
+
+func TestContextBeginReadOnly(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec("CREATE TABLE test (v INTEGER)")
+		ctx, cancel := context.WithCancel(context.Background())
+		defer cancel()
+
+		tx, err := dbt.db.BeginTx(ctx, &sql.TxOptions{
+			ReadOnly: true,
+		})
+		if _, ok := err.(*MySQLError); ok {
+			dbt.Skip("It seems that your MySQL does not support READ ONLY transactions")
+			return
+		} else if err != nil {
+			dbt.Fatal(err)
+		}
+
+		// INSERT queries fail in a READ ONLY transaction.
+		_, err = tx.ExecContext(ctx, "INSERT INTO test VALUES (1)")
+		if _, ok := err.(*MySQLError); !ok {
+			dbt.Errorf("expected MySQLError, got %v", err)
+		}
+
+		// SELECT queries can be executed.
+		var v int
+		row := tx.QueryRowContext(ctx, "SELECT COUNT(*) FROM test")
+		if err := row.Scan(&v); err != nil {
+			dbt.Fatal(err)
+		}
+		if v != 0 {
+			dbt.Errorf("expected val to be 0, got %d", v)
+		}
+
+		if err := tx.Commit(); err != nil {
+			dbt.Fatal(err)
+		}
+	})
+}

From be22b3051b3467094e9908389d900e74f72cf08f Mon Sep 17 00:00:00 2001
From: Alangi Derick <alangiderick@gmail.com>
Date: Sun, 17 Sep 2017 21:13:19 +0100
Subject: [PATCH 032/282] Added -u flag for `go get ...` command (#665)

* With the `-u` flag, this will allows the user's network to look for updates to the existing package.

* Typo fixes.
---
 README.md | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/README.md b/README.md
index 925a69cf1..ab1252f14 100644
--- a/README.md
+++ b/README.md
@@ -47,7 +47,7 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
 ## Installation
 Simple install the package to your [$GOPATH](https://github.com/golang/go/wiki/GOPATH "GOPATH") with the [go tool](https://golang.org/cmd/go/ "go command") from shell:
 ```bash
-$ go get github.com/go-sql-driver/mysql
+$ go get -u github.com/go-sql-driver/mysql
 ```
 Make sure [Git is installed](https://git-scm.com/downloads) on your machine and in your system's `PATH`.
 
@@ -344,9 +344,9 @@ Any other parameters are interpreted as system variables:
   * `<string_var>=%27<value>%27`: `SET <string_var>='<value>'`
 
 Rules:
-* The values for string variables must be quoted with '
+* The values for string variables must be quoted with `'`.
 * The values must also be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed!
- (which implies values of string variables must be wrapped with `%27`)
+ (which implies values of string variables must be wrapped with `%27`).
 
 Examples:
   * `autocommit=1`: `SET autocommit=1`
@@ -426,7 +426,7 @@ See the [godoc of Go-MySQL-Driver](https://godoc.org/github.com/go-sql-driver/my
 
 
 ### `time.Time` support
-The default internal output type of MySQL `DATE` and `DATETIME` values is `[]byte` which allows you to scan the value into a `[]byte`, `string` or `sql.RawBytes` variable in your programm.
+The default internal output type of MySQL `DATE` and `DATETIME` values is `[]byte` which allows you to scan the value into a `[]byte`, `string` or `sql.RawBytes` variable in your program.
 
 However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` variables, which is the logical opposite in Go to `DATE` and `DATETIME` in MySQL. You can do that by changing the internal output type from `[]byte` to `time.Time` with the DSN parameter `parseTime=true`. You can set the default [`time.Time` location](https://golang.org/pkg/time/#Location) with the `loc` DSN parameter.
 
@@ -466,13 +466,13 @@ Mozilla summarizes the license scope as follows:
 
 
 That means:
-  * You can **use** the **unchanged** source code both in private and commercially
-  * When distributing, you **must publish** the source code of any **changed files** licensed under the MPL 2.0 under a) the MPL 2.0 itself or b) a compatible license (e.g. GPL 3.0 or Apache License 2.0)
-  * You **needn't publish** the source code of your library as long as the files licensed under the MPL 2.0 are **unchanged**
+  * You can **use** the **unchanged** source code both in private and commercially.
+  * When distributing, you **must publish** the source code of any **changed files** licensed under the MPL 2.0 under a) the MPL 2.0 itself or b) a compatible license (e.g. GPL 3.0 or Apache License 2.0).
+  * You **needn't publish** the source code of your library as long as the files licensed under the MPL 2.0 are **unchanged**.
 
 Please read the [MPL 2.0 FAQ](https://www.mozilla.org/en-US/MPL/2.0/FAQ/) if you have further questions regarding the license.
 
-You can read the full terms here: [LICENSE](https://raw.github.com/go-sql-driver/mysql/master/LICENSE)
+You can read the full terms here: [LICENSE](https://raw.github.com/go-sql-driver/mysql/master/LICENSE).
 
 ![Go Gopher and MySQL Dolphin](https://raw.github.com/wiki/go-sql-driver/mysql/go-mysql-driver_m.jpg "Golang Gopher transporting the MySQL Dolphin in a wheelbarrow")
 

From 7785c74297136c027fdf2fd6f8931c0e19be8aa7 Mon Sep 17 00:00:00 2001
From: Bulat Gaifullin <gaifullinbf@gmail.com>
Date: Thu, 21 Sep 2017 09:28:06 +0300
Subject: [PATCH 033/282] use default port if port is missing in dsn (#668)
 (#669)

---
 README.md   |  3 ++-
 dsn.go      | 10 ++++++++++
 dsn_test.go |  9 ++++++++-
 3 files changed, 20 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index ab1252f14..779ada5ba 100644
--- a/README.md
+++ b/README.md
@@ -101,7 +101,8 @@ See [net.Dial](https://golang.org/pkg/net/#Dial) for more information which netw
 In general you should use an Unix domain socket if available and TCP otherwise for best performance.
 
 #### Address
-For TCP and UDP networks, addresses have the form `host:port`.
+For TCP and UDP networks, addresses have the form `host[:port]`.
+If `port` is omitted, the default port will be used.
 If `host` is a literal IPv6 address, it must be enclosed in square brackets.
 The functions [net.JoinHostPort](https://golang.org/pkg/net/#JoinHostPort) and [net.SplitHostPort](https://golang.org/pkg/net/#SplitHostPort) manipulate addresses in this form.
 
diff --git a/dsn.go b/dsn.go
index 9cf4bb94a..432ca43b8 100644
--- a/dsn.go
+++ b/dsn.go
@@ -376,6 +376,9 @@ func ParseDSN(dsn string) (cfg *Config, err error) {
 		}
 
 	}
+	if cfg.Net == "tcp" {
+		cfg.Addr = ensureHavePort(cfg.Addr)
+	}
 
 	return
 }
@@ -575,3 +578,10 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 
 	return
 }
+
+func ensureHavePort(addr string) string {
+	if _, _, err := net.SplitHostPort(addr); err != nil {
+		return net.JoinHostPort(addr, "3306")
+	}
+	return addr
+}
diff --git a/dsn_test.go b/dsn_test.go
index 671af020b..01b57212f 100644
--- a/dsn_test.go
+++ b/dsn_test.go
@@ -65,7 +65,14 @@ var testDSNs = []struct {
 }, {
 	"unix/?arg=%2Fsome%2Fpath.ext",
 	&Config{Net: "unix", Addr: "/tmp/mysql.sock", Params: map[string]string{"arg": "/some/path.ext"}, Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true},
-}}
+}, {
+	"tcp(127.0.0.1)/dbname",
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true},
+}, {
+	"tcp(de:ad:be:ef::ca:fe)/dbname",
+	&Config{Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true},
+},
+}
 
 func TestDSNParser(t *testing.T) {
 	for i, tst := range testDSNs {

From a8b7ed4454a6a4f98f85d3ad558cd6d97cec6959 Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Tue, 3 Oct 2017 18:05:15 +0200
Subject: [PATCH 034/282] Remove strict mode (#676)

* Remove strict mode

Fixes #556 #602 #635
Closes #609

* dsn: panic in case of strict mode
---
 README.md         | 16 ++-------
 benchmark_test.go |  6 +---
 connection.go     |  1 -
 driver.go         |  1 -
 driver_test.go    | 82 ++---------------------------------------------
 dsn.go            | 16 +--------
 errors.go         | 73 -----------------------------------------
 packets.go        | 14 --------
 8 files changed, 7 insertions(+), 202 deletions(-)

diff --git a/README.md b/README.md
index 779ada5ba..b5882e6c8 100644
--- a/README.md
+++ b/README.md
@@ -294,20 +294,6 @@ supposed to happen, setting this on some MySQL providers (such as AWS Aurora)
 is safer for failovers.
 
 
-##### `strict`
-
-```
-Type:           bool
-Valid Values:   true, false
-Default:        false
-```
-
-`strict=true` enables a driver-side strict mode in which MySQL warnings are treated as errors. This mode should not be used in production as it may lead to data corruption in certain situations.
-
-A server-side strict mode, which is safe for production use, can be set via the [`sql_mode`](https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html) system variable.
-
-By default MySQL also treats notes as warnings. Use [`sql_notes=false`](http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_sql_notes) to ignore notes.
-
 ##### `timeout`
 
 ```
@@ -317,6 +303,7 @@ Default:        OS default
 
 Timeout for establishing connections, aka dial timeout. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*.
 
+
 ##### `tls`
 
 ```
@@ -327,6 +314,7 @@ Default:        false
 
 `tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side). Use a custom value registered with [`mysql.RegisterTLSConfig`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig).
 
+
 ##### `writeTimeout`
 
 ```
diff --git a/benchmark_test.go b/benchmark_test.go
index 7da833a2a..c1de8672b 100644
--- a/benchmark_test.go
+++ b/benchmark_test.go
@@ -48,11 +48,7 @@ func initDB(b *testing.B, queries ...string) *sql.DB {
 	db := tb.checkDB(sql.Open("mysql", dsn))
 	for _, query := range queries {
 		if _, err := db.Exec(query); err != nil {
-			if w, ok := err.(MySQLWarnings); ok {
-				b.Logf("warning on %q: %v", query, w)
-			} else {
-				b.Fatalf("error on %q: %v", query, err)
-			}
+			b.Fatalf("error on %q: %v", query, err)
 		}
 	}
 	return db
diff --git a/connection.go b/connection.go
index 948a59561..58ae29988 100644
--- a/connection.go
+++ b/connection.go
@@ -40,7 +40,6 @@ type mysqlConn struct {
 	status           statusFlag
 	sequence         uint8
 	parseTime        bool
-	strict           bool
 
 	// for context support (Go 1.8+)
 	watching bool
diff --git a/driver.go b/driver.go
index c341b6680..d42ce7a3d 100644
--- a/driver.go
+++ b/driver.go
@@ -64,7 +64,6 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
 		return nil, err
 	}
 	mc.parseTime = mc.cfg.ParseTime
-	mc.strict = mc.cfg.Strict
 
 	// Connect to Server
 	if dial, ok := dials[mc.cfg.Net]; ok {
diff --git a/driver_test.go b/driver_test.go
index 206e07cc9..bc0386a09 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -63,7 +63,7 @@ func init() {
 	addr = env("MYSQL_TEST_ADDR", "localhost:3306")
 	dbname = env("MYSQL_TEST_DBNAME", "gotest")
 	netAddr = fmt.Sprintf("%s(%s)", prot, addr)
-	dsn = fmt.Sprintf("%s:%s@%s/%s?timeout=30s&strict=true", user, pass, netAddr, dbname)
+	dsn = fmt.Sprintf("%s:%s@%s/%s?timeout=30s", user, pass, netAddr, dbname)
 	c, err := net.Dial(prot, addr)
 	if err == nil {
 		available = true
@@ -1170,82 +1170,6 @@ func TestFoundRows(t *testing.T) {
 	})
 }
 
-func TestStrict(t *testing.T) {
-	// ALLOW_INVALID_DATES to get rid of stricter modes - we want to test for warnings, not errors
-	relaxedDsn := dsn + "&sql_mode='ALLOW_INVALID_DATES,NO_AUTO_CREATE_USER'"
-	// make sure the MySQL version is recent enough with a separate connection
-	// before running the test
-	conn, err := MySQLDriver{}.Open(relaxedDsn)
-	if conn != nil {
-		conn.Close()
-	}
-	// Error 1231: Variable 'sql_mode' can't be set to the value of
-	// 'ALLOW_INVALID_DATES' => skip test, MySQL server version is too old
-	maybeSkip(t, err, 1231)
-	runTests(t, relaxedDsn, func(dbt *DBTest) {
-		dbt.mustExec("CREATE TABLE test (a TINYINT NOT NULL, b CHAR(4))")
-
-		var queries = [...]struct {
-			in    string
-			codes []string
-		}{
-			{"DROP TABLE IF EXISTS no_such_table", []string{"1051"}},
-			{"INSERT INTO test VALUES(10,'mysql'),(NULL,'test'),(300,'Open Source')", []string{"1265", "1048", "1264", "1265"}},
-		}
-		var err error
-
-		var checkWarnings = func(err error, mode string, idx int) {
-			if err == nil {
-				dbt.Errorf("expected STRICT error on query [%s] %s", mode, queries[idx].in)
-			}
-
-			if warnings, ok := err.(MySQLWarnings); ok {
-				var codes = make([]string, len(warnings))
-				for i := range warnings {
-					codes[i] = warnings[i].Code
-				}
-				if len(codes) != len(queries[idx].codes) {
-					dbt.Errorf("unexpected STRICT error count on query [%s] %s: Wanted %v, Got %v", mode, queries[idx].in, queries[idx].codes, codes)
-				}
-
-				for i := range warnings {
-					if codes[i] != queries[idx].codes[i] {
-						dbt.Errorf("unexpected STRICT error codes on query [%s] %s: Wanted %v, Got %v", mode, queries[idx].in, queries[idx].codes, codes)
-						return
-					}
-				}
-
-			} else {
-				dbt.Errorf("unexpected error on query [%s] %s: %s", mode, queries[idx].in, err.Error())
-			}
-		}
-
-		// text protocol
-		for i := range queries {
-			_, err = dbt.db.Exec(queries[i].in)
-			checkWarnings(err, "text", i)
-		}
-
-		var stmt *sql.Stmt
-
-		// binary protocol
-		for i := range queries {
-			stmt, err = dbt.db.Prepare(queries[i].in)
-			if err != nil {
-				dbt.Errorf("error on preparing query %s: %s", queries[i].in, err.Error())
-			}
-
-			_, err = stmt.Exec()
-			checkWarnings(err, "binary", i)
-
-			err = stmt.Close()
-			if err != nil {
-				dbt.Errorf("error on closing stmt for query %s: %s", queries[i].in, err.Error())
-			}
-		}
-	})
-}
-
 func TestTLS(t *testing.T) {
 	tlsTest := func(dbt *DBTest) {
 		if err := dbt.db.Ping(); err != nil {
@@ -1762,7 +1686,7 @@ func TestCustomDial(t *testing.T) {
 		return net.Dial(prot, addr)
 	})
 
-	db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@mydial(%s)/%s?timeout=30s&strict=true", user, pass, addr, dbname))
+	db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@mydial(%s)/%s?timeout=30s", user, pass, addr, dbname))
 	if err != nil {
 		t.Fatalf("error connecting: %s", err.Error())
 	}
@@ -1859,7 +1783,7 @@ func TestUnixSocketAuthFail(t *testing.T) {
 			}
 		}
 		t.Logf("socket: %s", socket)
-		badDSN := fmt.Sprintf("%s:%s@unix(%s)/%s?timeout=30s&strict=true", user, badPass, socket, dbname)
+		badDSN := fmt.Sprintf("%s:%s@unix(%s)/%s?timeout=30s", user, badPass, socket, dbname)
 		db, err := sql.Open("mysql", badDSN)
 		if err != nil {
 			t.Fatalf("error connecting: %s", err.Error())
diff --git a/dsn.go b/dsn.go
index 432ca43b8..6ce5cc020 100644
--- a/dsn.go
+++ b/dsn.go
@@ -55,7 +55,6 @@ type Config struct {
 	MultiStatements         bool // Allow multiple statements in one query
 	ParseTime               bool // Parse time values to time.Time
 	RejectReadOnly          bool // Reject read-only connections
-	Strict                  bool // Return warnings as errors
 }
 
 // FormatDSN formats the given Config into a DSN string which can be passed to
@@ -206,15 +205,6 @@ func (cfg *Config) FormatDSN() string {
 		}
 	}
 
-	if cfg.Strict {
-		if hasParam {
-			buf.WriteString("&strict=true")
-		} else {
-			hasParam = true
-			buf.WriteString("?strict=true")
-		}
-	}
-
 	if cfg.Timeout > 0 {
 		if hasParam {
 			buf.WriteString("&timeout=")
@@ -502,11 +492,7 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 
 		// Strict mode
 		case "strict":
-			var isBool bool
-			cfg.Strict, isBool = readBool(value)
-			if !isBool {
-				return errors.New("invalid bool value: " + value)
-			}
+			panic("strict mode has been removed. See https://github.com/go-sql-driver/mysql/wiki/strict-mode")
 
 		// Dial Timeout
 		case "timeout":
diff --git a/errors.go b/errors.go
index d0d0d2e11..760782ff2 100644
--- a/errors.go
+++ b/errors.go
@@ -9,10 +9,8 @@
 package mysql
 
 import (
-	"database/sql/driver"
 	"errors"
 	"fmt"
-	"io"
 	"log"
 	"os"
 )
@@ -65,74 +63,3 @@ type MySQLError struct {
 func (me *MySQLError) Error() string {
 	return fmt.Sprintf("Error %d: %s", me.Number, me.Message)
 }
-
-// MySQLWarnings is an error type which represents a group of one or more MySQL
-// warnings
-type MySQLWarnings []MySQLWarning
-
-func (mws MySQLWarnings) Error() string {
-	var msg string
-	for i, warning := range mws {
-		if i > 0 {
-			msg += "\r\n"
-		}
-		msg += fmt.Sprintf(
-			"%s %s: %s",
-			warning.Level,
-			warning.Code,
-			warning.Message,
-		)
-	}
-	return msg
-}
-
-// MySQLWarning is an error type which represents a single MySQL warning.
-// Warnings are returned in groups only. See MySQLWarnings
-type MySQLWarning struct {
-	Level   string
-	Code    string
-	Message string
-}
-
-func (mc *mysqlConn) getWarnings() (err error) {
-	rows, err := mc.Query("SHOW WARNINGS", nil)
-	if err != nil {
-		return
-	}
-
-	var warnings = MySQLWarnings{}
-	var values = make([]driver.Value, 3)
-
-	for {
-		err = rows.Next(values)
-		switch err {
-		case nil:
-			warning := MySQLWarning{}
-
-			if raw, ok := values[0].([]byte); ok {
-				warning.Level = string(raw)
-			} else {
-				warning.Level = fmt.Sprintf("%s", values[0])
-			}
-			if raw, ok := values[1].([]byte); ok {
-				warning.Code = string(raw)
-			} else {
-				warning.Code = fmt.Sprintf("%s", values[1])
-			}
-			if raw, ok := values[2].([]byte); ok {
-				warning.Message = string(raw)
-			} else {
-				warning.Message = fmt.Sprintf("%s", values[0])
-			}
-
-			warnings = append(warnings, warning)
-
-		case io.EOF:
-			return warnings
-
-		default:
-			rows.Close()
-			return
-		}
-	}
-}
diff --git a/packets.go b/packets.go
index 79648d572..1887467df 100644
--- a/packets.go
+++ b/packets.go
@@ -624,14 +624,7 @@ func (mc *mysqlConn) handleOkPacket(data []byte) error {
 	}
 
 	// warning count [2 bytes]
-	if !mc.strict {
-		return nil
-	}
 
-	pos := 1 + n + m + 2
-	if binary.LittleEndian.Uint16(data[pos:pos+2]) > 0 {
-		return mc.getWarnings()
-	}
 	return nil
 }
 
@@ -843,14 +836,7 @@ func (stmt *mysqlStmt) readPrepareResultPacket() (uint16, error) {
 		// Reserved [8 bit]
 
 		// Warning count [16 bit uint]
-		if !stmt.mc.strict {
-			return columnCount, nil
-		}
 
-		// Check for warnings count > 0, only available in MySQL > 4.1
-		if len(data) >= 12 && binary.LittleEndian.Uint16(data[10:12]) > 0 {
-			return columnCount, stmt.mc.getWarnings()
-		}
 		return columnCount, nil
 	}
 	return 0, err

From ee359f95877bdef36cbb602711e49b6f0becfca9 Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Sat, 7 Oct 2017 17:01:58 +0200
Subject: [PATCH 035/282] Export function to initialize Config with defaults
 (#679)

* dsn: export NewConfig

* dsn: move DSN normalization to separate func

* dsn: add godoc

* dsn: add missing return
---
 dsn.go      | 73 +++++++++++++++++++++++++++++++----------------------
 dsn_test.go |  1 +
 2 files changed, 44 insertions(+), 30 deletions(-)

diff --git a/dsn.go b/dsn.go
index 6ce5cc020..af3dfa303 100644
--- a/dsn.go
+++ b/dsn.go
@@ -28,7 +28,9 @@ var (
 	errInvalidDSNUnsafeCollation = errors.New("invalid DSN: interpolateParams can not be used with unsafe collations")
 )
 
-// Config is a configuration parsed from a DSN string
+// Config is a configuration parsed from a DSN string.
+// If a new Config is created instead of being parsed from a DSN string,
+// the NewConfig function should be used, which sets default values.
 type Config struct {
 	User             string            // Username
 	Passwd           string            // Password (requires User)
@@ -57,6 +59,43 @@ type Config struct {
 	RejectReadOnly          bool // Reject read-only connections
 }
 
+// NewConfig creates a new Config and sets default values.
+func NewConfig() *Config {
+	return &Config{
+		Collation:            defaultCollation,
+		Loc:                  time.UTC,
+		AllowNativePasswords: true,
+	}
+}
+
+func (cfg *Config) normalize() error {
+	if cfg.InterpolateParams && unsafeCollations[cfg.Collation] {
+		return errInvalidDSNUnsafeCollation
+	}
+
+	// Set default network if empty
+	if cfg.Net == "" {
+		cfg.Net = "tcp"
+	}
+
+	// Set default address if empty
+	if cfg.Addr == "" {
+		switch cfg.Net {
+		case "tcp":
+			cfg.Addr = "127.0.0.1:3306"
+		case "unix":
+			cfg.Addr = "/tmp/mysql.sock"
+		default:
+			return errors.New("default addr for network '" + cfg.Net + "' unknown")
+		}
+
+	} else if cfg.Net == "tcp" {
+		cfg.Addr = ensureHavePort(cfg.Addr)
+	}
+
+	return nil
+}
+
 // FormatDSN formats the given Config into a DSN string which can be passed to
 // the driver.
 func (cfg *Config) FormatDSN() string {
@@ -273,11 +312,7 @@ func (cfg *Config) FormatDSN() string {
 // ParseDSN parses the DSN string to a Config
 func ParseDSN(dsn string) (cfg *Config, err error) {
 	// New config with some default values
-	cfg = &Config{
-		Loc:                  time.UTC,
-		Collation:            defaultCollation,
-		AllowNativePasswords: true,
-	}
+	cfg = NewConfig()
 
 	// [user[:password]@][net[(addr)]]/dbname[?param1=value1&paramN=valueN]
 	// Find the last '/' (since the password or the net addr might contain a '/')
@@ -345,31 +380,9 @@ func ParseDSN(dsn string) (cfg *Config, err error) {
 		return nil, errInvalidDSNNoSlash
 	}
 
-	if cfg.InterpolateParams && unsafeCollations[cfg.Collation] {
-		return nil, errInvalidDSNUnsafeCollation
-	}
-
-	// Set default network if empty
-	if cfg.Net == "" {
-		cfg.Net = "tcp"
+	if err = cfg.normalize(); err != nil {
+		return nil, err
 	}
-
-	// Set default address if empty
-	if cfg.Addr == "" {
-		switch cfg.Net {
-		case "tcp":
-			cfg.Addr = "127.0.0.1:3306"
-		case "unix":
-			cfg.Addr = "/tmp/mysql.sock"
-		default:
-			return nil, errors.New("default addr for network '" + cfg.Net + "' unknown")
-		}
-
-	}
-	if cfg.Net == "tcp" {
-		cfg.Addr = ensureHavePort(cfg.Addr)
-	}
-
 	return
 }
 
diff --git a/dsn_test.go b/dsn_test.go
index 01b57212f..af28da351 100644
--- a/dsn_test.go
+++ b/dsn_test.go
@@ -98,6 +98,7 @@ func TestDSNParserInvalid(t *testing.T) {
 		"(/",                          // no closing brace
 		"net(addr)//",                 // unescaped
 		"User:pass@tcp(1.2.3.4:3306)", // no trailing slash
+		"net()/",                      // unknown default addr
 		//"/dbname?arg=/some/unescaped/path",
 	}
 

From eb92a8dc628ca52501ddae1ed90eca7514f215bf Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Thu, 12 Oct 2017 10:31:18 +0200
Subject: [PATCH 036/282] do not query max_allowed_packet by default (#680)

---
 README.md      |  4 ++--
 const.go       |  3 ++-
 driver_test.go |  2 +-
 dsn.go         |  3 ++-
 dsn_test.go    | 50 ++++++++++++++++++++++++--------------------------
 5 files changed, 31 insertions(+), 31 deletions(-)

diff --git a/README.md b/README.md
index b5882e6c8..6a306bb30 100644
--- a/README.md
+++ b/README.md
@@ -232,10 +232,10 @@ Please keep in mind, that param values must be [url.QueryEscape](https://golang.
 ##### `maxAllowedPacket`
 ```
 Type:          decimal number
-Default:       0
+Default:       4194304
 ```
 
-Max packet size allowed in bytes. Use `maxAllowedPacket=0` to automatically fetch the `max_allowed_packet` variable from server.
+Max packet size allowed in bytes. The default value is 4 MiB and should be adjusted to match the server settings. `maxAllowedPacket=0` can be used to automatically fetch the `max_allowed_packet` variable from server *on every connection*.
 
 ##### `multiStatements`
 
diff --git a/const.go b/const.go
index 88cfff3fd..2570b23fe 100644
--- a/const.go
+++ b/const.go
@@ -9,7 +9,8 @@
 package mysql
 
 const (
-	minProtocolVersion byte = 10
+	defaultMaxAllowedPacket = 4 << 20 // 4 MiB
+	minProtocolVersion      = 10
 	maxPacketSize           = 1<<24 - 1
 	timeFormat              = "2006-01-02 15:04:05.999999"
 )
diff --git a/driver_test.go b/driver_test.go
index bc0386a09..27b067dff 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -964,7 +964,7 @@ func TestUint64(t *testing.T) {
 }
 
 func TestLongData(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
+	runTests(t, dsn+"&maxAllowedPacket=0", func(dbt *DBTest) {
 		var maxAllowedPacketSize int
 		err := dbt.db.QueryRow("select @@max_allowed_packet").Scan(&maxAllowedPacketSize)
 		if err != nil {
diff --git a/dsn.go b/dsn.go
index af3dfa303..3ade963ee 100644
--- a/dsn.go
+++ b/dsn.go
@@ -64,6 +64,7 @@ func NewConfig() *Config {
 	return &Config{
 		Collation:            defaultCollation,
 		Loc:                  time.UTC,
+		MaxAllowedPacket:     defaultMaxAllowedPacket,
 		AllowNativePasswords: true,
 	}
 }
@@ -274,7 +275,7 @@ func (cfg *Config) FormatDSN() string {
 		buf.WriteString(cfg.WriteTimeout.String())
 	}
 
-	if cfg.MaxAllowedPacket > 0 {
+	if cfg.MaxAllowedPacket != defaultMaxAllowedPacket {
 		if hasParam {
 			buf.WriteString("&maxAllowedPacket=")
 		} else {
diff --git a/dsn_test.go b/dsn_test.go
index af28da351..07b223f6b 100644
--- a/dsn_test.go
+++ b/dsn_test.go
@@ -22,55 +22,55 @@ var testDSNs = []struct {
 	out *Config
 }{{
 	"username:password@protocol(address)/dbname?param=value",
-	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true},
+	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
 }, {
 	"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true",
-	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true, ColumnsWithAlias: true},
+	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, ColumnsWithAlias: true},
 }, {
 	"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true&multiStatements=true",
-	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true, ColumnsWithAlias: true, MultiStatements: true},
+	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, ColumnsWithAlias: true, MultiStatements: true},
 }, {
 	"user@unix(/path/to/socket)/dbname?charset=utf8",
-	&Config{User: "user", Net: "unix", Addr: "/path/to/socket", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true},
+	&Config{User: "user", Net: "unix", Addr: "/path/to/socket", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
 }, {
 	"user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true",
-	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true, TLSConfig: "true"},
+	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, TLSConfig: "true"},
 }, {
 	"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify",
-	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8mb4,utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true, TLSConfig: "skip-verify"},
+	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8mb4,utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, TLSConfig: "skip-verify"},
 }, {
 	"user:password@/dbname?loc=UTC&timeout=30s&readTimeout=1s&writeTimeout=1s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci&maxAllowedPacket=16777216",
 	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_unicode_ci", Loc: time.UTC, AllowNativePasswords: true, Timeout: 30 * time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, AllowAllFiles: true, AllowOldPasswords: true, ClientFoundRows: true, MaxAllowedPacket: 16777216},
 }, {
-	"user:password@/dbname?allowNativePasswords=false",
-	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: false},
+	"user:password@/dbname?allowNativePasswords=false&maxAllowedPacket=0",
+	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: 0, AllowNativePasswords: false},
 }, {
 	"user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local",
-	&Config{User: "user", Passwd: "p@ss(word)", Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:80", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.Local, AllowNativePasswords: true},
+	&Config{User: "user", Passwd: "p@ss(word)", Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:80", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.Local, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
 }, {
 	"/dbname",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
 }, {
 	"@/",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
 }, {
 	"/",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
 }, {
 	"",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
 }, {
 	"user:p@/ssword@/",
-	&Config{User: "user", Passwd: "p@/ssword", Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true},
+	&Config{User: "user", Passwd: "p@/ssword", Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
 }, {
 	"unix/?arg=%2Fsome%2Fpath.ext",
-	&Config{Net: "unix", Addr: "/tmp/mysql.sock", Params: map[string]string{"arg": "/some/path.ext"}, Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true},
+	&Config{Net: "unix", Addr: "/tmp/mysql.sock", Params: map[string]string{"arg": "/some/path.ext"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
 }, {
 	"tcp(127.0.0.1)/dbname",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
 }, {
 	"tcp(de:ad:be:ef::ca:fe)/dbname",
-	&Config{Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC, AllowNativePasswords: true},
+	&Config{Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
 },
 }
 
@@ -233,16 +233,14 @@ func TestDSNUnsafeCollation(t *testing.T) {
 
 func TestParamsAreSorted(t *testing.T) {
 	expected := "/dbname?interpolateParams=true&foobar=baz&quux=loo"
-	dsn := &Config{
-		DBName:               "dbname",
-		InterpolateParams:    true,
-		AllowNativePasswords: true,
-		Params: map[string]string{
-			"quux":   "loo",
-			"foobar": "baz",
-		},
+	cfg := NewConfig()
+	cfg.DBName = "dbname"
+	cfg.InterpolateParams = true
+	cfg.Params = map[string]string{
+		"quux":   "loo",
+		"foobar": "baz",
 	}
-	actual := dsn.FormatDSN()
+	actual := cfg.FormatDSN()
 	if actual != expected {
 		t.Errorf("generic Config.Params were not sorted: want %#v, got %#v", expected, actual)
 	}

From fae512fbd064ec5e786eeccddf7e3c0eeec7c5d9 Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Mon, 16 Oct 2017 22:44:03 +0200
Subject: [PATCH 037/282] packets: do not call function on nulled value (#678)

---
 packets.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/packets.go b/packets.go
index 1887467df..ca5d3810e 100644
--- a/packets.go
+++ b/packets.go
@@ -1143,10 +1143,11 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
 			}
 			return io.EOF
 		}
+		mc := rows.mc
 		rows.mc = nil
 
 		// Error otherwise
-		return rows.mc.handleErrorPacket(data)
+		return mc.handleErrorPacket(data)
 	}
 
 	// NULL-bitmap,  [(column-count + 7 + 2) / 8 bytes]

From c6c4e3cbc82eb3310911407935306a32f4582d05 Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Tue, 17 Oct 2017 14:45:56 +0200
Subject: [PATCH 038/282] ColumnType interfaces (#667)

* rows: implement driver.RowsColumnTypeScanType

Implementation for time.Time not yet complete!

* rows: implement driver.RowsColumnTypeNullable

* rows: move fields related code to fields.go

* fields: use NullTime for nullable datetime fields

* fields: make fieldType its own type

* rows: implement driver.RowsColumnTypeDatabaseTypeName

* fields: fix copyright year

* rows: compile time interface implementation checks

* rows: move tests to versioned driver test files

* rows: cache parseTime in resultSet instead of mysqlConn

* fields: fix string and time types

* rows: implement ColumnTypeLength

* rows: implement ColumnTypePrecisionScale

* rows: fix ColumnTypeNullable

* rows: ColumnTypes tests part1

* rows: use keyed composite literals in ColumnTypes tests

* rows: ColumnTypes tests part2

* rows: always use NullTime as ScanType for datetime

* rows: avoid errors through rounding of time values

* rows: remove parseTime cache

* fields: remove unused scanTypes

* rows: fix ColumnTypePrecisionScale implementation

* fields: sort types alphabetical

* rows: remove ColumnTypeLength implementation for now

* README: document ColumnType Support
---
 README.md           |  14 ++-
 connection.go       |   1 +
 const.go            |   6 +-
 driver_go18_test.go | 220 ++++++++++++++++++++++++++++++++++++++++++++
 driver_test.go      |   6 ++
 fields.go           | 140 ++++++++++++++++++++++++++++
 packets.go          |  23 +++--
 rows.go             |  51 ++++++++--
 8 files changed, 436 insertions(+), 25 deletions(-)
 create mode 100644 fields.go

diff --git a/README.md b/README.md
index 6a306bb30..f6eb0b0d2 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.
 
diff --git a/connection.go b/connection.go
index 58ae29988..e57061412 100644
--- a/connection.go
+++ b/connection.go
@@ -403,6 +403,7 @@ func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error)
 					return nil, err
 				}
 			}
+
 			// Columns
 			rows.rs.columns, err = mc.readColumns(resLen)
 			return rows, err
diff --git a/const.go b/const.go
index 2570b23fe..4a19ca523 100644
--- a/const.go
+++ b/const.go
@@ -88,8 +88,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
@@ -108,7 +110,7 @@ const (
 	fieldTypeBit
 )
 const (
-	fieldTypeJSON byte = iota + 0xf5
+	fieldTypeJSON fieldType = iota + 0xf5
 	fieldTypeNewDecimal
 	fieldTypeEnum
 	fieldTypeSet
diff --git a/driver_go18_test.go b/driver_go18_test.go
index 4962838f2..953adeb8a 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"
@@ -35,6 +36,22 @@ var (
 	_ driver.StmtQueryContext = &mysqlStmt{}
 )
 
+// Ensure that all the driver interfaces are implemented
+var (
+	// _ driver.RowsColumnTypeLength        = &binaryRows{}
+	// _ driver.RowsColumnTypeLength        = &textRows{}
+	_ driver.RowsColumnTypeDatabaseTypeName = &binaryRows{}
+	_ driver.RowsColumnTypeDatabaseTypeName = &textRows{}
+	_ driver.RowsColumnTypeNullable         = &binaryRows{}
+	_ driver.RowsColumnTypeNullable         = &textRows{}
+	_ driver.RowsColumnTypePrecisionScale   = &binaryRows{}
+	_ driver.RowsColumnTypePrecisionScale   = &textRows{}
+	_ driver.RowsColumnTypeScanType         = &binaryRows{}
+	_ driver.RowsColumnTypeScanType         = &textRows{}
+	_ driver.RowsNextResultSet              = &binaryRows{}
+	_ driver.RowsNextResultSet              = &textRows{}
+)
+
 func TestMultiResultSet(t *testing.T) {
 	type result struct {
 		values  [][]int
@@ -558,3 +575,206 @@ func TestContextBeginReadOnly(t *testing.T) {
 		}
 	})
 }
+
+func TestRowsColumnTypes(t *testing.T) {
+	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}
+	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, 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")
+	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
+		precision        int64 // 0 if not ok
+		scale            int64
+		valuesIn         [3]string
+		valuesOut        [3]interface{}
+	}{
+		{"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 := ""
+	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]
+
+	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 + ")")
+
+			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 and Scale
+				precision, scale, 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
+				}
+				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))
+			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]) {
+						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++
+			}
+			if i != 3 {
+				t.Errorf("expected 3 rows, got %d", i)
+			}
+
+			if err := rows.Close(); err != nil {
+				t.Errorf("error closing rows: %s", err)
+			}
+		})
+	}
+}
diff --git a/driver_test.go b/driver_test.go
index 27b067dff..53e70dab7 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/fields.go b/fields.go
new file mode 100644
index 000000000..cded986d2
--- /dev/null
+++ b/fields.go
@@ -0,0 +1,140 @@
+// 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"
+	"reflect"
+)
+
+var typeDatabaseName = map[fieldType]string{
+	fieldTypeBit:        "BIT",
+	fieldTypeBLOB:       "BLOB",
+	fieldTypeDate:       "DATE",
+	fieldTypeDateTime:   "DATETIME",
+	fieldTypeDecimal:    "DECIMAL",
+	fieldTypeDouble:     "DOUBLE",
+	fieldTypeEnum:       "ENUM",
+	fieldTypeFloat:      "FLOAT",
+	fieldTypeGeometry:   "GEOMETRY",
+	fieldTypeInt24:      "MEDIUMINT",
+	fieldTypeJSON:       "JSON",
+	fieldTypeLong:       "INT",
+	fieldTypeLongBLOB:   "LONGBLOB",
+	fieldTypeLongLong:   "BIGINT",
+	fieldTypeMediumBLOB: "MEDIUMBLOB",
+	fieldTypeNewDate:    "DATE",
+	fieldTypeNewDecimal: "DECIMAL",
+	fieldTypeNULL:       "NULL",
+	fieldTypeSet:        "SET",
+	fieldTypeShort:      "SMALLINT",
+	fieldTypeString:     "CHAR",
+	fieldTypeTime:       "TIME",
+	fieldTypeTimestamp:  "TIMESTAMP",
+	fieldTypeTiny:       "TINYINT",
+	fieldTypeTinyBLOB:   "TINYBLOB",
+	fieldTypeVarChar:    "VARCHAR",
+	fieldTypeVarString:  "VARCHAR",
+	fieldTypeYear:       "YEAR",
+}
+
+var (
+	scanTypeFloat32   = reflect.TypeOf(float32(0))
+	scanTypeFloat64   = reflect.TypeOf(float64(0))
+	scanTypeInt8      = reflect.TypeOf(int8(0))
+	scanTypeInt16     = reflect.TypeOf(int16(0))
+	scanTypeInt32     = reflect.TypeOf(int32(0))
+	scanTypeInt64     = reflect.TypeOf(int64(0))
+	scanTypeNullFloat = reflect.TypeOf(sql.NullFloat64{})
+	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{}))
+)
+
+type mysqlField struct {
+	tableName string
+	name      string
+	length    uint32
+	flags     fieldFlag
+	fieldType fieldType
+	decimals  byte
+}
+
+func (mf *mysqlField) scanType() reflect.Type {
+	switch mf.fieldType {
+	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:
+		return scanTypeRawBytes
+
+	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:
+		return scanTypeUnknown
+	}
+}
diff --git a/packets.go b/packets.go
index ca5d3810e..4ed6b45e1 100644
--- a/packets.go
+++ b/packets.go
@@ -699,11 +699,14 @@ 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 = data[pos]
+		columns[i].fieldType = fieldType(data[pos])
 		pos++
 
 		// Flags [uint16]
@@ -980,7 +983,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 +991,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 +1007,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 +1023,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 +1035,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 +1053,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 +1072,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
diff --git a/rows.go b/rows.go
index c7f5ee26c..18f41693e 100644
--- a/rows.go
+++ b/rows.go
@@ -11,16 +11,10 @@ package mysql
 import (
 	"database/sql/driver"
 	"io"
+	"math"
+	"reflect"
 )
 
-type mysqlField struct {
-	tableName string
-	name      string
-	flags     fieldFlag
-	fieldType byte
-	decimals  byte
-}
-
 type resultSet struct {
 	columns     []mysqlField
 	columnNames []string
@@ -65,6 +59,47 @@ 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) 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
+}
+
+func (rows *mysqlRows) ColumnTypePrecisionScale(i int) (int64, int64, bool) {
+	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
+}
+
+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 e52f1902cae0a08f56b280b48b7cee3e403a33e2 Mon Sep 17 00:00:00 2001
From: Jeffrey Charles <jeffreycharles@gmail.com>
Date: Tue, 17 Oct 2017 13:10:23 -0400
Subject: [PATCH 039/282] Add Aurora errno to rejectReadOnly check (#634)

AWS Aurora returns a 1290 after failing over requiring the connection to
be closed and opened again to be able to perform writes.
---
 AUTHORS    | 1 +
 README.md  | 7 ++++++-
 packets.go | 3 ++-
 3 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 646159940..ac36be9a7 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -34,6 +34,7 @@ INADA Naoki <songofacandy at gmail.com>
 Jacek Szwec <szwec.jacek at gmail.com>
 James Harr <james.harr at gmail.com>
 Jeff Hodges <jeff at somethingsimilar.com>
+Jeffrey Charles <jeffreycharles at gmail.com>
 Jian Zhen <zhenjl at gmail.com>
 Joshua Prunier <joshua.prunier at gmail.com>
 Julien Lefevre <julien.lefevr at gmail.com>
diff --git a/README.md b/README.md
index f6eb0b0d2..d24aaa0f0 100644
--- a/README.md
+++ b/README.md
@@ -279,7 +279,7 @@ Default:        false
 ```
 
 
-`rejectreadOnly=true` causes the driver to reject read-only connections. This
+`rejectReadOnly=true` causes the driver to reject read-only connections. This
 is for a possible race condition during an automatic failover, where the mysql
 client gets connected to a read-only replica after the failover.
 
@@ -294,6 +294,11 @@ If you are not relying on read-only transactions to reject writes that aren't
 supposed to happen, setting this on some MySQL providers (such as AWS Aurora)
 is safer for failovers.
 
+Note that ERROR 1290 can be returned for a `read-only` server and this option will
+cause a retry for that error. However the same error number is used for some
+other cases. You should ensure your application will never cause an ERROR 1290
+except for `read-only` mode when enabling this option.
+
 
 ##### `timeout`
 
diff --git a/packets.go b/packets.go
index 4ed6b45e1..f63d25072 100644
--- a/packets.go
+++ b/packets.go
@@ -571,7 +571,8 @@ func (mc *mysqlConn) handleErrorPacket(data []byte) error {
 	errno := binary.LittleEndian.Uint16(data[1:3])
 
 	// 1792: ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION
-	if errno == 1792 && mc.cfg.RejectReadOnly {
+	// 1290: ER_OPTION_PREVENTS_STATEMENT (returned by Aurora during failover)
+	if (errno == 1792 || errno == 1290) && mc.cfg.RejectReadOnly {
 		// Oops; we are connected to a read-only connection, and won't be able
 		// to issue any write statements. Since RejectReadOnly is configured,
 		// we throw away this connection hoping this one would have write

From fade21009797158e7b79e04c340118a9220c6f9e Mon Sep 17 00:00:00 2001
From: Jeff Hodges <jeff@somethingsimilar.com>
Date: Tue, 17 Oct 2017 11:16:16 -0700
Subject: [PATCH 040/282] allow successful TravisCI runs in forks (#639)

Most forks won't be in goveralls and so this command in travis.yml was,
previously, failing and causing the build to fail.

Now, it doesn't!
---
 .travis.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.travis.yml b/.travis.yml
index fa0b2c933..6369281e8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -90,4 +90,5 @@ script:
   - go test -v -covermode=count -coverprofile=coverage.out
   - go vet ./...
   - test -z "$(gofmt -d -s . | tee /dev/stderr)"
+after_script:
   - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci

From 949221881fa8e37a73d073dd3e33d857909d909e Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Sun, 12 Nov 2017 22:30:34 +0100
Subject: [PATCH 041/282] Drop support for Go 1.6 and lower (#696)

* Drop support for Go 1.6 and lower

* Remove cloneTLSConfig for legacy Go versions
---
 .travis.yml     |  2 --
 README.md       |  2 +-
 utils_legacy.go | 18 ------------------
 3 files changed, 1 insertion(+), 21 deletions(-)
 delete mode 100644 utils_legacy.go

diff --git a/.travis.yml b/.travis.yml
index 6369281e8..64b06a70c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,6 @@
 sudo: false
 language: go
 go:
-  - 1.5
-  - 1.6
   - 1.7
   - 1.8
   - 1.9
diff --git a/README.md b/README.md
index d24aaa0f0..299198d53 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
   * Optional placeholder interpolation
 
 ## Requirements
-  * Go 1.5 or higher
+  * Go 1.7 or higher. We aim to support the 3 latest versions of Go.
   * MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
 
 ---------------------------------------
diff --git a/utils_legacy.go b/utils_legacy.go
deleted file mode 100644
index a03b10de2..000000000
--- a/utils_legacy.go
+++ /dev/null
@@ -1,18 +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/.
-
-// +build !go1.7
-
-package mysql
-
-import "crypto/tls"
-
-func cloneTLSConfig(c *tls.Config) *tls.Config {
-	clone := *c
-	return &clone
-}

From 404c02a8a39c65a587e5ceedbd6e29c2ca185aca Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Tue, 14 Nov 2017 09:18:14 +0100
Subject: [PATCH 042/282] Make gofmt happy (#704)

---
 driver_test.go | 1 -
 dsn.go         | 1 -
 utils.go       | 2 +-
 3 files changed, 1 insertion(+), 3 deletions(-)

diff --git a/driver_test.go b/driver_test.go
index 53e70dab7..f6965b191 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -1375,7 +1375,6 @@ func TestTimezoneConversion(t *testing.T) {
 
 	// Regression test for timezone handling
 	tzTest := func(dbt *DBTest) {
-
 		// Create table
 		dbt.mustExec("CREATE TABLE test (ts TIMESTAMP)")
 
diff --git a/dsn.go b/dsn.go
index 3ade963ee..f5ea0d470 100644
--- a/dsn.go
+++ b/dsn.go
@@ -398,7 +398,6 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 
 		// cfg params
 		switch value := param[1]; param[0] {
-
 		// Disable INFILE whitelist / enable all files
 		case "allowAllFiles":
 			var isBool bool
diff --git a/utils.go b/utils.go
index 82da83099..a92a4029b 100644
--- a/utils.go
+++ b/utils.go
@@ -566,8 +566,8 @@ func readLengthEncodedInteger(b []byte) (uint64, bool, int) {
 	if len(b) == 0 {
 		return 0, true, 1
 	}
-	switch b[0] {
 
+	switch b[0] {
 	// 251: NULL
 	case 0xfb:
 		return 0, true, 1

From 0aa39ff15f267e3545e9f7b5b51ba8e994f517c7 Mon Sep 17 00:00:00 2001
From: Daniel Montoya <dsmontoyam@gmail.com>
Date: Wed, 15 Nov 2017 16:37:47 -0600
Subject: [PATCH 043/282] Added support for custom string types in
 ConvertValue. (#623)

* Added support for custom string types.

* Add author name

* Added license header

* Added a newline to force a commit.

* Remove newline.
---
 AUTHORS           |  1 +
 statement.go      |  2 ++
 statement_test.go | 21 +++++++++++++++++++++
 3 files changed, 24 insertions(+)
 create mode 100644 statement_test.go

diff --git a/AUTHORS b/AUTHORS
index ac36be9a7..ca6a1daeb 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -18,6 +18,7 @@ Asta Xie <xiemengjun at gmail.com>
 Bulat Gaifullin <gaifullinbf at gmail.com>
 Carlos Nieto <jose.carlos at menteslibres.net>
 Chris Moos <chris at tech9computers.com>
+Daniel Montoya <dsmontoyam at gmail.com>
 Daniel Nichter <nil at codenode.com>
 Daniël van Eeden <git at myname.nl>
 Dave Protasowski <dprotaso at gmail.com>
diff --git a/statement.go b/statement.go
index ae223507f..628174b64 100644
--- a/statement.go
+++ b/statement.go
@@ -157,6 +157,8 @@ func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
 		return int64(u64), nil
 	case reflect.Float32, reflect.Float64:
 		return rv.Float(), nil
+	case reflect.String:
+		return rv.String(), nil
 	}
 	return nil, fmt.Errorf("unsupported type %T, a %s", v, rv.Kind())
 }
diff --git a/statement_test.go b/statement_test.go
new file mode 100644
index 000000000..8de4a8b26
--- /dev/null
+++ b/statement_test.go
@@ -0,0 +1,21 @@
+// 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 "testing"
+
+type customString string
+
+func TestConvertValueCustomTypes(t *testing.T) {
+	var cstr customString = "string"
+	c := converter{}
+	if _, err := c.ConvertValue(cstr); err != nil {
+		t.Errorf("custom string type should be valid")
+	}
+}

From 78d399c0b71ef4261e5712c4c91628ca14b18b3c Mon Sep 17 00:00:00 2001
From: Justin Li <jli.justinli@gmail.com>
Date: Thu, 16 Nov 2017 02:25:03 -0500
Subject: [PATCH 044/282] Implement NamedValueChecker for mysqlConn (#690)

* Also add conversions for additional types in ConvertValue
  ref https://github.com/golang/go/commit/d7c0de98a96893e5608358f7578c85be7ba12b25
---
 AUTHORS                 |   1 +
 connection_go18.go      |   5 ++
 connection_go18_test.go |  30 ++++++++++
 statement.go            |   8 +++
 statement_test.go       | 119 +++++++++++++++++++++++++++++++++++++---
 5 files changed, 156 insertions(+), 7 deletions(-)
 create mode 100644 connection_go18_test.go

diff --git a/AUTHORS b/AUTHORS
index ca6a1daeb..c405b8912 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -40,6 +40,7 @@ Jian Zhen <zhenjl at gmail.com>
 Joshua Prunier <joshua.prunier at gmail.com>
 Julien Lefevre <julien.lefevr at gmail.com>
 Julien Schmidt <go-sql-driver at julienschmidt.com>
+Justin Li <jli at j-li.net>
 Justin Nuß <nuss.justin at gmail.com>
 Kamil Dziedzic <kamil at klecza.pl>
 Kevin Malachowski <kevin at chowski.com>
diff --git a/connection_go18.go b/connection_go18.go
index 48a9cca64..1306b70b7 100644
--- a/connection_go18.go
+++ b/connection_go18.go
@@ -195,3 +195,8 @@ func (mc *mysqlConn) startWatcher() {
 		}
 	}()
 }
+
+func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) {
+	nv.Value, err = converter{}.ConvertValue(nv.Value)
+	return
+}
diff --git a/connection_go18_test.go b/connection_go18_test.go
new file mode 100644
index 000000000..2719ab3b7
--- /dev/null
+++ b/connection_go18_test.go
@@ -0,0 +1,30 @@
+// 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/.
+
+// +build go1.8
+
+package mysql
+
+import (
+	"database/sql/driver"
+	"testing"
+)
+
+func TestCheckNamedValue(t *testing.T) {
+	value := driver.NamedValue{Value: ^uint64(0)}
+	x := &mysqlConn{}
+	err := x.CheckNamedValue(&value)
+
+	if err != nil {
+		t.Fatal("uint64 high-bit not convertible", err)
+	}
+
+	if value.Value != "18446744073709551615" {
+		t.Fatalf("uint64 high-bit not converted, got %#v %T", value.Value, value.Value)
+	}
+}
diff --git a/statement.go b/statement.go
index 628174b64..4870a307c 100644
--- a/statement.go
+++ b/statement.go
@@ -157,6 +157,14 @@ func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
 		return int64(u64), nil
 	case reflect.Float32, reflect.Float64:
 		return rv.Float(), nil
+	case reflect.Bool:
+		return rv.Bool(), nil
+	case reflect.Slice:
+		ek := rv.Type().Elem().Kind()
+		if ek == reflect.Uint8 {
+			return rv.Bytes(), nil
+		}
+		return nil, fmt.Errorf("unsupported type %T, a slice of %s", v, ek)
 	case reflect.String:
 		return rv.String(), nil
 	}
diff --git a/statement_test.go b/statement_test.go
index 8de4a8b26..98a6c1933 100644
--- a/statement_test.go
+++ b/statement_test.go
@@ -8,14 +8,119 @@
 
 package mysql
 
-import "testing"
+import (
+	"bytes"
+	"testing"
+)
 
-type customString string
+func TestConvertDerivedString(t *testing.T) {
+	type derived string
 
-func TestConvertValueCustomTypes(t *testing.T) {
-	var cstr customString = "string"
-	c := converter{}
-	if _, err := c.ConvertValue(cstr); err != nil {
-		t.Errorf("custom string type should be valid")
+	output, err := converter{}.ConvertValue(derived("value"))
+	if err != nil {
+		t.Fatal("Derived string type not convertible", err)
+	}
+
+	if output != "value" {
+		t.Fatalf("Derived string type not converted, got %#v %T", output, output)
+	}
+}
+
+func TestConvertDerivedByteSlice(t *testing.T) {
+	type derived []uint8
+
+	output, err := converter{}.ConvertValue(derived("value"))
+	if err != nil {
+		t.Fatal("Byte slice not convertible", err)
+	}
+
+	if bytes.Compare(output.([]byte), []byte("value")) != 0 {
+		t.Fatalf("Byte slice not converted, got %#v %T", output, output)
+	}
+}
+
+func TestConvertDerivedUnsupportedSlice(t *testing.T) {
+	type derived []int
+
+	_, err := converter{}.ConvertValue(derived{1})
+	if err == nil || err.Error() != "unsupported type mysql.derived, a slice of int" {
+		t.Fatal("Unexpected error", err)
+	}
+}
+
+func TestConvertDerivedBool(t *testing.T) {
+	type derived bool
+
+	output, err := converter{}.ConvertValue(derived(true))
+	if err != nil {
+		t.Fatal("Derived bool type not convertible", err)
+	}
+
+	if output != true {
+		t.Fatalf("Derived bool type not converted, got %#v %T", output, output)
+	}
+}
+
+func TestConvertPointer(t *testing.T) {
+	str := "value"
+
+	output, err := converter{}.ConvertValue(&str)
+	if err != nil {
+		t.Fatal("Pointer type not convertible", err)
+	}
+
+	if output != "value" {
+		t.Fatalf("Pointer type not converted, got %#v %T", output, output)
+	}
+}
+
+func TestConvertSignedIntegers(t *testing.T) {
+	values := []interface{}{
+		int8(-42),
+		int16(-42),
+		int32(-42),
+		int64(-42),
+		int(-42),
+	}
+
+	for _, value := range values {
+		output, err := converter{}.ConvertValue(value)
+		if err != nil {
+			t.Fatalf("%T type not convertible %s", value, err)
+		}
+
+		if output != int64(-42) {
+			t.Fatalf("%T type not converted, got %#v %T", value, output, output)
+		}
+	}
+}
+
+func TestConvertUnsignedIntegers(t *testing.T) {
+	values := []interface{}{
+		uint8(42),
+		uint16(42),
+		uint32(42),
+		uint64(42),
+		uint(42),
+	}
+
+	for _, value := range values {
+		output, err := converter{}.ConvertValue(value)
+		if err != nil {
+			t.Fatalf("%T type not convertible %s", value, err)
+		}
+
+		if output != int64(42) {
+			t.Fatalf("%T type not converted, got %#v %T", value, output, output)
+		}
+	}
+
+	output, err := converter{}.ConvertValue(^uint64(0))
+	if err != nil {
+		t.Fatal("uint64 high-bit not convertible", err)
+	}
+
+	if output != "18446744073709551615" {
+		t.Fatalf("uint64 high-bit not converted, got %#v %T", output, output)
 	}
 }

From b816f3da1576bc2985a25016903652373de4733f Mon Sep 17 00:00:00 2001
From: Dave Stubbs <dave@randomjunk.co.uk>
Date: Thu, 16 Nov 2017 16:10:24 +0000
Subject: [PATCH 045/282] Fix Valuers by returning driver.ErrSkip if couldn't
 convert type internally (#709)

Fixes #708
---
 AUTHORS            |  1 +
 connection_go18.go |  6 +++++-
 driver_test.go     | 30 ++++++++++++++++++++++++++++++
 3 files changed, 36 insertions(+), 1 deletion(-)

diff --git a/AUTHORS b/AUTHORS
index c405b8912..1ace1fa45 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -71,6 +71,7 @@ Zhenye Xie <xiezhenye at gmail.com>
 # Organizations
 
 Barracuda Networks, Inc.
+Counting Ltd.
 Google Inc.
 Keybase Inc.
 Pivotal Inc.
diff --git a/connection_go18.go b/connection_go18.go
index 1306b70b7..65cc63ef2 100644
--- a/connection_go18.go
+++ b/connection_go18.go
@@ -197,6 +197,10 @@ func (mc *mysqlConn) startWatcher() {
 }
 
 func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) {
-	nv.Value, err = converter{}.ConvertValue(nv.Value)
+	value, err := converter{}.ConvertValue(nv.Value)
+	if err != nil {
+		return driver.ErrSkip
+	}
+	nv.Value = value
 	return
 }
diff --git a/driver_test.go b/driver_test.go
index f6965b191..392e752a3 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -499,6 +499,36 @@ func TestString(t *testing.T) {
 	})
 }
 
+type testValuer struct {
+	value string
+}
+
+func (tv testValuer) Value() (driver.Value, error) {
+	return tv.value, nil
+}
+
+func TestValuer(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		in := testValuer{"a_value"}
+		var out string
+		var rows *sql.Rows
+
+		dbt.mustExec("CREATE TABLE test (value VARCHAR(255)) CHARACTER SET utf8")
+		dbt.mustExec("INSERT INTO test VALUES (?)", in)
+		rows = dbt.mustQuery("SELECT value FROM test")
+		if rows.Next() {
+			rows.Scan(&out)
+			if in.value != out {
+				dbt.Errorf("Valuer: %v != %s", in, out)
+			}
+		} else {
+			dbt.Errorf("Valuer: no data")
+		}
+
+		dbt.mustExec("DROP TABLE IF EXISTS test")
+	})
+}
+
 type timeTests struct {
 	dbtype  string
 	tlayout string

From 370fec2ee5c33c6e2bc3b63da3ef17f6a66d2229 Mon Sep 17 00:00:00 2001
From: Linh Tran Tuan <linxGnu@users.noreply.github.com>
Date: Fri, 17 Nov 2017 14:23:23 +0700
Subject: [PATCH 046/282] statement: Fix conversion of Valuer (#710)

Updates #709
Fixes #706
---
 AUTHORS            |  1 +
 connection_go18.go |  6 +-----
 driver_test.go     | 49 ++++++++++++++++++++++++++++++++++++++++++++++
 statement.go       |  6 ++++++
 4 files changed, 57 insertions(+), 5 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 1ace1fa45..c648e1835 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -46,6 +46,7 @@ Kamil Dziedzic <kamil at klecza.pl>
 Kevin Malachowski <kevin at chowski.com>
 Lennart Rudolph <lrudolph at hmc.edu>
 Leonardo YongUk Kim <dalinaum at gmail.com>
+Linh Tran Tuan <linhduonggnu at gmail.com>
 Lion Yang <lion at aosc.xyz>
 Luca Looz <luca.looz92 at gmail.com>
 Lucas Liu <extrafliu at gmail.com>
diff --git a/connection_go18.go b/connection_go18.go
index 65cc63ef2..1306b70b7 100644
--- a/connection_go18.go
+++ b/connection_go18.go
@@ -197,10 +197,6 @@ func (mc *mysqlConn) startWatcher() {
 }
 
 func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) {
-	value, err := converter{}.ConvertValue(nv.Value)
-	if err != nil {
-		return driver.ErrSkip
-	}
-	nv.Value = value
+	nv.Value, err = converter{}.ConvertValue(nv.Value)
 	return
 }
diff --git a/driver_test.go b/driver_test.go
index 392e752a3..224a24c53 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -529,6 +529,55 @@ func TestValuer(t *testing.T) {
 	})
 }
 
+type testValuerWithValidation struct {
+	value string
+}
+
+func (tv testValuerWithValidation) Value() (driver.Value, error) {
+	if len(tv.value) == 0 {
+		return nil, fmt.Errorf("Invalid string valuer. Value must not be empty")
+	}
+
+	return tv.value, nil
+}
+
+func TestValuerWithValidation(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		in := testValuerWithValidation{"a_value"}
+		var out string
+		var rows *sql.Rows
+
+		dbt.mustExec("CREATE TABLE testValuer (value VARCHAR(255)) CHARACTER SET utf8")
+		dbt.mustExec("INSERT INTO testValuer VALUES (?)", in)
+
+		rows = dbt.mustQuery("SELECT value FROM testValuer")
+		defer rows.Close()
+
+		if rows.Next() {
+			rows.Scan(&out)
+			if in.value != out {
+				dbt.Errorf("Valuer: %v != %s", in, out)
+			}
+		} else {
+			dbt.Errorf("Valuer: no data")
+		}
+
+		if _, err := dbt.db.Exec("INSERT INTO testValuer VALUES (?)", testValuerWithValidation{""}); err == nil {
+			dbt.Errorf("Failed to check valuer error")
+		}
+
+		if _, err := dbt.db.Exec("INSERT INTO testValuer VALUES (?)", nil); err != nil {
+			dbt.Errorf("Failed to check nil")
+		}
+
+		if _, err := dbt.db.Exec("INSERT INTO testValuer VALUES (?)", map[string]bool{}); err == nil {
+			dbt.Errorf("Failed to check not valuer")
+		}
+
+		dbt.mustExec("DROP TABLE IF EXISTS testValuer")
+	})
+}
+
 type timeTests struct {
 	dbtype  string
 	tlayout string
diff --git a/statement.go b/statement.go
index 4870a307c..98e57bcd8 100644
--- a/statement.go
+++ b/statement.go
@@ -137,6 +137,12 @@ func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
 		return v, nil
 	}
 
+	if v != nil {
+		if valuer, ok := v.(driver.Valuer); ok {
+			return valuer.Value()
+		}
+	}
+
 	rv := reflect.ValueOf(v)
 	switch rv.Kind() {
 	case reflect.Ptr:

From cd4cb909ce1a31435164be29bf3682031f61539a Mon Sep 17 00:00:00 2001
From: "Robert R. Russell" <robert@rrbrussell.com>
Date: Fri, 17 Nov 2017 05:51:24 -0600
Subject: [PATCH 047/282] Fixed imports for appengine/cloudsql (#700)

* Fixed broken import for appengine/cloudsql

appengine.go
import path of appengine/cloudsql has changed to google.golang.org/appengine/cloudsql - Fixed.

* Added my name to the AUTHORS
---
 AUTHORS      | 1 +
 appengine.go | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/AUTHORS b/AUTHORS
index c648e1835..cf68a3c70 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -60,6 +60,7 @@ Paul Bonser <misterpib at gmail.com>
 Peter Schultz <peter.schultz at classmarkets.com>
 Rebecca Chin <rchin at pivotal.io>
 Runrioter Wung <runrioter at gmail.com>
+Robert Russell <robert at rrbrussell.com>
 Shuode Li <elemount at qq.com>
 Soroush Pour <me at soroushjp.com>
 Stan Putrya <root.vagner at gmail.com>
diff --git a/appengine.go b/appengine.go
index 565614eef..be41f2ee6 100644
--- a/appengine.go
+++ b/appengine.go
@@ -11,7 +11,7 @@
 package mysql
 
 import (
-	"appengine/cloudsql"
+	"google.golang.org/appengine/cloudsql"
 )
 
 func init() {

From 9181e3a86a19bacd63e68d43ae8b7b36320d8092 Mon Sep 17 00:00:00 2001
From: INADA Naoki <methane@users.noreply.github.com>
Date: Mon, 4 Dec 2017 09:43:26 +0900
Subject: [PATCH 048/282] Fix tls=true didn't work with host without port
 (#718)

Fixes #717
---
 dsn.go      | 20 +++++++++-----------
 dsn_test.go | 28 ++++++++++++++++++++++++++++
 2 files changed, 37 insertions(+), 11 deletions(-)

diff --git a/dsn.go b/dsn.go
index f5ea0d470..47eab6945 100644
--- a/dsn.go
+++ b/dsn.go
@@ -94,6 +94,15 @@ func (cfg *Config) normalize() error {
 		cfg.Addr = ensureHavePort(cfg.Addr)
 	}
 
+	if cfg.tls != nil {
+		if cfg.tls.ServerName == "" && !cfg.tls.InsecureSkipVerify {
+			host, _, err := net.SplitHostPort(cfg.Addr)
+			if err == nil {
+				cfg.tls.ServerName = host
+			}
+		}
+	}
+
 	return nil
 }
 
@@ -521,10 +530,6 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 				if boolValue {
 					cfg.TLSConfig = "true"
 					cfg.tls = &tls.Config{}
-					host, _, err := net.SplitHostPort(cfg.Addr)
-					if err == nil {
-						cfg.tls.ServerName = host
-					}
 				} else {
 					cfg.TLSConfig = "false"
 				}
@@ -538,13 +543,6 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 				}
 
 				if tlsConfig := getTLSConfigClone(name); tlsConfig != nil {
-					if len(tlsConfig.ServerName) == 0 && !tlsConfig.InsecureSkipVerify {
-						host, _, err := net.SplitHostPort(cfg.Addr)
-						if err == nil {
-							tlsConfig.ServerName = host
-						}
-					}
-
 					cfg.TLSConfig = name
 					cfg.tls = tlsConfig
 				} else {
diff --git a/dsn_test.go b/dsn_test.go
index 07b223f6b..7507d1201 100644
--- a/dsn_test.go
+++ b/dsn_test.go
@@ -177,6 +177,34 @@ func TestDSNWithCustomTLS(t *testing.T) {
 	DeregisterTLSConfig("utils_test")
 }
 
+func TestDSNTLSConfig(t *testing.T) {
+	expectedServerName := "example.com"
+	dsn := "tcp(example.com:1234)/?tls=true"
+
+	cfg, err := ParseDSN(dsn)
+	if err != nil {
+		t.Error(err.Error())
+	}
+	if cfg.tls == nil {
+		t.Error("cfg.tls should not be nil")
+	}
+	if cfg.tls.ServerName != expectedServerName {
+		t.Errorf("cfg.tls.ServerName should be %q, got %q (host with port)", expectedServerName, cfg.tls.ServerName)
+	}
+
+	dsn = "tcp(example.com)/?tls=true"
+	cfg, err = ParseDSN(dsn)
+	if err != nil {
+		t.Error(err.Error())
+	}
+	if cfg.tls == nil {
+		t.Error("cfg.tls should not be nil")
+	}
+	if cfg.tls.ServerName != expectedServerName {
+		t.Errorf("cfg.tls.ServerName should be %q, got %q (host without port)", expectedServerName, cfg.tls.ServerName)
+	}
+}
+
 func TestDSNWithCustomTLSQueryEscape(t *testing.T) {
 	const configKey = "&%!:"
 	dsn := "User:password@tcp(localhost:5555)/dbname?tls=" + url.QueryEscape(configKey)

From 9889442273dc37f9df31cf2055b5169844b36fb3 Mon Sep 17 00:00:00 2001
From: Kieron Woodhouse <kieron.woodhouse@yahoo.co.uk>
Date: Wed, 10 Jan 2018 11:31:24 +0000
Subject: [PATCH 049/282] Differentiate between BINARY and CHAR (#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
---
 AUTHORS             |   2 +
 collations.go       |   1 +
 driver_go18_test.go |  22 ++++++++-
 fields.go           | 112 ++++++++++++++++++++++++++++++++------------
 packets.go          |   6 ++-
 rows.go             |   5 +-
 6 files changed, 112 insertions(+), 36 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index cf68a3c70..861905d23 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -44,6 +44,7 @@ Justin Li <jli at j-li.net>
 Justin Nuß <nuss.justin at gmail.com>
 Kamil Dziedzic <kamil at klecza.pl>
 Kevin Malachowski <kevin at chowski.com>
+Kieron Woodhouse <kieron.woodhouse at infosum.com>
 Lennart Rudolph <lrudolph at hmc.edu>
 Leonardo YongUk Kim <dalinaum at gmail.com>
 Linh Tran Tuan <linhduonggnu at gmail.com>
@@ -75,6 +76,7 @@ Zhenye Xie <xiezhenye at gmail.com>
 Barracuda Networks, Inc.
 Counting Ltd.
 Google Inc.
+InfoSum Ltd.
 Keybase Inc.
 Pivotal Inc.
 Stripe Inc.
diff --git a/collations.go b/collations.go
index 82079cfb9..136c9e4d1 100644
--- a/collations.go
+++ b/collations.go
@@ -9,6 +9,7 @@
 package mysql
 
 const defaultCollation = "utf8_general_ci"
+const binaryCollation = "binary"
 
 // A list of available collations mapped to the internal ID.
 // To update this map use the following MySQL query:
diff --git a/driver_go18_test.go b/driver_go18_test.go
index 953adeb8a..e461455dd 100644
--- a/driver_go18_test.go
+++ b/driver_go18_test.go
@@ -588,10 +588,16 @@ func TestRowsColumnTypes(t *testing.T) {
 	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}
+	nd1 := NullTime{Time: time.Date(2006, 01, 02, 0, 0, 0, 0, time.UTC), Valid: true}
+	nd2 := NullTime{Time: time.Date(2006, 03, 04, 0, 0, 0, 0, time.UTC), Valid: true}
+	ndNULL := NullTime{Time: time.Time{}, Valid: false}
 	rbNULL := sql.RawBytes(nil)
 	rb0 := sql.RawBytes("0")
 	rb42 := sql.RawBytes("42")
 	rbTest := sql.RawBytes("Test")
+	rb0pad4 := sql.RawBytes("0\x00\x00\x00") // BINARY right-pads values with 0x00
+	rbx0 := sql.RawBytes("\x00")
+	rbx42 := sql.RawBytes("\x42")
 
 	var columns = []struct {
 		name             string
@@ -604,6 +610,7 @@ func TestRowsColumnTypes(t *testing.T) {
 		valuesIn         [3]string
 		valuesOut        [3]interface{}
 	}{
+		{"bit8null", "BIT(8)", "BIT", scanTypeRawBytes, true, 0, 0, [3]string{"0x0", "NULL", "0x42"}, [3]interface{}{rbx0, rbNULL, rbx42}},
 		{"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}},
@@ -611,6 +618,7 @@ func TestRowsColumnTypes(t *testing.T) {
 		{"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)}},
+		{"mediumintnull", "MEDIUMINT", "MEDIUMINT", scanTypeNullInt, true, 0, 0, [3]string{"0", "42", "NULL"}, [3]interface{}{ni0, ni42, niNULL}},
 		{"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)}},
@@ -630,11 +638,21 @@ func TestRowsColumnTypes(t *testing.T) {
 		{"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}},
+		{"binary4null", "BINARY(4)", "BINARY", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0pad4, rbNULL, rbTest}},
+		{"varbinary42", "VARBINARY(42) NOT NULL", "VARBINARY", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
+		{"tinyblobnull", "TINYBLOB", "BLOB", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
+		{"tinytextnull", "TINYTEXT", "TEXT", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
+		{"blobnull", "BLOB", "BLOB", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
+		{"textnull", "TEXT", "TEXT", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
+		{"mediumblob", "MEDIUMBLOB NOT NULL", "BLOB", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
+		{"mediumtext", "MEDIUMTEXT NOT NULL", "TEXT", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
+		{"longblob", "LONGBLOB NOT NULL", "BLOB", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
+		{"longtext", "LONGTEXT NOT NULL", "TEXT", 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}},
+		{"date", "DATE", "DATE", scanTypeNullTime, true, 0, 0, [3]string{"'2006-01-02'", "NULL", "'2006-03-04'"}, [3]interface{}{nd1, ndNULL, nd2}},
+		{"year", "YEAR NOT NULL", "YEAR", scanTypeUint16, false, 0, 0, [3]string{"2006", "2000", "1994"}, [3]interface{}{uint16(2006), uint16(2000), uint16(1994)}},
 	}
 
 	schema := ""
diff --git a/fields.go b/fields.go
index cded986d2..e1e2ece4b 100644
--- a/fields.go
+++ b/fields.go
@@ -13,35 +13,88 @@ import (
 	"reflect"
 )
 
-var typeDatabaseName = map[fieldType]string{
-	fieldTypeBit:        "BIT",
-	fieldTypeBLOB:       "BLOB",
-	fieldTypeDate:       "DATE",
-	fieldTypeDateTime:   "DATETIME",
-	fieldTypeDecimal:    "DECIMAL",
-	fieldTypeDouble:     "DOUBLE",
-	fieldTypeEnum:       "ENUM",
-	fieldTypeFloat:      "FLOAT",
-	fieldTypeGeometry:   "GEOMETRY",
-	fieldTypeInt24:      "MEDIUMINT",
-	fieldTypeJSON:       "JSON",
-	fieldTypeLong:       "INT",
-	fieldTypeLongBLOB:   "LONGBLOB",
-	fieldTypeLongLong:   "BIGINT",
-	fieldTypeMediumBLOB: "MEDIUMBLOB",
-	fieldTypeNewDate:    "DATE",
-	fieldTypeNewDecimal: "DECIMAL",
-	fieldTypeNULL:       "NULL",
-	fieldTypeSet:        "SET",
-	fieldTypeShort:      "SMALLINT",
-	fieldTypeString:     "CHAR",
-	fieldTypeTime:       "TIME",
-	fieldTypeTimestamp:  "TIMESTAMP",
-	fieldTypeTiny:       "TINYINT",
-	fieldTypeTinyBLOB:   "TINYBLOB",
-	fieldTypeVarChar:    "VARCHAR",
-	fieldTypeVarString:  "VARCHAR",
-	fieldTypeYear:       "YEAR",
+func (mf *mysqlField) typeDatabaseName() string {
+	switch mf.fieldType {
+	case fieldTypeBit:
+		return "BIT"
+	case fieldTypeBLOB:
+		if mf.charSet != collations[binaryCollation] {
+			return "TEXT"
+		}
+		return "BLOB"
+	case fieldTypeDate:
+		return "DATE"
+	case fieldTypeDateTime:
+		return "DATETIME"
+	case fieldTypeDecimal:
+		return "DECIMAL"
+	case fieldTypeDouble:
+		return "DOUBLE"
+	case fieldTypeEnum:
+		return "ENUM"
+	case fieldTypeFloat:
+		return "FLOAT"
+	case fieldTypeGeometry:
+		return "GEOMETRY"
+	case fieldTypeInt24:
+		return "MEDIUMINT"
+	case fieldTypeJSON:
+		return "JSON"
+	case fieldTypeLong:
+		return "INT"
+	case fieldTypeLongBLOB:
+		if mf.charSet != collations[binaryCollation] {
+			return "LONGTEXT"
+		}
+		return "LONGBLOB"
+	case fieldTypeLongLong:
+		return "BIGINT"
+	case fieldTypeMediumBLOB:
+		if mf.charSet != collations[binaryCollation] {
+			return "MEDIUMTEXT"
+		}
+		return "MEDIUMBLOB"
+	case fieldTypeNewDate:
+		return "DATE"
+	case fieldTypeNewDecimal:
+		return "DECIMAL"
+	case fieldTypeNULL:
+		return "NULL"
+	case fieldTypeSet:
+		return "SET"
+	case fieldTypeShort:
+		return "SMALLINT"
+	case fieldTypeString:
+		if mf.charSet == collations[binaryCollation] {
+			return "BINARY"
+		}
+		return "CHAR"
+	case fieldTypeTime:
+		return "TIME"
+	case fieldTypeTimestamp:
+		return "TIMESTAMP"
+	case fieldTypeTiny:
+		return "TINYINT"
+	case fieldTypeTinyBLOB:
+		if mf.charSet != collations[binaryCollation] {
+			return "TINYTEXT"
+		}
+		return "TINYBLOB"
+	case fieldTypeVarChar:
+		if mf.charSet == collations[binaryCollation] {
+			return "VARBINARY"
+		}
+		return "VARCHAR"
+	case fieldTypeVarString:
+		if mf.charSet == collations[binaryCollation] {
+			return "VARBINARY"
+		}
+		return "VARCHAR"
+	case fieldTypeYear:
+		return "YEAR"
+	default:
+		return ""
+	}
 }
 
 var (
@@ -69,6 +122,7 @@ type mysqlField struct {
 	flags     fieldFlag
 	fieldType fieldType
 	decimals  byte
+	charSet   uint8
 }
 
 func (mf *mysqlField) scanType() reflect.Type {
diff --git a/packets.go b/packets.go
index f63d25072..4e66639c4 100644
--- a/packets.go
+++ b/packets.go
@@ -697,10 +697,14 @@ func (mc *mysqlConn) readColumns(count int) ([]mysqlField, error) {
 		if err != nil {
 			return nil, err
 		}
+		pos += n
 
 		// Filler [uint8]
+		pos++
+
 		// Charset [charset, collation uint8]
-		pos += n + 1 + 2
+		columns[i].charSet = data[pos]
+		pos += 2
 
 		// Length [uint32]
 		columns[i].length = binary.LittleEndian.Uint32(data[pos : pos+4])
diff --git a/rows.go b/rows.go
index 18f41693e..d3b1e2822 100644
--- a/rows.go
+++ b/rows.go
@@ -60,10 +60,7 @@ func (rows *mysqlRows) Columns() []string {
 }
 
 func (rows *mysqlRows) ColumnTypeDatabaseTypeName(i int) string {
-	if name, ok := typeDatabaseName[rows.rs.columns[i].fieldType]; ok {
-		return name
-	}
-	return ""
+	return rows.rs.columns[i].typeDatabaseName()
 }
 
 // func (rows *mysqlRows) ColumnTypeLength(i int) (length int64, ok bool) {

From 4a0c3d73d8579f9fc535cf5e654a651cbd57dd6e Mon Sep 17 00:00:00 2001
From: Alexey Palazhchenko <alexey.palazhchenko@gmail.com>
Date: Wed, 10 Jan 2018 13:44:24 +0200
Subject: [PATCH 050/282] Test with latest Go patch versions (#693)

---
 .travis.yml | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 64b06a70c..e922f9187 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,10 +1,10 @@
 sudo: false
 language: go
 go:
-  - 1.7
-  - 1.8
-  - 1.9
-  - tip
+  - 1.7.x
+  - 1.8.x
+  - 1.9.x
+  - master
 
 before_install:
   - go get golang.org/x/tools/cmd/cover
@@ -21,7 +21,7 @@ matrix:
     - env: DB=MYSQL57
       sudo: required
       dist: trusty
-      go: 1.9
+      go: 1.9.x
       services:
         - docker
       before_install:
@@ -43,7 +43,7 @@ matrix:
     - env: DB=MARIA55
       sudo: required
       dist: trusty
-      go: 1.9
+      go: 1.9.x
       services:
         - docker
       before_install:
@@ -65,7 +65,7 @@ matrix:
     - env: DB=MARIA10_1
       sudo: required
       dist: trusty
-      go: 1.9
+      go: 1.9.x
       services:
         - docker
       before_install:

From 2cc627ac8defc45d65066ae98f898166f580f9a4 Mon Sep 17 00:00:00 2001
From: INADA Naoki <methane@users.noreply.github.com>
Date: Sun, 14 Jan 2018 05:07:44 +0900
Subject: [PATCH 051/282] Fix prepared statement (#734)

* Fix prepared statement

When there are many args and maxAllowedPacket is not enough,
writeExecutePacket() attempted to use STMT_LONG_DATA even for
0byte string.
But writeCommandLongData() doesn't support 0byte data. So it
caused to send malfold packet.

This commit loosen threshold for using STMT_LONG_DATA.

* Change minimum size of LONG_DATA to 64byte

* Add test which reproduce issue 730

* TestPreparedManyCols test only numParams = 65535 case

* s/as possible//
---
 driver_test.go | 17 ++++++++++++++---
 packets.go     | 10 ++++++++--
 2 files changed, 22 insertions(+), 5 deletions(-)

diff --git a/driver_test.go b/driver_test.go
index 224a24c53..7877aa979 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -1669,8 +1669,9 @@ func TestStmtMultiRows(t *testing.T) {
 // Regression test for
 // * more than 32 NULL parameters (issue 209)
 // * more parameters than fit into the buffer (issue 201)
+// * parameters * 64 > max_allowed_packet (issue 734)
 func TestPreparedManyCols(t *testing.T) {
-	const numParams = defaultBufSize
+	numParams := 65535
 	runTests(t, dsn, func(dbt *DBTest) {
 		query := "SELECT ?" + strings.Repeat(",?", numParams-1)
 		stmt, err := dbt.db.Prepare(query)
@@ -1678,15 +1679,25 @@ func TestPreparedManyCols(t *testing.T) {
 			dbt.Fatal(err)
 		}
 		defer stmt.Close()
+
 		// create more parameters than fit into the buffer
 		// which will take nil-values
 		params := make([]interface{}, numParams)
 		rows, err := stmt.Query(params...)
 		if err != nil {
-			stmt.Close()
 			dbt.Fatal(err)
 		}
-		defer rows.Close()
+		rows.Close()
+
+		// Create 0byte string which we can't send via STMT_LONG_DATA.
+		for i := 0; i < numParams; i++ {
+			params[i] = ""
+		}
+		rows, err = stmt.Query(params...)
+		if err != nil {
+			dbt.Fatal(err)
+		}
+		rows.Close()
 	})
 }
 
diff --git a/packets.go b/packets.go
index 4e66639c4..d5dfbbae4 100644
--- a/packets.go
+++ b/packets.go
@@ -916,6 +916,12 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 	const minPktLen = 4 + 1 + 4 + 1 + 4
 	mc := stmt.mc
 
+	// Determine threshould dynamically to avoid packet size shortage.
+	longDataSize := mc.maxAllowedPacket / (stmt.paramCount + 1)
+	if longDataSize < 64 {
+		longDataSize = 64
+	}
+
 	// Reset packet-sequence
 	mc.sequence = 0
 
@@ -1043,7 +1049,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 					paramTypes[i+i] = byte(fieldTypeString)
 					paramTypes[i+i+1] = 0x00
 
-					if len(v) < mc.maxAllowedPacket-pos-len(paramValues)-(len(args)-(i+1))*64 {
+					if len(v) < longDataSize {
 						paramValues = appendLengthEncodedInteger(paramValues,
 							uint64(len(v)),
 						)
@@ -1065,7 +1071,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 				paramTypes[i+i] = byte(fieldTypeString)
 				paramTypes[i+i+1] = 0x00
 
-				if len(v) < mc.maxAllowedPacket-pos-len(paramValues)-(len(args)-(i+1))*64 {
+				if len(v) < longDataSize {
 					paramValues = appendLengthEncodedInteger(paramValues,
 						uint64(len(v)),
 					)

From bc14601d1bd56421dd60f561e6052c9ed77f9daf Mon Sep 17 00:00:00 2001
From: Reed Allman <rdallman10@gmail.com>
Date: Wed, 24 Jan 2018 21:47:45 -0800
Subject: [PATCH 052/282] driver.ErrBadConn when init packet read fails (#736)

Thank you!
---
 AUTHORS    | 1 +
 packets.go | 5 +++++
 2 files changed, 6 insertions(+)

diff --git a/AUTHORS b/AUTHORS
index 861905d23..4702c83ab 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -60,6 +60,7 @@ oscarzhao <oscarzhaosl at gmail.com>
 Paul Bonser <misterpib at gmail.com>
 Peter Schultz <peter.schultz at classmarkets.com>
 Rebecca Chin <rchin at pivotal.io>
+Reed Allman <rdallman10 at gmail.com>
 Runrioter Wung <runrioter at gmail.com>
 Robert Russell <robert at rrbrussell.com>
 Shuode Li <elemount at qq.com>
diff --git a/packets.go b/packets.go
index d5dfbbae4..afc3fcc46 100644
--- a/packets.go
+++ b/packets.go
@@ -157,6 +157,11 @@ func (mc *mysqlConn) writePacket(data []byte) error {
 func (mc *mysqlConn) readInitPacket() ([]byte, error) {
 	data, err := mc.readPacket()
 	if err != nil {
+		// for init we can rewrite this to ErrBadConn for sql.Driver to retry, since
+		// in connection initialization we don't risk retrying non-idempotent actions.
+		if err == ErrInvalidConn {
+			return nil, driver.ErrBadConn
+		}
 		return nil, err
 	}
 

From 5890359fb7194c7b6a139fa0ea3822a07827a675 Mon Sep 17 00:00:00 2001
From: ssp <chenshaobo65@gmail.com>
Date: Fri, 2 Mar 2018 15:14:49 +0800
Subject: [PATCH 053/282] fix typo (#761)

---
 utils.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/utils.go b/utils.go
index a92a4029b..91f228c00 100644
--- a/utils.go
+++ b/utils.go
@@ -800,7 +800,7 @@ func (ab *atomicBool) TrySet(value bool) bool {
 	return atomic.SwapUint32(&ab.value, 0) > 0
 }
 
-// atomicBool is a wrapper for atomically accessed error values
+// atomicError is a wrapper for atomically accessed error values
 type atomicError struct {
 	_noCopy noCopy
 	value   atomic.Value

From 02eb68a684ce8ccb467a8280c5b51232d44643b9 Mon Sep 17 00:00:00 2001
From: Alexey Palazhchenko <alexey.palazhchenko@gmail.com>
Date: Sat, 3 Mar 2018 19:06:37 +0300
Subject: [PATCH 054/282] Fix error message for unsupported isolation level.
 (#744)

* Fix error message for unsupported isolation level.

It was "mysql: unsupported isolation level: \a"
due to wrong conversion from int to string.

See also https://github.com/golang/go/issues/23632

* Add myself and sort authors.
---
 AUTHORS            | 4 +++-
 utils_go18.go      | 3 ++-
 utils_go18_test.go | 9 ++++++---
 3 files changed, 11 insertions(+), 5 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 4702c83ab..301ce573f 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -13,6 +13,7 @@
 
 Aaron Hopkins <go-sql-driver at die.net>
 Achille Roussel <achille.roussel at gmail.com>
+Alexey Palazhchenko <alexey.palazhchenko at gmail.com>
 Arne Hormann <arnehormann at gmail.com>
 Asta Xie <xiemengjun at gmail.com>
 Bulat Gaifullin <gaifullinbf at gmail.com>
@@ -61,8 +62,8 @@ Paul Bonser <misterpib at gmail.com>
 Peter Schultz <peter.schultz at classmarkets.com>
 Rebecca Chin <rchin at pivotal.io>
 Reed Allman <rdallman10 at gmail.com>
-Runrioter Wung <runrioter at gmail.com>
 Robert Russell <robert at rrbrussell.com>
+Runrioter Wung <runrioter at gmail.com>
 Shuode Li <elemount at qq.com>
 Soroush Pour <me at soroushjp.com>
 Stan Putrya <root.vagner at gmail.com>
@@ -79,5 +80,6 @@ Counting Ltd.
 Google Inc.
 InfoSum Ltd.
 Keybase Inc.
+Percona LLC
 Pivotal Inc.
 Stripe Inc.
diff --git a/utils_go18.go b/utils_go18.go
index 7d8c9b16e..c35c2a6aa 100644
--- a/utils_go18.go
+++ b/utils_go18.go
@@ -15,6 +15,7 @@ import (
 	"database/sql"
 	"database/sql/driver"
 	"errors"
+	"fmt"
 )
 
 func cloneTLSConfig(c *tls.Config) *tls.Config {
@@ -44,6 +45,6 @@ func mapIsolationLevel(level driver.IsolationLevel) (string, error) {
 	case sql.LevelSerializable:
 		return "SERIALIZABLE", nil
 	default:
-		return "", errors.New("mysql: unsupported isolation level: " + string(level))
+		return "", fmt.Errorf("mysql: unsupported isolation level: %v", level)
 	}
 }
diff --git a/utils_go18_test.go b/utils_go18_test.go
index 856c25f56..f63dbecc4 100644
--- a/utils_go18_test.go
+++ b/utils_go18_test.go
@@ -17,7 +17,6 @@ import (
 )
 
 func TestIsolationLevelMapping(t *testing.T) {
-
 	data := []struct {
 		level    driver.IsolationLevel
 		expected string
@@ -47,8 +46,12 @@ func TestIsolationLevelMapping(t *testing.T) {
 	}
 
 	// check unsupported mapping
-	if actual, err := mapIsolationLevel(driver.IsolationLevel(sql.LevelLinearizable)); actual != "" || err == nil {
+	expectedErr := "mysql: unsupported isolation level: 7"
+	actual, err := mapIsolationLevel(driver.IsolationLevel(sql.LevelLinearizable))
+	if actual != "" || err == nil {
 		t.Fatal("Expected error on unsupported isolation level")
 	}
-
+	if err.Error() != expectedErr {
+		t.Fatalf("Expected error to be %q, got %q", expectedErr, err)
+	}
 }

From e8153fbb244c9d3d799d1a86bd075ab7214c4274 Mon Sep 17 00:00:00 2001
From: Andrew Reid <andrew.reid@tixtrack.com>
Date: Thu, 8 Mar 2018 22:14:15 +1300
Subject: [PATCH 055/282] Update ConvertValue() to match the
 database/sql/driver implementation except for uint64 (#760)

This simply copies recent changes to ConvertValue from
database/sql/driver to ensure that our behaviour only differs for
uint64.

Fixes #739
---
 AUTHORS             |  1 +
 driver_go18_test.go |  8 ++++++++
 statement.go        | 41 +++++++++++++++++++++++++++++++++++++----
 3 files changed, 46 insertions(+), 4 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 301ce573f..14e8398fd 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -14,6 +14,7 @@
 Aaron Hopkins <go-sql-driver at die.net>
 Achille Roussel <achille.roussel at gmail.com>
 Alexey Palazhchenko <alexey.palazhchenko at gmail.com>
+Andrew Reid <andrew.reid at tixtrack.com>
 Arne Hormann <arnehormann at gmail.com>
 Asta Xie <xiemengjun at gmail.com>
 Bulat Gaifullin <gaifullinbf at gmail.com>
diff --git a/driver_go18_test.go b/driver_go18_test.go
index e461455dd..afd5694ec 100644
--- a/driver_go18_test.go
+++ b/driver_go18_test.go
@@ -796,3 +796,11 @@ func TestRowsColumnTypes(t *testing.T) {
 		})
 	}
 }
+
+func TestValuerWithValueReceiverGivenNilValue(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec("CREATE TABLE test (value VARCHAR(255))")
+		dbt.db.Exec("INSERT INTO test VALUES (?)", (*testValuer)(nil))
+		// This test will panic on the INSERT if ConvertValue() does not check for typed nil before calling Value()
+	})
+}
diff --git a/statement.go b/statement.go
index 98e57bcd8..ce7fe4cd0 100644
--- a/statement.go
+++ b/statement.go
@@ -132,15 +132,25 @@ func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) {
 
 type converter struct{}
 
+// ConvertValue mirrors the reference/default converter in database/sql/driver
+// with _one_ exception.  We support uint64 with their high bit and the default
+// implementation does not.  This function should be kept in sync with
+// database/sql/driver defaultConverter.ConvertValue() except for that
+// deliberate difference.
 func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
 	if driver.IsValue(v) {
 		return v, nil
 	}
 
-	if v != nil {
-		if valuer, ok := v.(driver.Valuer); ok {
-			return valuer.Value()
+	if vr, ok := v.(driver.Valuer); ok {
+		sv, err := callValuerValue(vr)
+		if err != nil {
+			return nil, err
+		}
+		if !driver.IsValue(sv) {
+			return nil, fmt.Errorf("non-Value type %T returned from Value", sv)
 		}
+		return sv, nil
 	}
 
 	rv := reflect.ValueOf(v)
@@ -149,8 +159,9 @@ func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
 		// indirect pointers
 		if rv.IsNil() {
 			return nil, nil
+		} else {
+			return c.ConvertValue(rv.Elem().Interface())
 		}
-		return c.ConvertValue(rv.Elem().Interface())
 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 		return rv.Int(), nil
 	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
@@ -176,3 +187,25 @@ func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
 	}
 	return nil, fmt.Errorf("unsupported type %T, a %s", v, rv.Kind())
 }
+
+var valuerReflectType = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
+
+// callValuerValue returns vr.Value(), with one exception:
+// If vr.Value is an auto-generated method on a pointer type and the
+// pointer is nil, it would panic at runtime in the panicwrap
+// method. Treat it like nil instead.
+//
+// This is so people can implement driver.Value on value types and
+// still use nil pointers to those types to mean nil/NULL, just like
+// string/*string.
+//
+// This is an exact copy of the same-named unexported function from the
+// database/sql package.
+func callValuerValue(vr driver.Valuer) (v driver.Value, err error) {
+	if rv := reflect.ValueOf(vr); rv.Kind() == reflect.Ptr &&
+		rv.IsNil() &&
+		rv.Type().Elem().Implements(valuerReflectType) {
+		return nil, nil
+	}
+	return vr.Value()
+}

From bcaf977ec3bfe5f07929b0a1541096537d617709 Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Thu, 8 Mar 2018 10:39:26 +0100
Subject: [PATCH 056/282] travis: Test with Go 1.10 (#764)

* travis: Test with Go 1.10

* travis: check gofmt only with go1.10+
---
 .travis.yml      | 9 +++++----
 .travis/gofmt.sh | 7 +++++++
 2 files changed, 12 insertions(+), 4 deletions(-)
 create mode 100755 .travis/gofmt.sh

diff --git a/.travis.yml b/.travis.yml
index e922f9187..4e60b0801 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,6 +4,7 @@ go:
   - 1.7.x
   - 1.8.x
   - 1.9.x
+  - 1.10.x
   - master
 
 before_install:
@@ -21,7 +22,7 @@ matrix:
     - env: DB=MYSQL57
       sudo: required
       dist: trusty
-      go: 1.9.x
+      go: 1.10.x
       services:
         - docker
       before_install:
@@ -43,7 +44,7 @@ matrix:
     - env: DB=MARIA55
       sudo: required
       dist: trusty
-      go: 1.9.x
+      go: 1.10.x
       services:
         - docker
       before_install:
@@ -65,7 +66,7 @@ matrix:
     - env: DB=MARIA10_1
       sudo: required
       dist: trusty
-      go: 1.9.x
+      go: 1.10.x
       services:
         - docker
       before_install:
@@ -87,6 +88,6 @@ matrix:
 script:
   - go test -v -covermode=count -coverprofile=coverage.out
   - go vet ./...
-  - test -z "$(gofmt -d -s . | tee /dev/stderr)"
+  - .travis/gofmt.sh
 after_script:
   - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci
diff --git a/.travis/gofmt.sh b/.travis/gofmt.sh
new file mode 100755
index 000000000..9bf0d1684
--- /dev/null
+++ b/.travis/gofmt.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+set -ev
+
+# Only check for go1.10+ since the gofmt style changed
+if [[ $(go version) =~ go1\.([0-9]+) ]] && ((${BASH_REMATCH[1]} >= 10)); then
+    test -z "$(gofmt -d -s . | tee /dev/stderr)"
+fi

From 1a676ac6e4dce68e9303a1800773861350374a9e Mon Sep 17 00:00:00 2001
From: INADA Naoki <methane@users.noreply.github.com>
Date: Thu, 8 Mar 2018 19:03:10 +0900
Subject: [PATCH 057/282] Fix readLengthEncodedString() returned []byte had
 wrong capacity (#766)

fixes #765
---
 driver_test.go | 28 ++++++++++++++++++++++++++++
 utils.go       |  2 +-
 2 files changed, 29 insertions(+), 1 deletion(-)

diff --git a/driver_test.go b/driver_test.go
index 7877aa979..ad93a37c3 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -499,6 +499,34 @@ func TestString(t *testing.T) {
 	})
 }
 
+func TestRawBytes(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		v1 := []byte("aaa")
+		v2 := []byte("bbb")
+		rows := dbt.mustQuery("SELECT ?, ?", v1, v2)
+		if rows.Next() {
+			var o1, o2 sql.RawBytes
+			if err := rows.Scan(&o1, &o2); err != nil {
+				dbt.Errorf("Got error: %v", err)
+			}
+			if !bytes.Equal(v1, o1) {
+				dbt.Errorf("expected %v, got %v", v1, o1)
+			}
+			if !bytes.Equal(v2, o2) {
+				dbt.Errorf("expected %v, got %v", v2, o2)
+			}
+			// https://github.com/go-sql-driver/mysql/issues/765
+			// Appending to RawBytes shouldn't overwrite next RawBytes.
+			o1 = append(o1, "xyzzy"...)
+			if !bytes.Equal(v2, o2) {
+				dbt.Errorf("expected %v, got %v", v2, o2)
+			}
+		} else {
+			dbt.Errorf("no data")
+		}
+	})
+}
+
 type testValuer struct {
 	value string
 }
diff --git a/utils.go b/utils.go
index 91f228c00..f986de2ab 100644
--- a/utils.go
+++ b/utils.go
@@ -537,7 +537,7 @@ func readLengthEncodedString(b []byte) ([]byte, bool, int, error) {
 
 	// Check data length
 	if len(b) >= n {
-		return b[n-int(num) : n], false, n, nil
+		return b[n-int(num) : n : n], false, n, nil
 	}
 	return nil, false, n, io.EOF
 }

From 3287d94d4c6a48a63e16fffaabf27ab20203af2a Mon Sep 17 00:00:00 2001
From: Ross Light <ross@zombiezen.com>
Date: Fri, 13 Apr 2018 11:15:57 -0700
Subject: [PATCH 058/282] Make RegisterDial safe to call from multiple
 goroutines. (#773)

Fixes #772
---
 driver.go | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/driver.go b/driver.go
index d42ce7a3d..27cf5ad4e 100644
--- a/driver.go
+++ b/driver.go
@@ -20,6 +20,7 @@ import (
 	"database/sql"
 	"database/sql/driver"
 	"net"
+	"sync"
 )
 
 // watcher interface is used for context support (From Go 1.8)
@@ -35,12 +36,17 @@ type MySQLDriver struct{}
 // Custom dial functions must be registered with RegisterDial
 type DialFunc func(addr string) (net.Conn, error)
 
-var dials map[string]DialFunc
+var (
+	dialsLock sync.RWMutex
+	dials     map[string]DialFunc
+)
 
 // RegisterDial registers a custom dial function. It can then be used by the
 // network address mynet(addr), where mynet is the registered new network.
 // addr is passed as a parameter to the dial function.
 func RegisterDial(net string, dial DialFunc) {
+	dialsLock.Lock()
+	defer dialsLock.Unlock()
 	if dials == nil {
 		dials = make(map[string]DialFunc)
 	}
@@ -66,7 +72,10 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
 	mc.parseTime = mc.cfg.ParseTime
 
 	// Connect to Server
-	if dial, ok := dials[mc.cfg.Net]; ok {
+	dialsLock.RLock()
+	dial, ok := dials[mc.cfg.Net]
+	dialsLock.RUnlock()
+	if ok {
 		mc.netConn, err = dial(mc.cfg.Addr)
 	} else {
 		nd := net.Dialer{Timeout: mc.cfg.Timeout}

From f55773078414cbe0891269c87d7963181b54e174 Mon Sep 17 00:00:00 2001
From: Hajime Nakagami <nakagami@gmail.com>
Date: Sat, 19 May 2018 23:40:55 +0900
Subject: [PATCH 059/282] Support caching_sha2_password (#794)

---
 AUTHORS        |  1 +
 const.go       |  6 ++++
 driver.go      | 28 ++++++++++++++---
 driver_test.go |  4 +--
 packets.go     | 84 ++++++++++++++++++++++++++++++++++++++++++--------
 utils.go       | 29 +++++++++++++++++
 utils_test.go  | 18 +++++++++++
 7 files changed, 152 insertions(+), 18 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 14e8398fd..2f3a8d68f 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -29,6 +29,7 @@ Egor Smolyakov <egorsmkv at gmail.com>
 Evan Shaw <evan at vendhq.com>
 Frederick Mayle <frederickmayle at gmail.com>
 Gustavo Kristic <gkristic at gmail.com>
+Hajime Nakagami <nakagami at gmail.com>
 Hanno Braun <mail at hannobraun.com>
 Henri Yandell <flamefew at gmail.com>
 Hirotaka Yamamoto <ymmt2005 at gmail.com>
diff --git a/const.go b/const.go
index 4a19ca523..1503f9e62 100644
--- a/const.go
+++ b/const.go
@@ -164,3 +164,9 @@ const (
 	statusInTransReadonly
 	statusSessionStateChanged
 )
+
+const (
+	cachingSha2PasswordRequestPublicKey          = 2
+	cachingSha2PasswordFastAuthSuccess           = 3
+	cachingSha2PasswordPerformFullAuthentication = 4
+)
diff --git a/driver.go b/driver.go
index 27cf5ad4e..f77b917a8 100644
--- a/driver.go
+++ b/driver.go
@@ -107,20 +107,20 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
 	mc.writeTimeout = mc.cfg.WriteTimeout
 
 	// Reading Handshake Initialization Packet
-	cipher, err := mc.readInitPacket()
+	cipher, pluginName, err := mc.readInitPacket()
 	if err != nil {
 		mc.cleanup()
 		return nil, err
 	}
 
 	// Send Client Authentication Packet
-	if err = mc.writeAuthPacket(cipher); err != nil {
+	if err = mc.writeAuthPacket(cipher, pluginName); err != nil {
 		mc.cleanup()
 		return nil, err
 	}
 
 	// Handle response to auth packet, switch methods if possible
-	if err = handleAuthResult(mc, cipher); err != nil {
+	if err = handleAuthResult(mc, cipher, pluginName); err != nil {
 		// Authentication failed and MySQL has already closed the connection
 		// (https://dev.mysql.com/doc/internals/en/authentication-fails.html).
 		// Do not send COM_QUIT, just cleanup and return the error.
@@ -153,7 +153,27 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
 	return mc, nil
 }
 
-func handleAuthResult(mc *mysqlConn, oldCipher []byte) error {
+func handleAuthResult(mc *mysqlConn, oldCipher []byte, pluginName string) error {
+
+	// handle caching_sha2_password
+	if pluginName == "caching_sha2_password" {
+		auth, err := mc.readCachingSha2PasswordAuthResult()
+		if err != nil {
+			return err
+		}
+		if auth == cachingSha2PasswordPerformFullAuthentication {
+			if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
+				if err = mc.writeClearAuthPacket(); err != nil {
+					return err
+				}
+			} else {
+				if err = mc.writePublicKeyAuthPacket(oldCipher); err != nil {
+					return err
+				}
+			}
+		}
+	}
+
 	// Read Result Packet
 	cipher, err := mc.readResultOK()
 	if err == nil {
diff --git a/driver_test.go b/driver_test.go
index ad93a37c3..8904b6587 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -1842,7 +1842,7 @@ func TestSQLInjection(t *testing.T) {
 
 	dsns := []string{
 		dsn,
-		dsn + "&sql_mode='NO_BACKSLASH_ESCAPES,NO_AUTO_CREATE_USER'",
+		dsn + "&sql_mode='NO_BACKSLASH_ESCAPES'",
 	}
 	for _, testdsn := range dsns {
 		runTests(t, testdsn, createTest("1 OR 1=1"))
@@ -1872,7 +1872,7 @@ func TestInsertRetrieveEscapedData(t *testing.T) {
 
 	dsns := []string{
 		dsn,
-		dsn + "&sql_mode='NO_BACKSLASH_ESCAPES,NO_AUTO_CREATE_USER'",
+		dsn + "&sql_mode='NO_BACKSLASH_ESCAPES'",
 	}
 	for _, testdsn := range dsns {
 		runTests(t, testdsn, testData)
diff --git a/packets.go b/packets.go
index afc3fcc46..6775d2860 100644
--- a/packets.go
+++ b/packets.go
@@ -10,9 +10,14 @@ package mysql
 
 import (
 	"bytes"
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/sha1"
 	"crypto/tls"
+	"crypto/x509"
 	"database/sql/driver"
 	"encoding/binary"
+	"encoding/pem"
 	"errors"
 	"fmt"
 	"io"
@@ -154,24 +159,24 @@ func (mc *mysqlConn) writePacket(data []byte) error {
 
 // Handshake Initialization Packet
 // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::Handshake
-func (mc *mysqlConn) readInitPacket() ([]byte, error) {
+func (mc *mysqlConn) readInitPacket() ([]byte, string, error) {
 	data, err := mc.readPacket()
 	if err != nil {
 		// for init we can rewrite this to ErrBadConn for sql.Driver to retry, since
 		// in connection initialization we don't risk retrying non-idempotent actions.
 		if err == ErrInvalidConn {
-			return nil, driver.ErrBadConn
+			return nil, "", driver.ErrBadConn
 		}
-		return nil, err
+		return nil, "", err
 	}
 
 	if data[0] == iERR {
-		return nil, mc.handleErrorPacket(data)
+		return nil, "", mc.handleErrorPacket(data)
 	}
 
 	// protocol version [1 byte]
 	if data[0] < minProtocolVersion {
-		return nil, fmt.Errorf(
+		return nil, "", fmt.Errorf(
 			"unsupported protocol version %d. Version %d or higher is required",
 			data[0],
 			minProtocolVersion,
@@ -191,13 +196,14 @@ func (mc *mysqlConn) readInitPacket() ([]byte, error) {
 	// capability flags (lower 2 bytes) [2 bytes]
 	mc.flags = clientFlag(binary.LittleEndian.Uint16(data[pos : pos+2]))
 	if mc.flags&clientProtocol41 == 0 {
-		return nil, ErrOldProtocol
+		return nil, "", ErrOldProtocol
 	}
 	if mc.flags&clientSSL == 0 && mc.cfg.tls != nil {
-		return nil, ErrNoTLS
+		return nil, "", ErrNoTLS
 	}
 	pos += 2
 
+	pluginName := ""
 	if len(data) > pos {
 		// character set [1 byte]
 		// status flags [2 bytes]
@@ -219,6 +225,8 @@ func (mc *mysqlConn) readInitPacket() ([]byte, error) {
 		// The official Python library uses the fixed length 12
 		// which seems to work but technically could have a hidden bug.
 		cipher = append(cipher, data[pos:pos+12]...)
+		pos += 13
+		pluginName = string(data[pos : pos+bytes.IndexByte(data[pos:], 0x00)])
 
 		// TODO: Verify string termination
 		// EOF if version (>= 5.5.7 and < 5.5.10) or (>= 5.6.0 and < 5.6.2)
@@ -232,18 +240,22 @@ func (mc *mysqlConn) readInitPacket() ([]byte, error) {
 		// make a memory safe copy of the cipher slice
 		var b [20]byte
 		copy(b[:], cipher)
-		return b[:], nil
+		return b[:], pluginName, nil
 	}
 
 	// make a memory safe copy of the cipher slice
 	var b [8]byte
 	copy(b[:], cipher)
-	return b[:], nil
+	return b[:], pluginName, nil
 }
 
 // Client Authentication Packet
 // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeResponse
-func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
+func (mc *mysqlConn) writeAuthPacket(cipher []byte, pluginName string) error {
+	if pluginName != "mysql_native_password" && pluginName != "caching_sha2_password" {
+		return fmt.Errorf("unknown authentication plugin name '%s'", pluginName)
+	}
+
 	// Adjust client flags based on server support
 	clientFlags := clientProtocol41 |
 		clientSecureConn |
@@ -268,7 +280,13 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
 	}
 
 	// User Password
-	scrambleBuff := scramblePassword(cipher, []byte(mc.cfg.Passwd))
+	var scrambleBuff []byte
+	switch pluginName {
+	case "mysql_native_password":
+		scrambleBuff = scramblePassword(cipher, []byte(mc.cfg.Passwd))
+	case "caching_sha2_password":
+		scrambleBuff = scrambleCachingSha2Password(cipher, []byte(mc.cfg.Passwd))
+	}
 
 	pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1 + 1 + len(scrambleBuff) + 21 + 1
 
@@ -350,7 +368,7 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
 	}
 
 	// Assume native client during response
-	pos += copy(data[pos:], "mysql_native_password")
+	pos += copy(data[pos:], pluginName)
 	data[pos] = 0x00
 
 	// Send Auth packet
@@ -422,6 +440,38 @@ func (mc *mysqlConn) writeNativeAuthPacket(cipher []byte) error {
 	return mc.writePacket(data)
 }
 
+//  Caching sha2 authentication. Public key request and send encrypted password
+// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
+func (mc *mysqlConn) writePublicKeyAuthPacket(cipher []byte) error {
+	// request public key
+	data := mc.buf.takeSmallBuffer(4 + 1)
+	data[4] = cachingSha2PasswordRequestPublicKey
+	mc.writePacket(data)
+
+	data, err := mc.readPacket()
+	if err != nil {
+		return err
+	}
+
+	block, _ := pem.Decode(data[1:])
+	pub, err := x509.ParsePKIXPublicKey(block.Bytes)
+	if err != nil {
+		return err
+	}
+
+	plain := make([]byte, len(mc.cfg.Passwd)+1)
+	copy(plain, mc.cfg.Passwd)
+	for i := range plain {
+		j := i % len(cipher)
+		plain[i] ^= cipher[j]
+	}
+	sha1 := sha1.New()
+	enc, _ := rsa.EncryptOAEP(sha1, rand.Reader, pub.(*rsa.PublicKey), plain, nil)
+	data = mc.buf.takeSmallBuffer(4 + len(enc))
+	copy(data[4:], enc)
+	return mc.writePacket(data)
+}
+
 /******************************************************************************
 *                             Command Packets                                 *
 ******************************************************************************/
@@ -535,6 +585,16 @@ func (mc *mysqlConn) readResultOK() ([]byte, error) {
 	return nil, err
 }
 
+func (mc *mysqlConn) readCachingSha2PasswordAuthResult() (int, error) {
+	data, err := mc.readPacket()
+	if err == nil {
+		if data[0] != 1 {
+			return 0, ErrMalformPkt
+		}
+	}
+	return int(data[1]), err
+}
+
 // Result Set Header Packet
 // http://dev.mysql.com/doc/internals/en/com-query-response.html#packet-ProtocolText::Resultset
 func (mc *mysqlConn) readResultSetHeaderPacket() (int, error) {
diff --git a/utils.go b/utils.go
index f986de2ab..9d1530b3d 100644
--- a/utils.go
+++ b/utils.go
@@ -10,6 +10,7 @@ package mysql
 
 import (
 	"crypto/sha1"
+	"crypto/sha256"
 	"crypto/tls"
 	"database/sql/driver"
 	"encoding/binary"
@@ -211,6 +212,34 @@ func scrambleOldPassword(scramble, password []byte) []byte {
 	return out[:]
 }
 
+// Encrypt password using 8.0 default method
+func scrambleCachingSha2Password(scramble, password []byte) []byte {
+	if len(password) == 0 {
+		return nil
+	}
+
+	// XOR(SHA256(password), SHA256(SHA256(SHA256(password)), scramble))
+
+	crypt := sha256.New()
+	crypt.Write(password)
+	message1 := crypt.Sum(nil)
+
+	crypt.Reset()
+	crypt.Write(message1)
+	message1Hash := crypt.Sum(nil)
+
+	crypt.Reset()
+	crypt.Write(message1Hash)
+	crypt.Write(scramble)
+	message2 := crypt.Sum(nil)
+
+	for i := range message1 {
+		message1[i] ^= message2[i]
+	}
+
+	return message1
+}
+
 /******************************************************************************
 *                           Time related utils                                *
 ******************************************************************************/
diff --git a/utils_test.go b/utils_test.go
index 0041892db..a599c55f3 100644
--- a/utils_test.go
+++ b/utils_test.go
@@ -112,6 +112,24 @@ func TestOldPass(t *testing.T) {
 	}
 }
 
+func TestCachingSha2Pass(t *testing.T) {
+	scramble := []byte{10, 47, 74, 111, 75, 73, 34, 48, 88, 76, 114, 74, 37, 13, 3, 80, 82, 2, 23, 21}
+	vectors := []struct {
+		pass string
+		out  string
+	}{
+		{"secret", "f490e76f66d9d86665ce54d98c78d0acfe2fb0b08b423da807144873d30b312c"},
+		{"secret2", "abc3934a012cf342e876071c8ee202de51785b430258a7a0138bc79c4d800bc6"},
+	}
+	for _, tuple := range vectors {
+		ours := scrambleCachingSha2Password(scramble, []byte(tuple.pass))
+		if tuple.out != fmt.Sprintf("%x", ours) {
+			t.Errorf("Failed caching sha2 password %q", tuple.pass)
+		}
+	}
+
+}
+
 func TestFormatBinaryDateTime(t *testing.T) {
 	rawDate := [11]byte{}
 	binary.LittleEndian.PutUint16(rawDate[:2], 1978)   // years

From 92a288d2426bb79e058d61dc5009a67e0568c431 Mon Sep 17 00:00:00 2001
From: Ichinose Shogo <shogo82148@gmail.com>
Date: Sun, 20 May 2018 03:41:39 +0900
Subject: [PATCH 060/282] implement SessionResetter (#779)

---
 connection_go18.go | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/connection_go18.go b/connection_go18.go
index 1306b70b7..474ea227f 100644
--- a/connection_go18.go
+++ b/connection_go18.go
@@ -200,3 +200,12 @@ func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) {
 	nv.Value, err = converter{}.ConvertValue(nv.Value)
 	return
 }
+
+// ResetSession implements driver.SessionResetter.
+// (From Go 1.10)
+func (mc *mysqlConn) ResetSession(ctx context.Context) error {
+	if mc.closed.IsSet() {
+		return driver.ErrBadConn
+	}
+	return nil
+}

From d03e4c28477ccf584ef564f3957b42550d5785f1 Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Mon, 21 May 2018 11:13:49 +0200
Subject: [PATCH 061/282] Travis: Add  MySQL 8.0 test enviornment (#798)

* travis: add mysql 8.0 env

* travis: disable version check for mysql 8
debian does not have a compatible client yet

* travis: set local-infile=1

* travis: try to fix waiting for mysql 8 and optimize wait time

* travis: show output of wait script

* travis: grep for successful execution

* travis: remove sleep
---
 .travis.yml           | 32 +++++++++++++++++++++++---------
 .travis/wait_mysql.sh |  4 ++--
 2 files changed, 25 insertions(+), 11 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 4e60b0801..cc1268c36 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -19,6 +19,26 @@ before_script:
 
 matrix:
   include:
+    - env: DB=MYSQL8
+      sudo: required
+      dist: trusty
+      go: 1.10.x
+      services:
+        - docker
+      before_install:
+        - go get golang.org/x/tools/cmd/cover
+        - go get github.com/mattn/goveralls
+        - docker pull mysql:8.0
+        - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
+          mysql:8.0 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
+        - cp .travis/docker.cnf ~/.my.cnf
+        - .travis/wait_mysql.sh
+      before_script:
+        - export MYSQL_TEST_USER=gotest
+        - export MYSQL_TEST_PASS=secret
+        - export MYSQL_TEST_ADDR=127.0.0.1:3307
+        - export MYSQL_TEST_CONCURRENT=1
+
     - env: DB=MYSQL57
       sudo: required
       dist: trusty
@@ -30,10 +50,8 @@ matrix:
         - go get github.com/mattn/goveralls
         - docker pull mysql:5.7
         - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
-          mysql:5.7 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB
-        - sleep 30
+          mysql:5.7 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
         - cp .travis/docker.cnf ~/.my.cnf
-        - mysql --print-defaults
         - .travis/wait_mysql.sh
       before_script:
         - export MYSQL_TEST_USER=gotest
@@ -52,10 +70,8 @@ matrix:
         - go get github.com/mattn/goveralls
         - docker pull mariadb:5.5
         - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
-          mariadb:5.5 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB
-        - sleep 30
+          mariadb:5.5 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
         - cp .travis/docker.cnf ~/.my.cnf
-        - mysql --print-defaults
         - .travis/wait_mysql.sh
       before_script:
         - export MYSQL_TEST_USER=gotest
@@ -74,10 +90,8 @@ matrix:
         - go get github.com/mattn/goveralls
         - docker pull mariadb:10.1
         - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
-          mariadb:10.1 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB
-        - sleep 30
+          mariadb:10.1 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
         - cp .travis/docker.cnf ~/.my.cnf
-        - mysql --print-defaults
         - .travis/wait_mysql.sh
       before_script:
         - export MYSQL_TEST_USER=gotest
diff --git a/.travis/wait_mysql.sh b/.travis/wait_mysql.sh
index abcf5f0ae..e87993e57 100755
--- a/.travis/wait_mysql.sh
+++ b/.travis/wait_mysql.sh
@@ -1,8 +1,8 @@
 #!/bin/sh
 while :
 do
-    sleep 3
-    if mysql -e 'select version()'; then
+    if mysql -e 'select version()' 2>&1 | grep 'version()\|ERROR 2059 (HY000):'; then
         break
     fi
+    sleep 3
 done

From 5afaf12c4ecbd6e6027400ccd74af424a78d1be4 Mon Sep 17 00:00:00 2001
From: Richard Wilkes <wilkes@me.com>
Date: Tue, 22 May 2018 10:26:19 -0700
Subject: [PATCH 062/282] Fix 'panic: runtime error: slice bounds out of range'
 (#801)

---
 AUTHORS    |  1 +
 packets.go | 12 +++++-------
 2 files changed, 6 insertions(+), 7 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 2f3a8d68f..07d0deee3 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -64,6 +64,7 @@ Paul Bonser <misterpib at gmail.com>
 Peter Schultz <peter.schultz at classmarkets.com>
 Rebecca Chin <rchin at pivotal.io>
 Reed Allman <rdallman10 at gmail.com>
+Richard Wilkes <wilkes at me.com>
 Robert Russell <robert at rrbrussell.com>
 Runrioter Wung <runrioter at gmail.com>
 Shuode Li <elemount at qq.com>
diff --git a/packets.go b/packets.go
index 6775d2860..9b76fb040 100644
--- a/packets.go
+++ b/packets.go
@@ -226,16 +226,14 @@ func (mc *mysqlConn) readInitPacket() ([]byte, string, error) {
 		// which seems to work but technically could have a hidden bug.
 		cipher = append(cipher, data[pos:pos+12]...)
 		pos += 13
-		pluginName = string(data[pos : pos+bytes.IndexByte(data[pos:], 0x00)])
 
-		// TODO: Verify string termination
 		// EOF if version (>= 5.5.7 and < 5.5.10) or (>= 5.6.0 and < 5.6.2)
 		// \NUL otherwise
-		//
-		//if data[len(data)-1] == 0 {
-		//	return
-		//}
-		//return ErrMalformPkt
+		if end := bytes.IndexByte(data[pos:], 0x00); end != -1 {
+			pluginName = string(data[pos : pos+end])
+		} else {
+			pluginName = string(data[pos:])
+		}
 
 		// make a memory safe copy of the cipher slice
 		var b [20]byte

From 44c24dfcb859550f6aa37c41d146efb4ff35ec82 Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Wed, 23 May 2018 08:50:40 +0200
Subject: [PATCH 063/282] packets: add regression test for #801 (#802)

Updates #801
---
 packets_test.go | 35 +++++++++++++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/packets_test.go b/packets_test.go
index 2f8207511..c9cfd9001 100644
--- a/packets_test.go
+++ b/packets_test.go
@@ -9,6 +9,7 @@
 package mysql
 
 import (
+	"bytes"
 	"errors"
 	"net"
 	"testing"
@@ -280,3 +281,37 @@ func TestReadPacketFail(t *testing.T) {
 		t.Errorf("expected ErrInvalidConn, got %v", err)
 	}
 }
+
+// https://github.com/go-sql-driver/mysql/pull/801
+// not-NUL terminated plugin_name in init packet
+func TestRegression801(t *testing.T) {
+	conn := new(mockConn)
+	mc := &mysqlConn{
+		buf:      newBuffer(conn),
+		cfg:      new(Config),
+		sequence: 42,
+		closech:  make(chan struct{}),
+	}
+
+	conn.data = []byte{72, 0, 0, 42, 10, 53, 46, 53, 46, 56, 0, 165, 0, 0, 0,
+		60, 70, 63, 58, 68, 104, 34, 97, 0, 223, 247, 33, 2, 0, 15, 128, 21, 0,
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 98, 120, 114, 47, 85, 75, 109, 99, 51, 77,
+		50, 64, 0, 109, 121, 115, 113, 108, 95, 110, 97, 116, 105, 118, 101, 95,
+		112, 97, 115, 115, 119, 111, 114, 100}
+	conn.maxReads = 1
+
+	authData, pluginName, err := mc.readInitPacket()
+	if err != nil {
+		t.Fatalf("got error: %v", err)
+	}
+
+	if pluginName != "mysql_native_password" {
+		t.Errorf("expected plugin name 'mysql_native_password', got '%s'", pluginName)
+	}
+
+	expectedAuthData := []byte{60, 70, 63, 58, 68, 104, 34, 97, 98, 120, 114,
+		47, 85, 75, 109, 99, 51, 77, 50, 64}
+	if !bytes.Equal(authData, expectedAuthData) {
+		t.Errorf("expected authData '%v', got '%v'", expectedAuthData, authData)
+	}
+}

From ceae19ce1c5ac766abad8db04d649a7a83e91ced Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Wed, 23 May 2018 15:44:17 +0200
Subject: [PATCH 064/282] Fix empty SHA2 password handling (#800)

* Fix empty SHA2 password handling

Fixes #799

* add empty password test

* fix auth switch
---
 const.go       |   9 ++--
 driver.go      | 115 ++++++++++++++++++++++++++++++-------------------
 driver_test.go |  27 ++++++++++++
 packets.go     |  81 +++++++++++++++++-----------------
 4 files changed, 141 insertions(+), 91 deletions(-)

diff --git a/const.go b/const.go
index 1503f9e62..2afde6dfb 100644
--- a/const.go
+++ b/const.go
@@ -19,10 +19,11 @@ const (
 // http://dev.mysql.com/doc/internals/en/client-server-protocol.html
 
 const (
-	iOK          byte = 0x00
-	iLocalInFile byte = 0xfb
-	iEOF         byte = 0xfe
-	iERR         byte = 0xff
+	iOK           byte = 0x00
+	iAuthMoreData byte = 0x01
+	iLocalInFile  byte = 0xfb
+	iEOF          byte = 0xfe
+	iERR          byte = 0xff
 )
 
 // https://dev.mysql.com/doc/internals/en/capability-flags.html#packet-Protocol::CapabilityFlags
diff --git a/driver.go b/driver.go
index f77b917a8..68679d7b5 100644
--- a/driver.go
+++ b/driver.go
@@ -154,66 +154,91 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
 }
 
 func handleAuthResult(mc *mysqlConn, oldCipher []byte, pluginName string) error {
-
-	// handle caching_sha2_password
-	if pluginName == "caching_sha2_password" {
-		auth, err := mc.readCachingSha2PasswordAuthResult()
-		if err != nil {
-			return err
-		}
-		if auth == cachingSha2PasswordPerformFullAuthentication {
-			if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
-				if err = mc.writeClearAuthPacket(); err != nil {
-					return err
+	// Read Result Packet
+	cipher, err := mc.readResultOK()
+	if err == nil {
+		// handle caching_sha2_password
+		// https://insidemysql.com/preparing-your-community-connector-for-mysql-8-part-2-sha256/
+		if pluginName == "caching_sha2_password" {
+			if len(cipher) == 1 {
+				switch cipher[0] {
+				case cachingSha2PasswordFastAuthSuccess:
+					cipher, err = mc.readResultOK()
+					if err == nil {
+						return nil // auth successful
+					}
+
+				case cachingSha2PasswordPerformFullAuthentication:
+					if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
+						if err = mc.writeClearAuthPacket(); err != nil {
+							return err
+						}
+					} else {
+						if err = mc.writePublicKeyAuthPacket(oldCipher); err != nil {
+							return err
+						}
+					}
+					cipher, err = mc.readResultOK()
+					if err == nil {
+						return nil // auth successful
+					}
+
+				default:
+					return ErrMalformPkt
 				}
 			} else {
-				if err = mc.writePublicKeyAuthPacket(oldCipher); err != nil {
-					return err
-				}
+				return ErrMalformPkt
 			}
-		}
-	}
 
-	// Read Result Packet
-	cipher, err := mc.readResultOK()
-	if err == nil {
-		return nil // auth successful
+		} else {
+			return nil // auth successful
+		}
 	}
 
 	if mc.cfg == nil {
 		return err // auth failed and retry not possible
 	}
 
-	// Retry auth if configured to do so.
-	if mc.cfg.AllowOldPasswords && err == ErrOldPassword {
-		// Retry with old authentication method. Note: there are edge cases
-		// where this should work but doesn't; this is currently "wontfix":
-		// https://github.com/go-sql-driver/mysql/issues/184
-
-		// If CLIENT_PLUGIN_AUTH capability is not supported, no new cipher is
-		// sent and we have to keep using the cipher sent in the init packet.
-		if cipher == nil {
-			cipher = oldCipher
+	// Retry auth if configured to do so
+	switch err {
+	case ErrCleartextPassword:
+		if mc.cfg.AllowCleartextPasswords {
+			// Retry with clear text password for
+			// http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html
+			// http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html
+			if err = mc.writeClearAuthPacket(); err != nil {
+				return err
+			}
+			_, err = mc.readResultOK()
 		}
 
-		if err = mc.writeOldAuthPacket(cipher); err != nil {
-			return err
-		}
-		_, err = mc.readResultOK()
-	} else if mc.cfg.AllowCleartextPasswords && err == ErrCleartextPassword {
-		// Retry with clear text password for
-		// http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html
-		// http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html
-		if err = mc.writeClearAuthPacket(); err != nil {
-			return err
+	case ErrNativePassword:
+		if mc.cfg.AllowNativePasswords {
+			if err = mc.writeNativeAuthPacket(cipher); err != nil {
+				return err
+			}
+			_, err = mc.readResultOK()
 		}
-		_, err = mc.readResultOK()
-	} else if mc.cfg.AllowNativePasswords && err == ErrNativePassword {
-		if err = mc.writeNativeAuthPacket(cipher); err != nil {
-			return err
+
+	case ErrOldPassword:
+		if mc.cfg.AllowOldPasswords {
+			// Retry with old authentication method. Note: there are edge cases
+			// where this should work but doesn't; this is currently "wontfix":
+			// https://github.com/go-sql-driver/mysql/issues/184
+
+			// If CLIENT_PLUGIN_AUTH capability is not supported, no new cipher is
+			// sent and we have to keep using the cipher sent in the init packet.
+			if cipher == nil {
+				cipher = oldCipher
+			}
+
+			if err = mc.writeOldAuthPacket(cipher); err != nil {
+				return err
+			}
+			_, err = mc.readResultOK()
 		}
-		_, err = mc.readResultOK()
 	}
+
 	return err
 }
 
diff --git a/driver_test.go b/driver_test.go
index 8904b6587..3cbd98c85 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -2046,3 +2046,30 @@ func TestPing(t *testing.T) {
 		}
 	})
 }
+
+// See Issue #799
+func TestEmptyPassword(t *testing.T) {
+	if !available {
+		t.Skipf("MySQL server not running on %s", netAddr)
+	}
+
+	dsn := fmt.Sprintf("%s:%s@%s/%s?timeout=30s", user, "", netAddr, dbname)
+	db, err := sql.Open("mysql", dsn)
+	if err == nil {
+		defer db.Close()
+		err = db.Ping()
+	}
+
+	if pass == "" {
+		if err != nil {
+			t.Fatal(err.Error())
+		}
+	} else {
+		if err == nil {
+			t.Fatal("expected authentication error")
+		}
+		if !strings.HasPrefix(err.Error(), "Error 1045") {
+			t.Fatal(err.Error())
+		}
+	}
+}
diff --git a/packets.go b/packets.go
index 9b76fb040..5324568ae 100644
--- a/packets.go
+++ b/packets.go
@@ -203,7 +203,7 @@ func (mc *mysqlConn) readInitPacket() ([]byte, string, error) {
 	}
 	pos += 2
 
-	pluginName := ""
+	pluginName := "mysql_native_password"
 	if len(data) > pos {
 		// character set [1 byte]
 		// status flags [2 bytes]
@@ -365,7 +365,6 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte, pluginName string) error {
 		pos++
 	}
 
-	// Assume native client during response
 	pos += copy(data[pos:], pluginName)
 	data[pos] = 0x00
 
@@ -542,55 +541,53 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
 *                              Result Packets                                 *
 ******************************************************************************/
 
+func readAuthSwitch(data []byte) ([]byte, error) {
+	if len(data) > 1 {
+		pluginEndIndex := bytes.IndexByte(data, 0x00)
+		plugin := string(data[1:pluginEndIndex])
+		cipher := data[pluginEndIndex+1:]
+
+		switch plugin {
+		case "mysql_old_password":
+			// using old_passwords
+			return cipher, ErrOldPassword
+		case "mysql_clear_password":
+			// using clear text password
+			return cipher, ErrCleartextPassword
+		case "mysql_native_password":
+			// using mysql default authentication method
+			return cipher, ErrNativePassword
+		default:
+			return cipher, ErrUnknownPlugin
+		}
+	}
+
+	// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest
+	return nil, ErrOldPassword
+}
+
 // Returns error if Packet is not an 'Result OK'-Packet
 func (mc *mysqlConn) readResultOK() ([]byte, error) {
 	data, err := mc.readPacket()
-	if err == nil {
-		// packet indicator
-		switch data[0] {
+	if err != nil {
+		return nil, err
+	}
 
-		case iOK:
-			return nil, mc.handleOkPacket(data)
+	// packet indicator
+	switch data[0] {
 
-		case iEOF:
-			if len(data) > 1 {
-				pluginEndIndex := bytes.IndexByte(data, 0x00)
-				plugin := string(data[1:pluginEndIndex])
-				cipher := data[pluginEndIndex+1:]
-
-				switch plugin {
-				case "mysql_old_password":
-					// using old_passwords
-					return cipher, ErrOldPassword
-				case "mysql_clear_password":
-					// using clear text password
-					return cipher, ErrCleartextPassword
-				case "mysql_native_password":
-					// using mysql default authentication method
-					return cipher, ErrNativePassword
-				default:
-					return cipher, ErrUnknownPlugin
-				}
-			}
+	case iOK:
+		return nil, mc.handleOkPacket(data)
 
-			// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest
-			return nil, ErrOldPassword
+	case iAuthMoreData:
+		return data[1:], nil
 
-		default: // Error otherwise
-			return nil, mc.handleErrorPacket(data)
-		}
-	}
-	return nil, err
-}
+	case iEOF:
+		return readAuthSwitch(data)
 
-func (mc *mysqlConn) readCachingSha2PasswordAuthResult() (int, error) {
-	data, err := mc.readPacket()
-	if err == nil {
-		if data[0] != 1 {
-			return 0, ErrMalformPkt
-		}
+	default: // Error otherwise
+		return nil, mc.handleErrorPacket(data)
 	}
-	return int(data[1]), err
 }
 
 // Result Set Header Packet

From 64db0f7ebe171b596aa9b26f39a79f7413a3b617 Mon Sep 17 00:00:00 2001
From: INADA Naoki <methane@users.noreply.github.com>
Date: Sat, 26 May 2018 18:24:51 +0900
Subject: [PATCH 065/282] Add one line note about 0000-00-00 date/datetime
 (#783)

Fixes #741

Signed-off-by: INADA Naoki <songofacandy@gmail.com>
---
 README.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/README.md b/README.md
index 299198d53..b3dcd9101 100644
--- a/README.md
+++ b/README.md
@@ -259,6 +259,7 @@ Default:        false
 ```
 
 `parseTime=true` changes the output type of `DATE` and `DATETIME` values to `time.Time` instead of `[]byte` / `string`
+The date or datetime like `0000-00-00 00:00:00` is converted into zero value of `time.Time`.
 
 
 ##### `readTimeout`

From affd4c93966fe80839dc3dd720440aaad00914b9 Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Tue, 29 May 2018 19:27:19 +0200
Subject: [PATCH 066/282] Auth refactoring and bug fixes (#807)

* log missing auth plugin name

* refactor auth handling

* auth: fix AllowNativePasswords

* auth: remove plugin name print

* packets: attempt to fix writePublicKeyAuthPacket

* packets: do not NUL-terminate auth switch packets

* move handleAuthResult to auth

* add old_password auth tests

* auth: add empty old_password test

* auth: add cleartext auth tests

* auth: add native auth tests

* auth: add caching_sha2 tests

* rename init and auth packets to documented names

* auth: fix plugin name for switched auth methods

* buffer: optimize default branches

* auth: add tests for switch to caching sha2

* auth: add tests for switch to cleartext password

* auth: add tests for switch to native password

* auth: sync NUL termination with official connectors

* packets: handle missing NUL bytes in AuthSwitchRequests

Updates #795
---
 AUTHORS            |   3 +-
 auth.go            | 309 ++++++++++++++++
 auth_test.go       | 853 +++++++++++++++++++++++++++++++++++++++++++++
 buffer.go          |  12 +-
 connection_go18.go |  15 +-
 const.go           |   1 +
 driver.go          | 106 +-----
 infile.go          |   3 +-
 packets.go         | 229 ++++--------
 packets_test.go    |  43 ++-
 utils.go           | 143 --------
 utils_test.go      |  38 --
 12 files changed, 1294 insertions(+), 461 deletions(-)
 create mode 100644 auth.go
 create mode 100644 auth_test.go

diff --git a/AUTHORS b/AUTHORS
index 07d0deee3..73ff68fbc 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -20,6 +20,7 @@ Asta Xie <xiemengjun at gmail.com>
 Bulat Gaifullin <gaifullinbf at gmail.com>
 Carlos Nieto <jose.carlos at menteslibres.net>
 Chris Moos <chris at tech9computers.com>
+Craig Wilson <craiggwilson at gmail.com>
 Daniel Montoya <dsmontoyam at gmail.com>
 Daniel Nichter <nil at codenode.com>
 Daniël van Eeden <git at myname.nl>
@@ -55,7 +56,7 @@ Lion Yang <lion at aosc.xyz>
 Luca Looz <luca.looz92 at gmail.com>
 Lucas Liu <extrafliu at gmail.com>
 Luke Scott <luke at webconnex.com>
-Maciej Zimnoch <maciej.zimnoch@codilime.com>
+Maciej Zimnoch <maciej.zimnoch at codilime.com>
 Michael Woolnough <michael.woolnough at gmail.com>
 Nicola Peduzzi <thenikso at gmail.com>
 Olivier Mengué <dolmen at cpan.org>
diff --git a/auth.go b/auth.go
new file mode 100644
index 000000000..8794d1136
--- /dev/null
+++ b/auth.go
@@ -0,0 +1,309 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2018 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 (
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/sha1"
+	"crypto/sha256"
+	"crypto/x509"
+	"encoding/pem"
+)
+
+// Hash password using pre 4.1 (old password) method
+// https://github.com/atcurtis/mariadb/blob/master/mysys/my_rnd.c
+type myRnd struct {
+	seed1, seed2 uint32
+}
+
+const myRndMaxVal = 0x3FFFFFFF
+
+// Pseudo random number generator
+func newMyRnd(seed1, seed2 uint32) *myRnd {
+	return &myRnd{
+		seed1: seed1 % myRndMaxVal,
+		seed2: seed2 % myRndMaxVal,
+	}
+}
+
+// Tested to be equivalent to MariaDB's floating point variant
+// http://play.golang.org/p/QHvhd4qved
+// http://play.golang.org/p/RG0q4ElWDx
+func (r *myRnd) NextByte() byte {
+	r.seed1 = (r.seed1*3 + r.seed2) % myRndMaxVal
+	r.seed2 = (r.seed1 + r.seed2 + 33) % myRndMaxVal
+
+	return byte(uint64(r.seed1) * 31 / myRndMaxVal)
+}
+
+// Generate binary hash from byte string using insecure pre 4.1 method
+func pwHash(password []byte) (result [2]uint32) {
+	var add uint32 = 7
+	var tmp uint32
+
+	result[0] = 1345345333
+	result[1] = 0x12345671
+
+	for _, c := range password {
+		// skip spaces and tabs in password
+		if c == ' ' || c == '\t' {
+			continue
+		}
+
+		tmp = uint32(c)
+		result[0] ^= (((result[0] & 63) + add) * tmp) + (result[0] << 8)
+		result[1] += (result[1] << 8) ^ result[0]
+		add += tmp
+	}
+
+	// Remove sign bit (1<<31)-1)
+	result[0] &= 0x7FFFFFFF
+	result[1] &= 0x7FFFFFFF
+
+	return
+}
+
+// Hash password using insecure pre 4.1 method
+func scrambleOldPassword(scramble []byte, password string) []byte {
+	if len(password) == 0 {
+		return nil
+	}
+
+	scramble = scramble[:8]
+
+	hashPw := pwHash([]byte(password))
+	hashSc := pwHash(scramble)
+
+	r := newMyRnd(hashPw[0]^hashSc[0], hashPw[1]^hashSc[1])
+
+	var out [8]byte
+	for i := range out {
+		out[i] = r.NextByte() + 64
+	}
+
+	mask := r.NextByte()
+	for i := range out {
+		out[i] ^= mask
+	}
+
+	return out[:]
+}
+
+// Hash password using 4.1+ method (SHA1)
+func scramblePassword(scramble []byte, password string) []byte {
+	if len(password) == 0 {
+		return nil
+	}
+
+	// stage1Hash = SHA1(password)
+	crypt := sha1.New()
+	crypt.Write([]byte(password))
+	stage1 := crypt.Sum(nil)
+
+	// scrambleHash = SHA1(scramble + SHA1(stage1Hash))
+	// inner Hash
+	crypt.Reset()
+	crypt.Write(stage1)
+	hash := crypt.Sum(nil)
+
+	// outer Hash
+	crypt.Reset()
+	crypt.Write(scramble)
+	crypt.Write(hash)
+	scramble = crypt.Sum(nil)
+
+	// token = scrambleHash XOR stage1Hash
+	for i := range scramble {
+		scramble[i] ^= stage1[i]
+	}
+	return scramble
+}
+
+// Hash password using MySQL 8+ method (SHA256)
+func scrambleSHA256Password(scramble []byte, password string) []byte {
+	if len(password) == 0 {
+		return nil
+	}
+
+	// XOR(SHA256(password), SHA256(SHA256(SHA256(password)), scramble))
+
+	crypt := sha256.New()
+	crypt.Write([]byte(password))
+	message1 := crypt.Sum(nil)
+
+	crypt.Reset()
+	crypt.Write(message1)
+	message1Hash := crypt.Sum(nil)
+
+	crypt.Reset()
+	crypt.Write(message1Hash)
+	crypt.Write(scramble)
+	message2 := crypt.Sum(nil)
+
+	for i := range message1 {
+		message1[i] ^= message2[i]
+	}
+
+	return message1
+}
+
+func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, bool, error) {
+	switch plugin {
+	case "caching_sha2_password":
+		authResp := scrambleSHA256Password(authData, mc.cfg.Passwd)
+		return authResp, (authResp == nil), nil
+
+	case "mysql_old_password":
+		if !mc.cfg.AllowOldPasswords {
+			return nil, false, ErrOldPassword
+		}
+		// Note: there are edge cases where this should work but doesn't;
+		// this is currently "wontfix":
+		// https://github.com/go-sql-driver/mysql/issues/184
+		authResp := scrambleOldPassword(authData[:8], mc.cfg.Passwd)
+		return authResp, true, nil
+
+	case "mysql_clear_password":
+		if !mc.cfg.AllowCleartextPasswords {
+			return nil, false, ErrCleartextPassword
+		}
+		// http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html
+		// http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html
+		return []byte(mc.cfg.Passwd), true, nil
+
+	case "mysql_native_password":
+		if !mc.cfg.AllowNativePasswords {
+			return nil, false, ErrNativePassword
+		}
+		// https://dev.mysql.com/doc/internals/en/secure-password-authentication.html
+		// Native password authentication only need and will need 20-byte challenge.
+		authResp := scramblePassword(authData[:20], mc.cfg.Passwd)
+		return authResp, false, nil
+
+	default:
+		errLog.Print("unknown auth plugin:", plugin)
+		return nil, false, ErrUnknownPlugin
+	}
+}
+
+func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
+	// Read Result Packet
+	authData, newPlugin, err := mc.readAuthResult()
+	if err != nil {
+		return err
+	}
+
+	// handle auth plugin switch, if requested
+	if newPlugin != "" {
+		// If CLIENT_PLUGIN_AUTH capability is not supported, no new cipher is
+		// sent and we have to keep using the cipher sent in the init packet.
+		if authData == nil {
+			authData = oldAuthData
+		}
+
+		plugin = newPlugin
+
+		authResp, addNUL, err := mc.auth(authData, plugin)
+		if err != nil {
+			return err
+		}
+		if err = mc.writeAuthSwitchPacket(authResp, addNUL); err != nil {
+			return err
+		}
+
+		// Read Result Packet
+		authData, newPlugin, err = mc.readAuthResult()
+		if err != nil {
+			return err
+		}
+		// Do not allow to change the auth plugin more than once
+		if newPlugin != "" {
+			return ErrMalformPkt
+		}
+	}
+
+	switch plugin {
+
+	// https://insidemysql.com/preparing-your-community-connector-for-mysql-8-part-2-sha256/
+	case "caching_sha2_password":
+		switch len(authData) {
+		case 0:
+			return nil // auth successful
+		case 1:
+			switch authData[0] {
+			case cachingSha2PasswordFastAuthSuccess:
+				if err = mc.readResultOK(); err == nil {
+					return nil // auth successful
+				}
+
+			case cachingSha2PasswordPerformFullAuthentication:
+				if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
+					// write cleartext auth packet
+					err = mc.writeAuthSwitchPacket([]byte(mc.cfg.Passwd), true)
+					if err != nil {
+						return err
+					}
+				} else {
+					seed := oldAuthData
+
+					// TODO: allow to specify a local file with the pub key via
+					// the DSN
+
+					// request public key
+					data := mc.buf.takeSmallBuffer(4 + 1)
+					data[4] = cachingSha2PasswordRequestPublicKey
+					mc.writePacket(data)
+
+					// parse public key
+					data, err := mc.readPacket()
+					if err != nil {
+						return err
+					}
+
+					block, _ := pem.Decode(data[1:])
+					pub, err := x509.ParsePKIXPublicKey(block.Bytes)
+					if err != nil {
+						return err
+					}
+
+					// send encrypted password
+					plain := make([]byte, len(mc.cfg.Passwd)+1)
+					copy(plain, mc.cfg.Passwd)
+					for i := range plain {
+						j := i % len(seed)
+						plain[i] ^= seed[j]
+					}
+					sha1 := sha1.New()
+					enc, err := rsa.EncryptOAEP(sha1, rand.Reader, pub.(*rsa.PublicKey), plain, nil)
+					if err != nil {
+						return err
+					}
+
+					if err = mc.writeAuthSwitchPacket(enc, false); err != nil {
+						return err
+					}
+				}
+				if err = mc.readResultOK(); err == nil {
+					return nil // auth successful
+				}
+
+			default:
+				return ErrMalformPkt
+			}
+		default:
+			return ErrMalformPkt
+		}
+
+	default:
+		return nil // auth successful
+	}
+
+	return err
+}
diff --git a/auth_test.go b/auth_test.go
new file mode 100644
index 000000000..8d852793a
--- /dev/null
+++ b/auth_test.go
@@ -0,0 +1,853 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2018 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 (
+	"bytes"
+	"crypto/tls"
+	"fmt"
+	"testing"
+)
+
+var serverPubKey = []byte{1, 45, 45, 45, 45, 45, 66, 69, 71, 73, 78, 32, 80, 85,
+	66, 76, 73, 67, 32, 75, 69, 89, 45, 45, 45, 45, 45, 10, 77, 73, 73, 66, 73,
+	106, 65, 78, 66, 103, 107, 113, 104, 107, 105, 71, 57, 119, 48, 66, 65, 81,
+	69, 70, 65, 65, 79, 67, 65, 81, 56, 65, 77, 73, 73, 66, 67, 103, 75, 67, 65,
+	81, 69, 65, 51, 72, 115, 120, 83, 53, 80, 47, 72, 97, 88, 80, 118, 109, 51,
+	109, 50, 65, 68, 110, 10, 98, 117, 54, 71, 81, 102, 112, 83, 71, 111, 55,
+	104, 50, 103, 104, 56, 49, 112, 109, 97, 120, 107, 67, 110, 68, 67, 119,
+	102, 54, 109, 109, 101, 72, 55, 76, 75, 104, 115, 110, 89, 110, 78, 52, 81,
+	48, 99, 122, 49, 81, 69, 47, 98, 104, 100, 80, 117, 54, 106, 115, 43, 86,
+	97, 89, 52, 10, 67, 99, 77, 117, 98, 80, 78, 49, 103, 79, 75, 97, 89, 118,
+	78, 99, 103, 69, 87, 112, 116, 73, 67, 105, 50, 88, 84, 116, 116, 66, 55,
+	117, 104, 43, 118, 67, 77, 106, 76, 118, 106, 65, 77, 100, 54, 47, 68, 109,
+	120, 100, 98, 85, 66, 48, 122, 80, 71, 113, 68, 79, 103, 105, 76, 68, 10,
+	75, 82, 79, 79, 53, 113, 100, 55, 115, 104, 98, 55, 49, 82, 47, 88, 74, 69,
+	70, 118, 76, 120, 71, 88, 69, 70, 48, 90, 116, 104, 72, 101, 78, 111, 57,
+	102, 69, 118, 120, 70, 81, 111, 109, 98, 49, 107, 90, 57, 74, 56, 110, 66,
+	119, 116, 101, 53, 83, 70, 53, 89, 108, 113, 86, 50, 10, 66, 66, 53, 113,
+	108, 97, 122, 43, 51, 81, 83, 78, 118, 109, 67, 49, 105, 87, 102, 108, 106,
+	88, 98, 89, 53, 107, 51, 47, 97, 54, 109, 107, 77, 47, 76, 97, 87, 104, 97,
+	117, 78, 53, 80, 82, 51, 115, 67, 120, 53, 85, 117, 49, 77, 102, 100, 115,
+	86, 105, 107, 53, 102, 88, 77, 77, 10, 100, 120, 107, 102, 70, 43, 88, 51,
+	99, 104, 107, 65, 110, 119, 73, 51, 70, 117, 119, 119, 50, 87, 71, 109, 87,
+	79, 71, 98, 75, 116, 109, 73, 101, 85, 109, 51, 98, 73, 82, 109, 100, 70,
+	85, 113, 97, 108, 81, 105, 70, 104, 113, 101, 90, 50, 105, 107, 106, 104,
+	103, 86, 73, 57, 112, 76, 10, 119, 81, 73, 68, 65, 81, 65, 66, 10, 45, 45,
+	45, 45, 45, 69, 78, 68, 32, 80, 85, 66, 76, 73, 67, 32, 75, 69, 89, 45, 45,
+	45, 45, 45, 10}
+
+func TestScrambleOldPass(t *testing.T) {
+	scramble := []byte{9, 8, 7, 6, 5, 4, 3, 2}
+	vectors := []struct {
+		pass string
+		out  string
+	}{
+		{" pass", "47575c5a435b4251"},
+		{"pass ", "47575c5a435b4251"},
+		{"123\t456", "575c47505b5b5559"},
+		{"C0mpl!ca ted#PASS123", "5d5d554849584a45"},
+	}
+	for _, tuple := range vectors {
+		ours := scrambleOldPassword(scramble, tuple.pass)
+		if tuple.out != fmt.Sprintf("%x", ours) {
+			t.Errorf("Failed old password %q", tuple.pass)
+		}
+	}
+}
+
+func TestScrambleSHA256Pass(t *testing.T) {
+	scramble := []byte{10, 47, 74, 111, 75, 73, 34, 48, 88, 76, 114, 74, 37, 13, 3, 80, 82, 2, 23, 21}
+	vectors := []struct {
+		pass string
+		out  string
+	}{
+		{"secret", "f490e76f66d9d86665ce54d98c78d0acfe2fb0b08b423da807144873d30b312c"},
+		{"secret2", "abc3934a012cf342e876071c8ee202de51785b430258a7a0138bc79c4d800bc6"},
+	}
+	for _, tuple := range vectors {
+		ours := scrambleSHA256Password(scramble, tuple.pass)
+		if tuple.out != fmt.Sprintf("%x", ours) {
+			t.Errorf("Failed SHA256 password %q", tuple.pass)
+		}
+	}
+}
+
+func TestAuthFastCachingSHA256PasswordCached(t *testing.T) {
+	conn, mc := newRWMockConn(1)
+	mc.cfg.User = "root"
+	mc.cfg.Passwd = "secret"
+
+	authData := []byte{90, 105, 74, 126, 30, 48, 37, 56, 3, 23, 115, 127, 69,
+		22, 41, 84, 32, 123, 43, 118}
+	plugin := "caching_sha2_password"
+
+	// Send Client Authentication Packet
+	authResp, addNUL, err := mc.auth(authData, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// check written auth response
+	authRespStart := 4 + 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1
+	authRespEnd := authRespStart + 1 + len(authResp)
+	writtenAuthRespLen := conn.written[authRespStart]
+	writtenAuthResp := conn.written[authRespStart+1 : authRespEnd]
+	expectedAuthResp := []byte{102, 32, 5, 35, 143, 161, 140, 241, 171, 232, 56,
+		139, 43, 14, 107, 196, 249, 170, 147, 60, 220, 204, 120, 178, 214, 15,
+		184, 150, 26, 61, 57, 235}
+	if writtenAuthRespLen != 32 || !bytes.Equal(writtenAuthResp, expectedAuthResp) {
+		t.Fatalf("unexpected written auth response (%d bytes): %v", writtenAuthRespLen, writtenAuthResp)
+	}
+	conn.written = nil
+
+	// auth response
+	conn.data = []byte{
+		2, 0, 0, 2, 1, 3, // Fast Auth Success
+		7, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, // OK
+	}
+	conn.maxReads = 1
+
+	// Handle response to auth packet
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+}
+
+func TestAuthFastCachingSHA256PasswordEmpty(t *testing.T) {
+	conn, mc := newRWMockConn(1)
+	mc.cfg.User = "root"
+	mc.cfg.Passwd = ""
+
+	authData := []byte{90, 105, 74, 126, 30, 48, 37, 56, 3, 23, 115, 127, 69,
+		22, 41, 84, 32, 123, 43, 118}
+	plugin := "caching_sha2_password"
+
+	// Send Client Authentication Packet
+	authResp, addNUL, err := mc.auth(authData, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// check written auth response
+	authRespStart := 4 + 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1
+	authRespEnd := authRespStart + 1 + len(authResp)
+	writtenAuthRespLen := conn.written[authRespStart]
+	writtenAuthResp := conn.written[authRespStart+1 : authRespEnd]
+	if writtenAuthRespLen != 0 {
+		t.Fatalf("unexpected written auth response (%d bytes): %v",
+			writtenAuthRespLen, writtenAuthResp)
+	}
+	conn.written = nil
+
+	// auth response
+	conn.data = []byte{
+		7, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, // OK
+	}
+	conn.maxReads = 1
+
+	// Handle response to auth packet
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+}
+
+func TestAuthFastCachingSHA256PasswordFullRSA(t *testing.T) {
+	conn, mc := newRWMockConn(1)
+	mc.cfg.User = "root"
+	mc.cfg.Passwd = "secret"
+
+	authData := []byte{6, 81, 96, 114, 14, 42, 50, 30, 76, 47, 1, 95, 126, 81,
+		62, 94, 83, 80, 52, 85}
+	plugin := "caching_sha2_password"
+
+	// Send Client Authentication Packet
+	authResp, addNUL, err := mc.auth(authData, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// check written auth response
+	authRespStart := 4 + 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1
+	authRespEnd := authRespStart + 1 + len(authResp)
+	writtenAuthRespLen := conn.written[authRespStart]
+	writtenAuthResp := conn.written[authRespStart+1 : authRespEnd]
+	expectedAuthResp := []byte{171, 201, 138, 146, 89, 159, 11, 170, 0, 67, 165,
+		49, 175, 94, 218, 68, 177, 109, 110, 86, 34, 33, 44, 190, 67, 240, 70,
+		110, 40, 139, 124, 41}
+	if writtenAuthRespLen != 32 || !bytes.Equal(writtenAuthResp, expectedAuthResp) {
+		t.Fatalf("unexpected written auth response (%d bytes): %v", writtenAuthRespLen, writtenAuthResp)
+	}
+	conn.written = nil
+
+	// auth response
+	conn.data = []byte{
+		2, 0, 0, 2, 1, 4, // Perform Full Authentication
+	}
+	conn.queuedReplies = [][]byte{
+		// pub key response
+		append([]byte{byte(len(serverPubKey)), 1, 0, 4}, serverPubKey...),
+
+		// OK
+		{7, 0, 0, 6, 0, 0, 0, 2, 0, 0, 0},
+	}
+	conn.maxReads = 3
+
+	// Handle response to auth packet
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+
+	if !bytes.HasPrefix(conn.written, []byte{1, 0, 0, 3, 2, 0, 1, 0, 5}) {
+		t.Errorf("unexpected written data: %v", conn.written)
+	}
+}
+
+func TestAuthFastCachingSHA256PasswordFullSecure(t *testing.T) {
+	conn, mc := newRWMockConn(1)
+	mc.cfg.User = "root"
+	mc.cfg.Passwd = "secret"
+
+	authData := []byte{6, 81, 96, 114, 14, 42, 50, 30, 76, 47, 1, 95, 126, 81,
+		62, 94, 83, 80, 52, 85}
+	plugin := "caching_sha2_password"
+
+	// Send Client Authentication Packet
+	authResp, addNUL, err := mc.auth(authData, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Hack to make the caching_sha2_password plugin believe that the connection
+	// is secure
+	mc.cfg.tls = &tls.Config{InsecureSkipVerify: true}
+
+	// check written auth response
+	authRespStart := 4 + 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1
+	authRespEnd := authRespStart + 1 + len(authResp)
+	writtenAuthRespLen := conn.written[authRespStart]
+	writtenAuthResp := conn.written[authRespStart+1 : authRespEnd]
+	expectedAuthResp := []byte{171, 201, 138, 146, 89, 159, 11, 170, 0, 67, 165,
+		49, 175, 94, 218, 68, 177, 109, 110, 86, 34, 33, 44, 190, 67, 240, 70,
+		110, 40, 139, 124, 41}
+	if writtenAuthRespLen != 32 || !bytes.Equal(writtenAuthResp, expectedAuthResp) {
+		t.Fatalf("unexpected written auth response (%d bytes): %v", writtenAuthRespLen, writtenAuthResp)
+	}
+	conn.written = nil
+
+	// auth response
+	conn.data = []byte{
+		2, 0, 0, 2, 1, 4, // Perform Full Authentication
+	}
+	conn.queuedReplies = [][]byte{
+		// OK
+		{7, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0},
+	}
+	conn.maxReads = 3
+
+	// Handle response to auth packet
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+
+	if !bytes.Equal(conn.written, []byte{7, 0, 0, 3, 115, 101, 99, 114, 101, 116, 0}) {
+		t.Errorf("unexpected written data: %v", conn.written)
+	}
+}
+
+func TestAuthFastCleartextPasswordNotAllowed(t *testing.T) {
+	_, mc := newRWMockConn(1)
+	mc.cfg.User = "root"
+	mc.cfg.Passwd = "secret"
+
+	authData := []byte{70, 114, 92, 94, 1, 38, 11, 116, 63, 114, 23, 101, 126,
+		103, 26, 95, 81, 17, 24, 21}
+	plugin := "mysql_clear_password"
+
+	// Send Client Authentication Packet
+	_, _, err := mc.auth(authData, plugin)
+	if err != ErrCleartextPassword {
+		t.Errorf("expected ErrCleartextPassword, got %v", err)
+	}
+}
+
+func TestAuthFastCleartextPassword(t *testing.T) {
+	conn, mc := newRWMockConn(1)
+	mc.cfg.User = "root"
+	mc.cfg.Passwd = "secret"
+	mc.cfg.AllowCleartextPasswords = true
+
+	authData := []byte{70, 114, 92, 94, 1, 38, 11, 116, 63, 114, 23, 101, 126,
+		103, 26, 95, 81, 17, 24, 21}
+	plugin := "mysql_clear_password"
+
+	// Send Client Authentication Packet
+	authResp, addNUL, err := mc.auth(authData, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// check written auth response
+	authRespStart := 4 + 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1
+	authRespEnd := authRespStart + 1 + len(authResp)
+	writtenAuthRespLen := conn.written[authRespStart]
+	writtenAuthResp := conn.written[authRespStart+1 : authRespEnd]
+	expectedAuthResp := []byte{115, 101, 99, 114, 101, 116}
+	if writtenAuthRespLen != 6 || !bytes.Equal(writtenAuthResp, expectedAuthResp) {
+		t.Fatalf("unexpected written auth response (%d bytes): %v", writtenAuthRespLen, writtenAuthResp)
+	}
+	conn.written = nil
+
+	// auth response
+	conn.data = []byte{
+		7, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, // OK
+	}
+	conn.maxReads = 1
+
+	// Handle response to auth packet
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+}
+
+func TestAuthFastCleartextPasswordEmpty(t *testing.T) {
+	conn, mc := newRWMockConn(1)
+	mc.cfg.User = "root"
+	mc.cfg.Passwd = ""
+	mc.cfg.AllowCleartextPasswords = true
+
+	authData := []byte{70, 114, 92, 94, 1, 38, 11, 116, 63, 114, 23, 101, 126,
+		103, 26, 95, 81, 17, 24, 21}
+	plugin := "mysql_clear_password"
+
+	// Send Client Authentication Packet
+	authResp, addNUL, err := mc.auth(authData, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// check written auth response
+	authRespStart := 4 + 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1
+	authRespEnd := authRespStart + 1 + len(authResp)
+	writtenAuthRespLen := conn.written[authRespStart]
+	writtenAuthResp := conn.written[authRespStart+1 : authRespEnd]
+	if writtenAuthRespLen != 0 {
+		t.Fatalf("unexpected written auth response (%d bytes): %v",
+			writtenAuthRespLen, writtenAuthResp)
+	}
+	conn.written = nil
+
+	// auth response
+	conn.data = []byte{
+		7, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, // OK
+	}
+	conn.maxReads = 1
+
+	// Handle response to auth packet
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+}
+
+func TestAuthFastNativePasswordNotAllowed(t *testing.T) {
+	_, mc := newRWMockConn(1)
+	mc.cfg.User = "root"
+	mc.cfg.Passwd = "secret"
+	mc.cfg.AllowNativePasswords = false
+
+	authData := []byte{70, 114, 92, 94, 1, 38, 11, 116, 63, 114, 23, 101, 126,
+		103, 26, 95, 81, 17, 24, 21}
+	plugin := "mysql_native_password"
+
+	// Send Client Authentication Packet
+	_, _, err := mc.auth(authData, plugin)
+	if err != ErrNativePassword {
+		t.Errorf("expected ErrNativePassword, got %v", err)
+	}
+}
+
+func TestAuthFastNativePassword(t *testing.T) {
+	conn, mc := newRWMockConn(1)
+	mc.cfg.User = "root"
+	mc.cfg.Passwd = "secret"
+
+	authData := []byte{70, 114, 92, 94, 1, 38, 11, 116, 63, 114, 23, 101, 126,
+		103, 26, 95, 81, 17, 24, 21}
+	plugin := "mysql_native_password"
+
+	// Send Client Authentication Packet
+	authResp, addNUL, err := mc.auth(authData, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// check written auth response
+	authRespStart := 4 + 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1
+	authRespEnd := authRespStart + 1 + len(authResp)
+	writtenAuthRespLen := conn.written[authRespStart]
+	writtenAuthResp := conn.written[authRespStart+1 : authRespEnd]
+	expectedAuthResp := []byte{53, 177, 140, 159, 251, 189, 127, 53, 109, 252,
+		172, 50, 211, 192, 240, 164, 26, 48, 207, 45}
+	if writtenAuthRespLen != 20 || !bytes.Equal(writtenAuthResp, expectedAuthResp) {
+		t.Fatalf("unexpected written auth response (%d bytes): %v", writtenAuthRespLen, writtenAuthResp)
+	}
+	conn.written = nil
+
+	// auth response
+	conn.data = []byte{
+		7, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, // OK
+	}
+	conn.maxReads = 1
+
+	// Handle response to auth packet
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+}
+
+func TestAuthFastNativePasswordEmpty(t *testing.T) {
+	conn, mc := newRWMockConn(1)
+	mc.cfg.User = "root"
+	mc.cfg.Passwd = ""
+
+	authData := []byte{70, 114, 92, 94, 1, 38, 11, 116, 63, 114, 23, 101, 126,
+		103, 26, 95, 81, 17, 24, 21}
+	plugin := "mysql_native_password"
+
+	// Send Client Authentication Packet
+	authResp, addNUL, err := mc.auth(authData, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// check written auth response
+	authRespStart := 4 + 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1
+	authRespEnd := authRespStart + 1 + len(authResp)
+	writtenAuthRespLen := conn.written[authRespStart]
+	writtenAuthResp := conn.written[authRespStart+1 : authRespEnd]
+	if writtenAuthRespLen != 0 {
+		t.Fatalf("unexpected written auth response (%d bytes): %v",
+			writtenAuthRespLen, writtenAuthResp)
+	}
+	conn.written = nil
+
+	// auth response
+	conn.data = []byte{
+		7, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, // OK
+	}
+	conn.maxReads = 1
+
+	// Handle response to auth packet
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+}
+
+func TestAuthSwitchCachingSHA256PasswordCached(t *testing.T) {
+	conn, mc := newRWMockConn(2)
+	mc.cfg.Passwd = "secret"
+
+	// auth switch request
+	conn.data = []byte{44, 0, 0, 2, 254, 99, 97, 99, 104, 105, 110, 103, 95,
+		115, 104, 97, 50, 95, 112, 97, 115, 115, 119, 111, 114, 100, 0, 101,
+		11, 26, 18, 94, 97, 22, 72, 2, 46, 70, 106, 29, 55, 45, 94, 76, 90, 84,
+		50, 0}
+
+	// auth response
+	conn.queuedReplies = [][]byte{
+		{7, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0}, // OK
+	}
+	conn.maxReads = 3
+
+	authData := []byte{123, 87, 15, 84, 20, 58, 37, 121, 91, 117, 51, 24, 19,
+		47, 43, 9, 41, 112, 67, 110}
+	plugin := "mysql_native_password"
+
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+
+	expectedReply := []byte{
+		// 1. Packet: Hash
+		32, 0, 0, 3, 129, 93, 132, 95, 114, 48, 79, 215, 128, 62, 193, 118, 128,
+		54, 75, 208, 159, 252, 227, 215, 129, 15, 242, 97, 19, 159, 31, 20, 58,
+		153, 9, 130,
+	}
+	if !bytes.Equal(conn.written, expectedReply) {
+		t.Errorf("got unexpected data: %v", conn.written)
+	}
+}
+
+func TestAuthSwitchCachingSHA256PasswordEmpty(t *testing.T) {
+	conn, mc := newRWMockConn(2)
+	mc.cfg.Passwd = ""
+
+	// auth switch request
+	conn.data = []byte{44, 0, 0, 2, 254, 99, 97, 99, 104, 105, 110, 103, 95,
+		115, 104, 97, 50, 95, 112, 97, 115, 115, 119, 111, 114, 100, 0, 101,
+		11, 26, 18, 94, 97, 22, 72, 2, 46, 70, 106, 29, 55, 45, 94, 76, 90, 84,
+		50, 0}
+
+	// auth response
+	conn.queuedReplies = [][]byte{{7, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0}}
+	conn.maxReads = 2
+
+	authData := []byte{123, 87, 15, 84, 20, 58, 37, 121, 91, 117, 51, 24, 19,
+		47, 43, 9, 41, 112, 67, 110}
+	plugin := "mysql_native_password"
+
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+
+	expectedReply := []byte{1, 0, 0, 3, 0}
+	if !bytes.Equal(conn.written, expectedReply) {
+		t.Errorf("got unexpected data: %v", conn.written)
+	}
+}
+
+func TestAuthSwitchCachingSHA256PasswordFullRSA(t *testing.T) {
+	conn, mc := newRWMockConn(2)
+	mc.cfg.Passwd = "secret"
+
+	// auth switch request
+	conn.data = []byte{44, 0, 0, 2, 254, 99, 97, 99, 104, 105, 110, 103, 95,
+		115, 104, 97, 50, 95, 112, 97, 115, 115, 119, 111, 114, 100, 0, 101,
+		11, 26, 18, 94, 97, 22, 72, 2, 46, 70, 106, 29, 55, 45, 94, 76, 90, 84,
+		50, 0}
+
+	conn.queuedReplies = [][]byte{
+		// Perform Full Authentication
+		{2, 0, 0, 4, 1, 4},
+
+		// Pub Key Response
+		append([]byte{byte(len(serverPubKey)), 1, 0, 6}, serverPubKey...),
+
+		// OK
+		{7, 0, 0, 8, 0, 0, 0, 2, 0, 0, 0},
+	}
+	conn.maxReads = 4
+
+	authData := []byte{123, 87, 15, 84, 20, 58, 37, 121, 91, 117, 51, 24, 19,
+		47, 43, 9, 41, 112, 67, 110}
+	plugin := "mysql_native_password"
+
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+
+	expectedReplyPrefix := []byte{
+		// 1. Packet: Hash
+		32, 0, 0, 3, 129, 93, 132, 95, 114, 48, 79, 215, 128, 62, 193, 118, 128,
+		54, 75, 208, 159, 252, 227, 215, 129, 15, 242, 97, 19, 159, 31, 20, 58,
+		153, 9, 130,
+
+		// 2. Packet: Pub Key Request
+		1, 0, 0, 5, 2,
+
+		// 3. Packet: Encrypted Password
+		0, 1, 0, 7, // [changing bytes]
+	}
+	if !bytes.HasPrefix(conn.written, expectedReplyPrefix) {
+		t.Errorf("got unexpected data: %v", conn.written)
+	}
+}
+
+func TestAuthSwitchCachingSHA256PasswordFullSecure(t *testing.T) {
+	conn, mc := newRWMockConn(2)
+	mc.cfg.Passwd = "secret"
+
+	// Hack to make the caching_sha2_password plugin believe that the connection
+	// is secure
+	mc.cfg.tls = &tls.Config{InsecureSkipVerify: true}
+
+	// auth switch request
+	conn.data = []byte{44, 0, 0, 2, 254, 99, 97, 99, 104, 105, 110, 103, 95,
+		115, 104, 97, 50, 95, 112, 97, 115, 115, 119, 111, 114, 100, 0, 101,
+		11, 26, 18, 94, 97, 22, 72, 2, 46, 70, 106, 29, 55, 45, 94, 76, 90, 84,
+		50, 0}
+
+	// auth response
+	conn.queuedReplies = [][]byte{
+		{2, 0, 0, 4, 1, 4},                // Perform Full Authentication
+		{7, 0, 0, 6, 0, 0, 0, 2, 0, 0, 0}, // OK
+	}
+	conn.maxReads = 3
+
+	authData := []byte{123, 87, 15, 84, 20, 58, 37, 121, 91, 117, 51, 24, 19,
+		47, 43, 9, 41, 112, 67, 110}
+	plugin := "mysql_native_password"
+
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+
+	expectedReply := []byte{
+		// 1. Packet: Hash
+		32, 0, 0, 3, 129, 93, 132, 95, 114, 48, 79, 215, 128, 62, 193, 118, 128,
+		54, 75, 208, 159, 252, 227, 215, 129, 15, 242, 97, 19, 159, 31, 20, 58,
+		153, 9, 130,
+
+		// 2. Packet: Cleartext password
+		7, 0, 0, 5, 115, 101, 99, 114, 101, 116, 0,
+	}
+	if !bytes.Equal(conn.written, expectedReply) {
+		t.Errorf("got unexpected data: %v", conn.written)
+	}
+}
+
+func TestAuthSwitchCleartextPasswordNotAllowed(t *testing.T) {
+	conn, mc := newRWMockConn(2)
+
+	conn.data = []byte{22, 0, 0, 2, 254, 109, 121, 115, 113, 108, 95, 99, 108,
+		101, 97, 114, 95, 112, 97, 115, 115, 119, 111, 114, 100, 0}
+	conn.maxReads = 1
+	authData := []byte{123, 87, 15, 84, 20, 58, 37, 121, 91, 117, 51, 24, 19,
+		47, 43, 9, 41, 112, 67, 110}
+	plugin := "mysql_native_password"
+	err := mc.handleAuthResult(authData, plugin)
+	if err != ErrCleartextPassword {
+		t.Errorf("expected ErrCleartextPassword, got %v", err)
+	}
+}
+
+func TestAuthSwitchCleartextPassword(t *testing.T) {
+	conn, mc := newRWMockConn(2)
+	mc.cfg.AllowCleartextPasswords = true
+	mc.cfg.Passwd = "secret"
+
+	// auth switch request
+	conn.data = []byte{22, 0, 0, 2, 254, 109, 121, 115, 113, 108, 95, 99, 108,
+		101, 97, 114, 95, 112, 97, 115, 115, 119, 111, 114, 100, 0}
+
+	// auth response
+	conn.queuedReplies = [][]byte{{7, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0}}
+	conn.maxReads = 2
+
+	authData := []byte{123, 87, 15, 84, 20, 58, 37, 121, 91, 117, 51, 24, 19,
+		47, 43, 9, 41, 112, 67, 110}
+	plugin := "mysql_native_password"
+
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+
+	expectedReply := []byte{7, 0, 0, 3, 115, 101, 99, 114, 101, 116, 0}
+	if !bytes.Equal(conn.written, expectedReply) {
+		t.Errorf("got unexpected data: %v", conn.written)
+	}
+}
+
+func TestAuthSwitchCleartextPasswordEmpty(t *testing.T) {
+	conn, mc := newRWMockConn(2)
+	mc.cfg.AllowCleartextPasswords = true
+	mc.cfg.Passwd = ""
+
+	// auth switch request
+	conn.data = []byte{22, 0, 0, 2, 254, 109, 121, 115, 113, 108, 95, 99, 108,
+		101, 97, 114, 95, 112, 97, 115, 115, 119, 111, 114, 100, 0}
+
+	// auth response
+	conn.queuedReplies = [][]byte{{7, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0}}
+	conn.maxReads = 2
+
+	authData := []byte{123, 87, 15, 84, 20, 58, 37, 121, 91, 117, 51, 24, 19,
+		47, 43, 9, 41, 112, 67, 110}
+	plugin := "mysql_native_password"
+
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+
+	expectedReply := []byte{1, 0, 0, 3, 0}
+	if !bytes.Equal(conn.written, expectedReply) {
+		t.Errorf("got unexpected data: %v", conn.written)
+	}
+}
+
+func TestAuthSwitchNativePasswordNotAllowed(t *testing.T) {
+	conn, mc := newRWMockConn(2)
+	mc.cfg.AllowNativePasswords = false
+
+	conn.data = []byte{44, 0, 0, 2, 254, 109, 121, 115, 113, 108, 95, 110, 97,
+		116, 105, 118, 101, 95, 112, 97, 115, 115, 119, 111, 114, 100, 0, 96,
+		71, 63, 8, 1, 58, 75, 12, 69, 95, 66, 60, 117, 31, 48, 31, 89, 39, 55,
+		31, 0}
+	conn.maxReads = 1
+	authData := []byte{96, 71, 63, 8, 1, 58, 75, 12, 69, 95, 66, 60, 117, 31,
+		48, 31, 89, 39, 55, 31}
+	plugin := "caching_sha2_password"
+	err := mc.handleAuthResult(authData, plugin)
+	if err != ErrNativePassword {
+		t.Errorf("expected ErrNativePassword, got %v", err)
+	}
+}
+
+func TestAuthSwitchNativePassword(t *testing.T) {
+	conn, mc := newRWMockConn(2)
+	mc.cfg.AllowNativePasswords = true
+	mc.cfg.Passwd = "secret"
+
+	// auth switch request
+	conn.data = []byte{44, 0, 0, 2, 254, 109, 121, 115, 113, 108, 95, 110, 97,
+		116, 105, 118, 101, 95, 112, 97, 115, 115, 119, 111, 114, 100, 0, 96,
+		71, 63, 8, 1, 58, 75, 12, 69, 95, 66, 60, 117, 31, 48, 31, 89, 39, 55,
+		31, 0}
+
+	// auth response
+	conn.queuedReplies = [][]byte{{7, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0}}
+	conn.maxReads = 2
+
+	authData := []byte{96, 71, 63, 8, 1, 58, 75, 12, 69, 95, 66, 60, 117, 31,
+		48, 31, 89, 39, 55, 31}
+	plugin := "caching_sha2_password"
+
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+
+	expectedReply := []byte{20, 0, 0, 3, 202, 41, 195, 164, 34, 226, 49, 103,
+		21, 211, 167, 199, 227, 116, 8, 48, 57, 71, 149, 146}
+	if !bytes.Equal(conn.written, expectedReply) {
+		t.Errorf("got unexpected data: %v", conn.written)
+	}
+}
+
+func TestAuthSwitchNativePasswordEmpty(t *testing.T) {
+	conn, mc := newRWMockConn(2)
+	mc.cfg.AllowNativePasswords = true
+	mc.cfg.Passwd = ""
+
+	// auth switch request
+	conn.data = []byte{44, 0, 0, 2, 254, 109, 121, 115, 113, 108, 95, 110, 97,
+		116, 105, 118, 101, 95, 112, 97, 115, 115, 119, 111, 114, 100, 0, 96,
+		71, 63, 8, 1, 58, 75, 12, 69, 95, 66, 60, 117, 31, 48, 31, 89, 39, 55,
+		31, 0}
+
+	// auth response
+	conn.queuedReplies = [][]byte{{7, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0}}
+	conn.maxReads = 2
+
+	authData := []byte{96, 71, 63, 8, 1, 58, 75, 12, 69, 95, 66, 60, 117, 31,
+		48, 31, 89, 39, 55, 31}
+	plugin := "caching_sha2_password"
+
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+
+	expectedReply := []byte{0, 0, 0, 3}
+	if !bytes.Equal(conn.written, expectedReply) {
+		t.Errorf("got unexpected data: %v", conn.written)
+	}
+}
+
+func TestAuthSwitchOldPasswordNotAllowed(t *testing.T) {
+	conn, mc := newRWMockConn(2)
+
+	conn.data = []byte{41, 0, 0, 2, 254, 109, 121, 115, 113, 108, 95, 111, 108,
+		100, 95, 112, 97, 115, 115, 119, 111, 114, 100, 0, 95, 84, 103, 43, 61,
+		49, 123, 61, 91, 50, 40, 113, 35, 84, 96, 101, 92, 123, 121, 107, 0}
+	conn.maxReads = 1
+	authData := []byte{95, 84, 103, 43, 61, 49, 123, 61, 91, 50, 40, 113, 35,
+		84, 96, 101, 92, 123, 121, 107}
+	plugin := "mysql_native_password"
+	err := mc.handleAuthResult(authData, plugin)
+	if err != ErrOldPassword {
+		t.Errorf("expected ErrOldPassword, got %v", err)
+	}
+}
+
+func TestAuthSwitchOldPassword(t *testing.T) {
+	conn, mc := newRWMockConn(2)
+	mc.cfg.AllowOldPasswords = true
+	mc.cfg.Passwd = "secret"
+
+	// auth switch request
+	conn.data = []byte{41, 0, 0, 2, 254, 109, 121, 115, 113, 108, 95, 111, 108,
+		100, 95, 112, 97, 115, 115, 119, 111, 114, 100, 0, 95, 84, 103, 43, 61,
+		49, 123, 61, 91, 50, 40, 113, 35, 84, 96, 101, 92, 123, 121, 107, 0}
+
+	// auth response
+	conn.queuedReplies = [][]byte{{8, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 0}}
+	conn.maxReads = 2
+
+	authData := []byte{95, 84, 103, 43, 61, 49, 123, 61, 91, 50, 40, 113, 35,
+		84, 96, 101, 92, 123, 121, 107}
+	plugin := "mysql_native_password"
+
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+
+	expectedReply := []byte{9, 0, 0, 3, 86, 83, 83, 79, 74, 78, 65, 66, 0}
+	if !bytes.Equal(conn.written, expectedReply) {
+		t.Errorf("got unexpected data: %v", conn.written)
+	}
+}
+
+func TestAuthSwitchOldPasswordEmpty(t *testing.T) {
+	conn, mc := newRWMockConn(2)
+	mc.cfg.AllowOldPasswords = true
+	mc.cfg.Passwd = ""
+
+	// auth switch request
+	conn.data = []byte{41, 0, 0, 2, 254, 109, 121, 115, 113, 108, 95, 111, 108,
+		100, 95, 112, 97, 115, 115, 119, 111, 114, 100, 0, 95, 84, 103, 43, 61,
+		49, 123, 61, 91, 50, 40, 113, 35, 84, 96, 101, 92, 123, 121, 107, 0}
+
+	// auth response
+	conn.queuedReplies = [][]byte{{8, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 0}}
+	conn.maxReads = 2
+
+	authData := []byte{95, 84, 103, 43, 61, 49, 123, 61, 91, 50, 40, 113, 35,
+		84, 96, 101, 92, 123, 121, 107}
+	plugin := "mysql_native_password"
+
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+
+	expectedReply := []byte{1, 0, 0, 3, 0}
+	if !bytes.Equal(conn.written, expectedReply) {
+		t.Errorf("got unexpected data: %v", conn.written)
+	}
+}
diff --git a/buffer.go b/buffer.go
index 2001feacd..eb4748bf4 100644
--- a/buffer.go
+++ b/buffer.go
@@ -130,18 +130,18 @@ func (b *buffer) takeBuffer(length int) []byte {
 // smaller than defaultBufSize
 // Only one buffer (total) can be used at a time.
 func (b *buffer) takeSmallBuffer(length int) []byte {
-	if b.length == 0 {
-		return b.buf[:length]
+	if b.length > 0 {
+		return nil
 	}
-	return nil
+	return b.buf[:length]
 }
 
 // takeCompleteBuffer returns the complete existing buffer.
 // This can be used if the necessary buffer size is unknown.
 // Only one buffer (total) can be used at a time.
 func (b *buffer) takeCompleteBuffer() []byte {
-	if b.length == 0 {
-		return b.buf
+	if b.length > 0 {
+		return nil
 	}
-	return nil
+	return b.buf
 }
diff --git a/connection_go18.go b/connection_go18.go
index 474ea227f..62796bfce 100644
--- a/connection_go18.go
+++ b/connection_go18.go
@@ -17,25 +17,22 @@ import (
 )
 
 // Ping implements driver.Pinger interface
-func (mc *mysqlConn) Ping(ctx context.Context) error {
+func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
 	if mc.closed.IsSet() {
 		errLog.Print(ErrInvalidConn)
 		return driver.ErrBadConn
 	}
 
-	if err := mc.watchCancel(ctx); err != nil {
-		return err
+	if err = mc.watchCancel(ctx); err != nil {
+		return
 	}
 	defer mc.finish()
 
-	if err := mc.writeCommandPacket(comPing); err != nil {
-		return err
-	}
-	if _, err := mc.readResultOK(); err != nil {
-		return err
+	if err = mc.writeCommandPacket(comPing); err != nil {
+		return
 	}
 
-	return nil
+	return mc.readResultOK()
 }
 
 // BeginTx implements driver.ConnBeginTx interface
diff --git a/const.go b/const.go
index 2afde6dfb..b1e6b85ef 100644
--- a/const.go
+++ b/const.go
@@ -9,6 +9,7 @@
 package mysql
 
 const (
+	defaultAuthPlugin       = "mysql_native_password"
 	defaultMaxAllowedPacket = 4 << 20 // 4 MiB
 	minProtocolVersion      = 10
 	maxPacketSize           = 1<<24 - 1
diff --git a/driver.go b/driver.go
index 68679d7b5..1a75a16ec 100644
--- a/driver.go
+++ b/driver.go
@@ -107,20 +107,31 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
 	mc.writeTimeout = mc.cfg.WriteTimeout
 
 	// Reading Handshake Initialization Packet
-	cipher, pluginName, err := mc.readInitPacket()
+	authData, plugin, err := mc.readHandshakePacket()
 	if err != nil {
 		mc.cleanup()
 		return nil, err
 	}
 
 	// Send Client Authentication Packet
-	if err = mc.writeAuthPacket(cipher, pluginName); err != nil {
+	authResp, addNUL, err := mc.auth(authData, plugin)
+	if err != nil {
+		// try the default auth plugin, if using the requested plugin failed
+		errLog.Print("could not use requested auth plugin '"+plugin+"': ", err.Error())
+		plugin = defaultAuthPlugin
+		authResp, addNUL, err = mc.auth(authData, plugin)
+		if err != nil {
+			mc.cleanup()
+			return nil, err
+		}
+	}
+	if err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin); err != nil {
 		mc.cleanup()
 		return nil, err
 	}
 
 	// Handle response to auth packet, switch methods if possible
-	if err = handleAuthResult(mc, cipher, pluginName); err != nil {
+	if err = mc.handleAuthResult(authData, plugin); err != nil {
 		// Authentication failed and MySQL has already closed the connection
 		// (https://dev.mysql.com/doc/internals/en/authentication-fails.html).
 		// Do not send COM_QUIT, just cleanup and return the error.
@@ -153,95 +164,6 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
 	return mc, nil
 }
 
-func handleAuthResult(mc *mysqlConn, oldCipher []byte, pluginName string) error {
-	// Read Result Packet
-	cipher, err := mc.readResultOK()
-	if err == nil {
-		// handle caching_sha2_password
-		// https://insidemysql.com/preparing-your-community-connector-for-mysql-8-part-2-sha256/
-		if pluginName == "caching_sha2_password" {
-			if len(cipher) == 1 {
-				switch cipher[0] {
-				case cachingSha2PasswordFastAuthSuccess:
-					cipher, err = mc.readResultOK()
-					if err == nil {
-						return nil // auth successful
-					}
-
-				case cachingSha2PasswordPerformFullAuthentication:
-					if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
-						if err = mc.writeClearAuthPacket(); err != nil {
-							return err
-						}
-					} else {
-						if err = mc.writePublicKeyAuthPacket(oldCipher); err != nil {
-							return err
-						}
-					}
-					cipher, err = mc.readResultOK()
-					if err == nil {
-						return nil // auth successful
-					}
-
-				default:
-					return ErrMalformPkt
-				}
-			} else {
-				return ErrMalformPkt
-			}
-
-		} else {
-			return nil // auth successful
-		}
-	}
-
-	if mc.cfg == nil {
-		return err // auth failed and retry not possible
-	}
-
-	// Retry auth if configured to do so
-	switch err {
-	case ErrCleartextPassword:
-		if mc.cfg.AllowCleartextPasswords {
-			// Retry with clear text password for
-			// http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html
-			// http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html
-			if err = mc.writeClearAuthPacket(); err != nil {
-				return err
-			}
-			_, err = mc.readResultOK()
-		}
-
-	case ErrNativePassword:
-		if mc.cfg.AllowNativePasswords {
-			if err = mc.writeNativeAuthPacket(cipher); err != nil {
-				return err
-			}
-			_, err = mc.readResultOK()
-		}
-
-	case ErrOldPassword:
-		if mc.cfg.AllowOldPasswords {
-			// Retry with old authentication method. Note: there are edge cases
-			// where this should work but doesn't; this is currently "wontfix":
-			// https://github.com/go-sql-driver/mysql/issues/184
-
-			// If CLIENT_PLUGIN_AUTH capability is not supported, no new cipher is
-			// sent and we have to keep using the cipher sent in the init packet.
-			if cipher == nil {
-				cipher = oldCipher
-			}
-
-			if err = mc.writeOldAuthPacket(cipher); err != nil {
-				return err
-			}
-			_, err = mc.readResultOK()
-		}
-	}
-
-	return err
-}
-
 func init() {
 	sql.Register("mysql", &MySQLDriver{})
 }
diff --git a/infile.go b/infile.go
index 4020f9192..273cb0ba5 100644
--- a/infile.go
+++ b/infile.go
@@ -174,8 +174,7 @@ func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
 
 	// read OK packet
 	if err == nil {
-		_, err = mc.readResultOK()
-		return err
+		return mc.readResultOK()
 	}
 
 	mc.readPacket()
diff --git a/packets.go b/packets.go
index 5324568ae..5d8abfc63 100644
--- a/packets.go
+++ b/packets.go
@@ -10,14 +10,9 @@ package mysql
 
 import (
 	"bytes"
-	"crypto/rand"
-	"crypto/rsa"
-	"crypto/sha1"
 	"crypto/tls"
-	"crypto/x509"
 	"database/sql/driver"
 	"encoding/binary"
-	"encoding/pem"
 	"errors"
 	"fmt"
 	"io"
@@ -154,12 +149,12 @@ func (mc *mysqlConn) writePacket(data []byte) error {
 }
 
 /******************************************************************************
-*                           Initialisation Process                            *
+*                           Initialization Process                            *
 ******************************************************************************/
 
 // Handshake Initialization Packet
 // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::Handshake
-func (mc *mysqlConn) readInitPacket() ([]byte, string, error) {
+func (mc *mysqlConn) readHandshakePacket() ([]byte, string, error) {
 	data, err := mc.readPacket()
 	if err != nil {
 		// for init we can rewrite this to ErrBadConn for sql.Driver to retry, since
@@ -188,7 +183,7 @@ func (mc *mysqlConn) readInitPacket() ([]byte, string, error) {
 	pos := 1 + bytes.IndexByte(data[1:], 0x00) + 1 + 4
 
 	// first part of the password cipher [8 bytes]
-	cipher := data[pos : pos+8]
+	authData := data[pos : pos+8]
 
 	// (filler) always 0x00 [1 byte]
 	pos += 8 + 1
@@ -203,7 +198,7 @@ func (mc *mysqlConn) readInitPacket() ([]byte, string, error) {
 	}
 	pos += 2
 
-	pluginName := "mysql_native_password"
+	plugin := ""
 	if len(data) > pos {
 		// character set [1 byte]
 		// status flags [2 bytes]
@@ -224,36 +219,34 @@ func (mc *mysqlConn) readInitPacket() ([]byte, string, error) {
 		//
 		// The official Python library uses the fixed length 12
 		// which seems to work but technically could have a hidden bug.
-		cipher = append(cipher, data[pos:pos+12]...)
+		authData = append(authData, data[pos:pos+12]...)
 		pos += 13
 
 		// EOF if version (>= 5.5.7 and < 5.5.10) or (>= 5.6.0 and < 5.6.2)
 		// \NUL otherwise
 		if end := bytes.IndexByte(data[pos:], 0x00); end != -1 {
-			pluginName = string(data[pos : pos+end])
+			plugin = string(data[pos : pos+end])
 		} else {
-			pluginName = string(data[pos:])
+			plugin = string(data[pos:])
 		}
 
 		// make a memory safe copy of the cipher slice
 		var b [20]byte
-		copy(b[:], cipher)
-		return b[:], pluginName, nil
+		copy(b[:], authData)
+		return b[:], plugin, nil
 	}
 
+	plugin = defaultAuthPlugin
+
 	// make a memory safe copy of the cipher slice
 	var b [8]byte
-	copy(b[:], cipher)
-	return b[:], pluginName, nil
+	copy(b[:], authData)
+	return b[:], plugin, nil
 }
 
 // Client Authentication Packet
 // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeResponse
-func (mc *mysqlConn) writeAuthPacket(cipher []byte, pluginName string) error {
-	if pluginName != "mysql_native_password" && pluginName != "caching_sha2_password" {
-		return fmt.Errorf("unknown authentication plugin name '%s'", pluginName)
-	}
-
+func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, addNUL bool, plugin string) error {
 	// Adjust client flags based on server support
 	clientFlags := clientProtocol41 |
 		clientSecureConn |
@@ -277,17 +270,11 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte, pluginName string) error {
 		clientFlags |= clientMultiStatements
 	}
 
-	// User Password
-	var scrambleBuff []byte
-	switch pluginName {
-	case "mysql_native_password":
-		scrambleBuff = scramblePassword(cipher, []byte(mc.cfg.Passwd))
-	case "caching_sha2_password":
-		scrambleBuff = scrambleCachingSha2Password(cipher, []byte(mc.cfg.Passwd))
+	pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1 + 1 + len(authResp) + 21 + 1
+	if addNUL {
+		pktLen++
 	}
 
-	pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1 + 1 + len(scrambleBuff) + 21 + 1
-
 	// To specify a db name
 	if n := len(mc.cfg.DBName); n > 0 {
 		clientFlags |= clientConnectWithDB
@@ -297,7 +284,7 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte, pluginName string) error {
 	// Calculate packet length and get buffer with that size
 	data := mc.buf.takeSmallBuffer(pktLen + 4)
 	if data == nil {
-		// can not take the buffer. Something must be wrong with the connection
+		// cannot take the buffer. Something must be wrong with the connection
 		errLog.Print(ErrBusyBuffer)
 		return errBadConnNoWrite
 	}
@@ -354,9 +341,13 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte, pluginName string) error {
 	data[pos] = 0x00
 	pos++
 
-	// ScrambleBuffer [length encoded integer]
-	data[pos] = byte(len(scrambleBuff))
-	pos += 1 + copy(data[pos+1:], scrambleBuff)
+	// Auth Data [length encoded integer]
+	data[pos] = byte(len(authResp))
+	pos += 1 + copy(data[pos+1:], authResp)
+	if addNUL {
+		data[pos] = 0x00
+		pos++
+	}
 
 	// Databasename [null terminated string]
 	if len(mc.cfg.DBName) > 0 {
@@ -365,107 +356,32 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte, pluginName string) error {
 		pos++
 	}
 
-	pos += copy(data[pos:], pluginName)
+	pos += copy(data[pos:], plugin)
 	data[pos] = 0x00
 
 	// Send Auth packet
 	return mc.writePacket(data)
 }
 
-//  Client old authentication packet
 // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
-func (mc *mysqlConn) writeOldAuthPacket(cipher []byte) error {
-	// User password
-	// https://dev.mysql.com/doc/internals/en/old-password-authentication.html
-	// Old password authentication only need and will need 8-byte challenge.
-	scrambleBuff := scrambleOldPassword(cipher[:8], []byte(mc.cfg.Passwd))
-
-	// Calculate the packet length and add a tailing 0
-	pktLen := len(scrambleBuff) + 1
-	data := mc.buf.takeSmallBuffer(4 + pktLen)
-	if data == nil {
-		// can not take the buffer. Something must be wrong with the connection
-		errLog.Print(ErrBusyBuffer)
-		return errBadConnNoWrite
+func (mc *mysqlConn) writeAuthSwitchPacket(authData []byte, addNUL bool) error {
+	pktLen := 4 + len(authData)
+	if addNUL {
+		pktLen++
 	}
-
-	// Add the scrambled password [null terminated string]
-	copy(data[4:], scrambleBuff)
-	data[4+pktLen-1] = 0x00
-
-	return mc.writePacket(data)
-}
-
-//  Client clear text authentication packet
-// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
-func (mc *mysqlConn) writeClearAuthPacket() error {
-	// Calculate the packet length and add a tailing 0
-	pktLen := len(mc.cfg.Passwd) + 1
-	data := mc.buf.takeSmallBuffer(4 + pktLen)
+	data := mc.buf.takeSmallBuffer(pktLen)
 	if data == nil {
-		// can not take the buffer. Something must be wrong with the connection
+		// cannot take the buffer. Something must be wrong with the connection
 		errLog.Print(ErrBusyBuffer)
 		return errBadConnNoWrite
 	}
 
-	// Add the clear password [null terminated string]
-	copy(data[4:], mc.cfg.Passwd)
-	data[4+pktLen-1] = 0x00
-
-	return mc.writePacket(data)
-}
-
-//  Native password authentication method
-// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
-func (mc *mysqlConn) writeNativeAuthPacket(cipher []byte) error {
-	// https://dev.mysql.com/doc/internals/en/secure-password-authentication.html
-	// Native password authentication only need and will need 20-byte challenge.
-	scrambleBuff := scramblePassword(cipher[0:20], []byte(mc.cfg.Passwd))
-
-	// Calculate the packet length and add a tailing 0
-	pktLen := len(scrambleBuff)
-	data := mc.buf.takeSmallBuffer(4 + pktLen)
-	if data == nil {
-		// can not take the buffer. Something must be wrong with the connection
-		errLog.Print(ErrBusyBuffer)
-		return errBadConnNoWrite
+	// Add the auth data [EOF]
+	copy(data[4:], authData)
+	if addNUL {
+		data[pktLen-1] = 0x00
 	}
 
-	// Add the scramble
-	copy(data[4:], scrambleBuff)
-
-	return mc.writePacket(data)
-}
-
-//  Caching sha2 authentication. Public key request and send encrypted password
-// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
-func (mc *mysqlConn) writePublicKeyAuthPacket(cipher []byte) error {
-	// request public key
-	data := mc.buf.takeSmallBuffer(4 + 1)
-	data[4] = cachingSha2PasswordRequestPublicKey
-	mc.writePacket(data)
-
-	data, err := mc.readPacket()
-	if err != nil {
-		return err
-	}
-
-	block, _ := pem.Decode(data[1:])
-	pub, err := x509.ParsePKIXPublicKey(block.Bytes)
-	if err != nil {
-		return err
-	}
-
-	plain := make([]byte, len(mc.cfg.Passwd)+1)
-	copy(plain, mc.cfg.Passwd)
-	for i := range plain {
-		j := i % len(cipher)
-		plain[i] ^= cipher[j]
-	}
-	sha1 := sha1.New()
-	enc, _ := rsa.EncryptOAEP(sha1, rand.Reader, pub.(*rsa.PublicKey), plain, nil)
-	data = mc.buf.takeSmallBuffer(4 + len(enc))
-	copy(data[4:], enc)
 	return mc.writePacket(data)
 }
 
@@ -479,7 +395,7 @@ func (mc *mysqlConn) writeCommandPacket(command byte) error {
 
 	data := mc.buf.takeSmallBuffer(4 + 1)
 	if data == nil {
-		// can not take the buffer. Something must be wrong with the connection
+		// cannot take the buffer. Something must be wrong with the connection
 		errLog.Print(ErrBusyBuffer)
 		return errBadConnNoWrite
 	}
@@ -498,7 +414,7 @@ func (mc *mysqlConn) writeCommandPacketStr(command byte, arg string) error {
 	pktLen := 1 + len(arg)
 	data := mc.buf.takeBuffer(pktLen + 4)
 	if data == nil {
-		// can not take the buffer. Something must be wrong with the connection
+		// cannot take the buffer. Something must be wrong with the connection
 		errLog.Print(ErrBusyBuffer)
 		return errBadConnNoWrite
 	}
@@ -519,7 +435,7 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
 
 	data := mc.buf.takeSmallBuffer(4 + 1 + 4)
 	if data == nil {
-		// can not take the buffer. Something must be wrong with the connection
+		// cannot take the buffer. Something must be wrong with the connection
 		errLog.Print(ErrBusyBuffer)
 		return errBadConnNoWrite
 	}
@@ -541,53 +457,50 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
 *                              Result Packets                                 *
 ******************************************************************************/
 
-func readAuthSwitch(data []byte) ([]byte, error) {
-	if len(data) > 1 {
-		pluginEndIndex := bytes.IndexByte(data, 0x00)
-		plugin := string(data[1:pluginEndIndex])
-		cipher := data[pluginEndIndex+1:]
-
-		switch plugin {
-		case "mysql_old_password":
-			// using old_passwords
-			return cipher, ErrOldPassword
-		case "mysql_clear_password":
-			// using clear text password
-			return cipher, ErrCleartextPassword
-		case "mysql_native_password":
-			// using mysql default authentication method
-			return cipher, ErrNativePassword
-		default:
-			return cipher, ErrUnknownPlugin
-		}
-	}
-
-	// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest
-	return nil, ErrOldPassword
-}
-
-// Returns error if Packet is not an 'Result OK'-Packet
-func (mc *mysqlConn) readResultOK() ([]byte, error) {
+func (mc *mysqlConn) readAuthResult() ([]byte, string, error) {
 	data, err := mc.readPacket()
 	if err != nil {
-		return nil, err
+		return nil, "", err
 	}
 
 	// packet indicator
 	switch data[0] {
 
 	case iOK:
-		return nil, mc.handleOkPacket(data)
+		return nil, "", mc.handleOkPacket(data)
 
 	case iAuthMoreData:
-		return data[1:], nil
+		return data[1:], "", err
 
 	case iEOF:
-		return readAuthSwitch(data)
+		if len(data) < 1 {
+			// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest
+			return nil, "mysql_old_password", nil
+		}
+		pluginEndIndex := bytes.IndexByte(data, 0x00)
+		if pluginEndIndex < 0 {
+			return nil, "", ErrMalformPkt
+		}
+		plugin := string(data[1:pluginEndIndex])
+		authData := data[pluginEndIndex+1:]
+		return authData, plugin, nil
 
 	default: // Error otherwise
-		return nil, mc.handleErrorPacket(data)
+		return nil, "", mc.handleErrorPacket(data)
+	}
+}
+
+// Returns error if Packet is not an 'Result OK'-Packet
+func (mc *mysqlConn) readResultOK() error {
+	data, err := mc.readPacket()
+	if err != nil {
+		return err
+	}
+
+	if data[0] == iOK {
+		return mc.handleOkPacket(data)
 	}
+	return mc.handleErrorPacket(data)
 }
 
 // Result Set Header Packet
@@ -921,7 +834,7 @@ func (stmt *mysqlStmt) writeCommandLongData(paramID int, arg []byte) error {
 	// 2 bytes paramID
 	const dataOffset = 1 + 4 + 2
 
-	// Can not use the write buffer since
+	// Cannot use the write buffer since
 	// a) the buffer is too small
 	// b) it is in use
 	data := make([]byte, 4+1+4+2+len(arg))
@@ -993,7 +906,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 		data = mc.buf.takeCompleteBuffer()
 	}
 	if data == nil {
-		// can not take the buffer. Something must be wrong with the connection
+		// cannot take the buffer. Something must be wrong with the connection
 		errLog.Print(ErrBusyBuffer)
 		return errBadConnNoWrite
 	}
@@ -1161,7 +1074,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 				paramValues = append(paramValues, b...)
 
 			default:
-				return fmt.Errorf("can not convert type: %T", arg)
+				return fmt.Errorf("cannot convert type: %T", arg)
 			}
 		}
 
diff --git a/packets_test.go b/packets_test.go
index c9cfd9001..b61e4dbf7 100644
--- a/packets_test.go
+++ b/packets_test.go
@@ -24,16 +24,17 @@ var (
 
 // struct to mock a net.Conn for testing purposes
 type mockConn struct {
-	laddr     net.Addr
-	raddr     net.Addr
-	data      []byte
-	closed    bool
-	read      int
-	written   int
-	reads     int
-	writes    int
-	maxReads  int
-	maxWrites int
+	laddr         net.Addr
+	raddr         net.Addr
+	data          []byte
+	written       []byte
+	queuedReplies [][]byte
+	closed        bool
+	read          int
+	reads         int
+	writes        int
+	maxReads      int
+	maxWrites     int
 }
 
 func (m *mockConn) Read(b []byte) (n int, err error) {
@@ -62,7 +63,12 @@ func (m *mockConn) Write(b []byte) (n int, err error) {
 	}
 
 	n = len(b)
-	m.written += n
+	m.written = append(m.written, b...)
+
+	if n > 0 && len(m.queuedReplies) > 0 {
+		m.data = m.queuedReplies[0]
+		m.queuedReplies = m.queuedReplies[1:]
+	}
 	return
 }
 func (m *mockConn) Close() error {
@@ -88,6 +94,19 @@ func (m *mockConn) SetWriteDeadline(t time.Time) error {
 // make sure mockConn implements the net.Conn interface
 var _ net.Conn = new(mockConn)
 
+func newRWMockConn(sequence uint8) (*mockConn, *mysqlConn) {
+	conn := new(mockConn)
+	mc := &mysqlConn{
+		buf:              newBuffer(conn),
+		cfg:              NewConfig(),
+		netConn:          conn,
+		closech:          make(chan struct{}),
+		maxAllowedPacket: defaultMaxAllowedPacket,
+		sequence:         sequence,
+	}
+	return conn, mc
+}
+
 func TestReadPacketSingleByte(t *testing.T) {
 	conn := new(mockConn)
 	mc := &mysqlConn{
@@ -300,7 +319,7 @@ func TestRegression801(t *testing.T) {
 		112, 97, 115, 115, 119, 111, 114, 100}
 	conn.maxReads = 1
 
-	authData, pluginName, err := mc.readInitPacket()
+	authData, pluginName, err := mc.readHandshakePacket()
 	if err != nil {
 		t.Fatalf("got error: %v", err)
 	}
diff --git a/utils.go b/utils.go
index 9d1530b3d..8472b8d2d 100644
--- a/utils.go
+++ b/utils.go
@@ -9,8 +9,6 @@
 package mysql
 
 import (
-	"crypto/sha1"
-	"crypto/sha256"
 	"crypto/tls"
 	"database/sql/driver"
 	"encoding/binary"
@@ -99,147 +97,6 @@ func readBool(input string) (value bool, valid bool) {
 	return
 }
 
-/******************************************************************************
-*                             Authentication                                  *
-******************************************************************************/
-
-// Encrypt password using 4.1+ method
-func scramblePassword(scramble, password []byte) []byte {
-	if len(password) == 0 {
-		return nil
-	}
-
-	// stage1Hash = SHA1(password)
-	crypt := sha1.New()
-	crypt.Write(password)
-	stage1 := crypt.Sum(nil)
-
-	// scrambleHash = SHA1(scramble + SHA1(stage1Hash))
-	// inner Hash
-	crypt.Reset()
-	crypt.Write(stage1)
-	hash := crypt.Sum(nil)
-
-	// outer Hash
-	crypt.Reset()
-	crypt.Write(scramble)
-	crypt.Write(hash)
-	scramble = crypt.Sum(nil)
-
-	// token = scrambleHash XOR stage1Hash
-	for i := range scramble {
-		scramble[i] ^= stage1[i]
-	}
-	return scramble
-}
-
-// Encrypt password using pre 4.1 (old password) method
-// https://github.com/atcurtis/mariadb/blob/master/mysys/my_rnd.c
-type myRnd struct {
-	seed1, seed2 uint32
-}
-
-const myRndMaxVal = 0x3FFFFFFF
-
-// Pseudo random number generator
-func newMyRnd(seed1, seed2 uint32) *myRnd {
-	return &myRnd{
-		seed1: seed1 % myRndMaxVal,
-		seed2: seed2 % myRndMaxVal,
-	}
-}
-
-// Tested to be equivalent to MariaDB's floating point variant
-// http://play.golang.org/p/QHvhd4qved
-// http://play.golang.org/p/RG0q4ElWDx
-func (r *myRnd) NextByte() byte {
-	r.seed1 = (r.seed1*3 + r.seed2) % myRndMaxVal
-	r.seed2 = (r.seed1 + r.seed2 + 33) % myRndMaxVal
-
-	return byte(uint64(r.seed1) * 31 / myRndMaxVal)
-}
-
-// Generate binary hash from byte string using insecure pre 4.1 method
-func pwHash(password []byte) (result [2]uint32) {
-	var add uint32 = 7
-	var tmp uint32
-
-	result[0] = 1345345333
-	result[1] = 0x12345671
-
-	for _, c := range password {
-		// skip spaces and tabs in password
-		if c == ' ' || c == '\t' {
-			continue
-		}
-
-		tmp = uint32(c)
-		result[0] ^= (((result[0] & 63) + add) * tmp) + (result[0] << 8)
-		result[1] += (result[1] << 8) ^ result[0]
-		add += tmp
-	}
-
-	// Remove sign bit (1<<31)-1)
-	result[0] &= 0x7FFFFFFF
-	result[1] &= 0x7FFFFFFF
-
-	return
-}
-
-// Encrypt password using insecure pre 4.1 method
-func scrambleOldPassword(scramble, password []byte) []byte {
-	if len(password) == 0 {
-		return nil
-	}
-
-	scramble = scramble[:8]
-
-	hashPw := pwHash(password)
-	hashSc := pwHash(scramble)
-
-	r := newMyRnd(hashPw[0]^hashSc[0], hashPw[1]^hashSc[1])
-
-	var out [8]byte
-	for i := range out {
-		out[i] = r.NextByte() + 64
-	}
-
-	mask := r.NextByte()
-	for i := range out {
-		out[i] ^= mask
-	}
-
-	return out[:]
-}
-
-// Encrypt password using 8.0 default method
-func scrambleCachingSha2Password(scramble, password []byte) []byte {
-	if len(password) == 0 {
-		return nil
-	}
-
-	// XOR(SHA256(password), SHA256(SHA256(SHA256(password)), scramble))
-
-	crypt := sha256.New()
-	crypt.Write(password)
-	message1 := crypt.Sum(nil)
-
-	crypt.Reset()
-	crypt.Write(message1)
-	message1Hash := crypt.Sum(nil)
-
-	crypt.Reset()
-	crypt.Write(message1Hash)
-	crypt.Write(scramble)
-	message2 := crypt.Sum(nil)
-
-	for i := range message1 {
-		message1[i] ^= message2[i]
-	}
-
-	return message1
-}
-
 /******************************************************************************
 *                           Time related utils                                *
 ******************************************************************************/
diff --git a/utils_test.go b/utils_test.go
index a599c55f3..a3b9cb4ad 100644
--- a/utils_test.go
+++ b/utils_test.go
@@ -11,7 +11,6 @@ package mysql
 import (
 	"bytes"
 	"encoding/binary"
-	"fmt"
 	"testing"
 	"time"
 )
@@ -93,43 +92,6 @@ func TestLengthEncodedInteger(t *testing.T) {
 	}
 }
 
-func TestOldPass(t *testing.T) {
-	scramble := []byte{9, 8, 7, 6, 5, 4, 3, 2}
-	vectors := []struct {
-		pass string
-		out  string
-	}{
-		{" pass", "47575c5a435b4251"},
-		{"pass ", "47575c5a435b4251"},
-		{"123\t456", "575c47505b5b5559"},
-		{"C0mpl!ca ted#PASS123", "5d5d554849584a45"},
-	}
-	for _, tuple := range vectors {
-		ours := scrambleOldPassword(scramble, []byte(tuple.pass))
-		if tuple.out != fmt.Sprintf("%x", ours) {
-			t.Errorf("Failed old password %q", tuple.pass)
-		}
-	}
-}
-
-func TestCachingSha2Pass(t *testing.T) {
-	scramble := []byte{10, 47, 74, 111, 75, 73, 34, 48, 88, 76, 114, 74, 37, 13, 3, 80, 82, 2, 23, 21}
-	vectors := []struct {
-		pass string
-		out  string
-	}{
-		{"secret", "f490e76f66d9d86665ce54d98c78d0acfe2fb0b08b423da807144873d30b312c"},
-		{"secret2", "abc3934a012cf342e876071c8ee202de51785b430258a7a0138bc79c4d800bc6"},
-	}
-	for _, tuple := range vectors {
-		ours := scrambleCachingSha2Password(scramble, []byte(tuple.pass))
-		if tuple.out != fmt.Sprintf("%x", ours) {
-			t.Errorf("Failed caching sha2 password %q", tuple.pass)
-		}
-	}
-
-}
-
 func TestFormatBinaryDateTime(t *testing.T) {
 	rawDate := [11]byte{}
 	binary.LittleEndian.PutUint16(rawDate[:2], 1978)   // years

From 7413002f368fba928eefaf762c91a2acfeabdf68 Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Wed, 30 May 2018 12:02:58 +0200
Subject: [PATCH 067/282] Skip flaky cancellation test checks that depend on a
 race-condition (#811)

* driver: do not hard fail on flaky checks

* driver: fix cancellation delay
---
 driver_go18_test.go | 28 ++++++++++++++--------------
 1 file changed, 14 insertions(+), 14 deletions(-)

diff --git a/driver_go18_test.go b/driver_go18_test.go
index afd5694ec..005e76204 100644
--- a/driver_go18_test.go
+++ b/driver_go18_test.go
@@ -248,7 +248,7 @@ func TestContextCancelExec(t *testing.T) {
 		ctx, cancel := context.WithCancel(context.Background())
 
 		// Delay execution for just a bit until db.ExecContext has begun.
-		defer time.AfterFunc(100*time.Millisecond, cancel).Stop()
+		defer time.AfterFunc(250*time.Millisecond, cancel).Stop()
 
 		// This query will be canceled.
 		startTime := time.Now()
@@ -259,7 +259,7 @@ func TestContextCancelExec(t *testing.T) {
 			dbt.Errorf("too long execution time: %s", d)
 		}
 
-		// Wait for the INSERT query has done.
+		// Wait for the INSERT query to be done.
 		time.Sleep(time.Second)
 
 		// Check how many times the query is executed.
@@ -268,7 +268,7 @@ func TestContextCancelExec(t *testing.T) {
 			dbt.Fatalf("%s", err.Error())
 		}
 		if v != 1 { // TODO: need to kill the query, and v should be 0.
-			dbt.Errorf("expected val to be 1, got %d", v)
+			dbt.Skipf("[WARN] expected val to be 1, got %d", v)
 		}
 
 		// Context is already canceled, so error should come before execution.
@@ -283,7 +283,7 @@ func TestContextCancelExec(t *testing.T) {
 			dbt.Fatalf("%s", err.Error())
 		}
 		if v != 1 {
-			dbt.Errorf("expected val to be 1, got %d", v)
+			dbt.Skipf("[WARN] expected val to be 1, got %d", v)
 		}
 	})
 }
@@ -294,7 +294,7 @@ func TestContextCancelQuery(t *testing.T) {
 		ctx, cancel := context.WithCancel(context.Background())
 
 		// Delay execution for just a bit until db.ExecContext has begun.
-		defer time.AfterFunc(100*time.Millisecond, cancel).Stop()
+		defer time.AfterFunc(250*time.Millisecond, cancel).Stop()
 
 		// This query will be canceled.
 		startTime := time.Now()
@@ -305,7 +305,7 @@ func TestContextCancelQuery(t *testing.T) {
 			dbt.Errorf("too long execution time: %s", d)
 		}
 
-		// Wait for the INSERT query has done.
+		// Wait for the INSERT query to be done.
 		time.Sleep(time.Second)
 
 		// Check how many times the query is executed.
@@ -314,7 +314,7 @@ func TestContextCancelQuery(t *testing.T) {
 			dbt.Fatalf("%s", err.Error())
 		}
 		if v != 1 { // TODO: need to kill the query, and v should be 0.
-			dbt.Errorf("expected val to be 1, got %d", v)
+			dbt.Skipf("[WARN] expected val to be 1, got %d", v)
 		}
 
 		// Context is already canceled, so error should come before execution.
@@ -327,7 +327,7 @@ func TestContextCancelQuery(t *testing.T) {
 			dbt.Fatalf("%s", err.Error())
 		}
 		if v != 1 {
-			dbt.Errorf("expected val to be 1, got %d", v)
+			dbt.Skipf("[WARN] expected val to be 1, got %d", v)
 		}
 	})
 }
@@ -353,7 +353,7 @@ func TestContextCancelQueryRow(t *testing.T) {
 		}
 
 		cancel()
-		// make sure the driver recieve cancel request.
+		// make sure the driver receives the cancel request.
 		time.Sleep(100 * time.Millisecond)
 
 		if rows.Next() {
@@ -385,7 +385,7 @@ func TestContextCancelStmtExec(t *testing.T) {
 		}
 
 		// Delay execution for just a bit until db.ExecContext has begun.
-		defer time.AfterFunc(100*time.Millisecond, cancel).Stop()
+		defer time.AfterFunc(250*time.Millisecond, cancel).Stop()
 
 		// This query will be canceled.
 		startTime := time.Now()
@@ -396,7 +396,7 @@ func TestContextCancelStmtExec(t *testing.T) {
 			dbt.Errorf("too long execution time: %s", d)
 		}
 
-		// Wait for the INSERT query has done.
+		// Wait for the INSERT query to be done.
 		time.Sleep(time.Second)
 
 		// Check how many times the query is executed.
@@ -405,7 +405,7 @@ func TestContextCancelStmtExec(t *testing.T) {
 			dbt.Fatalf("%s", err.Error())
 		}
 		if v != 1 { // TODO: need to kill the query, and v should be 0.
-			dbt.Errorf("expected val to be 1, got %d", v)
+			dbt.Skipf("[WARN] expected val to be 1, got %d", v)
 		}
 	})
 }
@@ -420,7 +420,7 @@ func TestContextCancelStmtQuery(t *testing.T) {
 		}
 
 		// Delay execution for just a bit until db.ExecContext has begun.
-		defer time.AfterFunc(100*time.Millisecond, cancel).Stop()
+		defer time.AfterFunc(250*time.Millisecond, cancel).Stop()
 
 		// This query will be canceled.
 		startTime := time.Now()
@@ -440,7 +440,7 @@ func TestContextCancelStmtQuery(t *testing.T) {
 			dbt.Fatalf("%s", err.Error())
 		}
 		if v != 1 { // TODO: need to kill the query, and v should be 0.
-			dbt.Errorf("expected val to be 1, got %d", v)
+			dbt.Skipf("[WARN] expected val to be 1, got %d", v)
 		}
 	})
 }

From d743639f9cefbadabe171d5fe22c9008b06dd2f5 Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Fri, 1 Jun 2018 14:27:13 +0200
Subject: [PATCH 068/282] sha256_password auth support (#808)

* auth: add sha256_password implementation

* auth: fix sha256_password implementation

* auth: add sha256_password tests

* auth: remove test pub key by ascii encoded version

* packets: allow auth responses longer than 255 bytes

* utils: correct naming of registries

* auth: allow using a local server pub key
---
 README.md    |  13 ++
 auth.go      | 181 ++++++++++++++++----
 auth_test.go | 465 +++++++++++++++++++++++++++++++++++++++++++++++----
 dsn.go       |  27 +++
 dsn_test.go  |  48 +++++-
 packets.go   |  15 +-
 utils.go     |  18 +-
 7 files changed, 690 insertions(+), 77 deletions(-)

diff --git a/README.md b/README.md
index b3dcd9101..2e9b07eeb 100644
--- a/README.md
+++ b/README.md
@@ -301,6 +301,19 @@ other cases. You should ensure your application will never cause an ERROR 1290
 except for `read-only` mode when enabling this option.
 
 
+##### `serverPubKey`
+
+```
+Type:           string
+Valid Values:   <name>
+Default:        none
+```
+
+Server public keys can be registered with [`mysql.RegisterServerPubKey`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterServerPubKey), which can then be used by the assigned name in the DSN.
+Public keys are used to transmit encrypted data, e.g. for authentication.
+If the server's public key is known, it should be set manually to avoid expensive and potentially insecure transmissions of the public key from the server to the client each time it is required.
+
+
 ##### `timeout`
 
 ```
diff --git a/auth.go b/auth.go
index 8794d1136..0b59f52ee 100644
--- a/auth.go
+++ b/auth.go
@@ -15,8 +15,72 @@ import (
 	"crypto/sha256"
 	"crypto/x509"
 	"encoding/pem"
+	"sync"
 )
 
+// server pub keys registry
+var (
+	serverPubKeyLock     sync.RWMutex
+	serverPubKeyRegistry map[string]*rsa.PublicKey
+)
+
+// RegisterServerPubKey registers a server RSA public key which can be used to
+// send data in a secure manner to the server without receiving the public key
+// in a potentially insecure way from the server first.
+// Registered keys can afterwards be used adding serverPubKey=<name> to the DSN.
+//
+// Note: The provided rsa.PublicKey instance is exclusively owned by the driver
+// after registering it and may not be modified.
+//
+//  data, err := ioutil.ReadFile("mykey.pem")
+//  if err != nil {
+//  	log.Fatal(err)
+//  }
+//
+//  block, _ := pem.Decode(data)
+//  if block == nil || block.Type != "PUBLIC KEY" {
+//  	log.Fatal("failed to decode PEM block containing public key")
+//  }
+//
+//  pub, err := x509.ParsePKIXPublicKey(block.Bytes)
+//  if err != nil {
+//  	log.Fatal(err)
+//  }
+//
+//  if rsaPubKey, ok := pub.(*rsa.PublicKey); ok {
+//  	mysql.RegisterServerPubKey("mykey", rsaPubKey)
+//  } else {
+//  	log.Fatal("not a RSA public key")
+//  }
+//
+func RegisterServerPubKey(name string, pubKey *rsa.PublicKey) {
+	serverPubKeyLock.Lock()
+	if serverPubKeyRegistry == nil {
+		serverPubKeyRegistry = make(map[string]*rsa.PublicKey)
+	}
+
+	serverPubKeyRegistry[name] = pubKey
+	serverPubKeyLock.Unlock()
+}
+
+// DeregisterServerPubKey removes the public key registered with the given name.
+func DeregisterServerPubKey(name string) {
+	serverPubKeyLock.Lock()
+	if serverPubKeyRegistry != nil {
+		delete(serverPubKeyRegistry, name)
+	}
+	serverPubKeyLock.Unlock()
+}
+
+func getServerPubKey(name string) (pubKey *rsa.PublicKey) {
+	serverPubKeyLock.RLock()
+	if v, ok := serverPubKeyRegistry[name]; ok {
+		pubKey = v
+	}
+	serverPubKeyLock.RUnlock()
+	return
+}
+
 // Hash password using pre 4.1 (old password) method
 // https://github.com/atcurtis/mariadb/blob/master/mysys/my_rnd.c
 type myRnd struct {
@@ -154,6 +218,25 @@ func scrambleSHA256Password(scramble []byte, password string) []byte {
 	return message1
 }
 
+func encryptPassword(password string, seed []byte, pub *rsa.PublicKey) ([]byte, error) {
+	plain := make([]byte, len(password)+1)
+	copy(plain, password)
+	for i := range plain {
+		j := i % len(seed)
+		plain[i] ^= seed[j]
+	}
+	sha1 := sha1.New()
+	return rsa.EncryptOAEP(sha1, rand.Reader, pub, plain, nil)
+}
+
+func (mc *mysqlConn) sendEncryptedPassword(seed []byte, pub *rsa.PublicKey) error {
+	enc, err := encryptPassword(mc.cfg.Passwd, seed, pub)
+	if err != nil {
+		return err
+	}
+	return mc.writeAuthSwitchPacket(enc, false)
+}
+
 func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, bool, error) {
 	switch plugin {
 	case "caching_sha2_password":
@@ -187,6 +270,25 @@ func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, bool, error)
 		authResp := scramblePassword(authData[:20], mc.cfg.Passwd)
 		return authResp, false, nil
 
+	case "sha256_password":
+		if len(mc.cfg.Passwd) == 0 {
+			return nil, true, nil
+		}
+		if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
+			// write cleartext auth packet
+			return []byte(mc.cfg.Passwd), true, nil
+		}
+
+		pubKey := mc.cfg.pubKey
+		if pubKey == nil {
+			// request public key from server
+			return []byte{1}, false, nil
+		}
+
+		// encrypted password
+		enc, err := encryptPassword(mc.cfg.Passwd, authData, pubKey)
+		return enc, false, err
+
 	default:
 		errLog.Print("unknown auth plugin:", plugin)
 		return nil, false, ErrUnknownPlugin
@@ -206,6 +308,9 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
 		// sent and we have to keep using the cipher sent in the init packet.
 		if authData == nil {
 			authData = oldAuthData
+		} else {
+			// copy data from read buffer to owned slice
+			copy(oldAuthData, authData)
 		}
 
 		plugin = newPlugin
@@ -223,6 +328,7 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
 		if err != nil {
 			return err
 		}
+
 		// Do not allow to change the auth plugin more than once
 		if newPlugin != "" {
 			return ErrMalformPkt
@@ -251,48 +357,34 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
 						return err
 					}
 				} else {
-					seed := oldAuthData
-
-					// TODO: allow to specify a local file with the pub key via
-					// the DSN
-
-					// request public key
-					data := mc.buf.takeSmallBuffer(4 + 1)
-					data[4] = cachingSha2PasswordRequestPublicKey
-					mc.writePacket(data)
-
-					// parse public key
-					data, err := mc.readPacket()
-					if err != nil {
-						return err
-					}
-
-					block, _ := pem.Decode(data[1:])
-					pub, err := x509.ParsePKIXPublicKey(block.Bytes)
-					if err != nil {
-						return err
+					pubKey := mc.cfg.pubKey
+					if pubKey == nil {
+						// request public key from server
+						data := mc.buf.takeSmallBuffer(4 + 1)
+						data[4] = cachingSha2PasswordRequestPublicKey
+						mc.writePacket(data)
+
+						// parse public key
+						data, err := mc.readPacket()
+						if err != nil {
+							return err
+						}
+
+						block, _ := pem.Decode(data[1:])
+						pkix, err := x509.ParsePKIXPublicKey(block.Bytes)
+						if err != nil {
+							return err
+						}
+						pubKey = pkix.(*rsa.PublicKey)
 					}
 
 					// send encrypted password
-					plain := make([]byte, len(mc.cfg.Passwd)+1)
-					copy(plain, mc.cfg.Passwd)
-					for i := range plain {
-						j := i % len(seed)
-						plain[i] ^= seed[j]
-					}
-					sha1 := sha1.New()
-					enc, err := rsa.EncryptOAEP(sha1, rand.Reader, pub.(*rsa.PublicKey), plain, nil)
+					err = mc.sendEncryptedPassword(oldAuthData, pubKey)
 					if err != nil {
 						return err
 					}
-
-					if err = mc.writeAuthSwitchPacket(enc, false); err != nil {
-						return err
-					}
-				}
-				if err = mc.readResultOK(); err == nil {
-					return nil // auth successful
 				}
+				return mc.readResultOK()
 
 			default:
 				return ErrMalformPkt
@@ -301,6 +393,25 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
 			return ErrMalformPkt
 		}
 
+	case "sha256_password":
+		switch len(authData) {
+		case 0:
+			return nil // auth successful
+		default:
+			block, _ := pem.Decode(authData)
+			pub, err := x509.ParsePKIXPublicKey(block.Bytes)
+			if err != nil {
+				return err
+			}
+
+			// send encrypted password
+			err = mc.sendEncryptedPassword(oldAuthData, pub.(*rsa.PublicKey))
+			if err != nil {
+				return err
+			}
+			return mc.readResultOK()
+		}
+
 	default:
 		return nil // auth successful
 	}
diff --git a/auth_test.go b/auth_test.go
index 8d852793a..407363be4 100644
--- a/auth_test.go
+++ b/auth_test.go
@@ -10,38 +10,34 @@ package mysql
 
 import (
 	"bytes"
+	"crypto/rsa"
 	"crypto/tls"
+	"crypto/x509"
+	"encoding/pem"
 	"fmt"
 	"testing"
 )
 
-var serverPubKey = []byte{1, 45, 45, 45, 45, 45, 66, 69, 71, 73, 78, 32, 80, 85,
-	66, 76, 73, 67, 32, 75, 69, 89, 45, 45, 45, 45, 45, 10, 77, 73, 73, 66, 73,
-	106, 65, 78, 66, 103, 107, 113, 104, 107, 105, 71, 57, 119, 48, 66, 65, 81,
-	69, 70, 65, 65, 79, 67, 65, 81, 56, 65, 77, 73, 73, 66, 67, 103, 75, 67, 65,
-	81, 69, 65, 51, 72, 115, 120, 83, 53, 80, 47, 72, 97, 88, 80, 118, 109, 51,
-	109, 50, 65, 68, 110, 10, 98, 117, 54, 71, 81, 102, 112, 83, 71, 111, 55,
-	104, 50, 103, 104, 56, 49, 112, 109, 97, 120, 107, 67, 110, 68, 67, 119,
-	102, 54, 109, 109, 101, 72, 55, 76, 75, 104, 115, 110, 89, 110, 78, 52, 81,
-	48, 99, 122, 49, 81, 69, 47, 98, 104, 100, 80, 117, 54, 106, 115, 43, 86,
-	97, 89, 52, 10, 67, 99, 77, 117, 98, 80, 78, 49, 103, 79, 75, 97, 89, 118,
-	78, 99, 103, 69, 87, 112, 116, 73, 67, 105, 50, 88, 84, 116, 116, 66, 55,
-	117, 104, 43, 118, 67, 77, 106, 76, 118, 106, 65, 77, 100, 54, 47, 68, 109,
-	120, 100, 98, 85, 66, 48, 122, 80, 71, 113, 68, 79, 103, 105, 76, 68, 10,
-	75, 82, 79, 79, 53, 113, 100, 55, 115, 104, 98, 55, 49, 82, 47, 88, 74, 69,
-	70, 118, 76, 120, 71, 88, 69, 70, 48, 90, 116, 104, 72, 101, 78, 111, 57,
-	102, 69, 118, 120, 70, 81, 111, 109, 98, 49, 107, 90, 57, 74, 56, 110, 66,
-	119, 116, 101, 53, 83, 70, 53, 89, 108, 113, 86, 50, 10, 66, 66, 53, 113,
-	108, 97, 122, 43, 51, 81, 83, 78, 118, 109, 67, 49, 105, 87, 102, 108, 106,
-	88, 98, 89, 53, 107, 51, 47, 97, 54, 109, 107, 77, 47, 76, 97, 87, 104, 97,
-	117, 78, 53, 80, 82, 51, 115, 67, 120, 53, 85, 117, 49, 77, 102, 100, 115,
-	86, 105, 107, 53, 102, 88, 77, 77, 10, 100, 120, 107, 102, 70, 43, 88, 51,
-	99, 104, 107, 65, 110, 119, 73, 51, 70, 117, 119, 119, 50, 87, 71, 109, 87,
-	79, 71, 98, 75, 116, 109, 73, 101, 85, 109, 51, 98, 73, 82, 109, 100, 70,
-	85, 113, 97, 108, 81, 105, 70, 104, 113, 101, 90, 50, 105, 107, 106, 104,
-	103, 86, 73, 57, 112, 76, 10, 119, 81, 73, 68, 65, 81, 65, 66, 10, 45, 45,
-	45, 45, 45, 69, 78, 68, 32, 80, 85, 66, 76, 73, 67, 32, 75, 69, 89, 45, 45,
-	45, 45, 45, 10}
+var testPubKey = []byte("-----BEGIN PUBLIC KEY-----\n" +
+	"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAol0Z8G8U+25Btxk/g/fm\n" +
+	"UAW/wEKjQCTjkibDE4B+qkuWeiumg6miIRhtilU6m9BFmLQSy1ltYQuu4k17A4tQ\n" +
+	"rIPpOQYZges/qsDFkZh3wyK5jL5WEFVdOasf6wsfszExnPmcZS4axxoYJfiuilrN\n" +
+	"hnwinBAqfi3S0sw5MpSI4Zl1AbOrHG4zDI62Gti2PKiMGyYDZTS9xPrBLbN95Kby\n" +
+	"FFclQLEzA9RJcS1nHFsWtRgHjGPhhjCQxEm9NQ1nePFhCfBfApyfH1VM2VCOQum6\n" +
+	"Ci9bMuHWjTjckC84mzF99kOxOWVU7mwS6gnJqBzpuz8t3zq8/iQ2y7QrmZV+jTJP\n" +
+	"WQIDAQAB\n" +
+	"-----END PUBLIC KEY-----\n")
+
+var testPubKeyRSA *rsa.PublicKey
+
+func init() {
+	block, _ := pem.Decode(testPubKey)
+	pub, err := x509.ParsePKIXPublicKey(block.Bytes)
+	if err != nil {
+		panic(err)
+	}
+	testPubKeyRSA = pub.(*rsa.PublicKey)
+}
 
 func TestScrambleOldPass(t *testing.T) {
 	scramble := []byte{9, 8, 7, 6, 5, 4, 3, 2}
@@ -204,7 +200,7 @@ func TestAuthFastCachingSHA256PasswordFullRSA(t *testing.T) {
 	}
 	conn.queuedReplies = [][]byte{
 		// pub key response
-		append([]byte{byte(len(serverPubKey)), 1, 0, 4}, serverPubKey...),
+		append([]byte{byte(1 + len(testPubKey)), 1, 0, 4, 1}, testPubKey...),
 
 		// OK
 		{7, 0, 0, 6, 0, 0, 0, 2, 0, 0, 0},
@@ -221,6 +217,59 @@ func TestAuthFastCachingSHA256PasswordFullRSA(t *testing.T) {
 	}
 }
 
+func TestAuthFastCachingSHA256PasswordFullRSAWithKey(t *testing.T) {
+	conn, mc := newRWMockConn(1)
+	mc.cfg.User = "root"
+	mc.cfg.Passwd = "secret"
+	mc.cfg.pubKey = testPubKeyRSA
+
+	authData := []byte{6, 81, 96, 114, 14, 42, 50, 30, 76, 47, 1, 95, 126, 81,
+		62, 94, 83, 80, 52, 85}
+	plugin := "caching_sha2_password"
+
+	// Send Client Authentication Packet
+	authResp, addNUL, err := mc.auth(authData, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// check written auth response
+	authRespStart := 4 + 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1
+	authRespEnd := authRespStart + 1 + len(authResp)
+	writtenAuthRespLen := conn.written[authRespStart]
+	writtenAuthResp := conn.written[authRespStart+1 : authRespEnd]
+	expectedAuthResp := []byte{171, 201, 138, 146, 89, 159, 11, 170, 0, 67, 165,
+		49, 175, 94, 218, 68, 177, 109, 110, 86, 34, 33, 44, 190, 67, 240, 70,
+		110, 40, 139, 124, 41}
+	if writtenAuthRespLen != 32 || !bytes.Equal(writtenAuthResp, expectedAuthResp) {
+		t.Fatalf("unexpected written auth response (%d bytes): %v", writtenAuthRespLen, writtenAuthResp)
+	}
+	conn.written = nil
+
+	// auth response
+	conn.data = []byte{
+		2, 0, 0, 2, 1, 4, // Perform Full Authentication
+	}
+	conn.queuedReplies = [][]byte{
+		// OK
+		{7, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0},
+	}
+	conn.maxReads = 2
+
+	// Handle response to auth packet
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+
+	if !bytes.HasPrefix(conn.written, []byte{0, 1, 0, 3}) {
+		t.Errorf("unexpected written data: %v", conn.written)
+	}
+}
+
 func TestAuthFastCachingSHA256PasswordFullSecure(t *testing.T) {
 	conn, mc := newRWMockConn(1)
 	mc.cfg.User = "root"
@@ -481,6 +530,183 @@ func TestAuthFastNativePasswordEmpty(t *testing.T) {
 	}
 }
 
+func TestAuthFastSHA256PasswordEmpty(t *testing.T) {
+	conn, mc := newRWMockConn(1)
+	mc.cfg.User = "root"
+	mc.cfg.Passwd = ""
+
+	authData := []byte{6, 81, 96, 114, 14, 42, 50, 30, 76, 47, 1, 95, 126, 81,
+		62, 94, 83, 80, 52, 85}
+	plugin := "sha256_password"
+
+	// Send Client Authentication Packet
+	authResp, addNUL, err := mc.auth(authData, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// check written auth response
+	authRespStart := 4 + 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1
+	authRespEnd := authRespStart + 1 + len(authResp)
+	writtenAuthRespLen := conn.written[authRespStart]
+	writtenAuthResp := conn.written[authRespStart+1 : authRespEnd]
+	if writtenAuthRespLen != 0 {
+		t.Fatalf("unexpected written auth response (%d bytes): %v", writtenAuthRespLen, writtenAuthResp)
+	}
+	conn.written = nil
+
+	// auth response (pub key response)
+	conn.data = append([]byte{byte(1 + len(testPubKey)), 1, 0, 2, 1}, testPubKey...)
+	conn.queuedReplies = [][]byte{
+		// OK
+		{7, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0},
+	}
+	conn.maxReads = 2
+
+	// Handle response to auth packet
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+
+	if !bytes.HasPrefix(conn.written, []byte{0, 1, 0, 3}) {
+		t.Errorf("unexpected written data: %v", conn.written)
+	}
+}
+
+func TestAuthFastSHA256PasswordRSA(t *testing.T) {
+	conn, mc := newRWMockConn(1)
+	mc.cfg.User = "root"
+	mc.cfg.Passwd = "secret"
+
+	authData := []byte{6, 81, 96, 114, 14, 42, 50, 30, 76, 47, 1, 95, 126, 81,
+		62, 94, 83, 80, 52, 85}
+	plugin := "sha256_password"
+
+	// Send Client Authentication Packet
+	authResp, addNUL, err := mc.auth(authData, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// check written auth response
+	authRespStart := 4 + 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1
+	authRespEnd := authRespStart + 1 + len(authResp)
+	writtenAuthRespLen := conn.written[authRespStart]
+	writtenAuthResp := conn.written[authRespStart+1 : authRespEnd]
+	expectedAuthResp := []byte{1}
+	if writtenAuthRespLen != 1 || !bytes.Equal(writtenAuthResp, expectedAuthResp) {
+		t.Fatalf("unexpected written auth response (%d bytes): %v", writtenAuthRespLen, writtenAuthResp)
+	}
+	conn.written = nil
+
+	// auth response (pub key response)
+	conn.data = append([]byte{byte(1 + len(testPubKey)), 1, 0, 2, 1}, testPubKey...)
+	conn.queuedReplies = [][]byte{
+		// OK
+		{7, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0},
+	}
+	conn.maxReads = 2
+
+	// Handle response to auth packet
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+
+	if !bytes.HasPrefix(conn.written, []byte{0, 1, 0, 3}) {
+		t.Errorf("unexpected written data: %v", conn.written)
+	}
+}
+
+func TestAuthFastSHA256PasswordRSAWithKey(t *testing.T) {
+	conn, mc := newRWMockConn(1)
+	mc.cfg.User = "root"
+	mc.cfg.Passwd = "secret"
+	mc.cfg.pubKey = testPubKeyRSA
+
+	authData := []byte{6, 81, 96, 114, 14, 42, 50, 30, 76, 47, 1, 95, 126, 81,
+		62, 94, 83, 80, 52, 85}
+	plugin := "sha256_password"
+
+	// Send Client Authentication Packet
+	authResp, addNUL, err := mc.auth(authData, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// auth response (OK)
+	conn.data = []byte{7, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0}
+	conn.maxReads = 1
+
+	// Handle response to auth packet
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+}
+
+func TestAuthFastSHA256PasswordSecure(t *testing.T) {
+	conn, mc := newRWMockConn(1)
+	mc.cfg.User = "root"
+	mc.cfg.Passwd = "secret"
+
+	// hack to make the caching_sha2_password plugin believe that the connection
+	// is secure
+	mc.cfg.tls = &tls.Config{InsecureSkipVerify: true}
+
+	authData := []byte{6, 81, 96, 114, 14, 42, 50, 30, 76, 47, 1, 95, 126, 81,
+		62, 94, 83, 80, 52, 85}
+	plugin := "sha256_password"
+
+	// send Client Authentication Packet
+	authResp, addNUL, err := mc.auth(authData, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// unset TLS config to prevent the actual establishment of a TLS wrapper
+	mc.cfg.tls = nil
+
+	err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// check written auth response
+	authRespStart := 4 + 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1
+	authRespEnd := authRespStart + 1 + len(authResp) + 1
+	writtenAuthRespLen := conn.written[authRespStart]
+	writtenAuthResp := conn.written[authRespStart+1 : authRespEnd]
+	expectedAuthResp := []byte{115, 101, 99, 114, 101, 116, 0}
+	if writtenAuthRespLen != 6 || !bytes.Equal(writtenAuthResp, expectedAuthResp) {
+		t.Fatalf("unexpected written auth response (%d bytes): %v", writtenAuthRespLen, writtenAuthResp)
+	}
+	conn.written = nil
+
+	// auth response (OK)
+	conn.data = []byte{7, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0}
+	conn.maxReads = 1
+
+	// Handle response to auth packet
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+
+	if !bytes.Equal(conn.written, []byte{}) {
+		t.Errorf("unexpected written data: %v", conn.written)
+	}
+}
+
 func TestAuthSwitchCachingSHA256PasswordCached(t *testing.T) {
 	conn, mc := newRWMockConn(2)
 	mc.cfg.Passwd = "secret"
@@ -559,7 +785,7 @@ func TestAuthSwitchCachingSHA256PasswordFullRSA(t *testing.T) {
 		{2, 0, 0, 4, 1, 4},
 
 		// Pub Key Response
-		append([]byte{byte(len(serverPubKey)), 1, 0, 6}, serverPubKey...),
+		append([]byte{byte(1 + len(testPubKey)), 1, 0, 6, 1}, testPubKey...),
 
 		// OK
 		{7, 0, 0, 8, 0, 0, 0, 2, 0, 0, 0},
@@ -591,6 +817,48 @@ func TestAuthSwitchCachingSHA256PasswordFullRSA(t *testing.T) {
 	}
 }
 
+func TestAuthSwitchCachingSHA256PasswordFullRSAWithKey(t *testing.T) {
+	conn, mc := newRWMockConn(2)
+	mc.cfg.Passwd = "secret"
+	mc.cfg.pubKey = testPubKeyRSA
+
+	// auth switch request
+	conn.data = []byte{44, 0, 0, 2, 254, 99, 97, 99, 104, 105, 110, 103, 95,
+		115, 104, 97, 50, 95, 112, 97, 115, 115, 119, 111, 114, 100, 0, 101,
+		11, 26, 18, 94, 97, 22, 72, 2, 46, 70, 106, 29, 55, 45, 94, 76, 90, 84,
+		50, 0}
+
+	conn.queuedReplies = [][]byte{
+		// Perform Full Authentication
+		{2, 0, 0, 4, 1, 4},
+
+		// OK
+		{7, 0, 0, 6, 0, 0, 0, 2, 0, 0, 0},
+	}
+	conn.maxReads = 3
+
+	authData := []byte{123, 87, 15, 84, 20, 58, 37, 121, 91, 117, 51, 24, 19,
+		47, 43, 9, 41, 112, 67, 110}
+	plugin := "mysql_native_password"
+
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+
+	expectedReplyPrefix := []byte{
+		// 1. Packet: Hash
+		32, 0, 0, 3, 129, 93, 132, 95, 114, 48, 79, 215, 128, 62, 193, 118, 128,
+		54, 75, 208, 159, 252, 227, 215, 129, 15, 242, 97, 19, 159, 31, 20, 58,
+		153, 9, 130,
+
+		// 2. Packet: Encrypted Password
+		0, 1, 0, 5, // [changing bytes]
+	}
+	if !bytes.HasPrefix(conn.written, expectedReplyPrefix) {
+		t.Errorf("got unexpected data: %v", conn.written)
+	}
+}
+
 func TestAuthSwitchCachingSHA256PasswordFullSecure(t *testing.T) {
 	conn, mc := newRWMockConn(2)
 	mc.cfg.Passwd = "secret"
@@ -851,3 +1119,142 @@ func TestAuthSwitchOldPasswordEmpty(t *testing.T) {
 		t.Errorf("got unexpected data: %v", conn.written)
 	}
 }
+
+func TestAuthSwitchSHA256PasswordEmpty(t *testing.T) {
+	conn, mc := newRWMockConn(2)
+	mc.cfg.Passwd = ""
+
+	// auth switch request
+	conn.data = []byte{38, 0, 0, 2, 254, 115, 104, 97, 50, 53, 54, 95, 112, 97,
+		115, 115, 119, 111, 114, 100, 0, 78, 82, 62, 40, 100, 1, 59, 31, 44, 69,
+		33, 112, 8, 81, 51, 96, 65, 82, 16, 114, 0}
+
+	conn.queuedReplies = [][]byte{
+		// OK
+		{7, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0},
+	}
+	conn.maxReads = 3
+
+	authData := []byte{123, 87, 15, 84, 20, 58, 37, 121, 91, 117, 51, 24, 19,
+		47, 43, 9, 41, 112, 67, 110}
+	plugin := "mysql_native_password"
+
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+
+	expectedReplyPrefix := []byte{
+		// 1. Packet: Empty Password
+		1, 0, 0, 3, 0,
+	}
+	if !bytes.HasPrefix(conn.written, expectedReplyPrefix) {
+		t.Errorf("got unexpected data: %v", conn.written)
+	}
+}
+
+func TestAuthSwitchSHA256PasswordRSA(t *testing.T) {
+	conn, mc := newRWMockConn(2)
+	mc.cfg.Passwd = "secret"
+
+	// auth switch request
+	conn.data = []byte{38, 0, 0, 2, 254, 115, 104, 97, 50, 53, 54, 95, 112, 97,
+		115, 115, 119, 111, 114, 100, 0, 78, 82, 62, 40, 100, 1, 59, 31, 44, 69,
+		33, 112, 8, 81, 51, 96, 65, 82, 16, 114, 0}
+
+	conn.queuedReplies = [][]byte{
+		// Pub Key Response
+		append([]byte{byte(1 + len(testPubKey)), 1, 0, 4, 1}, testPubKey...),
+
+		// OK
+		{7, 0, 0, 6, 0, 0, 0, 2, 0, 0, 0},
+	}
+	conn.maxReads = 3
+
+	authData := []byte{123, 87, 15, 84, 20, 58, 37, 121, 91, 117, 51, 24, 19,
+		47, 43, 9, 41, 112, 67, 110}
+	plugin := "mysql_native_password"
+
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+
+	expectedReplyPrefix := []byte{
+		// 1. Packet: Pub Key Request
+		1, 0, 0, 3, 1,
+
+		// 2. Packet: Encrypted Password
+		0, 1, 0, 5, // [changing bytes]
+	}
+	if !bytes.HasPrefix(conn.written, expectedReplyPrefix) {
+		t.Errorf("got unexpected data: %v", conn.written)
+	}
+}
+
+func TestAuthSwitchSHA256PasswordRSAWithKey(t *testing.T) {
+	conn, mc := newRWMockConn(2)
+	mc.cfg.Passwd = "secret"
+	mc.cfg.pubKey = testPubKeyRSA
+
+	// auth switch request
+	conn.data = []byte{38, 0, 0, 2, 254, 115, 104, 97, 50, 53, 54, 95, 112, 97,
+		115, 115, 119, 111, 114, 100, 0, 78, 82, 62, 40, 100, 1, 59, 31, 44, 69,
+		33, 112, 8, 81, 51, 96, 65, 82, 16, 114, 0}
+
+	conn.queuedReplies = [][]byte{
+		// OK
+		{7, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0},
+	}
+	conn.maxReads = 2
+
+	authData := []byte{123, 87, 15, 84, 20, 58, 37, 121, 91, 117, 51, 24, 19,
+		47, 43, 9, 41, 112, 67, 110}
+	plugin := "mysql_native_password"
+
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+
+	expectedReplyPrefix := []byte{
+		// 1. Packet: Encrypted Password
+		0, 1, 0, 3, // [changing bytes]
+	}
+	if !bytes.HasPrefix(conn.written, expectedReplyPrefix) {
+		t.Errorf("got unexpected data: %v", conn.written)
+	}
+}
+
+func TestAuthSwitchSHA256PasswordSecure(t *testing.T) {
+	conn, mc := newRWMockConn(2)
+	mc.cfg.Passwd = "secret"
+
+	// Hack to make the caching_sha2_password plugin believe that the connection
+	// is secure
+	mc.cfg.tls = &tls.Config{InsecureSkipVerify: true}
+
+	// auth switch request
+	conn.data = []byte{38, 0, 0, 2, 254, 115, 104, 97, 50, 53, 54, 95, 112, 97,
+		115, 115, 119, 111, 114, 100, 0, 78, 82, 62, 40, 100, 1, 59, 31, 44, 69,
+		33, 112, 8, 81, 51, 96, 65, 82, 16, 114, 0}
+
+	conn.queuedReplies = [][]byte{
+		// OK
+		{7, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0},
+	}
+	conn.maxReads = 2
+
+	authData := []byte{123, 87, 15, 84, 20, 58, 37, 121, 91, 117, 51, 24, 19,
+		47, 43, 9, 41, 112, 67, 110}
+	plugin := "mysql_native_password"
+
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+
+	expectedReplyPrefix := []byte{
+		// 1. Packet: Cleartext Password
+		7, 0, 0, 3, 115, 101, 99, 114, 101, 116, 0,
+	}
+	if !bytes.Equal(conn.written, expectedReplyPrefix) {
+		t.Errorf("got unexpected data: %v", conn.written)
+	}
+}
diff --git a/dsn.go b/dsn.go
index 47eab6945..be014babe 100644
--- a/dsn.go
+++ b/dsn.go
@@ -10,6 +10,7 @@ package mysql
 
 import (
 	"bytes"
+	"crypto/rsa"
 	"crypto/tls"
 	"errors"
 	"fmt"
@@ -41,6 +42,8 @@ type Config struct {
 	Collation        string            // Connection collation
 	Loc              *time.Location    // Location for time.Time values
 	MaxAllowedPacket int               // Max packet size allowed
+	ServerPubKey     string            // Server public key name
+	pubKey           *rsa.PublicKey    // Server public key
 	TLSConfig        string            // TLS configuration name
 	tls              *tls.Config       // TLS configuration
 	Timeout          time.Duration     // Dial timeout
@@ -254,6 +257,16 @@ func (cfg *Config) FormatDSN() string {
 		}
 	}
 
+	if len(cfg.ServerPubKey) > 0 {
+		if hasParam {
+			buf.WriteString("&serverPubKey=")
+		} else {
+			hasParam = true
+			buf.WriteString("?serverPubKey=")
+		}
+		buf.WriteString(url.QueryEscape(cfg.ServerPubKey))
+	}
+
 	if cfg.Timeout > 0 {
 		if hasParam {
 			buf.WriteString("&timeout=")
@@ -512,6 +525,20 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 				return errors.New("invalid bool value: " + value)
 			}
 
+		// Server public key
+		case "serverPubKey":
+			name, err := url.QueryUnescape(value)
+			if err != nil {
+				return fmt.Errorf("invalid value for server pub key name: %v", err)
+			}
+
+			if pubKey := getServerPubKey(name); pubKey != nil {
+				cfg.ServerPubKey = name
+				cfg.pubKey = pubKey
+			} else {
+				return errors.New("invalid value / unknown server pub key name: " + name)
+			}
+
 		// Strict mode
 		case "strict":
 			panic("strict mode has been removed. See https://github.com/go-sql-driver/mysql/wiki/strict-mode")
diff --git a/dsn_test.go b/dsn_test.go
index 7507d1201..1cd095496 100644
--- a/dsn_test.go
+++ b/dsn_test.go
@@ -135,11 +135,56 @@ func TestDSNReformat(t *testing.T) {
 	}
 }
 
+func TestDSNServerPubKey(t *testing.T) {
+	baseDSN := "User:password@tcp(localhost:5555)/dbname?serverPubKey="
+
+	RegisterServerPubKey("testKey", testPubKeyRSA)
+	defer DeregisterServerPubKey("testKey")
+
+	tst := baseDSN + "testKey"
+	cfg, err := ParseDSN(tst)
+	if err != nil {
+		t.Error(err.Error())
+	}
+
+	if cfg.ServerPubKey != "testKey" {
+		t.Errorf("unexpected cfg.ServerPubKey value: %v", cfg.ServerPubKey)
+	}
+	if cfg.pubKey != testPubKeyRSA {
+		t.Error("pub key pointer doesn't match")
+	}
+
+	// Key is missing
+	tst = baseDSN + "invalid_name"
+	cfg, err = ParseDSN(tst)
+	if err == nil {
+		t.Errorf("invalid name in DSN (%s) but did not error. Got config: %#v", tst, cfg)
+	}
+}
+
+func TestDSNServerPubKeyQueryEscape(t *testing.T) {
+	const name = "&%!:"
+	dsn := "User:password@tcp(localhost:5555)/dbname?serverPubKey=" + url.QueryEscape(name)
+
+	RegisterServerPubKey(name, testPubKeyRSA)
+	defer DeregisterServerPubKey(name)
+
+	cfg, err := ParseDSN(dsn)
+	if err != nil {
+		t.Error(err.Error())
+	}
+
+	if cfg.pubKey != testPubKeyRSA {
+		t.Error("pub key pointer doesn't match")
+	}
+}
+
 func TestDSNWithCustomTLS(t *testing.T) {
 	baseDSN := "User:password@tcp(localhost:5555)/dbname?tls="
 	tlsCfg := tls.Config{}
 
 	RegisterTLSConfig("utils_test", &tlsCfg)
+	defer DeregisterTLSConfig("utils_test")
 
 	// Custom TLS is missing
 	tst := baseDSN + "invalid_tls"
@@ -173,8 +218,6 @@ func TestDSNWithCustomTLS(t *testing.T) {
 	} else if tlsCfg.ServerName != "" {
 		t.Errorf("tlsCfg was mutated ServerName (%s) should be empty parsing DSN (%s).", name, tst)
 	}
-
-	DeregisterTLSConfig("utils_test")
 }
 
 func TestDSNTLSConfig(t *testing.T) {
@@ -212,6 +255,7 @@ func TestDSNWithCustomTLSQueryEscape(t *testing.T) {
 	tlsCfg := tls.Config{ServerName: name}
 
 	RegisterTLSConfig(configKey, &tlsCfg)
+	defer DeregisterTLSConfig(configKey)
 
 	cfg, err := ParseDSN(dsn)
 
diff --git a/packets.go b/packets.go
index 5d8abfc63..d873a97b2 100644
--- a/packets.go
+++ b/packets.go
@@ -270,7 +270,16 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, addNUL bool,
 		clientFlags |= clientMultiStatements
 	}
 
-	pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1 + 1 + len(authResp) + 21 + 1
+	// encode length of the auth plugin data
+	var authRespLEIBuf [9]byte
+	authRespLEI := appendLengthEncodedInteger(authRespLEIBuf[:0], uint64(len(authResp)))
+	if len(authRespLEI) > 1 {
+		// if the length can not be written in 1 byte, it must be written as a
+		// length encoded integer
+		clientFlags |= clientPluginAuthLenEncClientData
+	}
+
+	pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1 + len(authRespLEI) + len(authResp) + 21 + 1
 	if addNUL {
 		pktLen++
 	}
@@ -342,8 +351,8 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, addNUL bool,
 	pos++
 
 	// Auth Data [length encoded integer]
-	data[pos] = byte(len(authResp))
-	pos += 1 + copy(data[pos+1:], authResp)
+	pos += copy(data[pos:], authRespLEI)
+	pos += copy(data[pos:], authResp)
 	if addNUL {
 		data[pos] = 0x00
 		pos++
diff --git a/utils.go b/utils.go
index 8472b8d2d..84d595b6b 100644
--- a/utils.go
+++ b/utils.go
@@ -20,15 +20,17 @@ import (
 	"time"
 )
 
+// Registry for custom tls.Configs
 var (
 	tlsConfigLock     sync.RWMutex
-	tlsConfigRegister map[string]*tls.Config // Register for custom tls.Configs
+	tlsConfigRegistry map[string]*tls.Config
 )
 
 // RegisterTLSConfig registers a custom tls.Config to be used with sql.Open.
 // Use the key as a value in the DSN where tls=value.
 //
-// Note: The tls.Config provided to needs to be exclusively owned by the driver after registering.
+// Note: The provided tls.Config is exclusively owned by the driver after
+// registering it.
 //
 //  rootCertPool := x509.NewCertPool()
 //  pem, err := ioutil.ReadFile("/path/ca-cert.pem")
@@ -56,11 +58,11 @@ func RegisterTLSConfig(key string, config *tls.Config) error {
 	}
 
 	tlsConfigLock.Lock()
-	if tlsConfigRegister == nil {
-		tlsConfigRegister = make(map[string]*tls.Config)
+	if tlsConfigRegistry == nil {
+		tlsConfigRegistry = make(map[string]*tls.Config)
 	}
 
-	tlsConfigRegister[key] = config
+	tlsConfigRegistry[key] = config
 	tlsConfigLock.Unlock()
 	return nil
 }
@@ -68,15 +70,15 @@ func RegisterTLSConfig(key string, config *tls.Config) error {
 // DeregisterTLSConfig removes the tls.Config associated with key.
 func DeregisterTLSConfig(key string) {
 	tlsConfigLock.Lock()
-	if tlsConfigRegister != nil {
-		delete(tlsConfigRegister, key)
+	if tlsConfigRegistry != nil {
+		delete(tlsConfigRegistry, key)
 	}
 	tlsConfigLock.Unlock()
 }
 
 func getTLSConfigClone(key string) (config *tls.Config) {
 	tlsConfigLock.RLock()
-	if v, ok := tlsConfigRegister[key]; ok {
+	if v, ok := tlsConfigRegistry[key]; ok {
 		config = cloneTLSConfig(v)
 	}
 	tlsConfigLock.RUnlock()

From d523deb1b23d913de5bdada721a6071e71283618 Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Sun, 3 Jun 2018 14:45:54 +0200
Subject: [PATCH 069/282] Release v1.4.0 (#809)

---
 CHANGELOG.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 48 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6bcad7eaa..2d87d74c9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,51 @@
+## Version 1.4 (2018-06-03)
+
+Changes:
+
+ - Documentation fixes (#530, #535, #567)
+ - Refactoring (#575, #579, #580, #581, #603, #615, #704)
+ - Cache column names (#444)
+ - Sort the DSN parameters in DSNs generated from a config (#637)
+ - Allow native password authentication by default (#644)
+ - Use the default port if it is missing in the DSN (#668)
+ - Removed the `strict` mode (#676)
+ - Do not query `max_allowed_packet` by default (#680)
+ - Dropped support Go 1.6 and lower (#696)
+ - Updated `ConvertValue()` to match the database/sql/driver implementation (#760)
+ - Document the usage of `0000-00-00T00:00:00` as the time.Time zero value (#783)
+ - Improved the compatibility of the authentication system (#807)
+
+New Features:
+
+ - Multi-Results support (#537)
+ - `rejectReadOnly` DSN option (#604)
+ - `context.Context` support (#608, #612, #627, #761)
+ - Transaction isolation level support (#619, #744)
+ - Read-Only transactions support (#618, #634)
+ - `NewConfig` function which initializes a config with default values (#679)
+ - Implemented the `ColumnType` interfaces (#667, #724)
+ - Support for custom string types in `ConvertValue` (#623)
+ - Implemented `NamedValueChecker`, improving support for uint64 with high bit set (#690, #709, #710)
+ - `caching_sha2_password` authentication plugin support (#794, #800, #801, #802)
+ - Implemented `driver.SessionResetter` (#779)
+ - `sha256_password` authentication plugin support (#808)
+
+Bugfixes:
+
+ - Use the DSN hostname as TLS default ServerName if `tls=true` (#564, #718)
+ - Fixed LOAD LOCAL DATA INFILE for empty files (#590)
+ - Removed columns definition cache since it sometimes cached invalid data (#592)
+ - Don't mutate registered TLS configs (#600)
+ - Make RegisterTLSConfig concurrency-safe (#613)
+ - Handle missing auth data in the handshake packet correctly (#646)
+ - Do not retry queries when data was written to avoid data corruption (#302, #736)
+ - Cache the connection pointer for error handling before invalidating it (#678)
+ - Fixed imports for appengine/cloudsql (#700)
+ - Fix sending STMT_LONG_DATA for 0 byte data (#734)
+ - Set correct capacity for []bytes read from length-encoded strings (#766)
+ - Make RegisterDial concurrency-safe (#773)
+
+
 ## Version 1.3 (2016-12-01)
 
 Changes:

From 2307b45d3a41dcc64a2bf46db80f61fd182e0186 Mon Sep 17 00:00:00 2001
From: INADA Naoki <methane@users.noreply.github.com>
Date: Wed, 13 Jun 2018 00:06:50 +0900
Subject: [PATCH 070/282] Fix TIME format for binary columns (#818)

---
 packets.go    |   4 +-
 utils.go      | 220 +++++++++++++++++++++++++++-----------------------
 utils_test.go |  39 ++++++++-
 3 files changed, 157 insertions(+), 106 deletions(-)

diff --git a/packets.go b/packets.go
index d873a97b2..f99934e73 100644
--- a/packets.go
+++ b/packets.go
@@ -1261,7 +1261,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
 						rows.rs.columns[i].decimals,
 					)
 				}
-				dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, true)
+				dest[i], err = formatBinaryTime(data[pos:pos+int(num)], dstlen)
 			case rows.mc.parseTime:
 				dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.Loc)
 			default:
@@ -1281,7 +1281,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
 						)
 					}
 				}
-				dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, false)
+				dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen)
 			}
 
 			if err == nil {
diff --git a/utils.go b/utils.go
index 84d595b6b..ca5d47d82 100644
--- a/utils.go
+++ b/utils.go
@@ -14,6 +14,7 @@ import (
 	"encoding/binary"
 	"fmt"
 	"io"
+	"strconv"
 	"strings"
 	"sync"
 	"sync/atomic"
@@ -227,141 +228,156 @@ var zeroDateTime = []byte("0000-00-00 00:00:00.000000")
 const digits01 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
 const digits10 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999"
 
-func formatBinaryDateTime(src []byte, length uint8, justTime bool) (driver.Value, error) {
-	// length expects the deterministic length of the zero value,
-	// negative time and 100+ hours are automatically added if needed
-	if len(src) == 0 {
-		if justTime {
-			return zeroDateTime[11 : 11+length], nil
-		}
-		return zeroDateTime[:length], nil
+func appendMicrosecs(dst, src []byte, decimals int) []byte {
+	if decimals <= 0 {
+		return dst
 	}
-	var dst []byte          // return value
-	var pt, p1, p2, p3 byte // current digit pair
-	var zOffs byte          // offset of value in zeroDateTime
-	if justTime {
-		switch length {
-		case
-			8,                      // time (can be up to 10 when negative and 100+ hours)
-			10, 11, 12, 13, 14, 15: // time with fractional seconds
-		default:
-			return nil, fmt.Errorf("illegal TIME length %d", length)
-		}
-		switch len(src) {
-		case 8, 12:
-		default:
-			return nil, fmt.Errorf("invalid TIME packet length %d", len(src))
-		}
-		// +2 to enable negative time and 100+ hours
-		dst = make([]byte, 0, length+2)
-		if src[0] == 1 {
-			dst = append(dst, '-')
-		}
-		if src[1] != 0 {
-			hour := uint16(src[1])*24 + uint16(src[5])
-			pt = byte(hour / 100)
-			p1 = byte(hour - 100*uint16(pt))
-			dst = append(dst, digits01[pt])
-		} else {
-			p1 = src[5]
-		}
-		zOffs = 11
-		src = src[6:]
-	} else {
-		switch length {
-		case 10, 19, 21, 22, 23, 24, 25, 26:
-		default:
-			t := "DATE"
-			if length > 10 {
-				t += "TIME"
-			}
-			return nil, fmt.Errorf("illegal %s length %d", t, length)
-		}
-		switch len(src) {
-		case 4, 7, 11:
-		default:
-			t := "DATE"
-			if length > 10 {
-				t += "TIME"
-			}
-			return nil, fmt.Errorf("illegal %s packet length %d", t, len(src))
-		}
-		dst = make([]byte, 0, length)
-		// start with the date
-		year := binary.LittleEndian.Uint16(src[:2])
-		pt = byte(year / 100)
-		p1 = byte(year - 100*uint16(pt))
-		p2, p3 = src[2], src[3]
-		dst = append(dst,
-			digits10[pt], digits01[pt],
-			digits10[p1], digits01[p1], '-',
-			digits10[p2], digits01[p2], '-',
-			digits10[p3], digits01[p3],
-		)
-		if length == 10 {
-			return dst, nil
-		}
-		if len(src) == 4 {
-			return append(dst, zeroDateTime[10:length]...), nil
-		}
-		dst = append(dst, ' ')
-		p1 = src[4] // hour
-		src = src[5:]
-	}
-	// p1 is 2-digit hour, src is after hour
-	p2, p3 = src[0], src[1]
-	dst = append(dst,
-		digits10[p1], digits01[p1], ':',
-		digits10[p2], digits01[p2], ':',
-		digits10[p3], digits01[p3],
-	)
-	if length <= byte(len(dst)) {
-		return dst, nil
-	}
-	src = src[2:]
 	if len(src) == 0 {
-		return append(dst, zeroDateTime[19:zOffs+length]...), nil
+		return append(dst, ".000000"[:decimals+1]...)
 	}
+
 	microsecs := binary.LittleEndian.Uint32(src[:4])
-	p1 = byte(microsecs / 10000)
+	p1 := byte(microsecs / 10000)
 	microsecs -= 10000 * uint32(p1)
-	p2 = byte(microsecs / 100)
+	p2 := byte(microsecs / 100)
 	microsecs -= 100 * uint32(p2)
-	p3 = byte(microsecs)
-	switch decimals := zOffs + length - 20; decimals {
+	p3 := byte(microsecs)
+
+	switch decimals {
 	default:
 		return append(dst, '.',
 			digits10[p1], digits01[p1],
 			digits10[p2], digits01[p2],
 			digits10[p3], digits01[p3],
-		), nil
+		)
 	case 1:
 		return append(dst, '.',
 			digits10[p1],
-		), nil
+		)
 	case 2:
 		return append(dst, '.',
 			digits10[p1], digits01[p1],
-		), nil
+		)
 	case 3:
 		return append(dst, '.',
 			digits10[p1], digits01[p1],
 			digits10[p2],
-		), nil
+		)
 	case 4:
 		return append(dst, '.',
 			digits10[p1], digits01[p1],
 			digits10[p2], digits01[p2],
-		), nil
+		)
 	case 5:
 		return append(dst, '.',
 			digits10[p1], digits01[p1],
 			digits10[p2], digits01[p2],
 			digits10[p3],
-		), nil
+		)
 	}
 }
 
+func formatBinaryDateTime(src []byte, length uint8) (driver.Value, error) {
+	// length expects the deterministic length of the zero value,
+	// negative time and 100+ hours are automatically added if needed
+	if len(src) == 0 {
+		return zeroDateTime[:length], nil
+	}
+	var dst []byte      // return value
+	var p1, p2, p3 byte // current digit pair
+
+	switch length {
+	case 10, 19, 21, 22, 23, 24, 25, 26:
+	default:
+		t := "DATE"
+		if length > 10 {
+			t += "TIME"
+		}
+		return nil, fmt.Errorf("illegal %s length %d", t, length)
+	}
+	switch len(src) {
+	case 4, 7, 11:
+	default:
+		t := "DATE"
+		if length > 10 {
+			t += "TIME"
+		}
+		return nil, fmt.Errorf("illegal %s packet length %d", t, len(src))
+	}
+	dst = make([]byte, 0, length)
+	// start with the date
+	year := binary.LittleEndian.Uint16(src[:2])
+	pt := year / 100
+	p1 = byte(year - 100*uint16(pt))
+	p2, p3 = src[2], src[3]
+	dst = append(dst,
+		digits10[pt], digits01[pt],
+		digits10[p1], digits01[p1], '-',
+		digits10[p2], digits01[p2], '-',
+		digits10[p3], digits01[p3],
+	)
+	if length == 10 {
+		return dst, nil
+	}
+	if len(src) == 4 {
+		return append(dst, zeroDateTime[10:length]...), nil
+	}
+	dst = append(dst, ' ')
+	p1 = src[4] // hour
+	src = src[5:]
+
+	// p1 is 2-digit hour, src is after hour
+	p2, p3 = src[0], src[1]
+	dst = append(dst,
+		digits10[p1], digits01[p1], ':',
+		digits10[p2], digits01[p2], ':',
+		digits10[p3], digits01[p3],
+	)
+	return appendMicrosecs(dst, src[2:], int(length)-20), nil
+}
+
+func formatBinaryTime(src []byte, length uint8) (driver.Value, error) {
+	// length expects the deterministic length of the zero value,
+	// negative time and 100+ hours are automatically added if needed
+	if len(src) == 0 {
+		return zeroDateTime[11 : 11+length], nil
+	}
+	var dst []byte // return value
+
+	switch length {
+	case
+		8,                      // time (can be up to 10 when negative and 100+ hours)
+		10, 11, 12, 13, 14, 15: // time with fractional seconds
+	default:
+		return nil, fmt.Errorf("illegal TIME length %d", length)
+	}
+	switch len(src) {
+	case 8, 12:
+	default:
+		return nil, fmt.Errorf("invalid TIME packet length %d", len(src))
+	}
+	// +2 to enable negative time and 100+ hours
+	dst = make([]byte, 0, length+2)
+	if src[0] == 1 {
+		dst = append(dst, '-')
+	}
+	days := binary.LittleEndian.Uint32(src[1:5])
+	hours := int64(days)*24 + int64(src[5])
+
+	if hours >= 100 {
+		dst = strconv.AppendInt(dst, hours, 10)
+	} else {
+		dst = append(dst, digits10[hours], digits01[hours])
+	}
+
+	min, sec := src[6], src[7]
+	dst = append(dst, ':',
+		digits10[min], digits01[min], ':',
+		digits10[sec], digits01[sec],
+	)
+	return appendMicrosecs(dst, src[8:], int(length)-9), nil
+}
+
 /******************************************************************************
 *                       Convert from and to bytes                             *
 ******************************************************************************/
diff --git a/utils_test.go b/utils_test.go
index a3b9cb4ad..4a72a108e 100644
--- a/utils_test.go
+++ b/utils_test.go
@@ -102,7 +102,7 @@ func TestFormatBinaryDateTime(t *testing.T) {
 	rawDate[6] = 23                                    // seconds
 	binary.LittleEndian.PutUint32(rawDate[7:], 987654) // microseconds
 	expect := func(expected string, inlen, outlen uint8) {
-		actual, _ := formatBinaryDateTime(rawDate[:inlen], outlen, false)
+		actual, _ := formatBinaryDateTime(rawDate[:inlen], outlen)
 		bytes, ok := actual.([]byte)
 		if !ok {
 			t.Errorf("formatBinaryDateTime must return []byte, was %T", actual)
@@ -110,7 +110,7 @@ func TestFormatBinaryDateTime(t *testing.T) {
 		if string(bytes) != expected {
 			t.Errorf(
 				"expected %q, got %q for length in %d, out %d",
-				bytes, actual, inlen, outlen,
+				expected, actual, inlen, outlen,
 			)
 		}
 	}
@@ -121,6 +121,41 @@ func TestFormatBinaryDateTime(t *testing.T) {
 	expect("1978-12-30 15:46:23.987654", 11, 26)
 }
 
+func TestFormatBinaryTime(t *testing.T) {
+	expect := func(expected string, src []byte, outlen uint8) {
+		actual, _ := formatBinaryTime(src, outlen)
+		bytes, ok := actual.([]byte)
+		if !ok {
+			t.Errorf("formatBinaryDateTime must return []byte, was %T", actual)
+		}
+		if string(bytes) != expected {
+			t.Errorf(
+				"expected %q, got %q for src=%q and outlen=%d",
+				expected, actual, src, outlen)
+		}
+	}
+
+	// binary format:
+	// sign (0: positive, 1: negative), days(4), hours, minutes, seconds, micro(4)
+
+	// Zeros
+	expect("00:00:00", []byte{}, 8)
+	expect("00:00:00.0", []byte{}, 10)
+	expect("00:00:00.000000", []byte{}, 15)
+
+	// Without micro(4)
+	expect("12:34:56", []byte{0, 0, 0, 0, 0, 12, 34, 56}, 8)
+	expect("-12:34:56", []byte{1, 0, 0, 0, 0, 12, 34, 56}, 8)
+	expect("12:34:56.00", []byte{0, 0, 0, 0, 0, 12, 34, 56}, 11)
+	expect("24:34:56", []byte{0, 1, 0, 0, 0, 0, 34, 56}, 8)
+	expect("-99:34:56", []byte{1, 4, 0, 0, 0, 3, 34, 56}, 8)
+	expect("103079215103:34:56", []byte{0, 255, 255, 255, 255, 23, 34, 56}, 8)
+
+	// With micro(4)
+	expect("12:34:56.00", []byte{0, 0, 0, 0, 0, 12, 34, 56, 99, 0, 0, 0}, 11)
+	expect("12:34:56.000099", []byte{0, 0, 0, 0, 0, 12, 34, 56, 99, 0, 0, 0}, 15)
+}
+
 func TestEscapeBackslash(t *testing.T) {
 	expect := func(expected, value string) {
 		actual := string(escapeBytesBackslash([]byte{}, []byte(value)))

From 749ddf1598b47e3cd909414bda735fe790ef3d30 Mon Sep 17 00:00:00 2001
From: Ichinose Shogo <shogo82148@gmail.com>
Date: Mon, 18 Jun 2018 20:59:01 +0900
Subject: [PATCH 071/282] Drop Go 1.7 Support (#823)

* Remove Go 1.7 from travis.yml

* drop Go 1.7 support

* remove the watcher interface

* update gofmt

* remove cloneTLSConfig, and use (*tls.Config).Clone directly instead.
---
 .travis.yml             |   1 -
 benchmark_go18_test.go  |  93 -----
 benchmark_test.go       |  77 ++++
 connection.go           | 193 ++++++++++
 connection_go18.go      | 208 -----------
 connection_go18_test.go |  30 --
 connection_test.go      |  14 +
 driver.go               |   9 +-
 driver_go18_test.go     | 806 ----------------------------------------
 driver_test.go          | 787 +++++++++++++++++++++++++++++++++++++++
 utils.go                |  31 +-
 utils_go17.go           |  40 --
 utils_go18.go           |  50 ---
 utils_go18_test.go      |  57 ---
 utils_test.go           |  42 +++
 15 files changed, 1144 insertions(+), 1294 deletions(-)
 delete mode 100644 benchmark_go18_test.go
 delete mode 100644 connection_go18.go
 delete mode 100644 connection_go18_test.go
 delete mode 100644 driver_go18_test.go
 delete mode 100644 utils_go17.go
 delete mode 100644 utils_go18.go
 delete mode 100644 utils_go18_test.go

diff --git a/.travis.yml b/.travis.yml
index cc1268c36..47dd289a0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,6 @@
 sudo: false
 language: go
 go:
-  - 1.7.x
   - 1.8.x
   - 1.9.x
   - 1.10.x
diff --git a/benchmark_go18_test.go b/benchmark_go18_test.go
deleted file mode 100644
index d6a7e9d6e..000000000
--- a/benchmark_go18_test.go
+++ /dev/null
@@ -1,93 +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/.
-
-// +build go1.8
-
-package mysql
-
-import (
-	"context"
-	"database/sql"
-	"fmt"
-	"runtime"
-	"testing"
-)
-
-func benchmarkQueryContext(b *testing.B, db *sql.DB, p int) {
-	ctx, cancel := context.WithCancel(context.Background())
-	defer cancel()
-	db.SetMaxIdleConns(p * runtime.GOMAXPROCS(0))
-
-	tb := (*TB)(b)
-	stmt := tb.checkStmt(db.PrepareContext(ctx, "SELECT val FROM foo WHERE id=?"))
-	defer stmt.Close()
-
-	b.SetParallelism(p)
-	b.ReportAllocs()
-	b.ResetTimer()
-	b.RunParallel(func(pb *testing.PB) {
-		var got string
-		for pb.Next() {
-			tb.check(stmt.QueryRow(1).Scan(&got))
-			if got != "one" {
-				b.Fatalf("query = %q; want one", got)
-			}
-		}
-	})
-}
-
-func BenchmarkQueryContext(b *testing.B) {
-	db := initDB(b,
-		"DROP TABLE IF EXISTS foo",
-		"CREATE TABLE foo (id INT PRIMARY KEY, val CHAR(50))",
-		`INSERT INTO foo VALUES (1, "one")`,
-		`INSERT INTO foo VALUES (2, "two")`,
-	)
-	defer db.Close()
-	for _, p := range []int{1, 2, 3, 4} {
-		b.Run(fmt.Sprintf("%d", p), func(b *testing.B) {
-			benchmarkQueryContext(b, db, p)
-		})
-	}
-}
-
-func benchmarkExecContext(b *testing.B, db *sql.DB, p int) {
-	ctx, cancel := context.WithCancel(context.Background())
-	defer cancel()
-	db.SetMaxIdleConns(p * runtime.GOMAXPROCS(0))
-
-	tb := (*TB)(b)
-	stmt := tb.checkStmt(db.PrepareContext(ctx, "DO 1"))
-	defer stmt.Close()
-
-	b.SetParallelism(p)
-	b.ReportAllocs()
-	b.ResetTimer()
-	b.RunParallel(func(pb *testing.PB) {
-		for pb.Next() {
-			if _, err := stmt.ExecContext(ctx); err != nil {
-				b.Fatal(err)
-			}
-		}
-	})
-}
-
-func BenchmarkExecContext(b *testing.B) {
-	db := initDB(b,
-		"DROP TABLE IF EXISTS foo",
-		"CREATE TABLE foo (id INT PRIMARY KEY, val CHAR(50))",
-		`INSERT INTO foo VALUES (1, "one")`,
-		`INSERT INTO foo VALUES (2, "two")`,
-	)
-	defer db.Close()
-	for _, p := range []int{1, 2, 3, 4} {
-		b.Run(fmt.Sprintf("%d", p), func(b *testing.B) {
-			benchmarkQueryContext(b, db, p)
-		})
-	}
-}
diff --git a/benchmark_test.go b/benchmark_test.go
index c1de8672b..5828d40f9 100644
--- a/benchmark_test.go
+++ b/benchmark_test.go
@@ -10,9 +10,12 @@ package mysql
 
 import (
 	"bytes"
+	"context"
 	"database/sql"
 	"database/sql/driver"
+	"fmt"
 	"math"
+	"runtime"
 	"strings"
 	"sync"
 	"sync/atomic"
@@ -240,3 +243,77 @@ func BenchmarkInterpolation(b *testing.B) {
 		}
 	}
 }
+
+func benchmarkQueryContext(b *testing.B, db *sql.DB, p int) {
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+	db.SetMaxIdleConns(p * runtime.GOMAXPROCS(0))
+
+	tb := (*TB)(b)
+	stmt := tb.checkStmt(db.PrepareContext(ctx, "SELECT val FROM foo WHERE id=?"))
+	defer stmt.Close()
+
+	b.SetParallelism(p)
+	b.ReportAllocs()
+	b.ResetTimer()
+	b.RunParallel(func(pb *testing.PB) {
+		var got string
+		for pb.Next() {
+			tb.check(stmt.QueryRow(1).Scan(&got))
+			if got != "one" {
+				b.Fatalf("query = %q; want one", got)
+			}
+		}
+	})
+}
+
+func BenchmarkQueryContext(b *testing.B) {
+	db := initDB(b,
+		"DROP TABLE IF EXISTS foo",
+		"CREATE TABLE foo (id INT PRIMARY KEY, val CHAR(50))",
+		`INSERT INTO foo VALUES (1, "one")`,
+		`INSERT INTO foo VALUES (2, "two")`,
+	)
+	defer db.Close()
+	for _, p := range []int{1, 2, 3, 4} {
+		b.Run(fmt.Sprintf("%d", p), func(b *testing.B) {
+			benchmarkQueryContext(b, db, p)
+		})
+	}
+}
+
+func benchmarkExecContext(b *testing.B, db *sql.DB, p int) {
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+	db.SetMaxIdleConns(p * runtime.GOMAXPROCS(0))
+
+	tb := (*TB)(b)
+	stmt := tb.checkStmt(db.PrepareContext(ctx, "DO 1"))
+	defer stmt.Close()
+
+	b.SetParallelism(p)
+	b.ReportAllocs()
+	b.ResetTimer()
+	b.RunParallel(func(pb *testing.PB) {
+		for pb.Next() {
+			if _, err := stmt.ExecContext(ctx); err != nil {
+				b.Fatal(err)
+			}
+		}
+	})
+}
+
+func BenchmarkExecContext(b *testing.B) {
+	db := initDB(b,
+		"DROP TABLE IF EXISTS foo",
+		"CREATE TABLE foo (id INT PRIMARY KEY, val CHAR(50))",
+		`INSERT INTO foo VALUES (1, "one")`,
+		`INSERT INTO foo VALUES (2, "two")`,
+	)
+	defer db.Close()
+	for _, p := range []int{1, 2, 3, 4} {
+		b.Run(fmt.Sprintf("%d", p), func(b *testing.B) {
+			benchmarkQueryContext(b, db, p)
+		})
+	}
+}
diff --git a/connection.go b/connection.go
index e57061412..911be2060 100644
--- a/connection.go
+++ b/connection.go
@@ -9,6 +9,8 @@
 package mysql
 
 import (
+	"context"
+	"database/sql"
 	"database/sql/driver"
 	"io"
 	"net"
@@ -459,3 +461,194 @@ func (mc *mysqlConn) finish() {
 	case <-mc.closech:
 	}
 }
+
+// Ping implements driver.Pinger interface
+func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
+	if mc.closed.IsSet() {
+		errLog.Print(ErrInvalidConn)
+		return driver.ErrBadConn
+	}
+
+	if err = mc.watchCancel(ctx); err != nil {
+		return
+	}
+	defer mc.finish()
+
+	if err = mc.writeCommandPacket(comPing); err != nil {
+		return
+	}
+
+	return mc.readResultOK()
+}
+
+// BeginTx implements driver.ConnBeginTx interface
+func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
+	if err := mc.watchCancel(ctx); err != nil {
+		return nil, err
+	}
+	defer mc.finish()
+
+	if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault {
+		level, err := mapIsolationLevel(opts.Isolation)
+		if err != nil {
+			return nil, err
+		}
+		err = mc.exec("SET TRANSACTION ISOLATION LEVEL " + level)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return mc.begin(opts.ReadOnly)
+}
+
+func (mc *mysqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
+	dargs, err := namedValueToValue(args)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := mc.watchCancel(ctx); err != nil {
+		return nil, err
+	}
+
+	rows, err := mc.query(query, dargs)
+	if err != nil {
+		mc.finish()
+		return nil, err
+	}
+	rows.finish = mc.finish
+	return rows, err
+}
+
+func (mc *mysqlConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
+	dargs, err := namedValueToValue(args)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := mc.watchCancel(ctx); err != nil {
+		return nil, err
+	}
+	defer mc.finish()
+
+	return mc.Exec(query, dargs)
+}
+
+func (mc *mysqlConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
+	if err := mc.watchCancel(ctx); err != nil {
+		return nil, err
+	}
+
+	stmt, err := mc.Prepare(query)
+	mc.finish()
+	if err != nil {
+		return nil, err
+	}
+
+	select {
+	default:
+	case <-ctx.Done():
+		stmt.Close()
+		return nil, ctx.Err()
+	}
+	return stmt, nil
+}
+
+func (stmt *mysqlStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
+	dargs, err := namedValueToValue(args)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := stmt.mc.watchCancel(ctx); err != nil {
+		return nil, err
+	}
+
+	rows, err := stmt.query(dargs)
+	if err != nil {
+		stmt.mc.finish()
+		return nil, err
+	}
+	rows.finish = stmt.mc.finish
+	return rows, err
+}
+
+func (stmt *mysqlStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
+	dargs, err := namedValueToValue(args)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := stmt.mc.watchCancel(ctx); err != nil {
+		return nil, err
+	}
+	defer stmt.mc.finish()
+
+	return stmt.Exec(dargs)
+}
+
+func (mc *mysqlConn) watchCancel(ctx context.Context) error {
+	if mc.watching {
+		// Reach here if canceled,
+		// so the connection is already invalid
+		mc.cleanup()
+		return nil
+	}
+	if ctx.Done() == nil {
+		return nil
+	}
+
+	mc.watching = true
+	select {
+	default:
+	case <-ctx.Done():
+		return ctx.Err()
+	}
+	if mc.watcher == nil {
+		return nil
+	}
+
+	mc.watcher <- ctx
+
+	return nil
+}
+
+func (mc *mysqlConn) startWatcher() {
+	watcher := make(chan mysqlContext, 1)
+	mc.watcher = watcher
+	finished := make(chan struct{})
+	mc.finished = finished
+	go func() {
+		for {
+			var ctx mysqlContext
+			select {
+			case ctx = <-watcher:
+			case <-mc.closech:
+				return
+			}
+
+			select {
+			case <-ctx.Done():
+				mc.cancel(ctx.Err())
+			case <-finished:
+			case <-mc.closech:
+				return
+			}
+		}
+	}()
+}
+
+func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) {
+	nv.Value, err = converter{}.ConvertValue(nv.Value)
+	return
+}
+
+// ResetSession implements driver.SessionResetter.
+// (From Go 1.10)
+func (mc *mysqlConn) ResetSession(ctx context.Context) error {
+	if mc.closed.IsSet() {
+		return driver.ErrBadConn
+	}
+	return nil
+}
diff --git a/connection_go18.go b/connection_go18.go
deleted file mode 100644
index 62796bfce..000000000
--- a/connection_go18.go
+++ /dev/null
@@ -1,208 +0,0 @@
-// 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/.
-
-// +build go1.8
-
-package mysql
-
-import (
-	"context"
-	"database/sql"
-	"database/sql/driver"
-)
-
-// Ping implements driver.Pinger interface
-func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
-	if mc.closed.IsSet() {
-		errLog.Print(ErrInvalidConn)
-		return driver.ErrBadConn
-	}
-
-	if err = mc.watchCancel(ctx); err != nil {
-		return
-	}
-	defer mc.finish()
-
-	if err = mc.writeCommandPacket(comPing); err != nil {
-		return
-	}
-
-	return mc.readResultOK()
-}
-
-// BeginTx implements driver.ConnBeginTx interface
-func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
-	if err := mc.watchCancel(ctx); err != nil {
-		return nil, err
-	}
-	defer mc.finish()
-
-	if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault {
-		level, err := mapIsolationLevel(opts.Isolation)
-		if err != nil {
-			return nil, err
-		}
-		err = mc.exec("SET TRANSACTION ISOLATION LEVEL " + level)
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	return mc.begin(opts.ReadOnly)
-}
-
-func (mc *mysqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
-	dargs, err := namedValueToValue(args)
-	if err != nil {
-		return nil, err
-	}
-
-	if err := mc.watchCancel(ctx); err != nil {
-		return nil, err
-	}
-
-	rows, err := mc.query(query, dargs)
-	if err != nil {
-		mc.finish()
-		return nil, err
-	}
-	rows.finish = mc.finish
-	return rows, err
-}
-
-func (mc *mysqlConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
-	dargs, err := namedValueToValue(args)
-	if err != nil {
-		return nil, err
-	}
-
-	if err := mc.watchCancel(ctx); err != nil {
-		return nil, err
-	}
-	defer mc.finish()
-
-	return mc.Exec(query, dargs)
-}
-
-func (mc *mysqlConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
-	if err := mc.watchCancel(ctx); err != nil {
-		return nil, err
-	}
-
-	stmt, err := mc.Prepare(query)
-	mc.finish()
-	if err != nil {
-		return nil, err
-	}
-
-	select {
-	default:
-	case <-ctx.Done():
-		stmt.Close()
-		return nil, ctx.Err()
-	}
-	return stmt, nil
-}
-
-func (stmt *mysqlStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
-	dargs, err := namedValueToValue(args)
-	if err != nil {
-		return nil, err
-	}
-
-	if err := stmt.mc.watchCancel(ctx); err != nil {
-		return nil, err
-	}
-
-	rows, err := stmt.query(dargs)
-	if err != nil {
-		stmt.mc.finish()
-		return nil, err
-	}
-	rows.finish = stmt.mc.finish
-	return rows, err
-}
-
-func (stmt *mysqlStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
-	dargs, err := namedValueToValue(args)
-	if err != nil {
-		return nil, err
-	}
-
-	if err := stmt.mc.watchCancel(ctx); err != nil {
-		return nil, err
-	}
-	defer stmt.mc.finish()
-
-	return stmt.Exec(dargs)
-}
-
-func (mc *mysqlConn) watchCancel(ctx context.Context) error {
-	if mc.watching {
-		// Reach here if canceled,
-		// so the connection is already invalid
-		mc.cleanup()
-		return nil
-	}
-	if ctx.Done() == nil {
-		return nil
-	}
-
-	mc.watching = true
-	select {
-	default:
-	case <-ctx.Done():
-		return ctx.Err()
-	}
-	if mc.watcher == nil {
-		return nil
-	}
-
-	mc.watcher <- ctx
-
-	return nil
-}
-
-func (mc *mysqlConn) startWatcher() {
-	watcher := make(chan mysqlContext, 1)
-	mc.watcher = watcher
-	finished := make(chan struct{})
-	mc.finished = finished
-	go func() {
-		for {
-			var ctx mysqlContext
-			select {
-			case ctx = <-watcher:
-			case <-mc.closech:
-				return
-			}
-
-			select {
-			case <-ctx.Done():
-				mc.cancel(ctx.Err())
-			case <-finished:
-			case <-mc.closech:
-				return
-			}
-		}
-	}()
-}
-
-func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) {
-	nv.Value, err = converter{}.ConvertValue(nv.Value)
-	return
-}
-
-// ResetSession implements driver.SessionResetter.
-// (From Go 1.10)
-func (mc *mysqlConn) ResetSession(ctx context.Context) error {
-	if mc.closed.IsSet() {
-		return driver.ErrBadConn
-	}
-	return nil
-}
diff --git a/connection_go18_test.go b/connection_go18_test.go
deleted file mode 100644
index 2719ab3b7..000000000
--- a/connection_go18_test.go
+++ /dev/null
@@ -1,30 +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/.
-
-// +build go1.8
-
-package mysql
-
-import (
-	"database/sql/driver"
-	"testing"
-)
-
-func TestCheckNamedValue(t *testing.T) {
-	value := driver.NamedValue{Value: ^uint64(0)}
-	x := &mysqlConn{}
-	err := x.CheckNamedValue(&value)
-
-	if err != nil {
-		t.Fatal("uint64 high-bit not convertible", err)
-	}
-
-	if value.Value != "18446744073709551615" {
-		t.Fatalf("uint64 high-bit not converted, got %#v %T", value.Value, value.Value)
-	}
-}
diff --git a/connection_test.go b/connection_test.go
index 65325f101..dec376117 100644
--- a/connection_test.go
+++ b/connection_test.go
@@ -65,3 +65,17 @@ func TestInterpolateParamsPlaceholderInString(t *testing.T) {
 		t.Errorf("Expected err=driver.ErrSkip, got err=%#v, q=%#v", err, q)
 	}
 }
+
+func TestCheckNamedValue(t *testing.T) {
+	value := driver.NamedValue{Value: ^uint64(0)}
+	x := &mysqlConn{}
+	err := x.CheckNamedValue(&value)
+
+	if err != nil {
+		t.Fatal("uint64 high-bit not convertible", err)
+	}
+
+	if value.Value != "18446744073709551615" {
+		t.Fatalf("uint64 high-bit not converted, got %#v %T", value.Value, value.Value)
+	}
+}
diff --git a/driver.go b/driver.go
index 1a75a16ec..8c35de73c 100644
--- a/driver.go
+++ b/driver.go
@@ -23,11 +23,6 @@ import (
 	"sync"
 )
 
-// watcher interface is used for context support (From Go 1.8)
-type watcher interface {
-	startWatcher()
-}
-
 // MySQLDriver is exported to make the driver directly accessible.
 // In general the driver is used via the database/sql package.
 type MySQLDriver struct{}
@@ -96,9 +91,7 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
 	}
 
 	// Call startWatcher for context support (From Go 1.8)
-	if s, ok := interface{}(mc).(watcher); ok {
-		s.startWatcher()
-	}
+	mc.startWatcher()
 
 	mc.buf = newBuffer(mc.netConn)
 
diff --git a/driver_go18_test.go b/driver_go18_test.go
deleted file mode 100644
index 005e76204..000000000
--- a/driver_go18_test.go
+++ /dev/null
@@ -1,806 +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/.
-
-// +build go1.8
-
-package mysql
-
-import (
-	"context"
-	"database/sql"
-	"database/sql/driver"
-	"fmt"
-	"math"
-	"reflect"
-	"testing"
-	"time"
-)
-
-// static interface implementation checks of mysqlConn
-var (
-	_ driver.ConnBeginTx        = &mysqlConn{}
-	_ driver.ConnPrepareContext = &mysqlConn{}
-	_ driver.ExecerContext      = &mysqlConn{}
-	_ driver.Pinger             = &mysqlConn{}
-	_ driver.QueryerContext     = &mysqlConn{}
-)
-
-// static interface implementation checks of mysqlStmt
-var (
-	_ driver.StmtExecContext  = &mysqlStmt{}
-	_ driver.StmtQueryContext = &mysqlStmt{}
-)
-
-// Ensure that all the driver interfaces are implemented
-var (
-	// _ driver.RowsColumnTypeLength        = &binaryRows{}
-	// _ driver.RowsColumnTypeLength        = &textRows{}
-	_ driver.RowsColumnTypeDatabaseTypeName = &binaryRows{}
-	_ driver.RowsColumnTypeDatabaseTypeName = &textRows{}
-	_ driver.RowsColumnTypeNullable         = &binaryRows{}
-	_ driver.RowsColumnTypeNullable         = &textRows{}
-	_ driver.RowsColumnTypePrecisionScale   = &binaryRows{}
-	_ driver.RowsColumnTypePrecisionScale   = &textRows{}
-	_ driver.RowsColumnTypeScanType         = &binaryRows{}
-	_ driver.RowsColumnTypeScanType         = &textRows{}
-	_ driver.RowsNextResultSet              = &binaryRows{}
-	_ driver.RowsNextResultSet              = &textRows{}
-)
-
-func TestMultiResultSet(t *testing.T) {
-	type result struct {
-		values  [][]int
-		columns []string
-	}
-
-	// checkRows is a helper test function to validate rows containing 3 result
-	// sets with specific values and columns. The basic query would look like this:
-	//
-	// SELECT 1 AS col1, 2 AS col2 UNION SELECT 3, 4;
-	// SELECT 0 UNION SELECT 1;
-	// SELECT 1 AS col1, 2 AS col2, 3 AS col3 UNION SELECT 4, 5, 6;
-	//
-	// to distinguish test cases the first string argument is put in front of
-	// every error or fatal message.
-	checkRows := func(desc string, rows *sql.Rows, dbt *DBTest) {
-		expected := []result{
-			{
-				values:  [][]int{{1, 2}, {3, 4}},
-				columns: []string{"col1", "col2"},
-			},
-			{
-				values:  [][]int{{1, 2, 3}, {4, 5, 6}},
-				columns: []string{"col1", "col2", "col3"},
-			},
-		}
-
-		var res1 result
-		for rows.Next() {
-			var res [2]int
-			if err := rows.Scan(&res[0], &res[1]); err != nil {
-				dbt.Fatal(err)
-			}
-			res1.values = append(res1.values, res[:])
-		}
-
-		cols, err := rows.Columns()
-		if err != nil {
-			dbt.Fatal(desc, err)
-		}
-		res1.columns = cols
-
-		if !reflect.DeepEqual(expected[0], res1) {
-			dbt.Error(desc, "want =", expected[0], "got =", res1)
-		}
-
-		if !rows.NextResultSet() {
-			dbt.Fatal(desc, "expected next result set")
-		}
-
-		// ignoring one result set
-
-		if !rows.NextResultSet() {
-			dbt.Fatal(desc, "expected next result set")
-		}
-
-		var res2 result
-		cols, err = rows.Columns()
-		if err != nil {
-			dbt.Fatal(desc, err)
-		}
-		res2.columns = cols
-
-		for rows.Next() {
-			var res [3]int
-			if err := rows.Scan(&res[0], &res[1], &res[2]); err != nil {
-				dbt.Fatal(desc, err)
-			}
-			res2.values = append(res2.values, res[:])
-		}
-
-		if !reflect.DeepEqual(expected[1], res2) {
-			dbt.Error(desc, "want =", expected[1], "got =", res2)
-		}
-
-		if rows.NextResultSet() {
-			dbt.Error(desc, "unexpected next result set")
-		}
-
-		if err := rows.Err(); err != nil {
-			dbt.Error(desc, err)
-		}
-	}
-
-	runTestsWithMultiStatement(t, dsn, func(dbt *DBTest) {
-		rows := dbt.mustQuery(`DO 1;
-		SELECT 1 AS col1, 2 AS col2 UNION SELECT 3, 4;
-		DO 1;
-		SELECT 0 UNION SELECT 1;
-		SELECT 1 AS col1, 2 AS col2, 3 AS col3 UNION SELECT 4, 5, 6;`)
-		defer rows.Close()
-		checkRows("query: ", rows, dbt)
-	})
-
-	runTestsWithMultiStatement(t, dsn, func(dbt *DBTest) {
-		queries := []string{
-			`
-			DROP PROCEDURE IF EXISTS test_mrss;
-			CREATE PROCEDURE test_mrss()
-			BEGIN
-				DO 1;
-				SELECT 1 AS col1, 2 AS col2 UNION SELECT 3, 4;
-				DO 1;
-				SELECT 0 UNION SELECT 1;
-				SELECT 1 AS col1, 2 AS col2, 3 AS col3 UNION SELECT 4, 5, 6;
-			END
-		`,
-			`
-			DROP PROCEDURE IF EXISTS test_mrss;
-			CREATE PROCEDURE test_mrss()
-			BEGIN
-				SELECT 1 AS col1, 2 AS col2 UNION SELECT 3, 4;
-				SELECT 0 UNION SELECT 1;
-				SELECT 1 AS col1, 2 AS col2, 3 AS col3 UNION SELECT 4, 5, 6;
-			END
-		`,
-		}
-
-		defer dbt.mustExec("DROP PROCEDURE IF EXISTS test_mrss")
-
-		for i, query := range queries {
-			dbt.mustExec(query)
-
-			stmt, err := dbt.db.Prepare("CALL test_mrss()")
-			if err != nil {
-				dbt.Fatalf("%v (i=%d)", err, i)
-			}
-			defer stmt.Close()
-
-			for j := 0; j < 2; j++ {
-				rows, err := stmt.Query()
-				if err != nil {
-					dbt.Fatalf("%v (i=%d) (j=%d)", err, i, j)
-				}
-				checkRows(fmt.Sprintf("prepared stmt query (i=%d) (j=%d): ", i, j), rows, dbt)
-			}
-		}
-	})
-}
-
-func TestMultiResultSetNoSelect(t *testing.T) {
-	runTestsWithMultiStatement(t, dsn, func(dbt *DBTest) {
-		rows := dbt.mustQuery("DO 1; DO 2;")
-		defer rows.Close()
-
-		if rows.Next() {
-			dbt.Error("unexpected row")
-		}
-
-		if rows.NextResultSet() {
-			dbt.Error("unexpected next result set")
-		}
-
-		if err := rows.Err(); err != nil {
-			dbt.Error("expected nil; got ", err)
-		}
-	})
-}
-
-// tests if rows are set in a proper state if some results were ignored before
-// calling rows.NextResultSet.
-func TestSkipResults(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		rows := dbt.mustQuery("SELECT 1, 2")
-		defer rows.Close()
-
-		if !rows.Next() {
-			dbt.Error("expected row")
-		}
-
-		if rows.NextResultSet() {
-			dbt.Error("unexpected next result set")
-		}
-
-		if err := rows.Err(); err != nil {
-			dbt.Error("expected nil; got ", err)
-		}
-	})
-}
-
-func TestPingContext(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		ctx, cancel := context.WithCancel(context.Background())
-		cancel()
-		if err := dbt.db.PingContext(ctx); err != context.Canceled {
-			dbt.Errorf("expected context.Canceled, got %v", err)
-		}
-	})
-}
-
-func TestContextCancelExec(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		dbt.mustExec("CREATE TABLE test (v INTEGER)")
-		ctx, cancel := context.WithCancel(context.Background())
-
-		// Delay execution for just a bit until db.ExecContext has begun.
-		defer time.AfterFunc(250*time.Millisecond, cancel).Stop()
-
-		// This query will be canceled.
-		startTime := time.Now()
-		if _, err := dbt.db.ExecContext(ctx, "INSERT INTO test VALUES (SLEEP(1))"); err != context.Canceled {
-			dbt.Errorf("expected context.Canceled, got %v", err)
-		}
-		if d := time.Since(startTime); d > 500*time.Millisecond {
-			dbt.Errorf("too long execution time: %s", d)
-		}
-
-		// Wait for the INSERT query to be done.
-		time.Sleep(time.Second)
-
-		// Check how many times the query is executed.
-		var v int
-		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM test").Scan(&v); err != nil {
-			dbt.Fatalf("%s", err.Error())
-		}
-		if v != 1 { // TODO: need to kill the query, and v should be 0.
-			dbt.Skipf("[WARN] expected val to be 1, got %d", v)
-		}
-
-		// Context is already canceled, so error should come before execution.
-		if _, err := dbt.db.ExecContext(ctx, "INSERT INTO test VALUES (1)"); err == nil {
-			dbt.Error("expected error")
-		} else if err.Error() != "context canceled" {
-			dbt.Fatalf("unexpected error: %s", err)
-		}
-
-		// The second insert query will fail, so the table has no changes.
-		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM test").Scan(&v); err != nil {
-			dbt.Fatalf("%s", err.Error())
-		}
-		if v != 1 {
-			dbt.Skipf("[WARN] expected val to be 1, got %d", v)
-		}
-	})
-}
-
-func TestContextCancelQuery(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		dbt.mustExec("CREATE TABLE test (v INTEGER)")
-		ctx, cancel := context.WithCancel(context.Background())
-
-		// Delay execution for just a bit until db.ExecContext has begun.
-		defer time.AfterFunc(250*time.Millisecond, cancel).Stop()
-
-		// This query will be canceled.
-		startTime := time.Now()
-		if _, err := dbt.db.QueryContext(ctx, "INSERT INTO test VALUES (SLEEP(1))"); err != context.Canceled {
-			dbt.Errorf("expected context.Canceled, got %v", err)
-		}
-		if d := time.Since(startTime); d > 500*time.Millisecond {
-			dbt.Errorf("too long execution time: %s", d)
-		}
-
-		// Wait for the INSERT query to be done.
-		time.Sleep(time.Second)
-
-		// Check how many times the query is executed.
-		var v int
-		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM test").Scan(&v); err != nil {
-			dbt.Fatalf("%s", err.Error())
-		}
-		if v != 1 { // TODO: need to kill the query, and v should be 0.
-			dbt.Skipf("[WARN] expected val to be 1, got %d", v)
-		}
-
-		// Context is already canceled, so error should come before execution.
-		if _, err := dbt.db.QueryContext(ctx, "INSERT INTO test VALUES (1)"); err != context.Canceled {
-			dbt.Errorf("expected context.Canceled, got %v", err)
-		}
-
-		// The second insert query will fail, so the table has no changes.
-		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM test").Scan(&v); err != nil {
-			dbt.Fatalf("%s", err.Error())
-		}
-		if v != 1 {
-			dbt.Skipf("[WARN] expected val to be 1, got %d", v)
-		}
-	})
-}
-
-func TestContextCancelQueryRow(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		dbt.mustExec("CREATE TABLE test (v INTEGER)")
-		dbt.mustExec("INSERT INTO test VALUES (1), (2), (3)")
-		ctx, cancel := context.WithCancel(context.Background())
-
-		rows, err := dbt.db.QueryContext(ctx, "SELECT v FROM test")
-		if err != nil {
-			dbt.Fatalf("%s", err.Error())
-		}
-
-		// the first row will be succeed.
-		var v int
-		if !rows.Next() {
-			dbt.Fatalf("unexpected end")
-		}
-		if err := rows.Scan(&v); err != nil {
-			dbt.Fatalf("%s", err.Error())
-		}
-
-		cancel()
-		// make sure the driver receives the cancel request.
-		time.Sleep(100 * time.Millisecond)
-
-		if rows.Next() {
-			dbt.Errorf("expected end, but not")
-		}
-		if err := rows.Err(); err != context.Canceled {
-			dbt.Errorf("expected context.Canceled, got %v", err)
-		}
-	})
-}
-
-func TestContextCancelPrepare(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		ctx, cancel := context.WithCancel(context.Background())
-		cancel()
-		if _, err := dbt.db.PrepareContext(ctx, "SELECT 1"); err != context.Canceled {
-			dbt.Errorf("expected context.Canceled, got %v", err)
-		}
-	})
-}
-
-func TestContextCancelStmtExec(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		dbt.mustExec("CREATE TABLE test (v INTEGER)")
-		ctx, cancel := context.WithCancel(context.Background())
-		stmt, err := dbt.db.PrepareContext(ctx, "INSERT INTO test VALUES (SLEEP(1))")
-		if err != nil {
-			dbt.Fatalf("unexpected error: %v", err)
-		}
-
-		// Delay execution for just a bit until db.ExecContext has begun.
-		defer time.AfterFunc(250*time.Millisecond, cancel).Stop()
-
-		// This query will be canceled.
-		startTime := time.Now()
-		if _, err := stmt.ExecContext(ctx); err != context.Canceled {
-			dbt.Errorf("expected context.Canceled, got %v", err)
-		}
-		if d := time.Since(startTime); d > 500*time.Millisecond {
-			dbt.Errorf("too long execution time: %s", d)
-		}
-
-		// Wait for the INSERT query to be done.
-		time.Sleep(time.Second)
-
-		// Check how many times the query is executed.
-		var v int
-		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM test").Scan(&v); err != nil {
-			dbt.Fatalf("%s", err.Error())
-		}
-		if v != 1 { // TODO: need to kill the query, and v should be 0.
-			dbt.Skipf("[WARN] expected val to be 1, got %d", v)
-		}
-	})
-}
-
-func TestContextCancelStmtQuery(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		dbt.mustExec("CREATE TABLE test (v INTEGER)")
-		ctx, cancel := context.WithCancel(context.Background())
-		stmt, err := dbt.db.PrepareContext(ctx, "INSERT INTO test VALUES (SLEEP(1))")
-		if err != nil {
-			dbt.Fatalf("unexpected error: %v", err)
-		}
-
-		// Delay execution for just a bit until db.ExecContext has begun.
-		defer time.AfterFunc(250*time.Millisecond, cancel).Stop()
-
-		// This query will be canceled.
-		startTime := time.Now()
-		if _, err := stmt.QueryContext(ctx); err != context.Canceled {
-			dbt.Errorf("expected context.Canceled, got %v", err)
-		}
-		if d := time.Since(startTime); d > 500*time.Millisecond {
-			dbt.Errorf("too long execution time: %s", d)
-		}
-
-		// Wait for the INSERT query has done.
-		time.Sleep(time.Second)
-
-		// Check how many times the query is executed.
-		var v int
-		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM test").Scan(&v); err != nil {
-			dbt.Fatalf("%s", err.Error())
-		}
-		if v != 1 { // TODO: need to kill the query, and v should be 0.
-			dbt.Skipf("[WARN] expected val to be 1, got %d", v)
-		}
-	})
-}
-
-func TestContextCancelBegin(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		dbt.mustExec("CREATE TABLE test (v INTEGER)")
-		ctx, cancel := context.WithCancel(context.Background())
-		tx, err := dbt.db.BeginTx(ctx, nil)
-		if err != nil {
-			dbt.Fatal(err)
-		}
-
-		// Delay execution for just a bit until db.ExecContext has begun.
-		defer time.AfterFunc(100*time.Millisecond, cancel).Stop()
-
-		// This query will be canceled.
-		startTime := time.Now()
-		if _, err := tx.ExecContext(ctx, "INSERT INTO test VALUES (SLEEP(1))"); err != context.Canceled {
-			dbt.Errorf("expected context.Canceled, got %v", err)
-		}
-		if d := time.Since(startTime); d > 500*time.Millisecond {
-			dbt.Errorf("too long execution time: %s", d)
-		}
-
-		// Transaction is canceled, so expect an error.
-		switch err := tx.Commit(); err {
-		case sql.ErrTxDone:
-			// because the transaction has already been rollbacked.
-			// the database/sql package watches ctx
-			// and rollbacks when ctx is canceled.
-		case context.Canceled:
-			// the database/sql package rollbacks on another goroutine,
-			// so the transaction may not be rollbacked depending on goroutine scheduling.
-		default:
-			dbt.Errorf("expected sql.ErrTxDone or context.Canceled, got %v", err)
-		}
-
-		// Context is canceled, so cannot begin a transaction.
-		if _, err := dbt.db.BeginTx(ctx, nil); err != context.Canceled {
-			dbt.Errorf("expected context.Canceled, got %v", err)
-		}
-	})
-}
-
-func TestContextBeginIsolationLevel(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		dbt.mustExec("CREATE TABLE test (v INTEGER)")
-		ctx, cancel := context.WithCancel(context.Background())
-		defer cancel()
-
-		tx1, err := dbt.db.BeginTx(ctx, &sql.TxOptions{
-			Isolation: sql.LevelRepeatableRead,
-		})
-		if err != nil {
-			dbt.Fatal(err)
-		}
-
-		tx2, err := dbt.db.BeginTx(ctx, &sql.TxOptions{
-			Isolation: sql.LevelReadCommitted,
-		})
-		if err != nil {
-			dbt.Fatal(err)
-		}
-
-		_, err = tx1.ExecContext(ctx, "INSERT INTO test VALUES (1)")
-		if err != nil {
-			dbt.Fatal(err)
-		}
-
-		var v int
-		row := tx2.QueryRowContext(ctx, "SELECT COUNT(*) FROM test")
-		if err := row.Scan(&v); err != nil {
-			dbt.Fatal(err)
-		}
-		// Because writer transaction wasn't commited yet, it should be available
-		if v != 0 {
-			dbt.Errorf("expected val to be 0, got %d", v)
-		}
-
-		err = tx1.Commit()
-		if err != nil {
-			dbt.Fatal(err)
-		}
-
-		row = tx2.QueryRowContext(ctx, "SELECT COUNT(*) FROM test")
-		if err := row.Scan(&v); err != nil {
-			dbt.Fatal(err)
-		}
-		// Data written by writer transaction is already commited, it should be selectable
-		if v != 1 {
-			dbt.Errorf("expected val to be 1, got %d", v)
-		}
-		tx2.Commit()
-	})
-}
-
-func TestContextBeginReadOnly(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		dbt.mustExec("CREATE TABLE test (v INTEGER)")
-		ctx, cancel := context.WithCancel(context.Background())
-		defer cancel()
-
-		tx, err := dbt.db.BeginTx(ctx, &sql.TxOptions{
-			ReadOnly: true,
-		})
-		if _, ok := err.(*MySQLError); ok {
-			dbt.Skip("It seems that your MySQL does not support READ ONLY transactions")
-			return
-		} else if err != nil {
-			dbt.Fatal(err)
-		}
-
-		// INSERT queries fail in a READ ONLY transaction.
-		_, err = tx.ExecContext(ctx, "INSERT INTO test VALUES (1)")
-		if _, ok := err.(*MySQLError); !ok {
-			dbt.Errorf("expected MySQLError, got %v", err)
-		}
-
-		// SELECT queries can be executed.
-		var v int
-		row := tx.QueryRowContext(ctx, "SELECT COUNT(*) FROM test")
-		if err := row.Scan(&v); err != nil {
-			dbt.Fatal(err)
-		}
-		if v != 0 {
-			dbt.Errorf("expected val to be 0, got %d", v)
-		}
-
-		if err := tx.Commit(); err != nil {
-			dbt.Fatal(err)
-		}
-	})
-}
-
-func TestRowsColumnTypes(t *testing.T) {
-	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}
-	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, 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}
-	nd1 := NullTime{Time: time.Date(2006, 01, 02, 0, 0, 0, 0, time.UTC), Valid: true}
-	nd2 := NullTime{Time: time.Date(2006, 03, 04, 0, 0, 0, 0, time.UTC), Valid: true}
-	ndNULL := NullTime{Time: time.Time{}, Valid: false}
-	rbNULL := sql.RawBytes(nil)
-	rb0 := sql.RawBytes("0")
-	rb42 := sql.RawBytes("42")
-	rbTest := sql.RawBytes("Test")
-	rb0pad4 := sql.RawBytes("0\x00\x00\x00") // BINARY right-pads values with 0x00
-	rbx0 := sql.RawBytes("\x00")
-	rbx42 := sql.RawBytes("\x42")
-
-	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
-		precision        int64 // 0 if not ok
-		scale            int64
-		valuesIn         [3]string
-		valuesOut        [3]interface{}
-	}{
-		{"bit8null", "BIT(8)", "BIT", scanTypeRawBytes, true, 0, 0, [3]string{"0x0", "NULL", "0x42"}, [3]interface{}{rbx0, rbNULL, rbx42}},
-		{"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)}},
-		{"mediumintnull", "MEDIUMINT", "MEDIUMINT", scanTypeNullInt, true, 0, 0, [3]string{"0", "42", "NULL"}, [3]interface{}{ni0, ni42, niNULL}},
-		{"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}},
-		{"binary4null", "BINARY(4)", "BINARY", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0pad4, rbNULL, rbTest}},
-		{"varbinary42", "VARBINARY(42) NOT NULL", "VARBINARY", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
-		{"tinyblobnull", "TINYBLOB", "BLOB", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
-		{"tinytextnull", "TINYTEXT", "TEXT", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
-		{"blobnull", "BLOB", "BLOB", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
-		{"textnull", "TEXT", "TEXT", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
-		{"mediumblob", "MEDIUMBLOB NOT NULL", "BLOB", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
-		{"mediumtext", "MEDIUMTEXT NOT NULL", "TEXT", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
-		{"longblob", "LONGBLOB NOT NULL", "BLOB", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
-		{"longtext", "LONGTEXT NOT NULL", "TEXT", 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}},
-		{"date", "DATE", "DATE", scanTypeNullTime, true, 0, 0, [3]string{"'2006-01-02'", "NULL", "'2006-03-04'"}, [3]interface{}{nd1, ndNULL, nd2}},
-		{"year", "YEAR NOT NULL", "YEAR", scanTypeUint16, false, 0, 0, [3]string{"2006", "2000", "1994"}, [3]interface{}{uint16(2006), uint16(2000), uint16(1994)}},
-	}
-
-	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]
-
-	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 + ")")
-
-			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 and Scale
-				precision, scale, 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
-				}
-				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))
-			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]) {
-						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++
-			}
-			if i != 3 {
-				t.Errorf("expected 3 rows, got %d", i)
-			}
-
-			if err := rows.Close(); err != nil {
-				t.Errorf("error closing rows: %s", err)
-			}
-		})
-	}
-}
-
-func TestValuerWithValueReceiverGivenNilValue(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		dbt.mustExec("CREATE TABLE test (value VARCHAR(255))")
-		dbt.db.Exec("INSERT INTO test VALUES (?)", (*testValuer)(nil))
-		// This test will panic on the INSERT if ConvertValue() does not check for typed nil before calling Value()
-	})
-}
diff --git a/driver_test.go b/driver_test.go
index 3cbd98c85..f2bf344e5 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -10,6 +10,7 @@ package mysql
 
 import (
 	"bytes"
+	"context"
 	"crypto/tls"
 	"database/sql"
 	"database/sql/driver"
@@ -17,9 +18,11 @@ import (
 	"io"
 	"io/ioutil"
 	"log"
+	"math"
 	"net"
 	"net/url"
 	"os"
+	"reflect"
 	"strings"
 	"sync"
 	"sync/atomic"
@@ -2073,3 +2076,787 @@ func TestEmptyPassword(t *testing.T) {
 		}
 	}
 }
+
+// static interface implementation checks of mysqlConn
+var (
+	_ driver.ConnBeginTx        = &mysqlConn{}
+	_ driver.ConnPrepareContext = &mysqlConn{}
+	_ driver.ExecerContext      = &mysqlConn{}
+	_ driver.Pinger             = &mysqlConn{}
+	_ driver.QueryerContext     = &mysqlConn{}
+)
+
+// static interface implementation checks of mysqlStmt
+var (
+	_ driver.StmtExecContext  = &mysqlStmt{}
+	_ driver.StmtQueryContext = &mysqlStmt{}
+)
+
+// Ensure that all the driver interfaces are implemented
+var (
+	// _ driver.RowsColumnTypeLength        = &binaryRows{}
+	// _ driver.RowsColumnTypeLength        = &textRows{}
+	_ driver.RowsColumnTypeDatabaseTypeName = &binaryRows{}
+	_ driver.RowsColumnTypeDatabaseTypeName = &textRows{}
+	_ driver.RowsColumnTypeNullable         = &binaryRows{}
+	_ driver.RowsColumnTypeNullable         = &textRows{}
+	_ driver.RowsColumnTypePrecisionScale   = &binaryRows{}
+	_ driver.RowsColumnTypePrecisionScale   = &textRows{}
+	_ driver.RowsColumnTypeScanType         = &binaryRows{}
+	_ driver.RowsColumnTypeScanType         = &textRows{}
+	_ driver.RowsNextResultSet              = &binaryRows{}
+	_ driver.RowsNextResultSet              = &textRows{}
+)
+
+func TestMultiResultSet(t *testing.T) {
+	type result struct {
+		values  [][]int
+		columns []string
+	}
+
+	// checkRows is a helper test function to validate rows containing 3 result
+	// sets with specific values and columns. The basic query would look like this:
+	//
+	// SELECT 1 AS col1, 2 AS col2 UNION SELECT 3, 4;
+	// SELECT 0 UNION SELECT 1;
+	// SELECT 1 AS col1, 2 AS col2, 3 AS col3 UNION SELECT 4, 5, 6;
+	//
+	// to distinguish test cases the first string argument is put in front of
+	// every error or fatal message.
+	checkRows := func(desc string, rows *sql.Rows, dbt *DBTest) {
+		expected := []result{
+			{
+				values:  [][]int{{1, 2}, {3, 4}},
+				columns: []string{"col1", "col2"},
+			},
+			{
+				values:  [][]int{{1, 2, 3}, {4, 5, 6}},
+				columns: []string{"col1", "col2", "col3"},
+			},
+		}
+
+		var res1 result
+		for rows.Next() {
+			var res [2]int
+			if err := rows.Scan(&res[0], &res[1]); err != nil {
+				dbt.Fatal(err)
+			}
+			res1.values = append(res1.values, res[:])
+		}
+
+		cols, err := rows.Columns()
+		if err != nil {
+			dbt.Fatal(desc, err)
+		}
+		res1.columns = cols
+
+		if !reflect.DeepEqual(expected[0], res1) {
+			dbt.Error(desc, "want =", expected[0], "got =", res1)
+		}
+
+		if !rows.NextResultSet() {
+			dbt.Fatal(desc, "expected next result set")
+		}
+
+		// ignoring one result set
+
+		if !rows.NextResultSet() {
+			dbt.Fatal(desc, "expected next result set")
+		}
+
+		var res2 result
+		cols, err = rows.Columns()
+		if err != nil {
+			dbt.Fatal(desc, err)
+		}
+		res2.columns = cols
+
+		for rows.Next() {
+			var res [3]int
+			if err := rows.Scan(&res[0], &res[1], &res[2]); err != nil {
+				dbt.Fatal(desc, err)
+			}
+			res2.values = append(res2.values, res[:])
+		}
+
+		if !reflect.DeepEqual(expected[1], res2) {
+			dbt.Error(desc, "want =", expected[1], "got =", res2)
+		}
+
+		if rows.NextResultSet() {
+			dbt.Error(desc, "unexpected next result set")
+		}
+
+		if err := rows.Err(); err != nil {
+			dbt.Error(desc, err)
+		}
+	}
+
+	runTestsWithMultiStatement(t, dsn, func(dbt *DBTest) {
+		rows := dbt.mustQuery(`DO 1;
+		SELECT 1 AS col1, 2 AS col2 UNION SELECT 3, 4;
+		DO 1;
+		SELECT 0 UNION SELECT 1;
+		SELECT 1 AS col1, 2 AS col2, 3 AS col3 UNION SELECT 4, 5, 6;`)
+		defer rows.Close()
+		checkRows("query: ", rows, dbt)
+	})
+
+	runTestsWithMultiStatement(t, dsn, func(dbt *DBTest) {
+		queries := []string{
+			`
+			DROP PROCEDURE IF EXISTS test_mrss;
+			CREATE PROCEDURE test_mrss()
+			BEGIN
+				DO 1;
+				SELECT 1 AS col1, 2 AS col2 UNION SELECT 3, 4;
+				DO 1;
+				SELECT 0 UNION SELECT 1;
+				SELECT 1 AS col1, 2 AS col2, 3 AS col3 UNION SELECT 4, 5, 6;
+			END
+		`,
+			`
+			DROP PROCEDURE IF EXISTS test_mrss;
+			CREATE PROCEDURE test_mrss()
+			BEGIN
+				SELECT 1 AS col1, 2 AS col2 UNION SELECT 3, 4;
+				SELECT 0 UNION SELECT 1;
+				SELECT 1 AS col1, 2 AS col2, 3 AS col3 UNION SELECT 4, 5, 6;
+			END
+		`,
+		}
+
+		defer dbt.mustExec("DROP PROCEDURE IF EXISTS test_mrss")
+
+		for i, query := range queries {
+			dbt.mustExec(query)
+
+			stmt, err := dbt.db.Prepare("CALL test_mrss()")
+			if err != nil {
+				dbt.Fatalf("%v (i=%d)", err, i)
+			}
+			defer stmt.Close()
+
+			for j := 0; j < 2; j++ {
+				rows, err := stmt.Query()
+				if err != nil {
+					dbt.Fatalf("%v (i=%d) (j=%d)", err, i, j)
+				}
+				checkRows(fmt.Sprintf("prepared stmt query (i=%d) (j=%d): ", i, j), rows, dbt)
+			}
+		}
+	})
+}
+
+func TestMultiResultSetNoSelect(t *testing.T) {
+	runTestsWithMultiStatement(t, dsn, func(dbt *DBTest) {
+		rows := dbt.mustQuery("DO 1; DO 2;")
+		defer rows.Close()
+
+		if rows.Next() {
+			dbt.Error("unexpected row")
+		}
+
+		if rows.NextResultSet() {
+			dbt.Error("unexpected next result set")
+		}
+
+		if err := rows.Err(); err != nil {
+			dbt.Error("expected nil; got ", err)
+		}
+	})
+}
+
+// tests if rows are set in a proper state if some results were ignored before
+// calling rows.NextResultSet.
+func TestSkipResults(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		rows := dbt.mustQuery("SELECT 1, 2")
+		defer rows.Close()
+
+		if !rows.Next() {
+			dbt.Error("expected row")
+		}
+
+		if rows.NextResultSet() {
+			dbt.Error("unexpected next result set")
+		}
+
+		if err := rows.Err(); err != nil {
+			dbt.Error("expected nil; got ", err)
+		}
+	})
+}
+
+func TestPingContext(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		ctx, cancel := context.WithCancel(context.Background())
+		cancel()
+		if err := dbt.db.PingContext(ctx); err != context.Canceled {
+			dbt.Errorf("expected context.Canceled, got %v", err)
+		}
+	})
+}
+
+func TestContextCancelExec(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec("CREATE TABLE test (v INTEGER)")
+		ctx, cancel := context.WithCancel(context.Background())
+
+		// Delay execution for just a bit until db.ExecContext has begun.
+		defer time.AfterFunc(250*time.Millisecond, cancel).Stop()
+
+		// This query will be canceled.
+		startTime := time.Now()
+		if _, err := dbt.db.ExecContext(ctx, "INSERT INTO test VALUES (SLEEP(1))"); err != context.Canceled {
+			dbt.Errorf("expected context.Canceled, got %v", err)
+		}
+		if d := time.Since(startTime); d > 500*time.Millisecond {
+			dbt.Errorf("too long execution time: %s", d)
+		}
+
+		// Wait for the INSERT query to be done.
+		time.Sleep(time.Second)
+
+		// Check how many times the query is executed.
+		var v int
+		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM test").Scan(&v); err != nil {
+			dbt.Fatalf("%s", err.Error())
+		}
+		if v != 1 { // TODO: need to kill the query, and v should be 0.
+			dbt.Skipf("[WARN] expected val to be 1, got %d", v)
+		}
+
+		// Context is already canceled, so error should come before execution.
+		if _, err := dbt.db.ExecContext(ctx, "INSERT INTO test VALUES (1)"); err == nil {
+			dbt.Error("expected error")
+		} else if err.Error() != "context canceled" {
+			dbt.Fatalf("unexpected error: %s", err)
+		}
+
+		// The second insert query will fail, so the table has no changes.
+		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM test").Scan(&v); err != nil {
+			dbt.Fatalf("%s", err.Error())
+		}
+		if v != 1 {
+			dbt.Skipf("[WARN] expected val to be 1, got %d", v)
+		}
+	})
+}
+
+func TestContextCancelQuery(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec("CREATE TABLE test (v INTEGER)")
+		ctx, cancel := context.WithCancel(context.Background())
+
+		// Delay execution for just a bit until db.ExecContext has begun.
+		defer time.AfterFunc(250*time.Millisecond, cancel).Stop()
+
+		// This query will be canceled.
+		startTime := time.Now()
+		if _, err := dbt.db.QueryContext(ctx, "INSERT INTO test VALUES (SLEEP(1))"); err != context.Canceled {
+			dbt.Errorf("expected context.Canceled, got %v", err)
+		}
+		if d := time.Since(startTime); d > 500*time.Millisecond {
+			dbt.Errorf("too long execution time: %s", d)
+		}
+
+		// Wait for the INSERT query to be done.
+		time.Sleep(time.Second)
+
+		// Check how many times the query is executed.
+		var v int
+		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM test").Scan(&v); err != nil {
+			dbt.Fatalf("%s", err.Error())
+		}
+		if v != 1 { // TODO: need to kill the query, and v should be 0.
+			dbt.Skipf("[WARN] expected val to be 1, got %d", v)
+		}
+
+		// Context is already canceled, so error should come before execution.
+		if _, err := dbt.db.QueryContext(ctx, "INSERT INTO test VALUES (1)"); err != context.Canceled {
+			dbt.Errorf("expected context.Canceled, got %v", err)
+		}
+
+		// The second insert query will fail, so the table has no changes.
+		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM test").Scan(&v); err != nil {
+			dbt.Fatalf("%s", err.Error())
+		}
+		if v != 1 {
+			dbt.Skipf("[WARN] expected val to be 1, got %d", v)
+		}
+	})
+}
+
+func TestContextCancelQueryRow(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec("CREATE TABLE test (v INTEGER)")
+		dbt.mustExec("INSERT INTO test VALUES (1), (2), (3)")
+		ctx, cancel := context.WithCancel(context.Background())
+
+		rows, err := dbt.db.QueryContext(ctx, "SELECT v FROM test")
+		if err != nil {
+			dbt.Fatalf("%s", err.Error())
+		}
+
+		// the first row will be succeed.
+		var v int
+		if !rows.Next() {
+			dbt.Fatalf("unexpected end")
+		}
+		if err := rows.Scan(&v); err != nil {
+			dbt.Fatalf("%s", err.Error())
+		}
+
+		cancel()
+		// make sure the driver receives the cancel request.
+		time.Sleep(100 * time.Millisecond)
+
+		if rows.Next() {
+			dbt.Errorf("expected end, but not")
+		}
+		if err := rows.Err(); err != context.Canceled {
+			dbt.Errorf("expected context.Canceled, got %v", err)
+		}
+	})
+}
+
+func TestContextCancelPrepare(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		ctx, cancel := context.WithCancel(context.Background())
+		cancel()
+		if _, err := dbt.db.PrepareContext(ctx, "SELECT 1"); err != context.Canceled {
+			dbt.Errorf("expected context.Canceled, got %v", err)
+		}
+	})
+}
+
+func TestContextCancelStmtExec(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec("CREATE TABLE test (v INTEGER)")
+		ctx, cancel := context.WithCancel(context.Background())
+		stmt, err := dbt.db.PrepareContext(ctx, "INSERT INTO test VALUES (SLEEP(1))")
+		if err != nil {
+			dbt.Fatalf("unexpected error: %v", err)
+		}
+
+		// Delay execution for just a bit until db.ExecContext has begun.
+		defer time.AfterFunc(250*time.Millisecond, cancel).Stop()
+
+		// This query will be canceled.
+		startTime := time.Now()
+		if _, err := stmt.ExecContext(ctx); err != context.Canceled {
+			dbt.Errorf("expected context.Canceled, got %v", err)
+		}
+		if d := time.Since(startTime); d > 500*time.Millisecond {
+			dbt.Errorf("too long execution time: %s", d)
+		}
+
+		// Wait for the INSERT query to be done.
+		time.Sleep(time.Second)
+
+		// Check how many times the query is executed.
+		var v int
+		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM test").Scan(&v); err != nil {
+			dbt.Fatalf("%s", err.Error())
+		}
+		if v != 1 { // TODO: need to kill the query, and v should be 0.
+			dbt.Skipf("[WARN] expected val to be 1, got %d", v)
+		}
+	})
+}
+
+func TestContextCancelStmtQuery(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec("CREATE TABLE test (v INTEGER)")
+		ctx, cancel := context.WithCancel(context.Background())
+		stmt, err := dbt.db.PrepareContext(ctx, "INSERT INTO test VALUES (SLEEP(1))")
+		if err != nil {
+			dbt.Fatalf("unexpected error: %v", err)
+		}
+
+		// Delay execution for just a bit until db.ExecContext has begun.
+		defer time.AfterFunc(250*time.Millisecond, cancel).Stop()
+
+		// This query will be canceled.
+		startTime := time.Now()
+		if _, err := stmt.QueryContext(ctx); err != context.Canceled {
+			dbt.Errorf("expected context.Canceled, got %v", err)
+		}
+		if d := time.Since(startTime); d > 500*time.Millisecond {
+			dbt.Errorf("too long execution time: %s", d)
+		}
+
+		// Wait for the INSERT query has done.
+		time.Sleep(time.Second)
+
+		// Check how many times the query is executed.
+		var v int
+		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM test").Scan(&v); err != nil {
+			dbt.Fatalf("%s", err.Error())
+		}
+		if v != 1 { // TODO: need to kill the query, and v should be 0.
+			dbt.Skipf("[WARN] expected val to be 1, got %d", v)
+		}
+	})
+}
+
+func TestContextCancelBegin(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec("CREATE TABLE test (v INTEGER)")
+		ctx, cancel := context.WithCancel(context.Background())
+		tx, err := dbt.db.BeginTx(ctx, nil)
+		if err != nil {
+			dbt.Fatal(err)
+		}
+
+		// Delay execution for just a bit until db.ExecContext has begun.
+		defer time.AfterFunc(100*time.Millisecond, cancel).Stop()
+
+		// This query will be canceled.
+		startTime := time.Now()
+		if _, err := tx.ExecContext(ctx, "INSERT INTO test VALUES (SLEEP(1))"); err != context.Canceled {
+			dbt.Errorf("expected context.Canceled, got %v", err)
+		}
+		if d := time.Since(startTime); d > 500*time.Millisecond {
+			dbt.Errorf("too long execution time: %s", d)
+		}
+
+		// Transaction is canceled, so expect an error.
+		switch err := tx.Commit(); err {
+		case sql.ErrTxDone:
+			// because the transaction has already been rollbacked.
+			// the database/sql package watches ctx
+			// and rollbacks when ctx is canceled.
+		case context.Canceled:
+			// the database/sql package rollbacks on another goroutine,
+			// so the transaction may not be rollbacked depending on goroutine scheduling.
+		default:
+			dbt.Errorf("expected sql.ErrTxDone or context.Canceled, got %v", err)
+		}
+
+		// Context is canceled, so cannot begin a transaction.
+		if _, err := dbt.db.BeginTx(ctx, nil); err != context.Canceled {
+			dbt.Errorf("expected context.Canceled, got %v", err)
+		}
+	})
+}
+
+func TestContextBeginIsolationLevel(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec("CREATE TABLE test (v INTEGER)")
+		ctx, cancel := context.WithCancel(context.Background())
+		defer cancel()
+
+		tx1, err := dbt.db.BeginTx(ctx, &sql.TxOptions{
+			Isolation: sql.LevelRepeatableRead,
+		})
+		if err != nil {
+			dbt.Fatal(err)
+		}
+
+		tx2, err := dbt.db.BeginTx(ctx, &sql.TxOptions{
+			Isolation: sql.LevelReadCommitted,
+		})
+		if err != nil {
+			dbt.Fatal(err)
+		}
+
+		_, err = tx1.ExecContext(ctx, "INSERT INTO test VALUES (1)")
+		if err != nil {
+			dbt.Fatal(err)
+		}
+
+		var v int
+		row := tx2.QueryRowContext(ctx, "SELECT COUNT(*) FROM test")
+		if err := row.Scan(&v); err != nil {
+			dbt.Fatal(err)
+		}
+		// Because writer transaction wasn't commited yet, it should be available
+		if v != 0 {
+			dbt.Errorf("expected val to be 0, got %d", v)
+		}
+
+		err = tx1.Commit()
+		if err != nil {
+			dbt.Fatal(err)
+		}
+
+		row = tx2.QueryRowContext(ctx, "SELECT COUNT(*) FROM test")
+		if err := row.Scan(&v); err != nil {
+			dbt.Fatal(err)
+		}
+		// Data written by writer transaction is already commited, it should be selectable
+		if v != 1 {
+			dbt.Errorf("expected val to be 1, got %d", v)
+		}
+		tx2.Commit()
+	})
+}
+
+func TestContextBeginReadOnly(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec("CREATE TABLE test (v INTEGER)")
+		ctx, cancel := context.WithCancel(context.Background())
+		defer cancel()
+
+		tx, err := dbt.db.BeginTx(ctx, &sql.TxOptions{
+			ReadOnly: true,
+		})
+		if _, ok := err.(*MySQLError); ok {
+			dbt.Skip("It seems that your MySQL does not support READ ONLY transactions")
+			return
+		} else if err != nil {
+			dbt.Fatal(err)
+		}
+
+		// INSERT queries fail in a READ ONLY transaction.
+		_, err = tx.ExecContext(ctx, "INSERT INTO test VALUES (1)")
+		if _, ok := err.(*MySQLError); !ok {
+			dbt.Errorf("expected MySQLError, got %v", err)
+		}
+
+		// SELECT queries can be executed.
+		var v int
+		row := tx.QueryRowContext(ctx, "SELECT COUNT(*) FROM test")
+		if err := row.Scan(&v); err != nil {
+			dbt.Fatal(err)
+		}
+		if v != 0 {
+			dbt.Errorf("expected val to be 0, got %d", v)
+		}
+
+		if err := tx.Commit(); err != nil {
+			dbt.Fatal(err)
+		}
+	})
+}
+
+func TestRowsColumnTypes(t *testing.T) {
+	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}
+	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, 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}
+	nd1 := NullTime{Time: time.Date(2006, 01, 02, 0, 0, 0, 0, time.UTC), Valid: true}
+	nd2 := NullTime{Time: time.Date(2006, 03, 04, 0, 0, 0, 0, time.UTC), Valid: true}
+	ndNULL := NullTime{Time: time.Time{}, Valid: false}
+	rbNULL := sql.RawBytes(nil)
+	rb0 := sql.RawBytes("0")
+	rb42 := sql.RawBytes("42")
+	rbTest := sql.RawBytes("Test")
+	rb0pad4 := sql.RawBytes("0\x00\x00\x00") // BINARY right-pads values with 0x00
+	rbx0 := sql.RawBytes("\x00")
+	rbx42 := sql.RawBytes("\x42")
+
+	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
+		precision        int64 // 0 if not ok
+		scale            int64
+		valuesIn         [3]string
+		valuesOut        [3]interface{}
+	}{
+		{"bit8null", "BIT(8)", "BIT", scanTypeRawBytes, true, 0, 0, [3]string{"0x0", "NULL", "0x42"}, [3]interface{}{rbx0, rbNULL, rbx42}},
+		{"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)}},
+		{"mediumintnull", "MEDIUMINT", "MEDIUMINT", scanTypeNullInt, true, 0, 0, [3]string{"0", "42", "NULL"}, [3]interface{}{ni0, ni42, niNULL}},
+		{"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}},
+		{"binary4null", "BINARY(4)", "BINARY", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0pad4, rbNULL, rbTest}},
+		{"varbinary42", "VARBINARY(42) NOT NULL", "VARBINARY", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
+		{"tinyblobnull", "TINYBLOB", "BLOB", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
+		{"tinytextnull", "TINYTEXT", "TEXT", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
+		{"blobnull", "BLOB", "BLOB", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
+		{"textnull", "TEXT", "TEXT", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
+		{"mediumblob", "MEDIUMBLOB NOT NULL", "BLOB", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
+		{"mediumtext", "MEDIUMTEXT NOT NULL", "TEXT", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
+		{"longblob", "LONGBLOB NOT NULL", "BLOB", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
+		{"longtext", "LONGTEXT NOT NULL", "TEXT", 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}},
+		{"date", "DATE", "DATE", scanTypeNullTime, true, 0, 0, [3]string{"'2006-01-02'", "NULL", "'2006-03-04'"}, [3]interface{}{nd1, ndNULL, nd2}},
+		{"year", "YEAR NOT NULL", "YEAR", scanTypeUint16, false, 0, 0, [3]string{"2006", "2000", "1994"}, [3]interface{}{uint16(2006), uint16(2000), uint16(1994)}},
+	}
+
+	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]
+
+	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 + ")")
+
+			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 and Scale
+				precision, scale, 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
+				}
+				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))
+			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]) {
+						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++
+			}
+			if i != 3 {
+				t.Errorf("expected 3 rows, got %d", i)
+			}
+
+			if err := rows.Close(); err != nil {
+				t.Errorf("error closing rows: %s", err)
+			}
+		})
+	}
+}
+
+func TestValuerWithValueReceiverGivenNilValue(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec("CREATE TABLE test (value VARCHAR(255))")
+		dbt.db.Exec("INSERT INTO test VALUES (?)", (*testValuer)(nil))
+		// This test will panic on the INSERT if ConvertValue() does not check for typed nil before calling Value()
+	})
+}
diff --git a/utils.go b/utils.go
index ca5d47d82..cb3650bb9 100644
--- a/utils.go
+++ b/utils.go
@@ -10,8 +10,10 @@ package mysql
 
 import (
 	"crypto/tls"
+	"database/sql"
 	"database/sql/driver"
 	"encoding/binary"
+	"errors"
 	"fmt"
 	"io"
 	"strconv"
@@ -80,7 +82,7 @@ func DeregisterTLSConfig(key string) {
 func getTLSConfigClone(key string) (config *tls.Config) {
 	tlsConfigLock.RLock()
 	if v, ok := tlsConfigRegistry[key]; ok {
-		config = cloneTLSConfig(v)
+		config = v.Clone()
 	}
 	tlsConfigLock.RUnlock()
 	return
@@ -724,3 +726,30 @@ func (ae *atomicError) Value() error {
 	}
 	return nil
 }
+
+func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) {
+	dargs := make([]driver.Value, len(named))
+	for n, param := range named {
+		if len(param.Name) > 0 {
+			// TODO: support the use of Named Parameters #561
+			return nil, errors.New("mysql: driver does not support the use of Named Parameters")
+		}
+		dargs[n] = param.Value
+	}
+	return dargs, nil
+}
+
+func mapIsolationLevel(level driver.IsolationLevel) (string, error) {
+	switch sql.IsolationLevel(level) {
+	case sql.LevelRepeatableRead:
+		return "REPEATABLE READ", nil
+	case sql.LevelReadCommitted:
+		return "READ COMMITTED", nil
+	case sql.LevelReadUncommitted:
+		return "READ UNCOMMITTED", nil
+	case sql.LevelSerializable:
+		return "SERIALIZABLE", nil
+	default:
+		return "", fmt.Errorf("mysql: unsupported isolation level: %v", level)
+	}
+}
diff --git a/utils_go17.go b/utils_go17.go
deleted file mode 100644
index f59563456..000000000
--- a/utils_go17.go
+++ /dev/null
@@ -1,40 +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/.
-
-// +build go1.7
-// +build !go1.8
-
-package mysql
-
-import "crypto/tls"
-
-func cloneTLSConfig(c *tls.Config) *tls.Config {
-	return &tls.Config{
-		Rand:                        c.Rand,
-		Time:                        c.Time,
-		Certificates:                c.Certificates,
-		NameToCertificate:           c.NameToCertificate,
-		GetCertificate:              c.GetCertificate,
-		RootCAs:                     c.RootCAs,
-		NextProtos:                  c.NextProtos,
-		ServerName:                  c.ServerName,
-		ClientAuth:                  c.ClientAuth,
-		ClientCAs:                   c.ClientCAs,
-		InsecureSkipVerify:          c.InsecureSkipVerify,
-		CipherSuites:                c.CipherSuites,
-		PreferServerCipherSuites:    c.PreferServerCipherSuites,
-		SessionTicketsDisabled:      c.SessionTicketsDisabled,
-		SessionTicketKey:            c.SessionTicketKey,
-		ClientSessionCache:          c.ClientSessionCache,
-		MinVersion:                  c.MinVersion,
-		MaxVersion:                  c.MaxVersion,
-		CurvePreferences:            c.CurvePreferences,
-		DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled,
-		Renegotiation:               c.Renegotiation,
-	}
-}
diff --git a/utils_go18.go b/utils_go18.go
deleted file mode 100644
index c35c2a6aa..000000000
--- a/utils_go18.go
+++ /dev/null
@@ -1,50 +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/.
-
-// +build go1.8
-
-package mysql
-
-import (
-	"crypto/tls"
-	"database/sql"
-	"database/sql/driver"
-	"errors"
-	"fmt"
-)
-
-func cloneTLSConfig(c *tls.Config) *tls.Config {
-	return c.Clone()
-}
-
-func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) {
-	dargs := make([]driver.Value, len(named))
-	for n, param := range named {
-		if len(param.Name) > 0 {
-			// TODO: support the use of Named Parameters #561
-			return nil, errors.New("mysql: driver does not support the use of Named Parameters")
-		}
-		dargs[n] = param.Value
-	}
-	return dargs, nil
-}
-
-func mapIsolationLevel(level driver.IsolationLevel) (string, error) {
-	switch sql.IsolationLevel(level) {
-	case sql.LevelRepeatableRead:
-		return "REPEATABLE READ", nil
-	case sql.LevelReadCommitted:
-		return "READ COMMITTED", nil
-	case sql.LevelReadUncommitted:
-		return "READ UNCOMMITTED", nil
-	case sql.LevelSerializable:
-		return "SERIALIZABLE", nil
-	default:
-		return "", fmt.Errorf("mysql: unsupported isolation level: %v", level)
-	}
-}
diff --git a/utils_go18_test.go b/utils_go18_test.go
deleted file mode 100644
index f63dbecc4..000000000
--- a/utils_go18_test.go
+++ /dev/null
@@ -1,57 +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/.
-
-// +build go1.8
-
-package mysql
-
-import (
-	"database/sql"
-	"database/sql/driver"
-	"testing"
-)
-
-func TestIsolationLevelMapping(t *testing.T) {
-	data := []struct {
-		level    driver.IsolationLevel
-		expected string
-	}{
-		{
-			level:    driver.IsolationLevel(sql.LevelReadCommitted),
-			expected: "READ COMMITTED",
-		},
-		{
-			level:    driver.IsolationLevel(sql.LevelRepeatableRead),
-			expected: "REPEATABLE READ",
-		},
-		{
-			level:    driver.IsolationLevel(sql.LevelReadUncommitted),
-			expected: "READ UNCOMMITTED",
-		},
-		{
-			level:    driver.IsolationLevel(sql.LevelSerializable),
-			expected: "SERIALIZABLE",
-		},
-	}
-
-	for i, td := range data {
-		if actual, err := mapIsolationLevel(td.level); actual != td.expected || err != nil {
-			t.Fatal(i, td.expected, actual, err)
-		}
-	}
-
-	// check unsupported mapping
-	expectedErr := "mysql: unsupported isolation level: 7"
-	actual, err := mapIsolationLevel(driver.IsolationLevel(sql.LevelLinearizable))
-	if actual != "" || err == nil {
-		t.Fatal("Expected error on unsupported isolation level")
-	}
-	if err.Error() != expectedErr {
-		t.Fatalf("Expected error to be %q, got %q", expectedErr, err)
-	}
-}
diff --git a/utils_test.go b/utils_test.go
index 4a72a108e..8951a7a81 100644
--- a/utils_test.go
+++ b/utils_test.go
@@ -10,6 +10,8 @@ package mysql
 
 import (
 	"bytes"
+	"database/sql"
+	"database/sql/driver"
 	"encoding/binary"
 	"testing"
 	"time"
@@ -290,3 +292,43 @@ func TestAtomicError(t *testing.T) {
 		t.Fatal("Error did not match")
 	}
 }
+
+func TestIsolationLevelMapping(t *testing.T) {
+	data := []struct {
+		level    driver.IsolationLevel
+		expected string
+	}{
+		{
+			level:    driver.IsolationLevel(sql.LevelReadCommitted),
+			expected: "READ COMMITTED",
+		},
+		{
+			level:    driver.IsolationLevel(sql.LevelRepeatableRead),
+			expected: "REPEATABLE READ",
+		},
+		{
+			level:    driver.IsolationLevel(sql.LevelReadUncommitted),
+			expected: "READ UNCOMMITTED",
+		},
+		{
+			level:    driver.IsolationLevel(sql.LevelSerializable),
+			expected: "SERIALIZABLE",
+		},
+	}
+
+	for i, td := range data {
+		if actual, err := mapIsolationLevel(td.level); actual != td.expected || err != nil {
+			t.Fatal(i, td.expected, actual, err)
+		}
+	}
+
+	// check unsupported mapping
+	expectedErr := "mysql: unsupported isolation level: 7"
+	actual, err := mapIsolationLevel(driver.IsolationLevel(sql.LevelLinearizable))
+	if actual != "" || err == nil {
+		t.Fatal("Expected error on unsupported isolation level")
+	}
+	if err.Error() != expectedErr {
+		t.Fatalf("Expected error to be %q, got %q", expectedErr, err)
+	}
+}

From 7ac0064e822156a17a6b598957ddf5e0287f8288 Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Wed, 18 Jul 2018 16:30:26 +0200
Subject: [PATCH 072/282] try to fix handling of empty auth plugin names (#835)

---
 driver.go  | 3 +++
 packets.go | 9 +++------
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/driver.go b/driver.go
index 8c35de73c..ba1297825 100644
--- a/driver.go
+++ b/driver.go
@@ -105,6 +105,9 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
 		mc.cleanup()
 		return nil, err
 	}
+	if plugin == "" {
+		plugin = defaultAuthPlugin
+	}
 
 	// Send Client Authentication Packet
 	authResp, addNUL, err := mc.auth(authData, plugin)
diff --git a/packets.go b/packets.go
index f99934e73..170aaa02b 100644
--- a/packets.go
+++ b/packets.go
@@ -154,15 +154,15 @@ func (mc *mysqlConn) writePacket(data []byte) error {
 
 // Handshake Initialization Packet
 // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::Handshake
-func (mc *mysqlConn) readHandshakePacket() ([]byte, string, error) {
-	data, err := mc.readPacket()
+func (mc *mysqlConn) readHandshakePacket() (data []byte, plugin string, err error) {
+	data, err = mc.readPacket()
 	if err != nil {
 		// for init we can rewrite this to ErrBadConn for sql.Driver to retry, since
 		// in connection initialization we don't risk retrying non-idempotent actions.
 		if err == ErrInvalidConn {
 			return nil, "", driver.ErrBadConn
 		}
-		return nil, "", err
+		return
 	}
 
 	if data[0] == iERR {
@@ -198,7 +198,6 @@ func (mc *mysqlConn) readHandshakePacket() ([]byte, string, error) {
 	}
 	pos += 2
 
-	plugin := ""
 	if len(data) > pos {
 		// character set [1 byte]
 		// status flags [2 bytes]
@@ -236,8 +235,6 @@ func (mc *mysqlConn) readHandshakePacket() ([]byte, string, error) {
 		return b[:], plugin, nil
 	}
 
-	plugin = defaultAuthPlugin
-
 	// make a memory safe copy of the cipher slice
 	var b [8]byte
 	copy(b[:], authData)

From 447ae1fd2f87c59f657f5c495f3db2612256717e Mon Sep 17 00:00:00 2001
From: Thomas Wodarek <wodarekwebpage@gmail.com>
Date: Wed, 18 Jul 2018 10:34:48 -0400
Subject: [PATCH 073/282] Update README.md to remove 1.7 support doc (#829)

* Update README.md to remove 1.7 support doc

Per commit https://github.com/go-sql-driver/mysql/commit/749ddf1598b47e3cd909414bda735fe790ef3d30, 1.7 support was removed, so this will fix the docs to reflect that change.

* Add myself to AUTHORS
---
 AUTHORS   | 1 +
 README.md | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/AUTHORS b/AUTHORS
index 73ff68fbc..fbe4ec442 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -72,6 +72,7 @@ Shuode Li <elemount at qq.com>
 Soroush Pour <me at soroushjp.com>
 Stan Putrya <root.vagner at gmail.com>
 Stanley Gunawan <gunawan.stanley at gmail.com>
+Thomas Wodarek <wodarekwebpage at gmail.com>
 Xiangyu Hu <xiangyu.hu at outlook.com>
 Xiaobing Jiang <s7v7nislands at gmail.com>
 Xiuming Chen <cc at cxm.cc>
diff --git a/README.md b/README.md
index 2e9b07eeb..7e7df1a3d 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
   * Optional placeholder interpolation
 
 ## Requirements
-  * Go 1.7 or higher. We aim to support the 3 latest versions of Go.
+  * Go 1.8 or higher. We aim to support the 3 latest versions of Go.
   * MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
 
 ---------------------------------------

From 99ff426eb706cffe92ff3d058e168b278cabf7c7 Mon Sep 17 00:00:00 2001
From: INADA Naoki <methane@users.noreply.github.com>
Date: Thu, 19 Jul 2018 16:19:42 +0900
Subject: [PATCH 074/282] Fix caching_sha2_password with empty password (#826)

There shouldn't be trailing NUL byte for caching_sha2_password.

Fixes #825
---
 auth.go      | 2 +-
 auth_test.go | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/auth.go b/auth.go
index 0b59f52ee..2f61ecd4f 100644
--- a/auth.go
+++ b/auth.go
@@ -241,7 +241,7 @@ func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, bool, error)
 	switch plugin {
 	case "caching_sha2_password":
 		authResp := scrambleSHA256Password(authData, mc.cfg.Passwd)
-		return authResp, (authResp == nil), nil
+		return authResp, false, nil
 
 	case "mysql_old_password":
 		if !mc.cfg.AllowOldPasswords {
diff --git a/auth_test.go b/auth_test.go
index 407363be4..bd0e2189c 100644
--- a/auth_test.go
+++ b/auth_test.go
@@ -764,7 +764,7 @@ func TestAuthSwitchCachingSHA256PasswordEmpty(t *testing.T) {
 		t.Errorf("got error: %v", err)
 	}
 
-	expectedReply := []byte{1, 0, 0, 3, 0}
+	expectedReply := []byte{0, 0, 0, 3}
 	if !bytes.Equal(conn.written, expectedReply) {
 		t.Errorf("got unexpected data: %v", conn.written)
 	}

From 361f66ef3b53de1f16b7f2af9ef38a6c159ceb3e Mon Sep 17 00:00:00 2001
From: INADA Naoki <methane@users.noreply.github.com>
Date: Mon, 1 Oct 2018 16:22:39 +0900
Subject: [PATCH 075/282] Fix cancelled context breaked mysqlConn (#862)

Fix #858
---
 connection.go      | 15 +++++++--------
 connection_test.go | 29 +++++++++++++++++++++++++++++
 2 files changed, 36 insertions(+), 8 deletions(-)

diff --git a/connection.go b/connection.go
index 911be2060..f74235519 100644
--- a/connection.go
+++ b/connection.go
@@ -595,22 +595,21 @@ func (mc *mysqlConn) watchCancel(ctx context.Context) error {
 		mc.cleanup()
 		return nil
 	}
+	// When ctx is already cancelled, don't watch it.
+	if err := ctx.Err(); err != nil {
+		return err
+	}
+	// When ctx is not cancellable, don't watch it.
 	if ctx.Done() == nil {
 		return nil
 	}
-
-	mc.watching = true
-	select {
-	default:
-	case <-ctx.Done():
-		return ctx.Err()
-	}
+	// When watcher is not alive, can't watch it.
 	if mc.watcher == nil {
 		return nil
 	}
 
+	mc.watching = true
 	mc.watcher <- ctx
-
 	return nil
 }
 
diff --git a/connection_test.go b/connection_test.go
index dec376117..352c54ed7 100644
--- a/connection_test.go
+++ b/connection_test.go
@@ -9,6 +9,7 @@
 package mysql
 
 import (
+	"context"
 	"database/sql/driver"
 	"testing"
 )
@@ -79,3 +80,31 @@ func TestCheckNamedValue(t *testing.T) {
 		t.Fatalf("uint64 high-bit not converted, got %#v %T", value.Value, value.Value)
 	}
 }
+
+// TestCleanCancel tests passed context is cancelled at start.
+// No packet should be sent.  Connection should keep current status.
+func TestCleanCancel(t *testing.T) {
+	mc := &mysqlConn{
+		closech: make(chan struct{}),
+	}
+	mc.startWatcher()
+	defer mc.cleanup()
+
+	ctx, cancel := context.WithCancel(context.Background())
+	cancel()
+
+	for i := 0; i < 3; i++ { // Repeat same behavior
+		err := mc.Ping(ctx)
+		if err != context.Canceled {
+			t.Errorf("expected context.Canceled, got %#v", err)
+		}
+
+		if mc.closed.IsSet() {
+			t.Error("expected mc is not closed, closed actually")
+		}
+
+		if mc.watching {
+			t.Error("expected watching is false, but true")
+		}
+	}
+}

From 0f257fc7d30f6ce2b1e115390594d96c36c7dd5c Mon Sep 17 00:00:00 2001
From: Ilia Cimpoes <ilia.cimpoes@ellation.com>
Date: Fri, 19 Oct 2018 18:40:22 +0300
Subject: [PATCH 076/282] Call markBadConn in Ping method (#875)

* Call markBadConn in Ping method

* Add myself to AUTHORS
---
 AUTHORS            |  1 +
 connection.go      |  2 +-
 connection_test.go | 47 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 49 insertions(+), 1 deletion(-)

diff --git a/AUTHORS b/AUTHORS
index fbe4ec442..3f322e802 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -35,6 +35,7 @@ Hanno Braun <mail at hannobraun.com>
 Henri Yandell <flamefew at gmail.com>
 Hirotaka Yamamoto <ymmt2005 at gmail.com>
 ICHINOSE Shogo <shogo82148 at gmail.com>
+Ilia Cimpoes <ichimpoesh at gmail.com>
 INADA Naoki <songofacandy at gmail.com>
 Jacek Szwec <szwec.jacek at gmail.com>
 James Harr <james.harr at gmail.com>
diff --git a/connection.go b/connection.go
index f74235519..12a382268 100644
--- a/connection.go
+++ b/connection.go
@@ -475,7 +475,7 @@ func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
 	defer mc.finish()
 
 	if err = mc.writeCommandPacket(comPing); err != nil {
-		return
+		return mc.markBadConn(err)
 	}
 
 	return mc.readResultOK()
diff --git a/connection_test.go b/connection_test.go
index 352c54ed7..2a1c8e888 100644
--- a/connection_test.go
+++ b/connection_test.go
@@ -11,6 +11,8 @@ package mysql
 import (
 	"context"
 	"database/sql/driver"
+	"errors"
+	"net"
 	"testing"
 )
 
@@ -108,3 +110,48 @@ func TestCleanCancel(t *testing.T) {
 		}
 	}
 }
+
+func TestPingMarkBadConnection(t *testing.T) {
+	nc := badConnection{err: errors.New("boom")}
+	ms := &mysqlConn{
+		netConn:          nc,
+		buf:              newBuffer(nc),
+		maxAllowedPacket: defaultMaxAllowedPacket,
+	}
+
+	err := ms.Ping(context.Background())
+
+	if err != driver.ErrBadConn {
+		t.Errorf("expected driver.ErrBadConn, got  %#v", err)
+	}
+}
+
+func TestPingErrInvalidConn(t *testing.T) {
+	nc := badConnection{err: errors.New("failed to write"), n: 10}
+	ms := &mysqlConn{
+		netConn:          nc,
+		buf:              newBuffer(nc),
+		maxAllowedPacket: defaultMaxAllowedPacket,
+		closech:          make(chan struct{}),
+	}
+
+	err := ms.Ping(context.Background())
+
+	if err != ErrInvalidConn {
+		t.Errorf("expected ErrInvalidConn, got  %#v", err)
+	}
+}
+
+type badConnection struct {
+	n   int
+	err error
+	net.Conn
+}
+
+func (bc badConnection) Write(b []byte) (n int, err error) {
+	return bc.n, bc.err
+}
+
+func (bc badConnection) Close() error {
+	return nil
+}

From 7daee5be2a29576f4bada53f3754b8cf886772a6 Mon Sep 17 00:00:00 2001
From: INADA Naoki <methane@users.noreply.github.com>
Date: Sat, 20 Oct 2018 22:49:31 +0900
Subject: [PATCH 077/282] Fix OldAuthSwitchRequest support (#870)

---
 auth_test.go | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 packets.go   |  2 +-
 2 files changed, 70 insertions(+), 1 deletion(-)

diff --git a/auth_test.go b/auth_test.go
index bd0e2189c..2bfc181ab 100644
--- a/auth_test.go
+++ b/auth_test.go
@@ -1064,6 +1064,22 @@ func TestAuthSwitchOldPasswordNotAllowed(t *testing.T) {
 	}
 }
 
+// Same to TestAuthSwitchOldPasswordNotAllowed, but use OldAuthSwitch request.
+func TestOldAuthSwitchNotAllowed(t *testing.T) {
+	conn, mc := newRWMockConn(2)
+
+	// OldAuthSwitch request
+	conn.data = []byte{1, 0, 0, 2, 0xfe}
+	conn.maxReads = 1
+	authData := []byte{95, 84, 103, 43, 61, 49, 123, 61, 91, 50, 40, 113, 35,
+		84, 96, 101, 92, 123, 121, 107}
+	plugin := "mysql_native_password"
+	err := mc.handleAuthResult(authData, plugin)
+	if err != ErrOldPassword {
+		t.Errorf("expected ErrOldPassword, got %v", err)
+	}
+}
+
 func TestAuthSwitchOldPassword(t *testing.T) {
 	conn, mc := newRWMockConn(2)
 	mc.cfg.AllowOldPasswords = true
@@ -1092,6 +1108,32 @@ func TestAuthSwitchOldPassword(t *testing.T) {
 	}
 }
 
+// Same to TestAuthSwitchOldPassword, but use OldAuthSwitch request.
+func TestOldAuthSwitch(t *testing.T) {
+	conn, mc := newRWMockConn(2)
+	mc.cfg.AllowOldPasswords = true
+	mc.cfg.Passwd = "secret"
+
+	// OldAuthSwitch request
+	conn.data = []byte{1, 0, 0, 2, 0xfe}
+
+	// auth response
+	conn.queuedReplies = [][]byte{{8, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 0}}
+	conn.maxReads = 2
+
+	authData := []byte{95, 84, 103, 43, 61, 49, 123, 61, 91, 50, 40, 113, 35,
+		84, 96, 101, 92, 123, 121, 107}
+	plugin := "mysql_native_password"
+
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+
+	expectedReply := []byte{9, 0, 0, 3, 86, 83, 83, 79, 74, 78, 65, 66, 0}
+	if !bytes.Equal(conn.written, expectedReply) {
+		t.Errorf("got unexpected data: %v", conn.written)
+	}
+}
 func TestAuthSwitchOldPasswordEmpty(t *testing.T) {
 	conn, mc := newRWMockConn(2)
 	mc.cfg.AllowOldPasswords = true
@@ -1120,6 +1162,33 @@ func TestAuthSwitchOldPasswordEmpty(t *testing.T) {
 	}
 }
 
+// Same to TestAuthSwitchOldPasswordEmpty, but use OldAuthSwitch request.
+func TestOldAuthSwitchPasswordEmpty(t *testing.T) {
+	conn, mc := newRWMockConn(2)
+	mc.cfg.AllowOldPasswords = true
+	mc.cfg.Passwd = ""
+
+	// OldAuthSwitch request.
+	conn.data = []byte{1, 0, 0, 2, 0xfe}
+
+	// auth response
+	conn.queuedReplies = [][]byte{{8, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 0}}
+	conn.maxReads = 2
+
+	authData := []byte{95, 84, 103, 43, 61, 49, 123, 61, 91, 50, 40, 113, 35,
+		84, 96, 101, 92, 123, 121, 107}
+	plugin := "mysql_native_password"
+
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+
+	expectedReply := []byte{1, 0, 0, 3, 0}
+	if !bytes.Equal(conn.written, expectedReply) {
+		t.Errorf("got unexpected data: %v", conn.written)
+	}
+}
+
 func TestAuthSwitchSHA256PasswordEmpty(t *testing.T) {
 	conn, mc := newRWMockConn(2)
 	mc.cfg.Passwd = ""
diff --git a/packets.go b/packets.go
index 170aaa02b..14aab59e3 100644
--- a/packets.go
+++ b/packets.go
@@ -479,7 +479,7 @@ func (mc *mysqlConn) readAuthResult() ([]byte, string, error) {
 		return data[1:], "", err
 
 	case iEOF:
-		if len(data) < 1 {
+		if len(data) == 1 {
 			// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest
 			return nil, "mysql_old_password", nil
 		}

From dea3c7b6f9f291f62983aaea85250de9773b4f9f Mon Sep 17 00:00:00 2001
From: Harald Nordgren <haraldnordgren@gmail.com>
Date: Fri, 26 Oct 2018 09:10:16 +0200
Subject: [PATCH 078/282] travis: Add go 1.11.x (#876)

---
 .travis.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.travis.yml b/.travis.yml
index 47dd289a0..75505f144 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,6 +4,7 @@ go:
   - 1.8.x
   - 1.9.x
   - 1.10.x
+  - 1.11.x
   - master
 
 before_install:

From 64cea2f07fbecc522a3719e6659e1d50749aad99 Mon Sep 17 00:00:00 2001
From: INADA Naoki <methane@users.noreply.github.com>
Date: Tue, 30 Oct 2018 22:01:07 +0900
Subject: [PATCH 079/282] Remove mysqlContext (#886)

It is not needed anymore because we dropped Go 1.7 support
---
 connection.go | 16 +++-------------
 1 file changed, 3 insertions(+), 13 deletions(-)

diff --git a/connection.go b/connection.go
index 12a382268..20aa50910 100644
--- a/connection.go
+++ b/connection.go
@@ -19,16 +19,6 @@ import (
 	"time"
 )
 
-// a copy of context.Context for Go 1.7 and earlier
-type mysqlContext interface {
-	Done() <-chan struct{}
-	Err() error
-
-	// defined in context.Context, but not used in this driver:
-	// Deadline() (deadline time.Time, ok bool)
-	// Value(key interface{}) interface{}
-}
-
 type mysqlConn struct {
 	buf              buffer
 	netConn          net.Conn
@@ -45,7 +35,7 @@ type mysqlConn struct {
 
 	// for context support (Go 1.8+)
 	watching bool
-	watcher  chan<- mysqlContext
+	watcher  chan<- context.Context
 	closech  chan struct{}
 	finished chan<- struct{}
 	canceled atomicError // set non-nil if conn is canceled
@@ -614,13 +604,13 @@ func (mc *mysqlConn) watchCancel(ctx context.Context) error {
 }
 
 func (mc *mysqlConn) startWatcher() {
-	watcher := make(chan mysqlContext, 1)
+	watcher := make(chan context.Context, 1)
 	mc.watcher = watcher
 	finished := make(chan struct{})
 	mc.finished = finished
 	go func() {
 		for {
-			var ctx mysqlContext
+			var ctx context.Context
 			select {
 			case ctx = <-watcher:
 			case <-mc.closech:

From fd197cdcfae0c686792ef5399f223f5b66a30b6b Mon Sep 17 00:00:00 2001
From: Tom Jenkinson <tjenkinson@users.noreply.github.com>
Date: Wed, 31 Oct 2018 15:07:16 +0100
Subject: [PATCH 080/282] Return ErrBadConn for temporary Dial error (#867)

When `Dial()` returned error and it's `Timeout() == true`, return ErrBadConn to
database/sql retry new connection.
---
 AUTHORS        |  1 +
 driver.go      |  4 ++++
 driver_test.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 54 insertions(+)

diff --git a/AUTHORS b/AUTHORS
index 3f322e802..806b5b121 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -74,6 +74,7 @@ Soroush Pour <me at soroushjp.com>
 Stan Putrya <root.vagner at gmail.com>
 Stanley Gunawan <gunawan.stanley at gmail.com>
 Thomas Wodarek <wodarekwebpage at gmail.com>
+Tom Jenkinson <tom at tjenkinson.me>
 Xiangyu Hu <xiangyu.hu at outlook.com>
 Xiaobing Jiang <s7v7nislands at gmail.com>
 Xiuming Chen <cc at cxm.cc>
diff --git a/driver.go b/driver.go
index ba1297825..eeb83df01 100644
--- a/driver.go
+++ b/driver.go
@@ -77,6 +77,10 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
 		mc.netConn, err = nd.Dial(mc.cfg.Net, mc.cfg.Addr)
 	}
 	if err != nil {
+		if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
+			errLog.Print("net.Error from Dial()': ", nerr.Error())
+			return nil, driver.ErrBadConn
+		}
 		return nil, err
 	}
 
diff --git a/driver_test.go b/driver_test.go
index f2bf344e5..cec4b5867 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -85,6 +85,23 @@ type DBTest struct {
 	db *sql.DB
 }
 
+type netErrorMock struct {
+	temporary bool
+	timeout   bool
+}
+
+func (e netErrorMock) Temporary() bool {
+	return e.temporary
+}
+
+func (e netErrorMock) Timeout() bool {
+	return e.timeout
+}
+
+func (e netErrorMock) Error() string {
+	return fmt.Sprintf("mock net error. Temporary: %v, Timeout %v", e.temporary, e.timeout)
+}
+
 func runTestsWithMultiStatement(t *testing.T, dsn string, tests ...func(dbt *DBTest)) {
 	if !available {
 		t.Skipf("MySQL server not running on %s", netAddr)
@@ -1801,6 +1818,38 @@ func TestConcurrent(t *testing.T) {
 	})
 }
 
+func testDialError(t *testing.T, dialErr error, expectErr error) {
+	RegisterDial("mydial", func(addr string) (net.Conn, error) {
+		return nil, dialErr
+	})
+
+	db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@mydial(%s)/%s?timeout=30s", user, pass, addr, dbname))
+	if err != nil {
+		t.Fatalf("error connecting: %s", err.Error())
+	}
+	defer db.Close()
+
+	_, err = db.Exec("DO 1")
+	if err != expectErr {
+		t.Fatalf("was expecting %s. Got: %s", dialErr, err)
+	}
+}
+
+func TestDialUnknownError(t *testing.T) {
+	testErr := fmt.Errorf("test")
+	testDialError(t, testErr, testErr)
+}
+
+func TestDialNonRetryableNetErr(t *testing.T) {
+	testErr := netErrorMock{}
+	testDialError(t, testErr, testErr)
+}
+
+func TestDialTemporaryNetErr(t *testing.T) {
+	testErr := netErrorMock{temporary: true}
+	testDialError(t, testErr, driver.ErrBadConn)
+}
+
 // Tests custom dial functions
 func TestCustomDial(t *testing.T) {
 	if !available {

From 369b5d6e5e8e108ed4ae2f2b1607d444b3807dfb Mon Sep 17 00:00:00 2001
From: INADA Naoki <methane@users.noreply.github.com>
Date: Tue, 13 Nov 2018 11:38:49 +0900
Subject: [PATCH 081/282] Fix Auth Resnponse packet for cleartext password
 (#887)

Trailing NUL char should be in `string[n] auth-response`.
But NUL was after auth-response.
---
 auth.go      | 36 +++++++++++++-------------
 auth_test.go | 73 ++++++++++++++++++++++++++--------------------------
 driver.go    |  6 ++---
 packets.go   | 24 +++++------------
 4 files changed, 64 insertions(+), 75 deletions(-)

diff --git a/auth.go b/auth.go
index 2f61ecd4f..14f678a87 100644
--- a/auth.go
+++ b/auth.go
@@ -234,64 +234,64 @@ func (mc *mysqlConn) sendEncryptedPassword(seed []byte, pub *rsa.PublicKey) erro
 	if err != nil {
 		return err
 	}
-	return mc.writeAuthSwitchPacket(enc, false)
+	return mc.writeAuthSwitchPacket(enc)
 }
 
-func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, bool, error) {
+func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, error) {
 	switch plugin {
 	case "caching_sha2_password":
 		authResp := scrambleSHA256Password(authData, mc.cfg.Passwd)
-		return authResp, false, nil
+		return authResp, nil
 
 	case "mysql_old_password":
 		if !mc.cfg.AllowOldPasswords {
-			return nil, false, ErrOldPassword
+			return nil, ErrOldPassword
 		}
 		// Note: there are edge cases where this should work but doesn't;
 		// this is currently "wontfix":
 		// https://github.com/go-sql-driver/mysql/issues/184
-		authResp := scrambleOldPassword(authData[:8], mc.cfg.Passwd)
-		return authResp, true, nil
+		authResp := append(scrambleOldPassword(authData[:8], mc.cfg.Passwd), 0)
+		return authResp, nil
 
 	case "mysql_clear_password":
 		if !mc.cfg.AllowCleartextPasswords {
-			return nil, false, ErrCleartextPassword
+			return nil, ErrCleartextPassword
 		}
 		// http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html
 		// http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html
-		return []byte(mc.cfg.Passwd), true, nil
+		return append([]byte(mc.cfg.Passwd), 0), nil
 
 	case "mysql_native_password":
 		if !mc.cfg.AllowNativePasswords {
-			return nil, false, ErrNativePassword
+			return nil, ErrNativePassword
 		}
 		// https://dev.mysql.com/doc/internals/en/secure-password-authentication.html
 		// Native password authentication only need and will need 20-byte challenge.
 		authResp := scramblePassword(authData[:20], mc.cfg.Passwd)
-		return authResp, false, nil
+		return authResp, nil
 
 	case "sha256_password":
 		if len(mc.cfg.Passwd) == 0 {
-			return nil, true, nil
+			return []byte{0}, nil
 		}
 		if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
 			// write cleartext auth packet
-			return []byte(mc.cfg.Passwd), true, nil
+			return append([]byte(mc.cfg.Passwd), 0), nil
 		}
 
 		pubKey := mc.cfg.pubKey
 		if pubKey == nil {
 			// request public key from server
-			return []byte{1}, false, nil
+			return []byte{1}, nil
 		}
 
 		// encrypted password
 		enc, err := encryptPassword(mc.cfg.Passwd, authData, pubKey)
-		return enc, false, err
+		return enc, err
 
 	default:
 		errLog.Print("unknown auth plugin:", plugin)
-		return nil, false, ErrUnknownPlugin
+		return nil, ErrUnknownPlugin
 	}
 }
 
@@ -315,11 +315,11 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
 
 		plugin = newPlugin
 
-		authResp, addNUL, err := mc.auth(authData, plugin)
+		authResp, err := mc.auth(authData, plugin)
 		if err != nil {
 			return err
 		}
-		if err = mc.writeAuthSwitchPacket(authResp, addNUL); err != nil {
+		if err = mc.writeAuthSwitchPacket(authResp); err != nil {
 			return err
 		}
 
@@ -352,7 +352,7 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
 			case cachingSha2PasswordPerformFullAuthentication:
 				if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
 					// write cleartext auth packet
-					err = mc.writeAuthSwitchPacket([]byte(mc.cfg.Passwd), true)
+					err = mc.writeAuthSwitchPacket(append([]byte(mc.cfg.Passwd), 0))
 					if err != nil {
 						return err
 					}
diff --git a/auth_test.go b/auth_test.go
index 2bfc181ab..1920ef39f 100644
--- a/auth_test.go
+++ b/auth_test.go
@@ -85,11 +85,11 @@ func TestAuthFastCachingSHA256PasswordCached(t *testing.T) {
 	plugin := "caching_sha2_password"
 
 	// Send Client Authentication Packet
-	authResp, addNUL, err := mc.auth(authData, plugin)
+	authResp, err := mc.auth(authData, plugin)
 	if err != nil {
 		t.Fatal(err)
 	}
-	err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin)
+	err = mc.writeHandshakeResponsePacket(authResp, plugin)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -130,11 +130,11 @@ func TestAuthFastCachingSHA256PasswordEmpty(t *testing.T) {
 	plugin := "caching_sha2_password"
 
 	// Send Client Authentication Packet
-	authResp, addNUL, err := mc.auth(authData, plugin)
+	authResp, err := mc.auth(authData, plugin)
 	if err != nil {
 		t.Fatal(err)
 	}
-	err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin)
+	err = mc.writeHandshakeResponsePacket(authResp, plugin)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -172,11 +172,11 @@ func TestAuthFastCachingSHA256PasswordFullRSA(t *testing.T) {
 	plugin := "caching_sha2_password"
 
 	// Send Client Authentication Packet
-	authResp, addNUL, err := mc.auth(authData, plugin)
+	authResp, err := mc.auth(authData, plugin)
 	if err != nil {
 		t.Fatal(err)
 	}
-	err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin)
+	err = mc.writeHandshakeResponsePacket(authResp, plugin)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -228,11 +228,11 @@ func TestAuthFastCachingSHA256PasswordFullRSAWithKey(t *testing.T) {
 	plugin := "caching_sha2_password"
 
 	// Send Client Authentication Packet
-	authResp, addNUL, err := mc.auth(authData, plugin)
+	authResp, err := mc.auth(authData, plugin)
 	if err != nil {
 		t.Fatal(err)
 	}
-	err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin)
+	err = mc.writeHandshakeResponsePacket(authResp, plugin)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -280,11 +280,11 @@ func TestAuthFastCachingSHA256PasswordFullSecure(t *testing.T) {
 	plugin := "caching_sha2_password"
 
 	// Send Client Authentication Packet
-	authResp, addNUL, err := mc.auth(authData, plugin)
+	authResp, err := mc.auth(authData, plugin)
 	if err != nil {
 		t.Fatal(err)
 	}
-	err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin)
+	err = mc.writeHandshakeResponsePacket(authResp, plugin)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -336,7 +336,7 @@ func TestAuthFastCleartextPasswordNotAllowed(t *testing.T) {
 	plugin := "mysql_clear_password"
 
 	// Send Client Authentication Packet
-	_, _, err := mc.auth(authData, plugin)
+	_, err := mc.auth(authData, plugin)
 	if err != ErrCleartextPassword {
 		t.Errorf("expected ErrCleartextPassword, got %v", err)
 	}
@@ -353,11 +353,11 @@ func TestAuthFastCleartextPassword(t *testing.T) {
 	plugin := "mysql_clear_password"
 
 	// Send Client Authentication Packet
-	authResp, addNUL, err := mc.auth(authData, plugin)
+	authResp, err := mc.auth(authData, plugin)
 	if err != nil {
 		t.Fatal(err)
 	}
-	err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin)
+	err = mc.writeHandshakeResponsePacket(authResp, plugin)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -367,8 +367,8 @@ func TestAuthFastCleartextPassword(t *testing.T) {
 	authRespEnd := authRespStart + 1 + len(authResp)
 	writtenAuthRespLen := conn.written[authRespStart]
 	writtenAuthResp := conn.written[authRespStart+1 : authRespEnd]
-	expectedAuthResp := []byte{115, 101, 99, 114, 101, 116}
-	if writtenAuthRespLen != 6 || !bytes.Equal(writtenAuthResp, expectedAuthResp) {
+	expectedAuthResp := []byte{115, 101, 99, 114, 101, 116, 0}
+	if writtenAuthRespLen != 7 || !bytes.Equal(writtenAuthResp, expectedAuthResp) {
 		t.Fatalf("unexpected written auth response (%d bytes): %v", writtenAuthRespLen, writtenAuthResp)
 	}
 	conn.written = nil
@@ -396,11 +396,11 @@ func TestAuthFastCleartextPasswordEmpty(t *testing.T) {
 	plugin := "mysql_clear_password"
 
 	// Send Client Authentication Packet
-	authResp, addNUL, err := mc.auth(authData, plugin)
+	authResp, err := mc.auth(authData, plugin)
 	if err != nil {
 		t.Fatal(err)
 	}
-	err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin)
+	err = mc.writeHandshakeResponsePacket(authResp, plugin)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -410,9 +410,9 @@ func TestAuthFastCleartextPasswordEmpty(t *testing.T) {
 	authRespEnd := authRespStart + 1 + len(authResp)
 	writtenAuthRespLen := conn.written[authRespStart]
 	writtenAuthResp := conn.written[authRespStart+1 : authRespEnd]
-	if writtenAuthRespLen != 0 {
-		t.Fatalf("unexpected written auth response (%d bytes): %v",
-			writtenAuthRespLen, writtenAuthResp)
+	expectedAuthResp := []byte{0}
+	if writtenAuthRespLen != 1 || !bytes.Equal(writtenAuthResp, expectedAuthResp) {
+		t.Fatalf("unexpected written auth response (%d bytes): %v", writtenAuthRespLen, writtenAuthResp)
 	}
 	conn.written = nil
 
@@ -439,7 +439,7 @@ func TestAuthFastNativePasswordNotAllowed(t *testing.T) {
 	plugin := "mysql_native_password"
 
 	// Send Client Authentication Packet
-	_, _, err := mc.auth(authData, plugin)
+	_, err := mc.auth(authData, plugin)
 	if err != ErrNativePassword {
 		t.Errorf("expected ErrNativePassword, got %v", err)
 	}
@@ -455,11 +455,11 @@ func TestAuthFastNativePassword(t *testing.T) {
 	plugin := "mysql_native_password"
 
 	// Send Client Authentication Packet
-	authResp, addNUL, err := mc.auth(authData, plugin)
+	authResp, err := mc.auth(authData, plugin)
 	if err != nil {
 		t.Fatal(err)
 	}
-	err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin)
+	err = mc.writeHandshakeResponsePacket(authResp, plugin)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -498,11 +498,11 @@ func TestAuthFastNativePasswordEmpty(t *testing.T) {
 	plugin := "mysql_native_password"
 
 	// Send Client Authentication Packet
-	authResp, addNUL, err := mc.auth(authData, plugin)
+	authResp, err := mc.auth(authData, plugin)
 	if err != nil {
 		t.Fatal(err)
 	}
-	err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin)
+	err = mc.writeHandshakeResponsePacket(authResp, plugin)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -540,11 +540,11 @@ func TestAuthFastSHA256PasswordEmpty(t *testing.T) {
 	plugin := "sha256_password"
 
 	// Send Client Authentication Packet
-	authResp, addNUL, err := mc.auth(authData, plugin)
+	authResp, err := mc.auth(authData, plugin)
 	if err != nil {
 		t.Fatal(err)
 	}
-	err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin)
+	err = mc.writeHandshakeResponsePacket(authResp, plugin)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -554,7 +554,8 @@ func TestAuthFastSHA256PasswordEmpty(t *testing.T) {
 	authRespEnd := authRespStart + 1 + len(authResp)
 	writtenAuthRespLen := conn.written[authRespStart]
 	writtenAuthResp := conn.written[authRespStart+1 : authRespEnd]
-	if writtenAuthRespLen != 0 {
+	expectedAuthResp := []byte{0}
+	if writtenAuthRespLen != 1 || !bytes.Equal(writtenAuthResp, expectedAuthResp) {
 		t.Fatalf("unexpected written auth response (%d bytes): %v", writtenAuthRespLen, writtenAuthResp)
 	}
 	conn.written = nil
@@ -587,11 +588,11 @@ func TestAuthFastSHA256PasswordRSA(t *testing.T) {
 	plugin := "sha256_password"
 
 	// Send Client Authentication Packet
-	authResp, addNUL, err := mc.auth(authData, plugin)
+	authResp, err := mc.auth(authData, plugin)
 	if err != nil {
 		t.Fatal(err)
 	}
-	err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin)
+	err = mc.writeHandshakeResponsePacket(authResp, plugin)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -636,11 +637,11 @@ func TestAuthFastSHA256PasswordRSAWithKey(t *testing.T) {
 	plugin := "sha256_password"
 
 	// Send Client Authentication Packet
-	authResp, addNUL, err := mc.auth(authData, plugin)
+	authResp, err := mc.auth(authData, plugin)
 	if err != nil {
 		t.Fatal(err)
 	}
-	err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin)
+	err = mc.writeHandshakeResponsePacket(authResp, plugin)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -669,7 +670,7 @@ func TestAuthFastSHA256PasswordSecure(t *testing.T) {
 	plugin := "sha256_password"
 
 	// send Client Authentication Packet
-	authResp, addNUL, err := mc.auth(authData, plugin)
+	authResp, err := mc.auth(authData, plugin)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -677,18 +678,18 @@ func TestAuthFastSHA256PasswordSecure(t *testing.T) {
 	// unset TLS config to prevent the actual establishment of a TLS wrapper
 	mc.cfg.tls = nil
 
-	err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin)
+	err = mc.writeHandshakeResponsePacket(authResp, plugin)
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	// check written auth response
 	authRespStart := 4 + 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1
-	authRespEnd := authRespStart + 1 + len(authResp) + 1
+	authRespEnd := authRespStart + 1 + len(authResp)
 	writtenAuthRespLen := conn.written[authRespStart]
 	writtenAuthResp := conn.written[authRespStart+1 : authRespEnd]
 	expectedAuthResp := []byte{115, 101, 99, 114, 101, 116, 0}
-	if writtenAuthRespLen != 6 || !bytes.Equal(writtenAuthResp, expectedAuthResp) {
+	if writtenAuthRespLen != 7 || !bytes.Equal(writtenAuthResp, expectedAuthResp) {
 		t.Fatalf("unexpected written auth response (%d bytes): %v", writtenAuthRespLen, writtenAuthResp)
 	}
 	conn.written = nil
diff --git a/driver.go b/driver.go
index eeb83df01..67126355c 100644
--- a/driver.go
+++ b/driver.go
@@ -114,18 +114,18 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
 	}
 
 	// Send Client Authentication Packet
-	authResp, addNUL, err := mc.auth(authData, plugin)
+	authResp, err := mc.auth(authData, plugin)
 	if err != nil {
 		// try the default auth plugin, if using the requested plugin failed
 		errLog.Print("could not use requested auth plugin '"+plugin+"': ", err.Error())
 		plugin = defaultAuthPlugin
-		authResp, addNUL, err = mc.auth(authData, plugin)
+		authResp, err = mc.auth(authData, plugin)
 		if err != nil {
 			mc.cleanup()
 			return nil, err
 		}
 	}
-	if err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin); err != nil {
+	if err = mc.writeHandshakeResponsePacket(authResp, plugin); err != nil {
 		mc.cleanup()
 		return nil, err
 	}
diff --git a/packets.go b/packets.go
index 14aab59e3..9ed640850 100644
--- a/packets.go
+++ b/packets.go
@@ -243,7 +243,7 @@ func (mc *mysqlConn) readHandshakePacket() (data []byte, plugin string, err erro
 
 // Client Authentication Packet
 // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeResponse
-func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, addNUL bool, plugin string) error {
+func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string) error {
 	// Adjust client flags based on server support
 	clientFlags := clientProtocol41 |
 		clientSecureConn |
@@ -269,7 +269,8 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, addNUL bool,
 
 	// encode length of the auth plugin data
 	var authRespLEIBuf [9]byte
-	authRespLEI := appendLengthEncodedInteger(authRespLEIBuf[:0], uint64(len(authResp)))
+	authRespLen := len(authResp)
+	authRespLEI := appendLengthEncodedInteger(authRespLEIBuf[:0], uint64(authRespLen))
 	if len(authRespLEI) > 1 {
 		// if the length can not be written in 1 byte, it must be written as a
 		// length encoded integer
@@ -277,9 +278,6 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, addNUL bool,
 	}
 
 	pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1 + len(authRespLEI) + len(authResp) + 21 + 1
-	if addNUL {
-		pktLen++
-	}
 
 	// To specify a db name
 	if n := len(mc.cfg.DBName); n > 0 {
@@ -350,10 +348,6 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, addNUL bool,
 	// Auth Data [length encoded integer]
 	pos += copy(data[pos:], authRespLEI)
 	pos += copy(data[pos:], authResp)
-	if addNUL {
-		data[pos] = 0x00
-		pos++
-	}
 
 	// Databasename [null terminated string]
 	if len(mc.cfg.DBName) > 0 {
@@ -364,17 +358,15 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, addNUL bool,
 
 	pos += copy(data[pos:], plugin)
 	data[pos] = 0x00
+	pos++
 
 	// Send Auth packet
-	return mc.writePacket(data)
+	return mc.writePacket(data[:pos])
 }
 
 // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
-func (mc *mysqlConn) writeAuthSwitchPacket(authData []byte, addNUL bool) error {
+func (mc *mysqlConn) writeAuthSwitchPacket(authData []byte) error {
 	pktLen := 4 + len(authData)
-	if addNUL {
-		pktLen++
-	}
 	data := mc.buf.takeSmallBuffer(pktLen)
 	if data == nil {
 		// cannot take the buffer. Something must be wrong with the connection
@@ -384,10 +376,6 @@ func (mc *mysqlConn) writeAuthSwitchPacket(authData []byte, addNUL bool) error {
 
 	// Add the auth data [EOF]
 	copy(data[4:], authData)
-	if addNUL {
-		data[pktLen-1] = 0x00
-	}
-
 	return mc.writePacket(data)
 }
 

From 6be42e0ff99645d7d9626d779001a46e39c5f280 Mon Sep 17 00:00:00 2001
From: Steven Hartland <steven.hartland@multiplay.co.uk>
Date: Fri, 16 Nov 2018 10:20:33 +0000
Subject: [PATCH 082/282] Improve buffer handling (#890)

* Eliminate redundant size test in takeBuffer.
* Change buffer takeXXX functions to return an error to make it explicit that they can fail.
* Add missing error check in handleAuthResult.
* Add buffer.store(..) method which can be used by external buffer consumers to update the raw buffer.
* Fix some typos and unnecessary UTF-8 characters in comments.
* Improve buffer function docs.
* Add comments to explain some non-obvious behavior around buffer handling.
---
 AUTHORS       |  2 ++
 auth.go       |  8 +++++---
 buffer.go     | 49 +++++++++++++++++++++++++++++-----------------
 connection.go |  6 +++---
 driver.go     |  2 +-
 packets.go    | 54 ++++++++++++++++++++++++++++-----------------------
 6 files changed, 72 insertions(+), 49 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 806b5b121..5ce4f7eca 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -73,6 +73,7 @@ Shuode Li <elemount at qq.com>
 Soroush Pour <me at soroushjp.com>
 Stan Putrya <root.vagner at gmail.com>
 Stanley Gunawan <gunawan.stanley at gmail.com>
+Steven Hartland <steven.hartland at multiplay.co.uk>
 Thomas Wodarek <wodarekwebpage at gmail.com>
 Tom Jenkinson <tom at tjenkinson.me>
 Xiangyu Hu <xiangyu.hu at outlook.com>
@@ -90,3 +91,4 @@ Keybase Inc.
 Percona LLC
 Pivotal Inc.
 Stripe Inc.
+Multiplay Ltd.
diff --git a/auth.go b/auth.go
index 14f678a87..fec7040d4 100644
--- a/auth.go
+++ b/auth.go
@@ -360,13 +360,15 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
 					pubKey := mc.cfg.pubKey
 					if pubKey == nil {
 						// request public key from server
-						data := mc.buf.takeSmallBuffer(4 + 1)
+						data, err := mc.buf.takeSmallBuffer(4 + 1)
+						if err != nil {
+							return err
+						}
 						data[4] = cachingSha2PasswordRequestPublicKey
 						mc.writePacket(data)
 
 						// parse public key
-						data, err := mc.readPacket()
-						if err != nil {
+						if data, err = mc.readPacket(); err != nil {
 							return err
 						}
 
diff --git a/buffer.go b/buffer.go
index eb4748bf4..19486bd6f 100644
--- a/buffer.go
+++ b/buffer.go
@@ -22,17 +22,17 @@ const defaultBufSize = 4096
 // The buffer is similar to bufio.Reader / Writer but zero-copy-ish
 // Also highly optimized for this particular use case.
 type buffer struct {
-	buf     []byte
+	buf     []byte // buf is a byte buffer who's length and capacity are equal.
 	nc      net.Conn
 	idx     int
 	length  int
 	timeout time.Duration
 }
 
+// newBuffer allocates and returns a new buffer.
 func newBuffer(nc net.Conn) buffer {
-	var b [defaultBufSize]byte
 	return buffer{
-		buf: b[:],
+		buf: make([]byte, defaultBufSize),
 		nc:  nc,
 	}
 }
@@ -105,43 +105,56 @@ func (b *buffer) readNext(need int) ([]byte, error) {
 	return b.buf[offset:b.idx], nil
 }
 
-// returns a buffer with the requested size.
+// takeBuffer returns a buffer with the requested size.
 // If possible, a slice from the existing buffer is returned.
 // Otherwise a bigger buffer is made.
 // Only one buffer (total) can be used at a time.
-func (b *buffer) takeBuffer(length int) []byte {
+func (b *buffer) takeBuffer(length int) ([]byte, error) {
 	if b.length > 0 {
-		return nil
+		return nil, ErrBusyBuffer
 	}
 
 	// test (cheap) general case first
-	if length <= defaultBufSize || length <= cap(b.buf) {
-		return b.buf[:length]
+	if length <= cap(b.buf) {
+		return b.buf[:length], nil
 	}
 
 	if length < maxPacketSize {
 		b.buf = make([]byte, length)
-		return b.buf
+		return b.buf, nil
 	}
-	return make([]byte, length)
+
+	// buffer is larger than we want to store.
+	return make([]byte, length), nil
 }
 
-// shortcut which can be used if the requested buffer is guaranteed to be
-// smaller than defaultBufSize
+// takeSmallBuffer is shortcut which can be used if length is
+// known to be smaller than defaultBufSize.
 // Only one buffer (total) can be used at a time.
-func (b *buffer) takeSmallBuffer(length int) []byte {
+func (b *buffer) takeSmallBuffer(length int) ([]byte, error) {
 	if b.length > 0 {
-		return nil
+		return nil, ErrBusyBuffer
 	}
-	return b.buf[:length]
+	return b.buf[:length], nil
 }
 
 // takeCompleteBuffer returns the complete existing buffer.
 // This can be used if the necessary buffer size is unknown.
+// cap and len of the returned buffer will be equal.
 // Only one buffer (total) can be used at a time.
-func (b *buffer) takeCompleteBuffer() []byte {
+func (b *buffer) takeCompleteBuffer() ([]byte, error) {
+	if b.length > 0 {
+		return nil, ErrBusyBuffer
+	}
+	return b.buf, nil
+}
+
+// store stores buf, an updated buffer, if its suitable to do so.
+func (b *buffer) store(buf []byte) error {
 	if b.length > 0 {
-		return nil
+		return ErrBusyBuffer
+	} else if cap(buf) <= maxPacketSize && cap(buf) > cap(b.buf) {
+		b.buf = buf[:cap(buf)]
 	}
-	return b.buf
+	return nil
 }
diff --git a/connection.go b/connection.go
index 20aa50910..fc4ec7597 100644
--- a/connection.go
+++ b/connection.go
@@ -182,10 +182,10 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
 		return "", driver.ErrSkip
 	}
 
-	buf := mc.buf.takeCompleteBuffer()
-	if buf == nil {
+	buf, err := mc.buf.takeCompleteBuffer()
+	if err != nil {
 		// can not take the buffer. Something must be wrong with the connection
-		errLog.Print(ErrBusyBuffer)
+		errLog.Print(err)
 		return "", ErrInvalidConn
 	}
 	buf = buf[:0]
diff --git a/driver.go b/driver.go
index 67126355c..9f4967087 100644
--- a/driver.go
+++ b/driver.go
@@ -50,7 +50,7 @@ func RegisterDial(net string, dial DialFunc) {
 
 // Open new Connection.
 // See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how
-// the DSN string is formated
+// the DSN string is formatted
 func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
 	var err error
 
diff --git a/packets.go b/packets.go
index 9ed640850..cfcfff360 100644
--- a/packets.go
+++ b/packets.go
@@ -51,7 +51,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
 		mc.sequence++
 
 		// packets with length 0 terminate a previous packet which is a
-		// multiple of (2^24)−1 bytes long
+		// multiple of (2^24)-1 bytes long
 		if pktLen == 0 {
 			// there was no previous packet
 			if prevData == nil {
@@ -286,10 +286,10 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 	}
 
 	// Calculate packet length and get buffer with that size
-	data := mc.buf.takeSmallBuffer(pktLen + 4)
-	if data == nil {
+	data, err := mc.buf.takeSmallBuffer(pktLen + 4)
+	if err != nil {
 		// cannot take the buffer. Something must be wrong with the connection
-		errLog.Print(ErrBusyBuffer)
+		errLog.Print(err)
 		return errBadConnNoWrite
 	}
 
@@ -367,10 +367,10 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
 func (mc *mysqlConn) writeAuthSwitchPacket(authData []byte) error {
 	pktLen := 4 + len(authData)
-	data := mc.buf.takeSmallBuffer(pktLen)
-	if data == nil {
+	data, err := mc.buf.takeSmallBuffer(pktLen)
+	if err != nil {
 		// cannot take the buffer. Something must be wrong with the connection
-		errLog.Print(ErrBusyBuffer)
+		errLog.Print(err)
 		return errBadConnNoWrite
 	}
 
@@ -387,10 +387,10 @@ func (mc *mysqlConn) writeCommandPacket(command byte) error {
 	// Reset Packet Sequence
 	mc.sequence = 0
 
-	data := mc.buf.takeSmallBuffer(4 + 1)
-	if data == nil {
+	data, err := mc.buf.takeSmallBuffer(4 + 1)
+	if err != nil {
 		// cannot take the buffer. Something must be wrong with the connection
-		errLog.Print(ErrBusyBuffer)
+		errLog.Print(err)
 		return errBadConnNoWrite
 	}
 
@@ -406,10 +406,10 @@ func (mc *mysqlConn) writeCommandPacketStr(command byte, arg string) error {
 	mc.sequence = 0
 
 	pktLen := 1 + len(arg)
-	data := mc.buf.takeBuffer(pktLen + 4)
-	if data == nil {
+	data, err := mc.buf.takeBuffer(pktLen + 4)
+	if err != nil {
 		// cannot take the buffer. Something must be wrong with the connection
-		errLog.Print(ErrBusyBuffer)
+		errLog.Print(err)
 		return errBadConnNoWrite
 	}
 
@@ -427,10 +427,10 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
 	// Reset Packet Sequence
 	mc.sequence = 0
 
-	data := mc.buf.takeSmallBuffer(4 + 1 + 4)
-	if data == nil {
+	data, err := mc.buf.takeSmallBuffer(4 + 1 + 4)
+	if err != nil {
 		// cannot take the buffer. Something must be wrong with the connection
-		errLog.Print(ErrBusyBuffer)
+		errLog.Print(err)
 		return errBadConnNoWrite
 	}
 
@@ -883,7 +883,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 	const minPktLen = 4 + 1 + 4 + 1 + 4
 	mc := stmt.mc
 
-	// Determine threshould dynamically to avoid packet size shortage.
+	// Determine threshold dynamically to avoid packet size shortage.
 	longDataSize := mc.maxAllowedPacket / (stmt.paramCount + 1)
 	if longDataSize < 64 {
 		longDataSize = 64
@@ -893,15 +893,17 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 	mc.sequence = 0
 
 	var data []byte
+	var err error
 
 	if len(args) == 0 {
-		data = mc.buf.takeBuffer(minPktLen)
+		data, err = mc.buf.takeBuffer(minPktLen)
 	} else {
-		data = mc.buf.takeCompleteBuffer()
+		data, err = mc.buf.takeCompleteBuffer()
+		// In this case the len(data) == cap(data) which is used to optimise the flow below.
 	}
-	if data == nil {
+	if err != nil {
 		// cannot take the buffer. Something must be wrong with the connection
-		errLog.Print(ErrBusyBuffer)
+		errLog.Print(err)
 		return errBadConnNoWrite
 	}
 
@@ -927,7 +929,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 		pos := minPktLen
 
 		var nullMask []byte
-		if maskLen, typesLen := (len(args)+7)/8, 1+2*len(args); pos+maskLen+typesLen >= len(data) {
+		if maskLen, typesLen := (len(args)+7)/8, 1+2*len(args); pos+maskLen+typesLen >= cap(data) {
 			// buffer has to be extended but we don't know by how much so
 			// we depend on append after all data with known sizes fit.
 			// We stop at that because we deal with a lot of columns here
@@ -936,10 +938,11 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 			copy(tmp[:pos], data[:pos])
 			data = tmp
 			nullMask = data[pos : pos+maskLen]
+			// No need to clean nullMask as make ensures that.
 			pos += maskLen
 		} else {
 			nullMask = data[pos : pos+maskLen]
-			for i := 0; i < maskLen; i++ {
+			for i := range nullMask {
 				nullMask[i] = 0
 			}
 			pos += maskLen
@@ -1076,7 +1079,10 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 		// In that case we must build the data packet with the new values buffer
 		if valuesCap != cap(paramValues) {
 			data = append(data[:pos], paramValues...)
-			mc.buf.buf = data
+			if err = mc.buf.store(data); err != nil {
+				errLog.Print(err)
+				return errBadConnNoWrite
+			}
 		}
 
 		pos += len(paramValues)

From 60d456a402782453be397030407e34decaf04d73 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dani=C3=ABl=20van=20Eeden?= <github@myname.nl>
Date: Sun, 2 Dec 2018 18:10:36 +0100
Subject: [PATCH 083/282] Implement support of Optional TLS (#900)

Issue: #899

Add `preferred` config value to the `tls` config variable on the DSN.

This results in a TLS connection when the server advertises this by the flag
send in the initial packet.
---
 README.md      |  4 ++--
 driver_test.go | 18 +++++++++++++-----
 dsn.go         |  2 +-
 packets.go     |  6 +++++-
 4 files changed, 21 insertions(+), 9 deletions(-)

diff --git a/README.md b/README.md
index 7e7df1a3d..6e4816e63 100644
--- a/README.md
+++ b/README.md
@@ -328,11 +328,11 @@ Timeout for establishing connections, aka dial timeout. The value must be a deci
 
 ```
 Type:           bool / string
-Valid Values:   true, false, skip-verify, <name>
+Valid Values:   true, false, skip-verify, preferred, <name>
 Default:        false
 ```
 
-`tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side). Use a custom value registered with [`mysql.RegisterTLSConfig`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig).
+`tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side). Use `preferred` to use TLS only when advertised by the server, this is similar to `skip-verify`, but additionally allows a fallback to a connection which is not encrypted. Use a custom value registered with [`mysql.RegisterTLSConfig`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig).
 
 
 ##### `writeTimeout`
diff --git a/driver_test.go b/driver_test.go
index cec4b5867..46d1f7ff4 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -1304,7 +1304,7 @@ func TestFoundRows(t *testing.T) {
 }
 
 func TestTLS(t *testing.T) {
-	tlsTest := func(dbt *DBTest) {
+	tlsTestReq := func(dbt *DBTest) {
 		if err := dbt.db.Ping(); err != nil {
 			if err == ErrNoTLS {
 				dbt.Skip("server does not support TLS")
@@ -1321,19 +1321,27 @@ func TestTLS(t *testing.T) {
 				dbt.Fatal(err.Error())
 			}
 
-			if value == nil {
-				dbt.Fatal("no Cipher")
+			if (*value == nil) || (len(*value) == 0) {
+				dbt.Fatalf("no Cipher")
+			} else {
+				dbt.Logf("Cipher: %s", *value)
 			}
 		}
 	}
+	tlsTestOpt := func(dbt *DBTest) {
+		if err := dbt.db.Ping(); err != nil {
+			dbt.Fatalf("error on Ping: %s", err.Error())
+		}
+	}
 
-	runTests(t, dsn+"&tls=skip-verify", tlsTest)
+	runTests(t, dsn+"&tls=preferred", tlsTestOpt)
+	runTests(t, dsn+"&tls=skip-verify", tlsTestReq)
 
 	// Verify that registering / using a custom cfg works
 	RegisterTLSConfig("custom-skip-verify", &tls.Config{
 		InsecureSkipVerify: true,
 	})
-	runTests(t, dsn+"&tls=custom-skip-verify", tlsTest)
+	runTests(t, dsn+"&tls=custom-skip-verify", tlsTestReq)
 }
 
 func TestReuseClosedConnection(t *testing.T) {
diff --git a/dsn.go b/dsn.go
index be014babe..b9134722e 100644
--- a/dsn.go
+++ b/dsn.go
@@ -560,7 +560,7 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 				} else {
 					cfg.TLSConfig = "false"
 				}
-			} else if vl := strings.ToLower(value); vl == "skip-verify" {
+			} else if vl := strings.ToLower(value); vl == "skip-verify" || vl == "preferred" {
 				cfg.TLSConfig = vl
 				cfg.tls = &tls.Config{InsecureSkipVerify: true}
 			} else {
diff --git a/packets.go b/packets.go
index cfcfff360..5e0853767 100644
--- a/packets.go
+++ b/packets.go
@@ -194,7 +194,11 @@ func (mc *mysqlConn) readHandshakePacket() (data []byte, plugin string, err erro
 		return nil, "", ErrOldProtocol
 	}
 	if mc.flags&clientSSL == 0 && mc.cfg.tls != nil {
-		return nil, "", ErrNoTLS
+		if mc.cfg.TLSConfig == "preferred" {
+			mc.cfg.tls = nil
+		} else {
+			return nil, "", ErrNoTLS
+		}
 	}
 	pos += 2
 

From c45f530f8e7fe40f4687eaa50d0c8c5f1b66f9e0 Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Tue, 18 Dec 2018 13:36:37 +0100
Subject: [PATCH 084/282] README: warning about insecure TLS configs (#901)

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 6e4816e63..341d9194c 100644
--- a/README.md
+++ b/README.md
@@ -332,7 +332,7 @@ Valid Values:   true, false, skip-verify, preferred, <name>
 Default:        false
 ```
 
-`tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side). Use `preferred` to use TLS only when advertised by the server, this is similar to `skip-verify`, but additionally allows a fallback to a connection which is not encrypted. Use a custom value registered with [`mysql.RegisterTLSConfig`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig).
+`tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side) or use `preferred` to use TLS only when advertised by the server. This is similar to `skip-verify`, but additionally allows a fallback to a connection which is not encrypted. Neither `skip-verify` nor `preferred` add any reliable security. You can use a custom TLS config after registering it with [`mysql.RegisterTLSConfig`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig).
 
 
 ##### `writeTimeout`

From 1b9eda25029c206f36637dde8e2acad26df99dab Mon Sep 17 00:00:00 2001
From: Jerome Meyer <peergynt@users.noreply.github.com>
Date: Sat, 26 Jan 2019 20:11:11 -0500
Subject: [PATCH 085/282] test: close rows (#918)

---
 AUTHORS        |  1 +
 driver_test.go | 23 ++++++++++++++++++++++-
 2 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/AUTHORS b/AUTHORS
index 5ce4f7eca..c89852c4d 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -41,6 +41,7 @@ Jacek Szwec <szwec.jacek at gmail.com>
 James Harr <james.harr at gmail.com>
 Jeff Hodges <jeff at somethingsimilar.com>
 Jeffrey Charles <jeffreycharles at gmail.com>
+Jerome Meyer <jxmeyer at gmail.com>
 Jian Zhen <zhenjl at gmail.com>
 Joshua Prunier <joshua.prunier at gmail.com>
 Julien Lefevre <julien.lefevr at gmail.com>
diff --git a/driver_test.go b/driver_test.go
index 46d1f7ff4..c35588a09 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -212,6 +212,7 @@ func TestEmptyQuery(t *testing.T) {
 	runTests(t, dsn, func(dbt *DBTest) {
 		// just a comment, no query
 		rows := dbt.mustQuery("--")
+		defer rows.Close()
 		// will hang before #255
 		if rows.Next() {
 			dbt.Errorf("next on rows must be false")
@@ -230,6 +231,7 @@ func TestCRUD(t *testing.T) {
 		if rows.Next() {
 			dbt.Error("unexpected data in empty table")
 		}
+		rows.Close()
 
 		// Create Data
 		res := dbt.mustExec("INSERT INTO test VALUES (1)")
@@ -263,6 +265,7 @@ func TestCRUD(t *testing.T) {
 		} else {
 			dbt.Error("no data")
 		}
+		rows.Close()
 
 		// Update
 		res = dbt.mustExec("UPDATE test SET value = ? WHERE value = ?", false, true)
@@ -288,6 +291,7 @@ func TestCRUD(t *testing.T) {
 		} else {
 			dbt.Error("no data")
 		}
+		rows.Close()
 
 		// Delete
 		res = dbt.mustExec("DELETE FROM test WHERE value = ?", false)
@@ -351,6 +355,7 @@ func TestMultiQuery(t *testing.T) {
 		} else {
 			dbt.Error("no data")
 		}
+		rows.Close()
 
 	})
 }
@@ -377,6 +382,7 @@ func TestInt(t *testing.T) {
 			} else {
 				dbt.Errorf("%s: no data", v)
 			}
+			rows.Close()
 
 			dbt.mustExec("DROP TABLE IF EXISTS test")
 		}
@@ -396,6 +402,7 @@ func TestInt(t *testing.T) {
 			} else {
 				dbt.Errorf("%s ZEROFILL: no data", v)
 			}
+			rows.Close()
 
 			dbt.mustExec("DROP TABLE IF EXISTS test")
 		}
@@ -420,6 +427,7 @@ func TestFloat32(t *testing.T) {
 			} else {
 				dbt.Errorf("%s: no data", v)
 			}
+			rows.Close()
 			dbt.mustExec("DROP TABLE IF EXISTS test")
 		}
 	})
@@ -443,6 +451,7 @@ func TestFloat64(t *testing.T) {
 			} else {
 				dbt.Errorf("%s: no data", v)
 			}
+			rows.Close()
 			dbt.mustExec("DROP TABLE IF EXISTS test")
 		}
 	})
@@ -466,6 +475,7 @@ func TestFloat64Placeholder(t *testing.T) {
 			} else {
 				dbt.Errorf("%s: no data", v)
 			}
+			rows.Close()
 			dbt.mustExec("DROP TABLE IF EXISTS test")
 		}
 	})
@@ -492,6 +502,7 @@ func TestString(t *testing.T) {
 			} else {
 				dbt.Errorf("%s: no data", v)
 			}
+			rows.Close()
 
 			dbt.mustExec("DROP TABLE IF EXISTS test")
 		}
@@ -524,6 +535,7 @@ func TestRawBytes(t *testing.T) {
 		v1 := []byte("aaa")
 		v2 := []byte("bbb")
 		rows := dbt.mustQuery("SELECT ?, ?", v1, v2)
+		defer rows.Close()
 		if rows.Next() {
 			var o1, o2 sql.RawBytes
 			if err := rows.Scan(&o1, &o2); err != nil {
@@ -572,6 +584,7 @@ func TestValuer(t *testing.T) {
 		} else {
 			dbt.Errorf("Valuer: no data")
 		}
+		rows.Close()
 
 		dbt.mustExec("DROP TABLE IF EXISTS test")
 	})
@@ -884,6 +897,7 @@ func TestTimestampMicros(t *testing.T) {
 		dbt.mustExec("INSERT INTO test SET value0=?, value1=?, value6=?", f0, f1, f6)
 		var res0, res1, res6 string
 		rows := dbt.mustQuery("SELECT * FROM test")
+		defer rows.Close()
 		if !rows.Next() {
 			dbt.Errorf("test contained no selectable values")
 		}
@@ -1042,6 +1056,7 @@ func TestNULL(t *testing.T) {
 
 		var out interface{}
 		rows := dbt.mustQuery("SELECT * FROM test")
+		defer rows.Close()
 		if rows.Next() {
 			rows.Scan(&out)
 			if out != nil {
@@ -1121,6 +1136,7 @@ func TestLongData(t *testing.T) {
 		inS := in[:maxAllowedPacketSize-nonDataQueryLen]
 		dbt.mustExec("INSERT INTO test VALUES('" + inS + "')")
 		rows = dbt.mustQuery("SELECT value FROM test")
+		defer rows.Close()
 		if rows.Next() {
 			rows.Scan(&out)
 			if inS != out {
@@ -1139,6 +1155,7 @@ func TestLongData(t *testing.T) {
 		// Long binary data
 		dbt.mustExec("INSERT INTO test VALUES(?)", in)
 		rows = dbt.mustQuery("SELECT value FROM test WHERE 1=?", 1)
+		defer rows.Close()
 		if rows.Next() {
 			rows.Scan(&out)
 			if in != out {
@@ -1314,6 +1331,7 @@ func TestTLS(t *testing.T) {
 		}
 
 		rows := dbt.mustQuery("SHOW STATUS LIKE 'Ssl_cipher'")
+		defer rows.Close()
 
 		var variable, value *sql.RawBytes
 		for rows.Next() {
@@ -1474,9 +1492,9 @@ func TestColumnsWithAlias(t *testing.T) {
 		if cols[0] != "A" {
 			t.Fatalf("expected column name \"A\", got \"%s\"", cols[0])
 		}
-		rows.Close()
 
 		rows = dbt.mustQuery("SELECT * FROM (SELECT 1 AS one) AS A")
+		defer rows.Close()
 		cols, _ = rows.Columns()
 		if len(cols) != 1 {
 			t.Fatalf("expected 1 column, got %d", len(cols))
@@ -1520,6 +1538,7 @@ func TestTimezoneConversion(t *testing.T) {
 
 		// Retrieve time from DB
 		rows := dbt.mustQuery("SELECT ts FROM test")
+		defer rows.Close()
 		if !rows.Next() {
 			dbt.Fatal("did not get any rows out")
 		}
@@ -2017,6 +2036,7 @@ func TestInterruptBySignal(t *testing.T) {
 				dbt.Errorf("expected val to be 42")
 			}
 		}
+		rows.Close()
 
 		// binary protocol
 		rows, err = dbt.db.Query("CALL test_signal(?)", 42)
@@ -2030,6 +2050,7 @@ func TestInterruptBySignal(t *testing.T) {
 				dbt.Errorf("expected val to be 42")
 			}
 		}
+		rows.Close()
 	})
 }
 

From 972a708cf97995463843c08c8585b26997daf0e1 Mon Sep 17 00:00:00 2001
From: Simon J Mudd <simon.mudd@booking.com>
Date: Sun, 17 Feb 2019 08:26:58 +0100
Subject: [PATCH 086/282] Utils typo wether --> whether. (#895)

---
 AUTHORS  | 1 +
 utils.go | 4 ++--
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index c89852c4d..5482a8536 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -71,6 +71,7 @@ Richard Wilkes <wilkes at me.com>
 Robert Russell <robert at rrbrussell.com>
 Runrioter Wung <runrioter at gmail.com>
 Shuode Li <elemount at qq.com>
+Simon J Mudd <sjmudd at pobox.com>
 Soroush Pour <me at soroushjp.com>
 Stan Putrya <root.vagner at gmail.com>
 Stanley Gunawan <gunawan.stanley at gmail.com>
diff --git a/utils.go b/utils.go
index cb3650bb9..201691fe8 100644
--- a/utils.go
+++ b/utils.go
@@ -684,7 +684,7 @@ type atomicBool struct {
 	value   uint32
 }
 
-// IsSet returns wether the current boolean value is true
+// IsSet returns whether the current boolean value is true
 func (ab *atomicBool) IsSet() bool {
 	return atomic.LoadUint32(&ab.value) > 0
 }
@@ -698,7 +698,7 @@ func (ab *atomicBool) Set(value bool) {
 	}
 }
 
-// TrySet sets the value of the bool and returns wether the value changed
+// TrySet sets the value of the bool and returns whether the value changed
 func (ab *atomicBool) TrySet(value bool) bool {
 	if value {
 		return atomic.SwapUint32(&ab.value, 1) == 0

From 2c9d54fefcfb443251f5b92cd5106cf4dc07b880 Mon Sep 17 00:00:00 2001
From: Tim Ruffles <timruffles@github.com>
Date: Thu, 7 Mar 2019 23:26:31 -0600
Subject: [PATCH 087/282] README: 'Equivalent' is clearer (#927)

* Equivalent is clearer

* Update AUTHORS
---
 AUTHORS   | 1 +
 README.md | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/AUTHORS b/AUTHORS
index 5482a8536..146cdffdd 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -77,6 +77,7 @@ Stan Putrya <root.vagner at gmail.com>
 Stanley Gunawan <gunawan.stanley at gmail.com>
 Steven Hartland <steven.hartland at multiplay.co.uk>
 Thomas Wodarek <wodarekwebpage at gmail.com>
+Tim Ruffles <timruffles at gmail.com>
 Tom Jenkinson <tom at tjenkinson.me>
 Xiangyu Hu <xiangyu.hu at outlook.com>
 Xiaobing Jiang <s7v7nislands at gmail.com>
diff --git a/README.md b/README.md
index 341d9194c..999ff4c2c 100644
--- a/README.md
+++ b/README.md
@@ -444,7 +444,7 @@ See the [godoc of Go-MySQL-Driver](https://godoc.org/github.com/go-sql-driver/my
 ### `time.Time` support
 The default internal output type of MySQL `DATE` and `DATETIME` values is `[]byte` which allows you to scan the value into a `[]byte`, `string` or `sql.RawBytes` variable in your program.
 
-However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` variables, which is the logical opposite in Go to `DATE` and `DATETIME` in MySQL. You can do that by changing the internal output type from `[]byte` to `time.Time` with the DSN parameter `parseTime=true`. You can set the default [`time.Time` location](https://golang.org/pkg/time/#Location) with the `loc` DSN parameter.
+However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` variables, which is the logical equivalent in Go to `DATE` and `DATETIME` in MySQL. You can do that by changing the internal output type from `[]byte` to `time.Time` with the DSN parameter `parseTime=true`. You can set the default [`time.Time` location](https://golang.org/pkg/time/#Location) with the `loc` DSN parameter.
 
 **Caution:** As of Go 1.1, this makes `time.Time` the only variable type you can scan `DATE` and `DATETIME` values into. This breaks for example [`sql.RawBytes` support](https://github.com/go-sql-driver/mysql/wiki/Examples#rawbytes).
 

From 1fbca2aabb09915d4f1006d01b271ad7778eec4f Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Sat, 16 Mar 2019 07:32:36 +0900
Subject: [PATCH 088/282] drop Go 1.8 support (#936)

* drop Go 1.8 support

* travis: add Go 1.12.x
---
 .travis.yml | 2 +-
 README.md   | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 75505f144..8f653936b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,10 +1,10 @@
 sudo: false
 language: go
 go:
-  - 1.8.x
   - 1.9.x
   - 1.10.x
   - 1.11.x
+  - 1.12.x
   - master
 
 before_install:
diff --git a/README.md b/README.md
index 999ff4c2c..7b435be7b 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
   * Optional placeholder interpolation
 
 ## Requirements
-  * Go 1.8 or higher. We aim to support the 3 latest versions of Go.
+  * Go 1.9 or higher. We aim to support the 3 latest versions of Go.
   * MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
 
 ---------------------------------------

From bc5e6eaa6d4fc8a7c8668050c7b1813afeafcabe Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Vicent=20Mart=C3=AD?= <vicent@github.com>
Date: Fri, 29 Mar 2019 09:29:56 +0100
Subject: [PATCH 089/282] check connection liveness before writing query (#934)

This commit contains a potential fix to the issue reported in #657.

As a summary: when a MySQL server kills a connection on the server-side
(either because it is actively pruning connections, or because the
connection has hit the server-side timeout), the Go MySQL client does
not immediately become aware of the connection being dead.

Because of the way TCP works, the client cannot know that the connection
has received a RST packet from the server (i.e. the server-side has
closed) until it actually reads from it. This causes an unfortunate bug
wherein a MySQL idle connection is pulled from the connection pool, a
query packet is written to it without error, and then the query fails
with an "unexpected EOF" error when trying to read the response packet.

Since the initial write to the socket does not fail with an error, it is
generally not safe to return `driver.ErrBadConn` when the read fails,
because in theory the write could have arrived to the server and could
have been committed. Returning `ErrBadConn` could lead to duplicate
inserts on the database and data corruption because of the way the Go
SQL package performs retries.

In order to significantly reduce the circumstances where this
"unexpected EOF" error is returned for stale connections, this commit
performs a liveness check before writing a new query.

When do we check?
-----------------

This check is not performed for all writes. Go 1.10 introduced a new
`sql/driver` interface called `driver.SessionResetter`, which calls the
`ResetSession` method on any connections _when they are returned to the
connection pool_. Since performing the liveness check during
`ResetSession` is not particularly useful (the connection can spend a
long time in the pool before it's checked out again, and become stale),
we simply mark the connection with a `reset` flag instead.

This `reset` flag is then checked from `mysqlConn.writePacket` to
perform the liveness checks. This ensures that the liveness check will
only be performed for the first query on a connection that has been
checked out of the connection pool. These are pretty much the semantics
we want: a fresh connection from the pool is more likely to be stale,
and it has not performed any previous writes that could cause data
corruption. If a connection is being consistently used by the client
(i.e. through an open transaction), we do NOT perform liveness checks.
If MySQL Server kills such active connection, we want to bubble up the
error to the user because any silent retrying can and will lead to data
corruption.

Since the `ResetSession` interface is only available in Go 1.10+, the
liveness checks will only be performed starting with that Go version.

How do we check?
----------------

To perform the actual liveness test on the connection, we use the new
`syscall.Conn` interface which is available for all `net.Conn`s since Go
1.9. The `SyscallConn` method returns a `RawConn` that lets us read
directly from the connection's file descriptor using syscalls, and
skipping the default read pipeline of the Go runtime.

When reading directly from the file descriptor using `syscall.Read`, we
pass in a 1-length buffer, as passing a 0-length buffer will always
result in a 0-length read, and the 1-length buffer will never be filled
because we're not expecting any reads from MySQL before we have written
any request packets in a fresh connection.

All sockets created in the Go runtime are set to non-blocking
(O_NONBLOCK). Consequently, we can detect a socket that has been closed
on the server-side because the `read` syscall will return a 0-length read
_and_ no error.

We assume that any other errors returned from the `read` also mean the
connection is in a bad state, except for `EAGAIN`/`EWOULDBLOCK`, which is
the expected return for a healthy non-blocking socket in this circumstance.

Because of the dependency on `syscall.Conn`, liveness checks can only be
performed in Go 1.9+. This restriction however overlaps with the fact
that we only mark connections as having been reset in Go 1.10+, as
explained in the previous section.
---
 AUTHORS              |  1 +
 conncheck.go         | 53 ++++++++++++++++++++++++++++++++++++++++++++
 conncheck_test.go    | 38 +++++++++++++++++++++++++++++++
 conncheck_windows.go | 15 +++++++++++++
 connection.go        |  3 +++
 packets.go           | 20 +++++++++++++++++
 6 files changed, 130 insertions(+)
 create mode 100644 conncheck.go
 create mode 100644 conncheck_test.go
 create mode 100644 conncheck_windows.go

diff --git a/AUTHORS b/AUTHORS
index 146cdffdd..fb6668346 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -88,6 +88,7 @@ Zhenye Xie <xiezhenye at gmail.com>
 
 Barracuda Networks, Inc.
 Counting Ltd.
+GitHub Inc.
 Google Inc.
 InfoSum Ltd.
 Keybase Inc.
diff --git a/conncheck.go b/conncheck.go
new file mode 100644
index 000000000..fa868e84d
--- /dev/null
+++ b/conncheck.go
@@ -0,0 +1,53 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2019 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/.
+
+// +build !windows
+
+package mysql
+
+import (
+	"errors"
+	"io"
+	"net"
+	"syscall"
+)
+
+var errUnexpectedRead = errors.New("unexpected read from socket")
+
+func connCheck(c net.Conn) error {
+	var (
+		n    int
+		err  error
+		buff [1]byte
+	)
+
+	sconn, ok := c.(syscall.Conn)
+	if !ok {
+		return nil
+	}
+	rc, err := sconn.SyscallConn()
+	if err != nil {
+		return err
+	}
+	rerr := rc.Read(func(fd uintptr) bool {
+		n, err = syscall.Read(int(fd), buff[:])
+		return true
+	})
+	switch {
+	case rerr != nil:
+		return rerr
+	case n == 0 && err == nil:
+		return io.EOF
+	case n > 0:
+		return errUnexpectedRead
+	case err == syscall.EAGAIN || err == syscall.EWOULDBLOCK:
+		return nil
+	default:
+		return err
+	}
+}
diff --git a/conncheck_test.go b/conncheck_test.go
new file mode 100644
index 000000000..b7234b0f5
--- /dev/null
+++ b/conncheck_test.go
@@ -0,0 +1,38 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2013 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/.
+
+// +build go1.10,!windows
+
+package mysql
+
+import (
+	"testing"
+	"time"
+)
+
+func TestStaleConnectionChecks(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec("SET @@SESSION.wait_timeout = 2")
+
+		if err := dbt.db.Ping(); err != nil {
+			dbt.Fatal(err)
+		}
+
+		// wait for MySQL to close our connection
+		time.Sleep(3 * time.Second)
+
+		tx, err := dbt.db.Begin()
+		if err != nil {
+			dbt.Fatal(err)
+		}
+
+		if err := tx.Rollback(); err != nil {
+			dbt.Fatal(err)
+		}
+	})
+}
diff --git a/conncheck_windows.go b/conncheck_windows.go
new file mode 100644
index 000000000..3d9e63f66
--- /dev/null
+++ b/conncheck_windows.go
@@ -0,0 +1,15 @@
+package mysql
+
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2019 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/.
+
+import "net"
+
+func connCheck(c net.Conn) error {
+	return nil
+}
diff --git a/connection.go b/connection.go
index fc4ec7597..265fd4e47 100644
--- a/connection.go
+++ b/connection.go
@@ -22,6 +22,7 @@ import (
 type mysqlConn struct {
 	buf              buffer
 	netConn          net.Conn
+	rawConn          net.Conn // underlying connection when netConn is TLS connection.
 	affectedRows     uint64
 	insertId         uint64
 	cfg              *Config
@@ -32,6 +33,7 @@ type mysqlConn struct {
 	status           statusFlag
 	sequence         uint8
 	parseTime        bool
+	reset            bool // set when the Go SQL package calls ResetSession
 
 	// for context support (Go 1.8+)
 	watching bool
@@ -639,5 +641,6 @@ func (mc *mysqlConn) ResetSession(ctx context.Context) error {
 	if mc.closed.IsSet() {
 		return driver.ErrBadConn
 	}
+	mc.reset = true
 	return nil
 }
diff --git a/packets.go b/packets.go
index 5e0853767..70d0d71f5 100644
--- a/packets.go
+++ b/packets.go
@@ -96,6 +96,25 @@ func (mc *mysqlConn) writePacket(data []byte) error {
 		return ErrPktTooLarge
 	}
 
+	// Perform a stale connection check. We only perform this check for
+	// the first query on a connection that has been checked out of the
+	// connection pool: a fresh connection from the pool is more likely
+	// to be stale, and it has not performed any previous writes that
+	// could cause data corruption, so it's safe to return ErrBadConn
+	// if the check fails.
+	if mc.reset {
+		mc.reset = false
+		conn := mc.netConn
+		if mc.rawConn != nil {
+			conn = mc.rawConn
+		}
+		if err := connCheck(conn); err != nil {
+			errLog.Print("closing bad idle connection: ", err)
+			mc.Close()
+			return driver.ErrBadConn
+		}
+	}
+
 	for {
 		var size int
 		if pktLen >= maxPacketSize {
@@ -332,6 +351,7 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 		if err := tlsConn.Handshake(); err != nil {
 			return err
 		}
+		mc.rawConn = mc.netConn
 		mc.netConn = tlsConn
 		mc.buf.nc = tlsConn
 	}

From c0f6b444ad8f92ecd68f86b27635c1fccdede4c4 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Sat, 30 Mar 2019 12:22:41 +0900
Subject: [PATCH 090/282] travis: add macOS test (#939)

---
 .travis.yml | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/.travis.yml b/.travis.yml
index 8f653936b..eae311b14 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -99,6 +99,27 @@ matrix:
         - export MYSQL_TEST_ADDR=127.0.0.1:3307
         - export MYSQL_TEST_CONCURRENT=1
 
+    - os: osx
+      osx_image: xcode10.1
+      addons:
+        homebrew:
+          packages:
+            - mysql
+      go: 1.12.x
+      before_install:
+        - go get golang.org/x/tools/cmd/cover
+        - go get github.com/mattn/goveralls
+      before_script:
+        - echo -e "[server]\ninnodb_log_file_size=256MB\ninnodb_buffer_pool_size=512MB\nmax_allowed_packet=16MB\nlocal_infile=1" >> /usr/local/etc/my.cnf
+        - mysql.server start
+        - mysql -uroot -e 'CREATE USER gotest IDENTIFIED BY "secret"'
+        - mysql -uroot -e 'GRANT ALL ON *.* TO gotest'
+        - mysql -uroot -e 'create database gotest;'
+        - export MYSQL_TEST_USER=gotest
+        - export MYSQL_TEST_PASS=secret
+        - export MYSQL_TEST_ADDR=127.0.0.1:3306
+        - export MYSQL_TEST_CONCURRENT=1
+
 script:
   - go test -v -covermode=count -coverprofile=coverage.out
   - go vet ./...

From df597a2343f1ef9dba230c7cf008fc31ca2b5d90 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Vicent=20Mart=C3=AD?= <vicent@github.com>
Date: Thu, 4 Apr 2019 12:18:22 +0200
Subject: [PATCH 091/282] buffer: Use a double-buffering scheme to prevent data
 races (#943)

Fixes #903

Co-Authored-By: vmg <vicent@github.com>
---
 benchmark_test.go | 54 ++++++++++++++++++++++++++++++++++++++++++++++
 buffer.go         | 48 ++++++++++++++++++++++++++++++-----------
 driver_test.go    | 55 +++++++++++++++++++++++++++++++++++++++++++++++
 rows.go           |  7 ++++++
 4 files changed, 151 insertions(+), 13 deletions(-)

diff --git a/benchmark_test.go b/benchmark_test.go
index 5828d40f9..3e25a3bf2 100644
--- a/benchmark_test.go
+++ b/benchmark_test.go
@@ -317,3 +317,57 @@ func BenchmarkExecContext(b *testing.B) {
 		})
 	}
 }
+
+// BenchmarkQueryRawBytes benchmarks fetching 100 blobs using sql.RawBytes.
+// "size=" means size of each blobs.
+func BenchmarkQueryRawBytes(b *testing.B) {
+	var sizes []int = []int{100, 1000, 2000, 4000, 8000, 12000, 16000, 32000, 64000, 256000}
+	db := initDB(b,
+		"DROP TABLE IF EXISTS bench_rawbytes",
+		"CREATE TABLE bench_rawbytes (id INT PRIMARY KEY, val LONGBLOB)",
+	)
+	defer db.Close()
+
+	blob := make([]byte, sizes[len(sizes)-1])
+	for i := range blob {
+		blob[i] = 42
+	}
+	for i := 0; i < 100; i++ {
+		_, err := db.Exec("INSERT INTO bench_rawbytes VALUES (?, ?)", i, blob)
+		if err != nil {
+			b.Fatal(err)
+		}
+	}
+
+	for _, s := range sizes {
+		b.Run(fmt.Sprintf("size=%v", s), func(b *testing.B) {
+			db.SetMaxIdleConns(0)
+			db.SetMaxIdleConns(1)
+			b.ReportAllocs()
+			b.ResetTimer()
+
+			for j := 0; j < b.N; j++ {
+				rows, err := db.Query("SELECT LEFT(val, ?) as v FROM bench_rawbytes", s)
+				if err != nil {
+					b.Fatal(err)
+				}
+				nrows := 0
+				for rows.Next() {
+					var buf sql.RawBytes
+					err := rows.Scan(&buf)
+					if err != nil {
+						b.Fatal(err)
+					}
+					if len(buf) != s {
+						b.Fatalf("size mismatch: expected %v, got %v", s, len(buf))
+					}
+					nrows++
+				}
+				rows.Close()
+				if nrows != 100 {
+					b.Fatalf("numbers of rows mismatch: expected %v, got %v", 100, nrows)
+				}
+			}
+		})
+	}
+}
diff --git a/buffer.go b/buffer.go
index 19486bd6f..0774c5c8c 100644
--- a/buffer.go
+++ b/buffer.go
@@ -15,47 +15,69 @@ import (
 )
 
 const defaultBufSize = 4096
+const maxCachedBufSize = 256 * 1024
 
 // A buffer which is used for both reading and writing.
 // This is possible since communication on each connection is synchronous.
 // In other words, we can't write and read simultaneously on the same connection.
 // The buffer is similar to bufio.Reader / Writer but zero-copy-ish
 // Also highly optimized for this particular use case.
+// This buffer is backed by two byte slices in a double-buffering scheme
 type buffer struct {
 	buf     []byte // buf is a byte buffer who's length and capacity are equal.
 	nc      net.Conn
 	idx     int
 	length  int
 	timeout time.Duration
+	dbuf    [2][]byte // dbuf is an array with the two byte slices that back this buffer
+	flipcnt uint      // flipccnt is the current buffer counter for double-buffering
 }
 
 // newBuffer allocates and returns a new buffer.
 func newBuffer(nc net.Conn) buffer {
+	fg := make([]byte, defaultBufSize)
 	return buffer{
-		buf: make([]byte, defaultBufSize),
-		nc:  nc,
+		buf:  fg,
+		nc:   nc,
+		dbuf: [2][]byte{fg, nil},
 	}
 }
 
+// flip replaces the active buffer with the background buffer
+// this is a delayed flip that simply increases the buffer counter;
+// the actual flip will be performed the next time we call `buffer.fill`
+func (b *buffer) flip() {
+	b.flipcnt += 1
+}
+
 // fill reads into the buffer until at least _need_ bytes are in it
 func (b *buffer) fill(need int) error {
 	n := b.length
+	// fill data into its double-buffering target: if we've called
+	// flip on this buffer, we'll be copying to the background buffer,
+	// and then filling it with network data; otherwise we'll just move
+	// the contents of the current buffer to the front before filling it
+	dest := b.dbuf[b.flipcnt&1]
+
+	// grow buffer if necessary to fit the whole packet.
+	if need > len(dest) {
+		// Round up to the next multiple of the default size
+		dest = make([]byte, ((need/defaultBufSize)+1)*defaultBufSize)
 
-	// move existing data to the beginning
-	if n > 0 && b.idx > 0 {
-		copy(b.buf[0:n], b.buf[b.idx:])
+		// if the allocated buffer is not too large, move it to backing storage
+		// to prevent extra allocations on applications that perform large reads
+		if len(dest) <= maxCachedBufSize {
+			b.dbuf[b.flipcnt&1] = dest
+		}
 	}
 
-	// grow buffer if necessary
-	// TODO: let the buffer shrink again at some point
-	//       Maybe keep the org buf slice and swap back?
-	if need > len(b.buf) {
-		// Round up to the next multiple of the default size
-		newBuf := make([]byte, ((need/defaultBufSize)+1)*defaultBufSize)
-		copy(newBuf, b.buf)
-		b.buf = newBuf
+	// if we're filling the fg buffer, move the existing data to the start of it.
+	// if we're filling the bg buffer, copy over the data
+	if n > 0 {
+		copy(dest[:n], b.buf[b.idx:])
 	}
 
+	b.buf = dest
 	b.idx = 0
 
 	for {
diff --git a/driver_test.go b/driver_test.go
index c35588a09..9c3d286ce 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -2938,3 +2938,58 @@ func TestValuerWithValueReceiverGivenNilValue(t *testing.T) {
 		// This test will panic on the INSERT if ConvertValue() does not check for typed nil before calling Value()
 	})
 }
+
+// TestRawBytesAreNotModified checks for a race condition that arises when a query context
+// is canceled while a user is calling rows.Scan. This is a more stringent test than the one
+// proposed in https://github.com/golang/go/issues/23519. Here we're explicitly using
+// `sql.RawBytes` to check the contents of our internal buffers are not modified after an implicit
+// call to `Rows.Close`, so Context cancellation should **not** invalidate the backing buffers.
+func TestRawBytesAreNotModified(t *testing.T) {
+	const blob = "abcdefghijklmnop"
+	const contextRaceIterations = 20
+	const blobSize = defaultBufSize * 3 / 4 // Second row overwrites first row.
+	const insertRows = 4
+
+	var sqlBlobs = [2]string{
+		strings.Repeat(blob, blobSize/len(blob)),
+		strings.Repeat(strings.ToUpper(blob), blobSize/len(blob)),
+	}
+
+	runTests(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec("CREATE TABLE test (id int, value BLOB) CHARACTER SET utf8")
+		for i := 0; i < insertRows; i++ {
+			dbt.mustExec("INSERT INTO test VALUES (?, ?)", i+1, sqlBlobs[i&1])
+		}
+
+		for i := 0; i < contextRaceIterations; i++ {
+			func() {
+				ctx, cancel := context.WithCancel(context.Background())
+				defer cancel()
+
+				rows, err := dbt.db.QueryContext(ctx, `SELECT id, value FROM test`)
+				if err != nil {
+					t.Fatal(err)
+				}
+
+				var b int
+				var raw sql.RawBytes
+				for rows.Next() {
+					if err := rows.Scan(&b, &raw); err != nil {
+						t.Fatal(err)
+					}
+
+					before := string(raw)
+					// Ensure cancelling the query does not corrupt the contents of `raw`
+					cancel()
+					time.Sleep(time.Microsecond * 100)
+					after := string(raw)
+
+					if before != after {
+						t.Fatalf("the backing storage for sql.RawBytes has been modified (i=%v)", i)
+					}
+				}
+				rows.Close()
+			}()
+		}
+	})
+}
diff --git a/rows.go b/rows.go
index d3b1e2822..888bdb5f0 100644
--- a/rows.go
+++ b/rows.go
@@ -111,6 +111,13 @@ func (rows *mysqlRows) Close() (err error) {
 		return err
 	}
 
+	// flip the buffer for this connection if we need to drain it.
+	// note that for a successful query (i.e. one where rows.next()
+	// has been called until it returns false), `rows.mc` will be nil
+	// by the time the user calls `(*Rows).Close`, so we won't reach this
+	// see: https://github.com/golang/go/commit/651ddbdb5056ded455f47f9c494c67b389622a47
+	mc.buf.flip()
+
 	// Remove unread packets from stream
 	if !rows.rs.done {
 		err = mc.readUntilEOF()

From 89ec2a9ec85e27afb0f0fcd3c2399e72cc360ae4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Vicent=20Mart=C3=AD?= <vicent@github.com>
Date: Thu, 4 Apr 2019 15:56:37 +0200
Subject: [PATCH 092/282] Support Go 1.10 Connector interface (#941)

---
 appengine.go         |   7 ++-
 connector.go         | 143 +++++++++++++++++++++++++++++++++++++++++++
 driver.go            | 138 ++++++++---------------------------------
 driver_go110.go      |  37 +++++++++++
 driver_go110_test.go | 137 +++++++++++++++++++++++++++++++++++++++++
 driver_test.go       |   7 ++-
 dsn.go               |  21 +++++++
 dsn_test.go          |  40 ++++++++++++
 8 files changed, 415 insertions(+), 115 deletions(-)
 create mode 100644 connector.go
 create mode 100644 driver_go110.go
 create mode 100644 driver_go110_test.go

diff --git a/appengine.go b/appengine.go
index be41f2ee6..44c0fd7c7 100644
--- a/appengine.go
+++ b/appengine.go
@@ -11,9 +11,14 @@
 package mysql
 
 import (
+	"context"
+
 	"google.golang.org/appengine/cloudsql"
 )
 
 func init() {
-	RegisterDial("cloudsql", cloudsql.Dial)
+	RegisterDialContext("cloudsql", func(_ context.Context, instance addr) (net.Conn, error) {
+		// XXX: the cloudsql driver still does not export a Context-aware dialer.
+		return cloudsql.Dial(instance)
+	})
 }
diff --git a/connector.go b/connector.go
new file mode 100644
index 000000000..5aaaba43e
--- /dev/null
+++ b/connector.go
@@ -0,0 +1,143 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2018 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 (
+	"context"
+	"database/sql/driver"
+	"net"
+)
+
+type connector struct {
+	cfg *Config // immutable private copy.
+}
+
+// Connect implements driver.Connector interface.
+// Connect returns a connection to the database.
+func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
+	var err error
+
+	// New mysqlConn
+	mc := &mysqlConn{
+		maxAllowedPacket: maxPacketSize,
+		maxWriteSize:     maxPacketSize - 1,
+		closech:          make(chan struct{}),
+		cfg:              c.cfg,
+	}
+	mc.parseTime = mc.cfg.ParseTime
+
+	// Connect to Server
+	dialsLock.RLock()
+	dial, ok := dials[mc.cfg.Net]
+	dialsLock.RUnlock()
+	if ok {
+		mc.netConn, err = dial(ctx, mc.cfg.Addr)
+	} else {
+		nd := net.Dialer{Timeout: mc.cfg.Timeout}
+		mc.netConn, err = nd.DialContext(ctx, mc.cfg.Net, mc.cfg.Addr)
+	}
+
+	if err != nil {
+		if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
+			errLog.Print("net.Error from Dial()': ", nerr.Error())
+			return nil, driver.ErrBadConn
+		}
+		return nil, err
+	}
+
+	// Enable TCP Keepalives on TCP connections
+	if tc, ok := mc.netConn.(*net.TCPConn); ok {
+		if err := tc.SetKeepAlive(true); err != nil {
+			// Don't send COM_QUIT before handshake.
+			mc.netConn.Close()
+			mc.netConn = nil
+			return nil, err
+		}
+	}
+
+	// Call startWatcher for context support (From Go 1.8)
+	mc.startWatcher()
+	if err := mc.watchCancel(ctx); err != nil {
+		return nil, err
+	}
+	defer mc.finish()
+
+	mc.buf = newBuffer(mc.netConn)
+
+	// Set I/O timeouts
+	mc.buf.timeout = mc.cfg.ReadTimeout
+	mc.writeTimeout = mc.cfg.WriteTimeout
+
+	// Reading Handshake Initialization Packet
+	authData, plugin, err := mc.readHandshakePacket()
+	if err != nil {
+		mc.cleanup()
+		return nil, err
+	}
+
+	if plugin == "" {
+		plugin = defaultAuthPlugin
+	}
+
+	// Send Client Authentication Packet
+	authResp, err := mc.auth(authData, plugin)
+	if err != nil {
+		// try the default auth plugin, if using the requested plugin failed
+		errLog.Print("could not use requested auth plugin '"+plugin+"': ", err.Error())
+		plugin = defaultAuthPlugin
+		authResp, err = mc.auth(authData, plugin)
+		if err != nil {
+			mc.cleanup()
+			return nil, err
+		}
+	}
+	if err = mc.writeHandshakeResponsePacket(authResp, plugin); err != nil {
+		mc.cleanup()
+		return nil, err
+	}
+
+	// Handle response to auth packet, switch methods if possible
+	if err = mc.handleAuthResult(authData, plugin); err != nil {
+		// Authentication failed and MySQL has already closed the connection
+		// (https://dev.mysql.com/doc/internals/en/authentication-fails.html).
+		// Do not send COM_QUIT, just cleanup and return the error.
+		mc.cleanup()
+		return nil, err
+	}
+
+	if mc.cfg.MaxAllowedPacket > 0 {
+		mc.maxAllowedPacket = mc.cfg.MaxAllowedPacket
+	} else {
+		// Get max allowed packet size
+		maxap, err := mc.getSystemVar("max_allowed_packet")
+		if err != nil {
+			mc.Close()
+			return nil, err
+		}
+		mc.maxAllowedPacket = stringToInt(maxap) - 1
+	}
+	if mc.maxAllowedPacket < maxPacketSize {
+		mc.maxWriteSize = mc.maxAllowedPacket
+	}
+
+	// Handle DSN Params
+	err = mc.handleParams()
+	if err != nil {
+		mc.Close()
+		return nil, err
+	}
+
+	return mc, nil
+}
+
+// Driver implements driver.Connector interface.
+// Driver returns &MySQLDriver{}.
+func (c *connector) Driver() driver.Driver {
+	return &MySQLDriver{}
+}
diff --git a/driver.go b/driver.go
index 9f4967087..1f9decf80 100644
--- a/driver.go
+++ b/driver.go
@@ -17,6 +17,7 @@
 package mysql
 
 import (
+	"context"
 	"database/sql"
 	"database/sql/driver"
 	"net"
@@ -29,139 +30,54 @@ type MySQLDriver struct{}
 
 // DialFunc is a function which can be used to establish the network connection.
 // Custom dial functions must be registered with RegisterDial
+//
+// Deprecated: users should register a DialContextFunc instead
 type DialFunc func(addr string) (net.Conn, error)
 
+// DialContextFunc is a function which can be used to establish the network connection.
+// Custom dial functions must be registered with RegisterDialContext
+type DialContextFunc func(ctx context.Context, addr string) (net.Conn, error)
+
 var (
 	dialsLock sync.RWMutex
-	dials     map[string]DialFunc
+	dials     map[string]DialContextFunc
 )
 
-// RegisterDial registers a custom dial function. It can then be used by the
+// RegisterDialContext registers a custom dial function. It can then be used by the
 // network address mynet(addr), where mynet is the registered new network.
-// addr is passed as a parameter to the dial function.
-func RegisterDial(net string, dial DialFunc) {
+// The current context for the connection and its address is passed to the dial function.
+func RegisterDialContext(net string, dial DialContextFunc) {
 	dialsLock.Lock()
 	defer dialsLock.Unlock()
 	if dials == nil {
-		dials = make(map[string]DialFunc)
+		dials = make(map[string]DialContextFunc)
 	}
 	dials[net] = dial
 }
 
+// RegisterDial registers a custom dial function. It can then be used by the
+// network address mynet(addr), where mynet is the registered new network.
+// addr is passed as a parameter to the dial function.
+//
+// Deprecated: users should call RegisterDialContext instead
+func RegisterDial(network string, dial DialFunc) {
+	RegisterDialContext(network, func(_ context.Context, addr string) (net.Conn, error) {
+		return dial(addr)
+	})
+}
+
 // Open new Connection.
 // See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how
 // the DSN string is formatted
 func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
-	var err error
-
-	// New mysqlConn
-	mc := &mysqlConn{
-		maxAllowedPacket: maxPacketSize,
-		maxWriteSize:     maxPacketSize - 1,
-		closech:          make(chan struct{}),
-	}
-	mc.cfg, err = ParseDSN(dsn)
-	if err != nil {
-		return nil, err
-	}
-	mc.parseTime = mc.cfg.ParseTime
-
-	// Connect to Server
-	dialsLock.RLock()
-	dial, ok := dials[mc.cfg.Net]
-	dialsLock.RUnlock()
-	if ok {
-		mc.netConn, err = dial(mc.cfg.Addr)
-	} else {
-		nd := net.Dialer{Timeout: mc.cfg.Timeout}
-		mc.netConn, err = nd.Dial(mc.cfg.Net, mc.cfg.Addr)
-	}
-	if err != nil {
-		if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
-			errLog.Print("net.Error from Dial()': ", nerr.Error())
-			return nil, driver.ErrBadConn
-		}
-		return nil, err
-	}
-
-	// Enable TCP Keepalives on TCP connections
-	if tc, ok := mc.netConn.(*net.TCPConn); ok {
-		if err := tc.SetKeepAlive(true); err != nil {
-			// Don't send COM_QUIT before handshake.
-			mc.netConn.Close()
-			mc.netConn = nil
-			return nil, err
-		}
-	}
-
-	// Call startWatcher for context support (From Go 1.8)
-	mc.startWatcher()
-
-	mc.buf = newBuffer(mc.netConn)
-
-	// Set I/O timeouts
-	mc.buf.timeout = mc.cfg.ReadTimeout
-	mc.writeTimeout = mc.cfg.WriteTimeout
-
-	// Reading Handshake Initialization Packet
-	authData, plugin, err := mc.readHandshakePacket()
+	cfg, err := ParseDSN(dsn)
 	if err != nil {
-		mc.cleanup()
 		return nil, err
 	}
-	if plugin == "" {
-		plugin = defaultAuthPlugin
+	c := &connector{
+		cfg: cfg,
 	}
-
-	// Send Client Authentication Packet
-	authResp, err := mc.auth(authData, plugin)
-	if err != nil {
-		// try the default auth plugin, if using the requested plugin failed
-		errLog.Print("could not use requested auth plugin '"+plugin+"': ", err.Error())
-		plugin = defaultAuthPlugin
-		authResp, err = mc.auth(authData, plugin)
-		if err != nil {
-			mc.cleanup()
-			return nil, err
-		}
-	}
-	if err = mc.writeHandshakeResponsePacket(authResp, plugin); err != nil {
-		mc.cleanup()
-		return nil, err
-	}
-
-	// Handle response to auth packet, switch methods if possible
-	if err = mc.handleAuthResult(authData, plugin); err != nil {
-		// Authentication failed and MySQL has already closed the connection
-		// (https://dev.mysql.com/doc/internals/en/authentication-fails.html).
-		// Do not send COM_QUIT, just cleanup and return the error.
-		mc.cleanup()
-		return nil, err
-	}
-
-	if mc.cfg.MaxAllowedPacket > 0 {
-		mc.maxAllowedPacket = mc.cfg.MaxAllowedPacket
-	} else {
-		// Get max allowed packet size
-		maxap, err := mc.getSystemVar("max_allowed_packet")
-		if err != nil {
-			mc.Close()
-			return nil, err
-		}
-		mc.maxAllowedPacket = stringToInt(maxap) - 1
-	}
-	if mc.maxAllowedPacket < maxPacketSize {
-		mc.maxWriteSize = mc.maxAllowedPacket
-	}
-
-	// Handle DSN Params
-	err = mc.handleParams()
-	if err != nil {
-		mc.Close()
-		return nil, err
-	}
-
-	return mc, nil
+	return c.Connect(context.Background())
 }
 
 func init() {
diff --git a/driver_go110.go b/driver_go110.go
new file mode 100644
index 000000000..eb5a8fe9b
--- /dev/null
+++ b/driver_go110.go
@@ -0,0 +1,37 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2018 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/.
+
+// +build go1.10
+
+package mysql
+
+import (
+	"database/sql/driver"
+)
+
+// NewConnector returns new driver.Connector.
+func NewConnector(cfg *Config) (driver.Connector, error) {
+	cfg = cfg.Clone()
+	// normalize the contents of cfg so calls to NewConnector have the same
+	// behavior as MySQLDriver.OpenConnector
+	if err := cfg.normalize(); err != nil {
+		return nil, err
+	}
+	return &connector{cfg: cfg}, nil
+}
+
+// OpenConnector implements driver.DriverContext.
+func (d MySQLDriver) OpenConnector(dsn string) (driver.Connector, error) {
+	cfg, err := ParseDSN(dsn)
+	if err != nil {
+		return nil, err
+	}
+	return &connector{
+		cfg: cfg,
+	}, nil
+}
diff --git a/driver_go110_test.go b/driver_go110_test.go
new file mode 100644
index 000000000..19a0e5956
--- /dev/null
+++ b/driver_go110_test.go
@@ -0,0 +1,137 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2018 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/.
+
+// +build go1.10
+
+package mysql
+
+import (
+	"context"
+	"database/sql"
+	"database/sql/driver"
+	"fmt"
+	"net"
+	"testing"
+	"time"
+)
+
+var _ driver.DriverContext = &MySQLDriver{}
+
+type dialCtxKey struct{}
+
+func TestConnectorObeysDialTimeouts(t *testing.T) {
+	if !available {
+		t.Skipf("MySQL server not running on %s", netAddr)
+	}
+
+	RegisterDialContext("dialctxtest", func(ctx context.Context, addr string) (net.Conn, error) {
+		var d net.Dialer
+		if !ctx.Value(dialCtxKey{}).(bool) {
+			return nil, fmt.Errorf("test error: query context is not propagated to our dialer")
+		}
+		return d.DialContext(ctx, prot, addr)
+	})
+
+	db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@dialctxtest(%s)/%s?timeout=30s", user, pass, addr, dbname))
+	if err != nil {
+		t.Fatalf("error connecting: %s", err.Error())
+	}
+	defer db.Close()
+
+	ctx := context.WithValue(context.Background(), dialCtxKey{}, true)
+
+	_, err = db.ExecContext(ctx, "DO 1")
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func configForTests(t *testing.T) *Config {
+	if !available {
+		t.Skipf("MySQL server not running on %s", netAddr)
+	}
+
+	mycnf := NewConfig()
+	mycnf.User = user
+	mycnf.Passwd = pass
+	mycnf.Addr = addr
+	mycnf.Net = prot
+	mycnf.DBName = dbname
+	return mycnf
+}
+
+func TestNewConnector(t *testing.T) {
+	mycnf := configForTests(t)
+	conn, err := NewConnector(mycnf)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	db := sql.OpenDB(conn)
+	defer db.Close()
+
+	if err := db.Ping(); err != nil {
+		t.Fatal(err)
+	}
+}
+
+type slowConnection struct {
+	net.Conn
+	slowdown time.Duration
+}
+
+func (sc *slowConnection) Read(b []byte) (int, error) {
+	time.Sleep(sc.slowdown)
+	return sc.Conn.Read(b)
+}
+
+type connectorHijack struct {
+	driver.Connector
+	connErr error
+}
+
+func (cw *connectorHijack) Connect(ctx context.Context) (driver.Conn, error) {
+	var conn driver.Conn
+	conn, cw.connErr = cw.Connector.Connect(ctx)
+	return conn, cw.connErr
+}
+
+func TestConnectorTimeoutsDuringOpen(t *testing.T) {
+	RegisterDialContext("slowconn", func(ctx context.Context, addr string) (net.Conn, error) {
+		var d net.Dialer
+		conn, err := d.DialContext(ctx, prot, addr)
+		if err != nil {
+			return nil, err
+		}
+		return &slowConnection{Conn: conn, slowdown: 100 * time.Millisecond}, nil
+	})
+
+	mycnf := configForTests(t)
+	mycnf.Net = "slowconn"
+
+	conn, err := NewConnector(mycnf)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	hijack := &connectorHijack{Connector: conn}
+
+	db := sql.OpenDB(hijack)
+	defer db.Close()
+
+	ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
+	defer cancel()
+
+	_, err = db.ExecContext(ctx, "DO 1")
+	if err != context.DeadlineExceeded {
+		t.Fatalf("ExecContext should have timed out")
+	}
+	if hijack.connErr != context.DeadlineExceeded {
+		t.Fatalf("(*Connector).Connect should have timed out")
+	}
+}
diff --git a/driver_test.go b/driver_test.go
index 9c3d286ce..b45a81eb1 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -1846,7 +1846,7 @@ func TestConcurrent(t *testing.T) {
 }
 
 func testDialError(t *testing.T, dialErr error, expectErr error) {
-	RegisterDial("mydial", func(addr string) (net.Conn, error) {
+	RegisterDialContext("mydial", func(ctx context.Context, addr string) (net.Conn, error) {
 		return nil, dialErr
 	})
 
@@ -1884,8 +1884,9 @@ func TestCustomDial(t *testing.T) {
 	}
 
 	// our custom dial function which justs wraps net.Dial here
-	RegisterDial("mydial", func(addr string) (net.Conn, error) {
-		return net.Dial(prot, addr)
+	RegisterDialContext("mydial", func(ctx context.Context, addr string) (net.Conn, error) {
+		var d net.Dialer
+		return d.DialContext(ctx, prot, addr)
 	})
 
 	db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@mydial(%s)/%s?timeout=30s", user, pass, addr, dbname))
diff --git a/dsn.go b/dsn.go
index b9134722e..6e19ab717 100644
--- a/dsn.go
+++ b/dsn.go
@@ -14,6 +14,7 @@ import (
 	"crypto/tls"
 	"errors"
 	"fmt"
+	"math/big"
 	"net"
 	"net/url"
 	"sort"
@@ -72,6 +73,26 @@ func NewConfig() *Config {
 	}
 }
 
+func (cfg *Config) Clone() *Config {
+	cp := *cfg
+	if cp.tls != nil {
+		cp.tls = cfg.tls.Clone()
+	}
+	if len(cp.Params) > 0 {
+		cp.Params = make(map[string]string, len(cfg.Params))
+		for k, v := range cfg.Params {
+			cp.Params[k] = v
+		}
+	}
+	if cfg.pubKey != nil {
+		cp.pubKey = &rsa.PublicKey{
+			N: new(big.Int).Set(cfg.pubKey.N),
+			E: cfg.pubKey.E,
+		}
+	}
+	return &cp
+}
+
 func (cfg *Config) normalize() error {
 	if cfg.InterpolateParams && unsafeCollations[cfg.Collation] {
 		return errInvalidDSNUnsafeCollation
diff --git a/dsn_test.go b/dsn_test.go
index 1cd095496..280fdf61f 100644
--- a/dsn_test.go
+++ b/dsn_test.go
@@ -318,6 +318,46 @@ func TestParamsAreSorted(t *testing.T) {
 	}
 }
 
+func TestCloneConfig(t *testing.T) {
+	RegisterServerPubKey("testKey", testPubKeyRSA)
+	defer DeregisterServerPubKey("testKey")
+
+	expectedServerName := "example.com"
+	dsn := "tcp(example.com:1234)/?tls=true&foobar=baz&serverPubKey=testKey"
+	cfg, err := ParseDSN(dsn)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+
+	cfg2 := cfg.Clone()
+	if cfg == cfg2 {
+		t.Errorf("Config.Clone did not create a separate config struct")
+	}
+
+	if cfg2.tls.ServerName != expectedServerName {
+		t.Errorf("cfg.tls.ServerName should be %q, got %q (host with port)", expectedServerName, cfg.tls.ServerName)
+	}
+
+	cfg2.tls.ServerName = "example2.com"
+	if cfg.tls.ServerName == cfg2.tls.ServerName {
+		t.Errorf("changed cfg.tls.Server name should not propagate to original Config")
+	}
+
+	if _, ok := cfg2.Params["foobar"]; !ok {
+		t.Errorf("cloned Config is missing custom params")
+	}
+
+	delete(cfg2.Params, "foobar")
+
+	if _, ok := cfg.Params["foobar"]; !ok {
+		t.Errorf("custom params in cloned Config should not propagate to original Config")
+	}
+
+	if !reflect.DeepEqual(cfg.pubKey, cfg2.pubKey) {
+		t.Errorf("public key in Config should be identical")
+	}
+}
+
 func BenchmarkParseDSN(b *testing.B) {
 	b.ReportAllocs()
 

From 8f4b98d14697cfd66f2264bfa2277a26426fbaea Mon Sep 17 00:00:00 2001
From: Erwan Martin <contact@erwanmartin.fr>
Date: Fri, 5 Apr 2019 01:10:27 +0200
Subject: [PATCH 093/282] Fix regression in appengine.go (#949)

---
 AUTHORS      | 1 +
 appengine.go | 3 ++-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/AUTHORS b/AUTHORS
index fb6668346..e85106a42 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -27,6 +27,7 @@ Daniël van Eeden <git at myname.nl>
 Dave Protasowski <dprotaso at gmail.com>
 DisposaBoy <disposaboy at dby.me>
 Egor Smolyakov <egorsmkv at gmail.com>
+Erwan Martin <hello at erwan.io>
 Evan Shaw <evan at vendhq.com>
 Frederick Mayle <frederickmayle at gmail.com>
 Gustavo Kristic <gkristic at gmail.com>
diff --git a/appengine.go b/appengine.go
index 44c0fd7c7..914e6623b 100644
--- a/appengine.go
+++ b/appengine.go
@@ -12,12 +12,13 @@ package mysql
 
 import (
 	"context"
+	"net"
 
 	"google.golang.org/appengine/cloudsql"
 )
 
 func init() {
-	RegisterDialContext("cloudsql", func(_ context.Context, instance addr) (net.Conn, error) {
+	RegisterDialContext("cloudsql", func(_ context.Context, instance string) (net.Conn, error) {
 		// XXX: the cloudsql driver still does not export a Context-aware dialer.
 		return cloudsql.Dial(instance)
 	})

From d3a0b0fcd73ce97db48da235e7e17a21228cb186 Mon Sep 17 00:00:00 2001
From: Darren Hoo <darren.hoo@gmail.com>
Date: Fri, 5 Apr 2019 08:15:06 +0800
Subject: [PATCH 094/282] pass unsigned int without converting it to string
 (#838)

---
 AUTHORS            |  1 +
 connection_test.go |  4 ++--
 packets.go         | 16 ++++++++++++++++
 statement.go       | 11 ++---------
 statement_test.go  |  6 +++---
 5 files changed, 24 insertions(+), 14 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index e85106a42..cdf7ae3b8 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -35,6 +35,7 @@ Hajime Nakagami <nakagami at gmail.com>
 Hanno Braun <mail at hannobraun.com>
 Henri Yandell <flamefew at gmail.com>
 Hirotaka Yamamoto <ymmt2005 at gmail.com>
+Huyiguang <hyg at webterren.com>
 ICHINOSE Shogo <shogo82148 at gmail.com>
 Ilia Cimpoes <ichimpoesh at gmail.com>
 INADA Naoki <songofacandy at gmail.com>
diff --git a/connection_test.go b/connection_test.go
index 2a1c8e888..8e78f36c6 100644
--- a/connection_test.go
+++ b/connection_test.go
@@ -78,8 +78,8 @@ func TestCheckNamedValue(t *testing.T) {
 		t.Fatal("uint64 high-bit not convertible", err)
 	}
 
-	if value.Value != "18446744073709551615" {
-		t.Fatalf("uint64 high-bit not converted, got %#v %T", value.Value, value.Value)
+	if value.Value != ^uint64(0) {
+		t.Fatalf("uint64 high-bit converted, got %#v %T", value.Value, value.Value)
 	}
 }
 
diff --git a/packets.go b/packets.go
index 70d0d71f5..cbed325f4 100644
--- a/packets.go
+++ b/packets.go
@@ -1011,6 +1011,22 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 					)
 				}
 
+			case uint64:
+				paramTypes[i+i] = byte(fieldTypeLongLong)
+				paramTypes[i+i+1] = 0x80 // type is unsigned
+
+				if cap(paramValues)-len(paramValues)-8 >= 0 {
+					paramValues = paramValues[:len(paramValues)+8]
+					binary.LittleEndian.PutUint64(
+						paramValues[len(paramValues)-8:],
+						uint64(v),
+					)
+				} else {
+					paramValues = append(paramValues,
+						uint64ToBytes(uint64(v))...,
+					)
+				}
+
 			case float64:
 				paramTypes[i+i] = byte(fieldTypeDouble)
 				paramTypes[i+i+1] = 0x00
diff --git a/statement.go b/statement.go
index ce7fe4cd0..f7e370939 100644
--- a/statement.go
+++ b/statement.go
@@ -13,7 +13,6 @@ import (
 	"fmt"
 	"io"
 	"reflect"
-	"strconv"
 )
 
 type mysqlStmt struct {
@@ -164,14 +163,8 @@ func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
 		}
 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 		return rv.Int(), nil
-	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
-		return int64(rv.Uint()), nil
-	case reflect.Uint64:
-		u64 := rv.Uint()
-		if u64 >= 1<<63 {
-			return strconv.FormatUint(u64, 10), nil
-		}
-		return int64(u64), nil
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+		return rv.Uint(), nil
 	case reflect.Float32, reflect.Float64:
 		return rv.Float(), nil
 	case reflect.Bool:
diff --git a/statement_test.go b/statement_test.go
index 98a6c1933..4b9914f8e 100644
--- a/statement_test.go
+++ b/statement_test.go
@@ -110,7 +110,7 @@ func TestConvertUnsignedIntegers(t *testing.T) {
 			t.Fatalf("%T type not convertible %s", value, err)
 		}
 
-		if output != int64(42) {
+		if output != uint64(42) {
 			t.Fatalf("%T type not converted, got %#v %T", value, output, output)
 		}
 	}
@@ -120,7 +120,7 @@ func TestConvertUnsignedIntegers(t *testing.T) {
 		t.Fatal("uint64 high-bit not convertible", err)
 	}
 
-	if output != "18446744073709551615" {
-		t.Fatalf("uint64 high-bit not converted, got %#v %T", output, output)
+	if output != ^uint64(0) {
+		t.Fatalf("uint64 high-bit converted, got %#v %T", output, output)
 	}
 }

From 93c3765e9bb2e29c6a63277c9f829dae34ec24eb Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Fri, 5 Apr 2019 11:49:13 +0900
Subject: [PATCH 095/282] Update collations and make utf8mb4 default (#877)

---
 README.md      |   7 +-
 collations.go  | 376 +++++++++++++++++++++++++------------------------
 driver_test.go |   2 +-
 dsn_test.go    |  32 ++---
 4 files changed, 218 insertions(+), 199 deletions(-)

diff --git a/README.md b/README.md
index 7b435be7b..c6adf1d63 100644
--- a/README.md
+++ b/README.md
@@ -171,13 +171,18 @@ Unless you need the fallback behavior, please use `collation` instead.
 ```
 Type:           string
 Valid Values:   <name>
-Default:        utf8_general_ci
+Default:        utf8mb4_general_ci
 ```
 
 Sets the collation used for client-server interaction on connection. In contrast to `charset`, `collation` does not issue additional queries. If the specified collation is unavailable on the target server, the connection will fail.
 
 A list of valid charsets for a server is retrievable with `SHOW COLLATION`.
 
+The default collation (`utf8mb4_general_ci`) is supported from MySQL 5.5.  You should use an older collation (e.g. `utf8_general_ci`) for older MySQL.
+
+Collations for charset "ucs2", "utf16", "utf16le", and "utf32" can not be used ([ref](https://dev.mysql.com/doc/refman/5.7/en/charset-connection.html#charset-connection-impermissible-client-charset)).
+
+
 ##### `clientFoundRows`
 
 ```
diff --git a/collations.go b/collations.go
index 136c9e4d1..8d2b55676 100644
--- a/collations.go
+++ b/collations.go
@@ -8,183 +8,190 @@
 
 package mysql
 
-const defaultCollation = "utf8_general_ci"
+const defaultCollation = "utf8mb4_general_ci"
 const binaryCollation = "binary"
 
 // A list of available collations mapped to the internal ID.
 // To update this map use the following MySQL query:
-//     SELECT COLLATION_NAME, ID FROM information_schema.COLLATIONS
+//     SELECT COLLATION_NAME, ID FROM information_schema.COLLATIONS WHERE ID<256 ORDER BY ID
+//
+// Handshake packet have only 1 byte for collation_id.  So we can't use collations with ID > 255.
+//
+// ucs2, utf16, and utf32 can't be used for connection charset.
+// https://dev.mysql.com/doc/refman/5.7/en/charset-connection.html#charset-connection-impermissible-client-charset
+// They are commented out to reduce this map.
 var collations = map[string]byte{
-	"big5_chinese_ci":          1,
-	"latin2_czech_cs":          2,
-	"dec8_swedish_ci":          3,
-	"cp850_general_ci":         4,
-	"latin1_german1_ci":        5,
-	"hp8_english_ci":           6,
-	"koi8r_general_ci":         7,
-	"latin1_swedish_ci":        8,
-	"latin2_general_ci":        9,
-	"swe7_swedish_ci":          10,
-	"ascii_general_ci":         11,
-	"ujis_japanese_ci":         12,
-	"sjis_japanese_ci":         13,
-	"cp1251_bulgarian_ci":      14,
-	"latin1_danish_ci":         15,
-	"hebrew_general_ci":        16,
-	"tis620_thai_ci":           18,
-	"euckr_korean_ci":          19,
-	"latin7_estonian_cs":       20,
-	"latin2_hungarian_ci":      21,
-	"koi8u_general_ci":         22,
-	"cp1251_ukrainian_ci":      23,
-	"gb2312_chinese_ci":        24,
-	"greek_general_ci":         25,
-	"cp1250_general_ci":        26,
-	"latin2_croatian_ci":       27,
-	"gbk_chinese_ci":           28,
-	"cp1257_lithuanian_ci":     29,
-	"latin5_turkish_ci":        30,
-	"latin1_german2_ci":        31,
-	"armscii8_general_ci":      32,
-	"utf8_general_ci":          33,
-	"cp1250_czech_cs":          34,
-	"ucs2_general_ci":          35,
-	"cp866_general_ci":         36,
-	"keybcs2_general_ci":       37,
-	"macce_general_ci":         38,
-	"macroman_general_ci":      39,
-	"cp852_general_ci":         40,
-	"latin7_general_ci":        41,
-	"latin7_general_cs":        42,
-	"macce_bin":                43,
-	"cp1250_croatian_ci":       44,
-	"utf8mb4_general_ci":       45,
-	"utf8mb4_bin":              46,
-	"latin1_bin":               47,
-	"latin1_general_ci":        48,
-	"latin1_general_cs":        49,
-	"cp1251_bin":               50,
-	"cp1251_general_ci":        51,
-	"cp1251_general_cs":        52,
-	"macroman_bin":             53,
-	"utf16_general_ci":         54,
-	"utf16_bin":                55,
-	"utf16le_general_ci":       56,
-	"cp1256_general_ci":        57,
-	"cp1257_bin":               58,
-	"cp1257_general_ci":        59,
-	"utf32_general_ci":         60,
-	"utf32_bin":                61,
-	"utf16le_bin":              62,
-	"binary":                   63,
-	"armscii8_bin":             64,
-	"ascii_bin":                65,
-	"cp1250_bin":               66,
-	"cp1256_bin":               67,
-	"cp866_bin":                68,
-	"dec8_bin":                 69,
-	"greek_bin":                70,
-	"hebrew_bin":               71,
-	"hp8_bin":                  72,
-	"keybcs2_bin":              73,
-	"koi8r_bin":                74,
-	"koi8u_bin":                75,
-	"latin2_bin":               77,
-	"latin5_bin":               78,
-	"latin7_bin":               79,
-	"cp850_bin":                80,
-	"cp852_bin":                81,
-	"swe7_bin":                 82,
-	"utf8_bin":                 83,
-	"big5_bin":                 84,
-	"euckr_bin":                85,
-	"gb2312_bin":               86,
-	"gbk_bin":                  87,
-	"sjis_bin":                 88,
-	"tis620_bin":               89,
-	"ucs2_bin":                 90,
-	"ujis_bin":                 91,
-	"geostd8_general_ci":       92,
-	"geostd8_bin":              93,
-	"latin1_spanish_ci":        94,
-	"cp932_japanese_ci":        95,
-	"cp932_bin":                96,
-	"eucjpms_japanese_ci":      97,
-	"eucjpms_bin":              98,
-	"cp1250_polish_ci":         99,
-	"utf16_unicode_ci":         101,
-	"utf16_icelandic_ci":       102,
-	"utf16_latvian_ci":         103,
-	"utf16_romanian_ci":        104,
-	"utf16_slovenian_ci":       105,
-	"utf16_polish_ci":          106,
-	"utf16_estonian_ci":        107,
-	"utf16_spanish_ci":         108,
-	"utf16_swedish_ci":         109,
-	"utf16_turkish_ci":         110,
-	"utf16_czech_ci":           111,
-	"utf16_danish_ci":          112,
-	"utf16_lithuanian_ci":      113,
-	"utf16_slovak_ci":          114,
-	"utf16_spanish2_ci":        115,
-	"utf16_roman_ci":           116,
-	"utf16_persian_ci":         117,
-	"utf16_esperanto_ci":       118,
-	"utf16_hungarian_ci":       119,
-	"utf16_sinhala_ci":         120,
-	"utf16_german2_ci":         121,
-	"utf16_croatian_ci":        122,
-	"utf16_unicode_520_ci":     123,
-	"utf16_vietnamese_ci":      124,
-	"ucs2_unicode_ci":          128,
-	"ucs2_icelandic_ci":        129,
-	"ucs2_latvian_ci":          130,
-	"ucs2_romanian_ci":         131,
-	"ucs2_slovenian_ci":        132,
-	"ucs2_polish_ci":           133,
-	"ucs2_estonian_ci":         134,
-	"ucs2_spanish_ci":          135,
-	"ucs2_swedish_ci":          136,
-	"ucs2_turkish_ci":          137,
-	"ucs2_czech_ci":            138,
-	"ucs2_danish_ci":           139,
-	"ucs2_lithuanian_ci":       140,
-	"ucs2_slovak_ci":           141,
-	"ucs2_spanish2_ci":         142,
-	"ucs2_roman_ci":            143,
-	"ucs2_persian_ci":          144,
-	"ucs2_esperanto_ci":        145,
-	"ucs2_hungarian_ci":        146,
-	"ucs2_sinhala_ci":          147,
-	"ucs2_german2_ci":          148,
-	"ucs2_croatian_ci":         149,
-	"ucs2_unicode_520_ci":      150,
-	"ucs2_vietnamese_ci":       151,
-	"ucs2_general_mysql500_ci": 159,
-	"utf32_unicode_ci":         160,
-	"utf32_icelandic_ci":       161,
-	"utf32_latvian_ci":         162,
-	"utf32_romanian_ci":        163,
-	"utf32_slovenian_ci":       164,
-	"utf32_polish_ci":          165,
-	"utf32_estonian_ci":        166,
-	"utf32_spanish_ci":         167,
-	"utf32_swedish_ci":         168,
-	"utf32_turkish_ci":         169,
-	"utf32_czech_ci":           170,
-	"utf32_danish_ci":          171,
-	"utf32_lithuanian_ci":      172,
-	"utf32_slovak_ci":          173,
-	"utf32_spanish2_ci":        174,
-	"utf32_roman_ci":           175,
-	"utf32_persian_ci":         176,
-	"utf32_esperanto_ci":       177,
-	"utf32_hungarian_ci":       178,
-	"utf32_sinhala_ci":         179,
-	"utf32_german2_ci":         180,
-	"utf32_croatian_ci":        181,
-	"utf32_unicode_520_ci":     182,
-	"utf32_vietnamese_ci":      183,
+	"big5_chinese_ci":      1,
+	"latin2_czech_cs":      2,
+	"dec8_swedish_ci":      3,
+	"cp850_general_ci":     4,
+	"latin1_german1_ci":    5,
+	"hp8_english_ci":       6,
+	"koi8r_general_ci":     7,
+	"latin1_swedish_ci":    8,
+	"latin2_general_ci":    9,
+	"swe7_swedish_ci":      10,
+	"ascii_general_ci":     11,
+	"ujis_japanese_ci":     12,
+	"sjis_japanese_ci":     13,
+	"cp1251_bulgarian_ci":  14,
+	"latin1_danish_ci":     15,
+	"hebrew_general_ci":    16,
+	"tis620_thai_ci":       18,
+	"euckr_korean_ci":      19,
+	"latin7_estonian_cs":   20,
+	"latin2_hungarian_ci":  21,
+	"koi8u_general_ci":     22,
+	"cp1251_ukrainian_ci":  23,
+	"gb2312_chinese_ci":    24,
+	"greek_general_ci":     25,
+	"cp1250_general_ci":    26,
+	"latin2_croatian_ci":   27,
+	"gbk_chinese_ci":       28,
+	"cp1257_lithuanian_ci": 29,
+	"latin5_turkish_ci":    30,
+	"latin1_german2_ci":    31,
+	"armscii8_general_ci":  32,
+	"utf8_general_ci":      33,
+	"cp1250_czech_cs":      34,
+	//"ucs2_general_ci":          35,
+	"cp866_general_ci":    36,
+	"keybcs2_general_ci":  37,
+	"macce_general_ci":    38,
+	"macroman_general_ci": 39,
+	"cp852_general_ci":    40,
+	"latin7_general_ci":   41,
+	"latin7_general_cs":   42,
+	"macce_bin":           43,
+	"cp1250_croatian_ci":  44,
+	"utf8mb4_general_ci":  45,
+	"utf8mb4_bin":         46,
+	"latin1_bin":          47,
+	"latin1_general_ci":   48,
+	"latin1_general_cs":   49,
+	"cp1251_bin":          50,
+	"cp1251_general_ci":   51,
+	"cp1251_general_cs":   52,
+	"macroman_bin":        53,
+	//"utf16_general_ci":         54,
+	//"utf16_bin":                55,
+	//"utf16le_general_ci":       56,
+	"cp1256_general_ci": 57,
+	"cp1257_bin":        58,
+	"cp1257_general_ci": 59,
+	//"utf32_general_ci":         60,
+	//"utf32_bin":                61,
+	//"utf16le_bin":              62,
+	"binary":          63,
+	"armscii8_bin":    64,
+	"ascii_bin":       65,
+	"cp1250_bin":      66,
+	"cp1256_bin":      67,
+	"cp866_bin":       68,
+	"dec8_bin":        69,
+	"greek_bin":       70,
+	"hebrew_bin":      71,
+	"hp8_bin":         72,
+	"keybcs2_bin":     73,
+	"koi8r_bin":       74,
+	"koi8u_bin":       75,
+	"utf8_tolower_ci": 76,
+	"latin2_bin":      77,
+	"latin5_bin":      78,
+	"latin7_bin":      79,
+	"cp850_bin":       80,
+	"cp852_bin":       81,
+	"swe7_bin":        82,
+	"utf8_bin":        83,
+	"big5_bin":        84,
+	"euckr_bin":       85,
+	"gb2312_bin":      86,
+	"gbk_bin":         87,
+	"sjis_bin":        88,
+	"tis620_bin":      89,
+	//"ucs2_bin":                 90,
+	"ujis_bin":            91,
+	"geostd8_general_ci":  92,
+	"geostd8_bin":         93,
+	"latin1_spanish_ci":   94,
+	"cp932_japanese_ci":   95,
+	"cp932_bin":           96,
+	"eucjpms_japanese_ci": 97,
+	"eucjpms_bin":         98,
+	"cp1250_polish_ci":    99,
+	//"utf16_unicode_ci":         101,
+	//"utf16_icelandic_ci":       102,
+	//"utf16_latvian_ci":         103,
+	//"utf16_romanian_ci":        104,
+	//"utf16_slovenian_ci":       105,
+	//"utf16_polish_ci":          106,
+	//"utf16_estonian_ci":        107,
+	//"utf16_spanish_ci":         108,
+	//"utf16_swedish_ci":         109,
+	//"utf16_turkish_ci":         110,
+	//"utf16_czech_ci":           111,
+	//"utf16_danish_ci":          112,
+	//"utf16_lithuanian_ci":      113,
+	//"utf16_slovak_ci":          114,
+	//"utf16_spanish2_ci":        115,
+	//"utf16_roman_ci":           116,
+	//"utf16_persian_ci":         117,
+	//"utf16_esperanto_ci":       118,
+	//"utf16_hungarian_ci":       119,
+	//"utf16_sinhala_ci":         120,
+	//"utf16_german2_ci":         121,
+	//"utf16_croatian_ci":        122,
+	//"utf16_unicode_520_ci":     123,
+	//"utf16_vietnamese_ci":      124,
+	//"ucs2_unicode_ci":          128,
+	//"ucs2_icelandic_ci":        129,
+	//"ucs2_latvian_ci":          130,
+	//"ucs2_romanian_ci":         131,
+	//"ucs2_slovenian_ci":        132,
+	//"ucs2_polish_ci":           133,
+	//"ucs2_estonian_ci":         134,
+	//"ucs2_spanish_ci":          135,
+	//"ucs2_swedish_ci":          136,
+	//"ucs2_turkish_ci":          137,
+	//"ucs2_czech_ci":            138,
+	//"ucs2_danish_ci":           139,
+	//"ucs2_lithuanian_ci":       140,
+	//"ucs2_slovak_ci":           141,
+	//"ucs2_spanish2_ci":         142,
+	//"ucs2_roman_ci":            143,
+	//"ucs2_persian_ci":          144,
+	//"ucs2_esperanto_ci":        145,
+	//"ucs2_hungarian_ci":        146,
+	//"ucs2_sinhala_ci":          147,
+	//"ucs2_german2_ci":          148,
+	//"ucs2_croatian_ci":         149,
+	//"ucs2_unicode_520_ci":      150,
+	//"ucs2_vietnamese_ci":       151,
+	//"ucs2_general_mysql500_ci": 159,
+	//"utf32_unicode_ci":         160,
+	//"utf32_icelandic_ci":       161,
+	//"utf32_latvian_ci":         162,
+	//"utf32_romanian_ci":        163,
+	//"utf32_slovenian_ci":       164,
+	//"utf32_polish_ci":          165,
+	//"utf32_estonian_ci":        166,
+	//"utf32_spanish_ci":         167,
+	//"utf32_swedish_ci":         168,
+	//"utf32_turkish_ci":         169,
+	//"utf32_czech_ci":           170,
+	//"utf32_danish_ci":          171,
+	//"utf32_lithuanian_ci":      172,
+	//"utf32_slovak_ci":          173,
+	//"utf32_spanish2_ci":        174,
+	//"utf32_roman_ci":           175,
+	//"utf32_persian_ci":         176,
+	//"utf32_esperanto_ci":       177,
+	//"utf32_hungarian_ci":       178,
+	//"utf32_sinhala_ci":         179,
+	//"utf32_german2_ci":         180,
+	//"utf32_croatian_ci":        181,
+	//"utf32_unicode_520_ci":     182,
+	//"utf32_vietnamese_ci":      183,
 	"utf8_unicode_ci":          192,
 	"utf8_icelandic_ci":        193,
 	"utf8_latvian_ci":          194,
@@ -234,18 +241,25 @@ var collations = map[string]byte{
 	"utf8mb4_croatian_ci":      245,
 	"utf8mb4_unicode_520_ci":   246,
 	"utf8mb4_vietnamese_ci":    247,
+	"gb18030_chinese_ci":       248,
+	"gb18030_bin":              249,
+	"gb18030_unicode_520_ci":   250,
+	"utf8mb4_0900_ai_ci":       255,
 }
 
 // A blacklist of collations which is unsafe to interpolate parameters.
 // These multibyte encodings may contains 0x5c (`\`) in their trailing bytes.
 var unsafeCollations = map[string]bool{
-	"big5_chinese_ci":   true,
-	"sjis_japanese_ci":  true,
-	"gbk_chinese_ci":    true,
-	"big5_bin":          true,
-	"gb2312_bin":        true,
-	"gbk_bin":           true,
-	"sjis_bin":          true,
-	"cp932_japanese_ci": true,
-	"cp932_bin":         true,
+	"big5_chinese_ci":        true,
+	"sjis_japanese_ci":       true,
+	"gbk_chinese_ci":         true,
+	"big5_bin":               true,
+	"gb2312_bin":             true,
+	"gbk_bin":                true,
+	"sjis_bin":               true,
+	"cp932_japanese_ci":      true,
+	"cp932_bin":              true,
+	"gb18030_chinese_ci":     true,
+	"gb18030_bin":            true,
+	"gb18030_unicode_520_ci": true,
 }
diff --git a/driver_test.go b/driver_test.go
index b45a81eb1..3dee1bab2 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -1448,7 +1448,7 @@ func TestCollation(t *testing.T) {
 		t.Skipf("MySQL server not running on %s", netAddr)
 	}
 
-	defaultCollation := "utf8_general_ci"
+	defaultCollation := "utf8mb4_general_ci"
 	testCollations := []string{
 		"",               // do not set
 		defaultCollation, // driver default
diff --git a/dsn_test.go b/dsn_test.go
index 280fdf61f..fe1572dea 100644
--- a/dsn_test.go
+++ b/dsn_test.go
@@ -22,55 +22,55 @@ var testDSNs = []struct {
 	out *Config
 }{{
 	"username:password@protocol(address)/dbname?param=value",
-	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
+	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
 }, {
 	"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true",
-	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, ColumnsWithAlias: true},
+	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, ColumnsWithAlias: true},
 }, {
 	"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true&multiStatements=true",
-	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, ColumnsWithAlias: true, MultiStatements: true},
+	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, ColumnsWithAlias: true, MultiStatements: true},
 }, {
 	"user@unix(/path/to/socket)/dbname?charset=utf8",
-	&Config{User: "user", Net: "unix", Addr: "/path/to/socket", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
+	&Config{User: "user", Net: "unix", Addr: "/path/to/socket", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
 }, {
 	"user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true",
-	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, TLSConfig: "true"},
+	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, TLSConfig: "true"},
 }, {
 	"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify",
-	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8mb4,utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, TLSConfig: "skip-verify"},
+	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8mb4,utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, TLSConfig: "skip-verify"},
 }, {
 	"user:password@/dbname?loc=UTC&timeout=30s&readTimeout=1s&writeTimeout=1s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci&maxAllowedPacket=16777216",
 	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_unicode_ci", Loc: time.UTC, AllowNativePasswords: true, Timeout: 30 * time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, AllowAllFiles: true, AllowOldPasswords: true, ClientFoundRows: true, MaxAllowedPacket: 16777216},
 }, {
 	"user:password@/dbname?allowNativePasswords=false&maxAllowedPacket=0",
-	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: 0, AllowNativePasswords: false},
+	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: 0, AllowNativePasswords: false},
 }, {
 	"user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local",
-	&Config{User: "user", Passwd: "p@ss(word)", Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:80", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.Local, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
+	&Config{User: "user", Passwd: "p@ss(word)", Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:80", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.Local, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
 }, {
 	"/dbname",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
 }, {
 	"@/",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
 }, {
 	"/",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
 }, {
 	"",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
 }, {
 	"user:p@/ssword@/",
-	&Config{User: "user", Passwd: "p@/ssword", Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
+	&Config{User: "user", Passwd: "p@/ssword", Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
 }, {
 	"unix/?arg=%2Fsome%2Fpath.ext",
-	&Config{Net: "unix", Addr: "/tmp/mysql.sock", Params: map[string]string{"arg": "/some/path.ext"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
+	&Config{Net: "unix", Addr: "/tmp/mysql.sock", Params: map[string]string{"arg": "/some/path.ext"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
 }, {
 	"tcp(127.0.0.1)/dbname",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
 }, {
 	"tcp(de:ad:be:ef::ca:fe)/dbname",
-	&Config{Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
+	&Config{Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
 },
 }
 

From 578c4c8066964679ef44f45de2b6c7e811cc665e Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Wed, 10 Apr 2019 12:04:54 +0900
Subject: [PATCH 096/282] fix appengine (#950)

---
 conncheck.go                               | 2 +-
 conncheck_windows.go => conncheck_dummy.go | 6 ++++--
 2 files changed, 5 insertions(+), 3 deletions(-)
 rename conncheck_windows.go => conncheck_dummy.go (93%)

diff --git a/conncheck.go b/conncheck.go
index fa868e84d..cc47aa559 100644
--- a/conncheck.go
+++ b/conncheck.go
@@ -6,7 +6,7 @@
 // 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/.
 
-// +build !windows
+// +build !windows,!appengine
 
 package mysql
 
diff --git a/conncheck_windows.go b/conncheck_dummy.go
similarity index 93%
rename from conncheck_windows.go
rename to conncheck_dummy.go
index 3d9e63f66..fd01f64c9 100644
--- a/conncheck_windows.go
+++ b/conncheck_dummy.go
@@ -1,5 +1,3 @@
-package mysql
-
 // Go MySQL Driver - A MySQL-Driver for Go's database/sql package
 //
 // Copyright 2019 The Go-MySQL-Driver Authors. All rights reserved.
@@ -8,6 +6,10 @@ package mysql
 // 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/.
 
+// +build windows appengine
+
+package mysql
+
 import "net"
 
 func connCheck(c net.Conn) error {

From d0a548181995c293eb09c61ef80099ba1cdbe8f5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Vicent=20Mart=C3=AD?= <vmg@strn.cat>
Date: Tue, 23 Apr 2019 13:20:50 +0200
Subject: [PATCH 097/282] connection: interpolate uint64 parameters (#955)

PR #838 introduced a fix for the driver's custom Value Converter that
stopped emitting large uint64 `driver.Value`s as a string. Instead, now
_all_ uint{8,16,32,64} values passed to the driver are returned as
uint64, and `packets.c` now explicitly handles `driver.Value` instances
that are uint64.

However, the update in `packets.c` only applies when sending
`driver.Value` arguments to the server. When a connection is set up
using `InterpolateParams = true` and query interpolation happens inside
of the driver, the `(*mysqlConn) interpolateParams` does **not** handle
uint64 values (which, again, are now passed by `database/sql` because
we've updated our value converter to generate them).

Because of this, any `DB.Query` operations which have an uint argument
(regardless of its size!!) will force the driver to return
`driver.ErrSkip`, disabling client interpolation for such queries.

We can fix this by updating `interpolateParams` like we previously
updated `writeExecutePacket`.
---
 connection.go      |  3 +++
 connection_test.go | 18 ++++++++++++++++++
 2 files changed, 21 insertions(+)

diff --git a/connection.go b/connection.go
index 265fd4e47..565a5480a 100644
--- a/connection.go
+++ b/connection.go
@@ -213,6 +213,9 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
 		switch v := arg.(type) {
 		case int64:
 			buf = strconv.AppendInt(buf, v, 10)
+		case uint64:
+			// Handle uint64 explicitly because our custom ConvertValue emits unsigned values
+			buf = strconv.AppendUint(buf, v, 10)
 		case float64:
 			buf = strconv.AppendFloat(buf, v, 'g', -1, 64)
 		case bool:
diff --git a/connection_test.go b/connection_test.go
index 8e78f36c6..19c17ff8b 100644
--- a/connection_test.go
+++ b/connection_test.go
@@ -69,6 +69,24 @@ func TestInterpolateParamsPlaceholderInString(t *testing.T) {
 	}
 }
 
+func TestInterpolateParamsUint64(t *testing.T) {
+	mc := &mysqlConn{
+		buf:              newBuffer(nil),
+		maxAllowedPacket: maxPacketSize,
+		cfg: &Config{
+			InterpolateParams: true,
+		},
+	}
+
+	q, err := mc.interpolateParams("SELECT ?", []driver.Value{uint64(42)})
+	if err != nil {
+		t.Errorf("Expected err=nil, got err=%#v, q=%#v", err, q)
+	}
+	if q != "SELECT 42" {
+		t.Errorf("Expected uint64 interpolation to work, got q=%#v", q)
+	}
+}
+
 func TestCheckNamedValue(t *testing.T) {
 	value := driver.NamedValue{Value: ^uint64(0)}
 	x := &mysqlConn{}

From 8056f2ca4aa7be2e2e10ab01426a630d5a5bfa81 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Vicent=20Mart=C3=AD?= <vmg@strn.cat>
Date: Tue, 7 May 2019 11:00:41 +0200
Subject: [PATCH 098/282] packets: reset read deadline before conn check (#964)

* packets: reset read deadline before conn check

If a MySQL connection has been configured with a short `ReadTimeout`,
each read from the TCP connection will be preceded by a
`SetReadDeadline` call, which lingers until the next `SetReadDeadline`.

This can be an issue if the connection becomes stale after staying too
long in the connection pool, because when we attempt to perform a stale
connection check, the Go runtime scheduler will return a timedout error
from the scheduler itself, without letting us get to the kernel to
perform the non-blocking read.

To fix this, reset the read deadline before we perform the connection
check.

* packets: set a 0 deadline
---
 packets.go | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/packets.go b/packets.go
index cbed325f4..30b3352c2 100644
--- a/packets.go
+++ b/packets.go
@@ -108,7 +108,17 @@ func (mc *mysqlConn) writePacket(data []byte) error {
 		if mc.rawConn != nil {
 			conn = mc.rawConn
 		}
-		if err := connCheck(conn); err != nil {
+		var err error
+		// If this connection has a ReadTimeout which we've been setting on
+		// reads, reset it to its default value before we attempt a non-blocking
+		// read, otherwise the scheduler will just time us out before we can read
+		if mc.cfg.ReadTimeout != 0 {
+			err = conn.SetReadDeadline(time.Time{})
+		}
+		if err == nil {
+			err = connCheck(conn)
+		}
+		if err != nil {
 			errLog.Print("closing bad idle connection: ", err)
 			mc.Close()
 			return driver.ErrBadConn

From 877a9775f06853f611fb2d4e817d92479242d1cd Mon Sep 17 00:00:00 2001
From: Brandon Bennett <bennetb@gmail.com>
Date: Fri, 10 May 2019 04:23:35 -0600
Subject: [PATCH 099/282] move tls and pubkey object creation to
 Config.normalize() (#958)

This is still less than ideal since we cannot directly pass in
tls.Config into Config and have it be used, but it is sill backwards
compatable.  In the future this should be revisited to be able to use a
custome tls.Config passed directly in without string
parsing/registering.
---
 AUTHORS     |  3 ++-
 dsn.go      | 50 +++++++++++++++++++++++++++-----------------------
 dsn_test.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++--
 utils.go    |  2 +-
 4 files changed, 76 insertions(+), 27 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index cdf7ae3b8..bfe74c4e1 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -90,11 +90,12 @@ Zhenye Xie <xiezhenye at gmail.com>
 
 Barracuda Networks, Inc.
 Counting Ltd.
+Facebook Inc.
 GitHub Inc.
 Google Inc.
 InfoSum Ltd.
 Keybase Inc.
+Multiplay Ltd.
 Percona LLC
 Pivotal Inc.
 Stripe Inc.
-Multiplay Ltd.
diff --git a/dsn.go b/dsn.go
index 6e19ab717..1d9b4ab0a 100644
--- a/dsn.go
+++ b/dsn.go
@@ -113,17 +113,35 @@ func (cfg *Config) normalize() error {
 		default:
 			return errors.New("default addr for network '" + cfg.Net + "' unknown")
 		}
-
 	} else if cfg.Net == "tcp" {
 		cfg.Addr = ensureHavePort(cfg.Addr)
 	}
 
-	if cfg.tls != nil {
-		if cfg.tls.ServerName == "" && !cfg.tls.InsecureSkipVerify {
-			host, _, err := net.SplitHostPort(cfg.Addr)
-			if err == nil {
-				cfg.tls.ServerName = host
-			}
+	switch cfg.TLSConfig {
+	case "false", "":
+		// don't set anything
+	case "true":
+		cfg.tls = &tls.Config{}
+	case "skip-verify", "preferred":
+		cfg.tls = &tls.Config{InsecureSkipVerify: true}
+	default:
+		cfg.tls = getTLSConfigClone(cfg.TLSConfig)
+		if cfg.tls == nil {
+			return errors.New("invalid value / unknown config name: " + cfg.TLSConfig)
+		}
+	}
+
+	if cfg.tls != nil && cfg.tls.ServerName == "" && !cfg.tls.InsecureSkipVerify {
+		host, _, err := net.SplitHostPort(cfg.Addr)
+		if err == nil {
+			cfg.tls.ServerName = host
+		}
+	}
+
+	if cfg.ServerPubKey != "" {
+		cfg.pubKey = getServerPubKey(cfg.ServerPubKey)
+		if cfg.pubKey == nil {
+			return errors.New("invalid value / unknown server pub key name: " + cfg.ServerPubKey)
 		}
 	}
 
@@ -552,13 +570,7 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 			if err != nil {
 				return fmt.Errorf("invalid value for server pub key name: %v", err)
 			}
-
-			if pubKey := getServerPubKey(name); pubKey != nil {
-				cfg.ServerPubKey = name
-				cfg.pubKey = pubKey
-			} else {
-				return errors.New("invalid value / unknown server pub key name: " + name)
-			}
+			cfg.ServerPubKey = name
 
 		// Strict mode
 		case "strict":
@@ -577,25 +589,17 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 			if isBool {
 				if boolValue {
 					cfg.TLSConfig = "true"
-					cfg.tls = &tls.Config{}
 				} else {
 					cfg.TLSConfig = "false"
 				}
 			} else if vl := strings.ToLower(value); vl == "skip-verify" || vl == "preferred" {
 				cfg.TLSConfig = vl
-				cfg.tls = &tls.Config{InsecureSkipVerify: true}
 			} else {
 				name, err := url.QueryUnescape(value)
 				if err != nil {
 					return fmt.Errorf("invalid value for TLS config name: %v", err)
 				}
-
-				if tlsConfig := getTLSConfigClone(name); tlsConfig != nil {
-					cfg.TLSConfig = name
-					cfg.tls = tlsConfig
-				} else {
-					return errors.New("invalid value / unknown config name: " + name)
-				}
+				cfg.TLSConfig = name
 			}
 
 		// I/O write Timeout
diff --git a/dsn_test.go b/dsn_test.go
index fe1572dea..50dc2932c 100644
--- a/dsn_test.go
+++ b/dsn_test.go
@@ -39,8 +39,8 @@ var testDSNs = []struct {
 	"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify",
 	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8mb4,utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, TLSConfig: "skip-verify"},
 }, {
-	"user:password@/dbname?loc=UTC&timeout=30s&readTimeout=1s&writeTimeout=1s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci&maxAllowedPacket=16777216",
-	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_unicode_ci", Loc: time.UTC, AllowNativePasswords: true, Timeout: 30 * time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, AllowAllFiles: true, AllowOldPasswords: true, ClientFoundRows: true, MaxAllowedPacket: 16777216},
+	"user:password@/dbname?loc=UTC&timeout=30s&readTimeout=1s&writeTimeout=1s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci&maxAllowedPacket=16777216&tls=false&allowCleartextPasswords=true&parseTime=true&rejectReadOnly=true",
+	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_unicode_ci", Loc: time.UTC, TLSConfig: "false", AllowCleartextPasswords: true, AllowNativePasswords: true, Timeout: 30 * time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, AllowAllFiles: true, AllowOldPasswords: true, ClientFoundRows: true, MaxAllowedPacket: 16777216, ParseTime: true, RejectReadOnly: true},
 }, {
 	"user:password@/dbname?allowNativePasswords=false&maxAllowedPacket=0",
 	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: 0, AllowNativePasswords: false},
@@ -358,6 +358,50 @@ func TestCloneConfig(t *testing.T) {
 	}
 }
 
+func TestNormalizeTLSConfig(t *testing.T) {
+	tt := []struct {
+		tlsConfig string
+		want      *tls.Config
+	}{
+		{"", nil},
+		{"false", nil},
+		{"true", &tls.Config{ServerName: "myserver"}},
+		{"skip-verify", &tls.Config{InsecureSkipVerify: true}},
+		{"preferred", &tls.Config{InsecureSkipVerify: true}},
+		{"test_tls_config", &tls.Config{ServerName: "myServerName"}},
+	}
+
+	RegisterTLSConfig("test_tls_config", &tls.Config{ServerName: "myServerName"})
+	defer func() { DeregisterTLSConfig("test_tls_config") }()
+
+	for _, tc := range tt {
+		t.Run(tc.tlsConfig, func(t *testing.T) {
+			cfg := &Config{
+				Addr:      "myserver:3306",
+				TLSConfig: tc.tlsConfig,
+			}
+
+			cfg.normalize()
+
+			if cfg.tls == nil {
+				if tc.want != nil {
+					t.Fatal("wanted a tls config but got nil instead")
+				}
+				return
+			}
+
+			if cfg.tls.ServerName != tc.want.ServerName {
+				t.Errorf("tls.ServerName doesn't match (want: '%s', got: '%s')",
+					tc.want.ServerName, cfg.tls.ServerName)
+			}
+			if cfg.tls.InsecureSkipVerify != tc.want.InsecureSkipVerify {
+				t.Errorf("tls.InsecureSkipVerify doesn't match (want: %T, got :%T)",
+					tc.want.InsecureSkipVerify, cfg.tls.InsecureSkipVerify)
+			}
+		})
+	}
+}
+
 func BenchmarkParseDSN(b *testing.B) {
 	b.ReportAllocs()
 
diff --git a/utils.go b/utils.go
index 201691fe8..cfa10e9c0 100644
--- a/utils.go
+++ b/utils.go
@@ -56,7 +56,7 @@ var (
 //  db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom")
 //
 func RegisterTLSConfig(key string, config *tls.Config) error {
-	if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" {
+	if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" || strings.ToLower(key) == "preferred" {
 		return fmt.Errorf("key '%s' is reserved", key)
 	}
 

From 7244e501438d2bcefc1474a6b2fa101200ffee82 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Wed, 4 Sep 2019 22:05:19 +0900
Subject: [PATCH 100/282] try fix osx build on Travis (#994)

---
 .travis.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.travis.yml b/.travis.yml
index eae311b14..9d3139207 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -105,6 +105,7 @@ matrix:
         homebrew:
           packages:
             - mysql
+          update: true
       go: 1.12.x
       before_install:
         - go get golang.org/x/tools/cmd/cover

From 23821f42b6688625ba2202899226983dc7055fad Mon Sep 17 00:00:00 2001
From: Vladimir Kovpak <cn007b@gmail.com>
Date: Wed, 4 Sep 2019 17:58:47 +0300
Subject: [PATCH 101/282] GCP DSN example for flexible environment (#993)

* Added GCP connection string example for flexible environment.

* Added one more GCP connection string example for flexible environment.

* Unified GCP connection strings examples for 2nd gen and flexible env.
---
 AUTHORS   | 1 +
 README.md | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/AUTHORS b/AUTHORS
index bfe74c4e1..d87414e82 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -81,6 +81,7 @@ Steven Hartland <steven.hartland at multiplay.co.uk>
 Thomas Wodarek <wodarekwebpage at gmail.com>
 Tim Ruffles <timruffles at gmail.com>
 Tom Jenkinson <tom at tjenkinson.me>
+Vladimir Kovpak <cn007b at gmail.com>
 Xiangyu Hu <xiangyu.hu at outlook.com>
 Xiaobing Jiang <s7v7nislands at gmail.com>
 Xiuming Chen <cc at cxm.cc>
diff --git a/README.md b/README.md
index c6adf1d63..20fff486f 100644
--- a/README.md
+++ b/README.md
@@ -403,7 +403,7 @@ user@cloudsql(project-id:instance-name)/dbname
 
 Google Cloud SQL on App Engine (Second Generation MySQL Server):
 ```
-user@cloudsql(project-id:regionname:instance-name)/dbname
+user:password@unix(/cloudsql/project-id:region-name:instance-name)/dbname
 ```
 
 TCP using default port (3306) on localhost:

From d9aa6d3abe0f1072adb4aa71e1eb1d3eb3d3921c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Olivier=20Mengu=C3=A9?= <dolmen@cpan.org>
Date: Thu, 5 Sep 2019 05:54:07 +0200
Subject: [PATCH 102/282] Refactor NullTime as go1.13's sql.NullTime (#995)

---
 nulltime.go        | 50 +++++++++++++++++++++++++++++++++++++
 nulltime_go113.go  | 31 +++++++++++++++++++++++
 nulltime_legacy.go | 34 +++++++++++++++++++++++++
 nulltime_test.go   | 62 ++++++++++++++++++++++++++++++++++++++++++++++
 utils.go           | 54 ----------------------------------------
 utils_test.go      | 41 ------------------------------
 6 files changed, 177 insertions(+), 95 deletions(-)
 create mode 100644 nulltime.go
 create mode 100644 nulltime_go113.go
 create mode 100644 nulltime_legacy.go
 create mode 100644 nulltime_test.go

diff --git a/nulltime.go b/nulltime.go
new file mode 100644
index 000000000..afa8a89e9
--- /dev/null
+++ b/nulltime.go
@@ -0,0 +1,50 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2013 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"
+	"fmt"
+	"time"
+)
+
+// Scan implements the Scanner interface.
+// The value type must be time.Time or string / []byte (formatted time-string),
+// otherwise Scan fails.
+func (nt *NullTime) Scan(value interface{}) (err error) {
+	if value == nil {
+		nt.Time, nt.Valid = time.Time{}, false
+		return
+	}
+
+	switch v := value.(type) {
+	case time.Time:
+		nt.Time, nt.Valid = v, true
+		return
+	case []byte:
+		nt.Time, err = parseDateTime(string(v), time.UTC)
+		nt.Valid = (err == nil)
+		return
+	case string:
+		nt.Time, err = parseDateTime(v, time.UTC)
+		nt.Valid = (err == nil)
+		return
+	}
+
+	nt.Valid = false
+	return fmt.Errorf("Can't convert %T to time.Time", value)
+}
+
+// Value implements the driver Valuer interface.
+func (nt NullTime) Value() (driver.Value, error) {
+	if !nt.Valid {
+		return nil, nil
+	}
+	return nt.Time, nil
+}
diff --git a/nulltime_go113.go b/nulltime_go113.go
new file mode 100644
index 000000000..c392594dd
--- /dev/null
+++ b/nulltime_go113.go
@@ -0,0 +1,31 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2013 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/.
+
+// +build go1.13
+
+package mysql
+
+import (
+	"database/sql"
+)
+
+// NullTime represents a time.Time that may be NULL.
+// NullTime implements the Scanner interface so
+// it can be used as a scan destination:
+//
+//  var nt NullTime
+//  err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
+//  ...
+//  if nt.Valid {
+//     // use nt.Time
+//  } else {
+//     // NULL value
+//  }
+//
+// This NullTime implementation is not driver-specific
+type NullTime sql.NullTime
diff --git a/nulltime_legacy.go b/nulltime_legacy.go
new file mode 100644
index 000000000..86d159d44
--- /dev/null
+++ b/nulltime_legacy.go
@@ -0,0 +1,34 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2013 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/.
+
+// +build !go1.13
+
+package mysql
+
+import (
+	"time"
+)
+
+// NullTime represents a time.Time that may be NULL.
+// NullTime implements the Scanner interface so
+// it can be used as a scan destination:
+//
+//  var nt NullTime
+//  err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
+//  ...
+//  if nt.Valid {
+//     // use nt.Time
+//  } else {
+//     // NULL value
+//  }
+//
+// This NullTime implementation is not driver-specific
+type NullTime struct {
+	Time  time.Time
+	Valid bool // Valid is true if Time is not NULL
+}
diff --git a/nulltime_test.go b/nulltime_test.go
new file mode 100644
index 000000000..a14ec0607
--- /dev/null
+++ b/nulltime_test.go
@@ -0,0 +1,62 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2013 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"
+	"database/sql/driver"
+	"testing"
+	"time"
+)
+
+var (
+	// Check implementation of interfaces
+	_ driver.Valuer = NullTime{}
+	_ sql.Scanner   = (*NullTime)(nil)
+)
+
+func TestScanNullTime(t *testing.T) {
+	var scanTests = []struct {
+		in    interface{}
+		error bool
+		valid bool
+		time  time.Time
+	}{
+		{tDate, false, true, tDate},
+		{sDate, false, true, tDate},
+		{[]byte(sDate), false, true, tDate},
+		{tDateTime, false, true, tDateTime},
+		{sDateTime, false, true, tDateTime},
+		{[]byte(sDateTime), false, true, tDateTime},
+		{tDate0, false, true, tDate0},
+		{sDate0, false, true, tDate0},
+		{[]byte(sDate0), false, true, tDate0},
+		{sDateTime0, false, true, tDate0},
+		{[]byte(sDateTime0), false, true, tDate0},
+		{"", true, false, tDate0},
+		{"1234", true, false, tDate0},
+		{0, true, false, tDate0},
+	}
+
+	var nt = NullTime{}
+	var err error
+
+	for _, tst := range scanTests {
+		err = nt.Scan(tst.in)
+		if (err != nil) != tst.error {
+			t.Errorf("%v: expected error status %t, got %t", tst.in, tst.error, (err != nil))
+		}
+		if nt.Valid != tst.valid {
+			t.Errorf("%v: expected valid status %t, got %t", tst.in, tst.valid, nt.Valid)
+		}
+		if nt.Time != tst.time {
+			t.Errorf("%v: expected time %v, got %v", tst.in, tst.time, nt.Time)
+		}
+	}
+}
diff --git a/utils.go b/utils.go
index cfa10e9c0..9552e80b5 100644
--- a/utils.go
+++ b/utils.go
@@ -106,60 +106,6 @@ func readBool(input string) (value bool, valid bool) {
 *                           Time related utils                                *
 ******************************************************************************/
 
-// NullTime represents a time.Time that may be NULL.
-// NullTime implements the Scanner interface so
-// it can be used as a scan destination:
-//
-//  var nt NullTime
-//  err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
-//  ...
-//  if nt.Valid {
-//     // use nt.Time
-//  } else {
-//     // NULL value
-//  }
-//
-// This NullTime implementation is not driver-specific
-type NullTime struct {
-	Time  time.Time
-	Valid bool // Valid is true if Time is not NULL
-}
-
-// Scan implements the Scanner interface.
-// The value type must be time.Time or string / []byte (formatted time-string),
-// otherwise Scan fails.
-func (nt *NullTime) Scan(value interface{}) (err error) {
-	if value == nil {
-		nt.Time, nt.Valid = time.Time{}, false
-		return
-	}
-
-	switch v := value.(type) {
-	case time.Time:
-		nt.Time, nt.Valid = v, true
-		return
-	case []byte:
-		nt.Time, err = parseDateTime(string(v), time.UTC)
-		nt.Valid = (err == nil)
-		return
-	case string:
-		nt.Time, err = parseDateTime(v, time.UTC)
-		nt.Valid = (err == nil)
-		return
-	}
-
-	nt.Valid = false
-	return fmt.Errorf("Can't convert %T to time.Time", value)
-}
-
-// Value implements the driver Valuer interface.
-func (nt NullTime) Value() (driver.Value, error) {
-	if !nt.Valid {
-		return nil, nil
-	}
-	return nt.Time, nil
-}
-
 func parseDateTime(str string, loc *time.Location) (t time.Time, err error) {
 	base := "0000-00-00 00:00:00.0000000"
 	switch len(str) {
diff --git a/utils_test.go b/utils_test.go
index 8951a7a81..10a60c2d0 100644
--- a/utils_test.go
+++ b/utils_test.go
@@ -14,49 +14,8 @@ import (
 	"database/sql/driver"
 	"encoding/binary"
 	"testing"
-	"time"
 )
 
-func TestScanNullTime(t *testing.T) {
-	var scanTests = []struct {
-		in    interface{}
-		error bool
-		valid bool
-		time  time.Time
-	}{
-		{tDate, false, true, tDate},
-		{sDate, false, true, tDate},
-		{[]byte(sDate), false, true, tDate},
-		{tDateTime, false, true, tDateTime},
-		{sDateTime, false, true, tDateTime},
-		{[]byte(sDateTime), false, true, tDateTime},
-		{tDate0, false, true, tDate0},
-		{sDate0, false, true, tDate0},
-		{[]byte(sDate0), false, true, tDate0},
-		{sDateTime0, false, true, tDate0},
-		{[]byte(sDateTime0), false, true, tDate0},
-		{"", true, false, tDate0},
-		{"1234", true, false, tDate0},
-		{0, true, false, tDate0},
-	}
-
-	var nt = NullTime{}
-	var err error
-
-	for _, tst := range scanTests {
-		err = nt.Scan(tst.in)
-		if (err != nil) != tst.error {
-			t.Errorf("%v: expected error status %t, got %t", tst.in, tst.error, (err != nil))
-		}
-		if nt.Valid != tst.valid {
-			t.Errorf("%v: expected valid status %t, got %t", tst.in, tst.valid, nt.Valid)
-		}
-		if nt.Time != tst.time {
-			t.Errorf("%v: expected time %v, got %v", tst.in, tst.time, nt.Time)
-		}
-	}
-}
-
 func TestLengthEncodedInteger(t *testing.T) {
 	var integerTests = []struct {
 		num     uint64

From b2c03bcae3d4bf9658e63ad4b9b32581d810fb20 Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Sat, 7 Sep 2019 14:21:37 +0200
Subject: [PATCH 103/282] conncheck: move var declarations into closure (#997)

---
 conncheck.go       | 43 ++++++++++++++++++++++---------------------
 conncheck_dummy.go |  2 +-
 2 files changed, 23 insertions(+), 22 deletions(-)

diff --git a/conncheck.go b/conncheck.go
index cc47aa559..70e9925f6 100644
--- a/conncheck.go
+++ b/conncheck.go
@@ -19,35 +19,36 @@ import (
 
 var errUnexpectedRead = errors.New("unexpected read from socket")
 
-func connCheck(c net.Conn) error {
-	var (
-		n    int
-		err  error
-		buff [1]byte
-	)
-
-	sconn, ok := c.(syscall.Conn)
+func connCheck(conn net.Conn) error {
+	var sysErr error
+
+	sysConn, ok := conn.(syscall.Conn)
 	if !ok {
 		return nil
 	}
-	rc, err := sconn.SyscallConn()
+	rawConn, err := sysConn.SyscallConn()
 	if err != nil {
 		return err
 	}
-	rerr := rc.Read(func(fd uintptr) bool {
-		n, err = syscall.Read(int(fd), buff[:])
+
+	err = rawConn.Read(func(fd uintptr) bool {
+		var buf [1]byte
+		n, err := syscall.Read(int(fd), buf[:])
+		switch {
+		case n == 0 && err == nil:
+			sysErr = io.EOF
+		case n > 0:
+			sysErr = errUnexpectedRead
+		case err == syscall.EAGAIN || err == syscall.EWOULDBLOCK:
+			sysErr = nil
+		default:
+			sysErr = err
+		}
 		return true
 	})
-	switch {
-	case rerr != nil:
-		return rerr
-	case n == 0 && err == nil:
-		return io.EOF
-	case n > 0:
-		return errUnexpectedRead
-	case err == syscall.EAGAIN || err == syscall.EWOULDBLOCK:
-		return nil
-	default:
+	if err != nil {
 		return err
 	}
+
+	return sysErr
 }
diff --git a/conncheck_dummy.go b/conncheck_dummy.go
index fd01f64c9..4888288aa 100644
--- a/conncheck_dummy.go
+++ b/conncheck_dummy.go
@@ -12,6 +12,6 @@ package mysql
 
 import "net"
 
-func connCheck(c net.Conn) error {
+func connCheck(conn net.Conn) error {
 	return nil
 }

From 6c79a37b2e06d0297e8a76a70b747f72bdfa8338 Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Tue, 17 Sep 2019 11:31:45 +0200
Subject: [PATCH 104/282] move CONTRIBUTING.md to .github/CONTRIBUTING.md
 (#1000)

---
 CONTRIBUTING.md => .github/CONTRIBUTING.md | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename CONTRIBUTING.md => .github/CONTRIBUTING.md (100%)

diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md
similarity index 100%
rename from CONTRIBUTING.md
rename to .github/CONTRIBUTING.md

From 59de189eea30b4ff6207a44f810232907f95b098 Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Tue, 1 Oct 2019 08:08:38 +0200
Subject: [PATCH 105/282] Remove Cloud SQL dialer (#1007)

CloudSQL is only available up to Go 1.9 on Google AppEngine, which was phased out. Starting from 2019-10-01, no new apps can be deployed to GAE/Go 1.9 anymore. This dialer is thus obsolete.

Fixes #1002
---
 README.md    |  7 +------
 appengine.go | 25 -------------------------
 2 files changed, 1 insertion(+), 31 deletions(-)
 delete mode 100644 appengine.go

diff --git a/README.md b/README.md
index 20fff486f..dce7a5325 100644
--- a/README.md
+++ b/README.md
@@ -396,12 +396,7 @@ TCP on a remote host, e.g. Amazon RDS:
 id:password@tcp(your-amazonaws-uri.com:3306)/dbname
 ```
 
-Google Cloud SQL on App Engine (First Generation MySQL Server):
-```
-user@cloudsql(project-id:instance-name)/dbname
-```
-
-Google Cloud SQL on App Engine (Second Generation MySQL Server):
+Google Cloud SQL on App Engine:
 ```
 user:password@unix(/cloudsql/project-id:region-name:instance-name)/dbname
 ```
diff --git a/appengine.go b/appengine.go
deleted file mode 100644
index 914e6623b..000000000
--- a/appengine.go
+++ /dev/null
@@ -1,25 +0,0 @@
-// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
-//
-// Copyright 2013 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/.
-
-// +build appengine
-
-package mysql
-
-import (
-	"context"
-	"net"
-
-	"google.golang.org/appengine/cloudsql"
-)
-
-func init() {
-	RegisterDialContext("cloudsql", func(_ context.Context, instance string) (net.Conn, error) {
-		// XXX: the cloudsql driver still does not export a Context-aware dialer.
-		return cloudsql.Dial(instance)
-	})
-}

From 14bb9c0fc20fc57f6c3339adeac7f469768d9524 Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Tue, 1 Oct 2019 08:09:45 +0200
Subject: [PATCH 106/282] Add go.mod (#1003)

---
 go.mod | 3 +++
 1 file changed, 3 insertions(+)
 create mode 100644 go.mod

diff --git a/go.mod b/go.mod
new file mode 100644
index 000000000..29e5d800d
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module github.com/go-sql-driver/mysql
+
+go 1.9

From 5ee934fb0eb6903ba5396bf34817ff178b29de39 Mon Sep 17 00:00:00 2001
From: Johann Forster <j-forster@users.noreply.github.com>
Date: Mon, 21 Oct 2019 12:25:53 +0200
Subject: [PATCH 107/282] Add string type to ColumnTypeScanType(). (#976)

ColumnTypeScanType() now returns string and sql.NullString.
---
 AUTHORS        |  1 +
 driver_test.go | 15 +++++++++------
 fields.go      | 52 +++++++++++++++++++++++++++++++-------------------
 3 files changed, 42 insertions(+), 26 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index d87414e82..dd27253e3 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -45,6 +45,7 @@ Jeff Hodges <jeff at somethingsimilar.com>
 Jeffrey Charles <jeffreycharles at gmail.com>
 Jerome Meyer <jxmeyer at gmail.com>
 Jian Zhen <zhenjl at gmail.com>
+Johann Forster <johannforster15 at gmail.com>
 Joshua Prunier <joshua.prunier at gmail.com>
 Julien Lefevre <julien.lefevr at gmail.com>
 Julien Schmidt <go-sql-driver at julienschmidt.com>
diff --git a/driver_test.go b/driver_test.go
index 3dee1bab2..e6c7f83a0 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -2733,6 +2733,9 @@ func TestRowsColumnTypes(t *testing.T) {
 	rb0pad4 := sql.RawBytes("0\x00\x00\x00") // BINARY right-pads values with 0x00
 	rbx0 := sql.RawBytes("\x00")
 	rbx42 := sql.RawBytes("\x42")
+	s0 := sql.NullString{String: "0", Valid: true}
+	sNULL := sql.NullString{String: "", Valid: false}
+	sTest := sql.NullString{String: "Test", Valid: true}
 
 	var columns = []struct {
 		name             string
@@ -2771,18 +2774,18 @@ func TestRowsColumnTypes(t *testing.T) {
 		{"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}},
+		{"char25null", "CHAR(25)", "CHAR", scanTypeNullString, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{s0, sNULL, sTest}},
+		{"varchar42", "VARCHAR(42) NOT NULL", "VARCHAR", scanTypeString, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{"0", "Test", "42"}},
 		{"binary4null", "BINARY(4)", "BINARY", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0pad4, rbNULL, rbTest}},
 		{"varbinary42", "VARBINARY(42) NOT NULL", "VARBINARY", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
 		{"tinyblobnull", "TINYBLOB", "BLOB", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
-		{"tinytextnull", "TINYTEXT", "TEXT", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
+		{"tinytextnull", "TINYTEXT", "TEXT", scanTypeNullString, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{s0, sNULL, sTest}},
 		{"blobnull", "BLOB", "BLOB", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
-		{"textnull", "TEXT", "TEXT", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
+		{"textnull", "TEXT", "TEXT", scanTypeNullString, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{s0, sNULL, sTest}},
 		{"mediumblob", "MEDIUMBLOB NOT NULL", "BLOB", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
-		{"mediumtext", "MEDIUMTEXT NOT NULL", "TEXT", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
+		{"mediumtext", "MEDIUMTEXT NOT NULL", "TEXT", scanTypeString, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{"0", "Test", "42"}},
 		{"longblob", "LONGBLOB NOT NULL", "BLOB", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
-		{"longtext", "LONGTEXT NOT NULL", "TEXT", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
+		{"longtext", "LONGTEXT NOT NULL", "TEXT", scanTypeString, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{"0", "Test", "42"}},
 		{"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}},
diff --git a/fields.go b/fields.go
index e1e2ece4b..ad2207cb3 100644
--- a/fields.go
+++ b/fields.go
@@ -98,21 +98,23 @@ func (mf *mysqlField) typeDatabaseName() string {
 }
 
 var (
-	scanTypeFloat32   = reflect.TypeOf(float32(0))
-	scanTypeFloat64   = reflect.TypeOf(float64(0))
-	scanTypeInt8      = reflect.TypeOf(int8(0))
-	scanTypeInt16     = reflect.TypeOf(int16(0))
-	scanTypeInt32     = reflect.TypeOf(int32(0))
-	scanTypeInt64     = reflect.TypeOf(int64(0))
-	scanTypeNullFloat = reflect.TypeOf(sql.NullFloat64{})
-	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{}))
+	scanTypeFloat32    = reflect.TypeOf(float32(0))
+	scanTypeFloat64    = reflect.TypeOf(float64(0))
+	scanTypeInt8       = reflect.TypeOf(int8(0))
+	scanTypeInt16      = reflect.TypeOf(int16(0))
+	scanTypeInt32      = reflect.TypeOf(int32(0))
+	scanTypeInt64      = reflect.TypeOf(int64(0))
+	scanTypeNullFloat  = reflect.TypeOf(sql.NullFloat64{})
+	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))
+	scanTypeString     = reflect.TypeOf(string(""))
+	scanTypeNullString = reflect.TypeOf(sql.NullString{})
+	scanTypeRawBytes   = reflect.TypeOf(sql.RawBytes{})
+	scanTypeUnknown    = reflect.TypeOf(new(interface{}))
 )
 
 type mysqlField struct {
@@ -175,11 +177,21 @@ func (mf *mysqlField) scanType() reflect.Type {
 		}
 		return scanTypeNullFloat
 
-	case fieldTypeDecimal, fieldTypeNewDecimal, fieldTypeVarChar,
-		fieldTypeBit, fieldTypeEnum, fieldTypeSet, fieldTypeTinyBLOB,
-		fieldTypeMediumBLOB, fieldTypeLongBLOB, fieldTypeBLOB,
-		fieldTypeVarString, fieldTypeString, fieldTypeGeometry, fieldTypeJSON,
-		fieldTypeTime:
+	case fieldTypeVarChar, fieldTypeString, fieldTypeVarString, fieldTypeTinyBLOB,
+		fieldTypeMediumBLOB, fieldTypeLongBLOB, fieldTypeBLOB:
+		// charsetnr == 63 means this column is binary.
+		// https://dev.mysql.com/doc/refman/8.0/en/c-api-data-structures.html
+		if mf.charSet == 63 {
+			return scanTypeRawBytes
+		}
+		if mf.flags&flagNotNULL != 0 {
+			return scanTypeString
+		}
+		return scanTypeNullString
+
+	case fieldTypeDecimal, fieldTypeNewDecimal, fieldTypeBit,
+		fieldTypeEnum, fieldTypeSet,
+		fieldTypeGeometry, fieldTypeJSON, fieldTypeTime:
 		return scanTypeRawBytes
 
 	case fieldTypeDate, fieldTypeNewDate,

From 6ea7374bc1b0cd6cc6398e2aec912f6764aa5adc Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Tue, 22 Oct 2019 13:23:24 +0200
Subject: [PATCH 108/282] Revert "Add string type to ColumnTypeScanType().
 (#976)" (#1018)

This reverts commit 5ee934fb0eb6903ba5396bf34817ff178b29de39.
---
 AUTHORS        |  1 -
 driver_test.go | 15 ++++++---------
 fields.go      | 52 +++++++++++++++++++-------------------------------
 3 files changed, 26 insertions(+), 42 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index dd27253e3..d87414e82 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -45,7 +45,6 @@ Jeff Hodges <jeff at somethingsimilar.com>
 Jeffrey Charles <jeffreycharles at gmail.com>
 Jerome Meyer <jxmeyer at gmail.com>
 Jian Zhen <zhenjl at gmail.com>
-Johann Forster <johannforster15 at gmail.com>
 Joshua Prunier <joshua.prunier at gmail.com>
 Julien Lefevre <julien.lefevr at gmail.com>
 Julien Schmidt <go-sql-driver at julienschmidt.com>
diff --git a/driver_test.go b/driver_test.go
index e6c7f83a0..3dee1bab2 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -2733,9 +2733,6 @@ func TestRowsColumnTypes(t *testing.T) {
 	rb0pad4 := sql.RawBytes("0\x00\x00\x00") // BINARY right-pads values with 0x00
 	rbx0 := sql.RawBytes("\x00")
 	rbx42 := sql.RawBytes("\x42")
-	s0 := sql.NullString{String: "0", Valid: true}
-	sNULL := sql.NullString{String: "", Valid: false}
-	sTest := sql.NullString{String: "Test", Valid: true}
 
 	var columns = []struct {
 		name             string
@@ -2774,18 +2771,18 @@ func TestRowsColumnTypes(t *testing.T) {
 		{"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", scanTypeNullString, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{s0, sNULL, sTest}},
-		{"varchar42", "VARCHAR(42) NOT NULL", "VARCHAR", scanTypeString, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{"0", "Test", "42"}},
+		{"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}},
 		{"binary4null", "BINARY(4)", "BINARY", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0pad4, rbNULL, rbTest}},
 		{"varbinary42", "VARBINARY(42) NOT NULL", "VARBINARY", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
 		{"tinyblobnull", "TINYBLOB", "BLOB", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
-		{"tinytextnull", "TINYTEXT", "TEXT", scanTypeNullString, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{s0, sNULL, sTest}},
+		{"tinytextnull", "TINYTEXT", "TEXT", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
 		{"blobnull", "BLOB", "BLOB", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
-		{"textnull", "TEXT", "TEXT", scanTypeNullString, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{s0, sNULL, sTest}},
+		{"textnull", "TEXT", "TEXT", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
 		{"mediumblob", "MEDIUMBLOB NOT NULL", "BLOB", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
-		{"mediumtext", "MEDIUMTEXT NOT NULL", "TEXT", scanTypeString, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{"0", "Test", "42"}},
+		{"mediumtext", "MEDIUMTEXT NOT NULL", "TEXT", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
 		{"longblob", "LONGBLOB NOT NULL", "BLOB", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
-		{"longtext", "LONGTEXT NOT NULL", "TEXT", scanTypeString, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{"0", "Test", "42"}},
+		{"longtext", "LONGTEXT NOT NULL", "TEXT", 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}},
diff --git a/fields.go b/fields.go
index ad2207cb3..e1e2ece4b 100644
--- a/fields.go
+++ b/fields.go
@@ -98,23 +98,21 @@ func (mf *mysqlField) typeDatabaseName() string {
 }
 
 var (
-	scanTypeFloat32    = reflect.TypeOf(float32(0))
-	scanTypeFloat64    = reflect.TypeOf(float64(0))
-	scanTypeInt8       = reflect.TypeOf(int8(0))
-	scanTypeInt16      = reflect.TypeOf(int16(0))
-	scanTypeInt32      = reflect.TypeOf(int32(0))
-	scanTypeInt64      = reflect.TypeOf(int64(0))
-	scanTypeNullFloat  = reflect.TypeOf(sql.NullFloat64{})
-	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))
-	scanTypeString     = reflect.TypeOf(string(""))
-	scanTypeNullString = reflect.TypeOf(sql.NullString{})
-	scanTypeRawBytes   = reflect.TypeOf(sql.RawBytes{})
-	scanTypeUnknown    = reflect.TypeOf(new(interface{}))
+	scanTypeFloat32   = reflect.TypeOf(float32(0))
+	scanTypeFloat64   = reflect.TypeOf(float64(0))
+	scanTypeInt8      = reflect.TypeOf(int8(0))
+	scanTypeInt16     = reflect.TypeOf(int16(0))
+	scanTypeInt32     = reflect.TypeOf(int32(0))
+	scanTypeInt64     = reflect.TypeOf(int64(0))
+	scanTypeNullFloat = reflect.TypeOf(sql.NullFloat64{})
+	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{}))
 )
 
 type mysqlField struct {
@@ -177,21 +175,11 @@ func (mf *mysqlField) scanType() reflect.Type {
 		}
 		return scanTypeNullFloat
 
-	case fieldTypeVarChar, fieldTypeString, fieldTypeVarString, fieldTypeTinyBLOB,
-		fieldTypeMediumBLOB, fieldTypeLongBLOB, fieldTypeBLOB:
-		// charsetnr == 63 means this column is binary.
-		// https://dev.mysql.com/doc/refman/8.0/en/c-api-data-structures.html
-		if mf.charSet == 63 {
-			return scanTypeRawBytes
-		}
-		if mf.flags&flagNotNULL != 0 {
-			return scanTypeString
-		}
-		return scanTypeNullString
-
-	case fieldTypeDecimal, fieldTypeNewDecimal, fieldTypeBit,
-		fieldTypeEnum, fieldTypeSet,
-		fieldTypeGeometry, fieldTypeJSON, fieldTypeTime:
+	case fieldTypeDecimal, fieldTypeNewDecimal, fieldTypeVarChar,
+		fieldTypeBit, fieldTypeEnum, fieldTypeSet, fieldTypeTinyBLOB,
+		fieldTypeMediumBLOB, fieldTypeLongBLOB, fieldTypeBLOB,
+		fieldTypeVarString, fieldTypeString, fieldTypeGeometry, fieldTypeJSON,
+		fieldTypeTime:
 		return scanTypeRawBytes
 
 	case fieldTypeDate, fieldTypeNewDate,

From 296987f946840022593fa9179ee8cfb58307caf5 Mon Sep 17 00:00:00 2001
From: Matthew Herrmann <47012945+mherr-google@users.noreply.github.com>
Date: Fri, 1 Nov 2019 17:35:43 +1100
Subject: [PATCH 109/282] Fix connection leak caused by rapid context
 cancellation (#1024)

Fixes #1023
---
 connector.go         |  1 +
 driver_go110_test.go | 53 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 54 insertions(+)

diff --git a/connector.go b/connector.go
index 5aaaba43e..bbad4e23b 100644
--- a/connector.go
+++ b/connector.go
@@ -64,6 +64,7 @@ func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
 	// Call startWatcher for context support (From Go 1.8)
 	mc.startWatcher()
 	if err := mc.watchCancel(ctx); err != nil {
+		mc.cleanup()
 		return nil, err
 	}
 	defer mc.finish()
diff --git a/driver_go110_test.go b/driver_go110_test.go
index 19a0e5956..fd8df8975 100644
--- a/driver_go110_test.go
+++ b/driver_go110_test.go
@@ -135,3 +135,56 @@ func TestConnectorTimeoutsDuringOpen(t *testing.T) {
 		t.Fatalf("(*Connector).Connect should have timed out")
 	}
 }
+
+// A connection which can only be closed.
+type dummyConnection struct {
+	net.Conn
+	closed bool
+}
+
+func (d *dummyConnection) Close() error {
+	d.closed = true
+	return nil
+}
+
+func TestConnectorTimeoutsWatchCancel(t *testing.T) {
+	var (
+		cancel  func()           // Used to cancel the context just after connecting.
+		created *dummyConnection // The created connection.
+	)
+
+	RegisterDialContext("TestConnectorTimeoutsWatchCancel", func(ctx context.Context, addr string) (net.Conn, error) {
+		// Canceling at this time triggers the watchCancel error branch in Connect().
+		cancel()
+		created = &dummyConnection{}
+		return created, nil
+	})
+
+	mycnf := NewConfig()
+	mycnf.User = "root"
+	mycnf.Addr = "foo"
+	mycnf.Net = "TestConnectorTimeoutsWatchCancel"
+
+	conn, err := NewConnector(mycnf)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	db := sql.OpenDB(conn)
+	defer db.Close()
+
+	var ctx context.Context
+	ctx, cancel = context.WithCancel(context.Background())
+	defer cancel()
+
+	if _, err := db.Conn(ctx); err != context.Canceled {
+		t.Errorf("got %v, want context.Canceled", err)
+	}
+
+	if created == nil {
+		t.Fatal("no connection created")
+	}
+	if !created.closed {
+		t.Errorf("connection not closed")
+	}
+}

From b57978c556ea168c02a3246eb044fd8b5afa5320 Mon Sep 17 00:00:00 2001
From: Bouke van der Bijl <me@bou.ke>
Date: Fri, 8 Nov 2019 14:06:54 +0100
Subject: [PATCH 110/282] connector: don't return ErrBadConn when failing to
 connect (#1020)

ErrBadConn should only be returned for an already established
connection, not when creating a new one.
---
 AUTHORS           |  1 +
 connector.go      |  4 ----
 connector_test.go | 30 ++++++++++++++++++++++++++++++
 driver_test.go    |  2 +-
 4 files changed, 32 insertions(+), 5 deletions(-)
 create mode 100644 connector_test.go

diff --git a/AUTHORS b/AUTHORS
index d87414e82..9765b5348 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -91,6 +91,7 @@ Zhenye Xie <xiezhenye at gmail.com>
 
 Barracuda Networks, Inc.
 Counting Ltd.
+DigitalOcean Inc.
 Facebook Inc.
 GitHub Inc.
 Google Inc.
diff --git a/connector.go b/connector.go
index bbad4e23b..eac0f01aa 100644
--- a/connector.go
+++ b/connector.go
@@ -44,10 +44,6 @@ func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
 	}
 
 	if err != nil {
-		if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
-			errLog.Print("net.Error from Dial()': ", nerr.Error())
-			return nil, driver.ErrBadConn
-		}
 		return nil, err
 	}
 
diff --git a/connector_test.go b/connector_test.go
new file mode 100644
index 000000000..976903c5b
--- /dev/null
+++ b/connector_test.go
@@ -0,0 +1,30 @@
+package mysql
+
+import (
+	"context"
+	"net"
+	"testing"
+	"time"
+)
+
+func TestConnectorReturnsTimeout(t *testing.T) {
+	connector := &connector{&Config{
+		Net:     "tcp",
+		Addr:    "1.1.1.1:1234",
+		Timeout: 10 * time.Millisecond,
+	}}
+
+	_, err := connector.Connect(context.Background())
+	if err == nil {
+		t.Fatal("error expected")
+	}
+
+	if nerr, ok := err.(*net.OpError); ok {
+		expected := "dial tcp 1.1.1.1:1234: i/o timeout"
+		if nerr.Error() != expected {
+			t.Fatalf("expected %q, got %q", expected, nerr.Error())
+		}
+	} else {
+		t.Fatalf("expected %T, got %T", nerr, err)
+	}
+}
diff --git a/driver_test.go b/driver_test.go
index 3dee1bab2..df7353dbb 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -1874,7 +1874,7 @@ func TestDialNonRetryableNetErr(t *testing.T) {
 
 func TestDialTemporaryNetErr(t *testing.T) {
 	testErr := netErrorMock{temporary: true}
-	testDialError(t, testErr, driver.ErrBadConn)
+	testDialError(t, testErr, testErr)
 }
 
 // Tests custom dial functions

From b4242bab7dc57d57fef955900943999b6fb34b67 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Thu, 14 Nov 2019 20:57:53 +0900
Subject: [PATCH 111/282] Return ErrBadConn in Prepare (#1030)

---
 connection.go | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/connection.go b/connection.go
index 565a5480a..e4bb59e67 100644
--- a/connection.go
+++ b/connection.go
@@ -154,7 +154,9 @@ func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
 	// Send command
 	err := mc.writeCommandPacketStr(comStmtPrepare, query)
 	if err != nil {
-		return nil, mc.markBadConn(err)
+		// STMT_PREPARE is safe to retry.  So we can return ErrBadConn here.
+		errLog.Print(err)
+		return nil, driver.ErrBadConn
 	}
 
 	stmt := &mysqlStmt{

From 15462c1d60d42ecca11d6ef9fec0b0afd5833459 Mon Sep 17 00:00:00 2001
From: zjj <jj@yuzao.org>
Date: Thu, 21 Nov 2019 14:26:41 +0800
Subject: [PATCH 112/282] Set timeout for custom dialer. (#1035)

Fixes #1034.
---
 AUTHORS      | 1 +
 connector.go | 8 +++++++-
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/AUTHORS b/AUTHORS
index 9765b5348..7d647012d 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -44,6 +44,7 @@ James Harr <james.harr at gmail.com>
 Jeff Hodges <jeff at somethingsimilar.com>
 Jeffrey Charles <jeffreycharles at gmail.com>
 Jerome Meyer <jxmeyer at gmail.com>
+Jiajia Zhong <zhong2plus at gmail.com>
 Jian Zhen <zhenjl at gmail.com>
 Joshua Prunier <joshua.prunier at gmail.com>
 Julien Lefevre <julien.lefevr at gmail.com>
diff --git a/connector.go b/connector.go
index eac0f01aa..d567b4e4f 100644
--- a/connector.go
+++ b/connector.go
@@ -37,7 +37,13 @@ func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
 	dial, ok := dials[mc.cfg.Net]
 	dialsLock.RUnlock()
 	if ok {
-		mc.netConn, err = dial(ctx, mc.cfg.Addr)
+		dctx := ctx
+		if mc.cfg.Timeout > 0 {
+			var cancel context.CancelFunc
+			dctx, cancel = context.WithTimeout(ctx, c.cfg.Timeout)
+			defer cancel()
+		}
+		mc.netConn, err = dial(dctx, mc.cfg.Addr)
 	} else {
 		nd := net.Dialer{Timeout: mc.cfg.Timeout}
 		mc.netConn, err = nd.DialContext(ctx, mc.cfg.Net, mc.cfg.Addr)

From 94084c99124c2870fa9c72c4f5e41b8d2d8335a1 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Thu, 12 Dec 2019 00:38:32 +0900
Subject: [PATCH 113/282] Drop Go 1.9 support (#1017)

* Drop Go 1.9 support

* Add test for Go 1.13.
---
 .travis.yml | 2 +-
 README.md   | 2 +-
 go.mod      | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 9d3139207..56fcf25f2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,10 +1,10 @@
 sudo: false
 language: go
 go:
-  - 1.9.x
   - 1.10.x
   - 1.11.x
   - 1.12.x
+  - 1.13.x
   - master
 
 before_install:
diff --git a/README.md b/README.md
index dce7a5325..2d15ffda3 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
   * Optional placeholder interpolation
 
 ## Requirements
-  * Go 1.9 or higher. We aim to support the 3 latest versions of Go.
+  * Go 1.10 or higher. We aim to support the 3 latest versions of Go.
   * MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
 
 ---------------------------------------
diff --git a/go.mod b/go.mod
index 29e5d800d..fffbf6a90 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,3 @@
 module github.com/go-sql-driver/mysql
 
-go 1.9
+go 1.10

From b66d043e6c8986ca01241b990326db395f9c0afd Mon Sep 17 00:00:00 2001
From: Nathanial Murphy <murphy.nathanial@gmail.com>
Date: Thu, 12 Dec 2019 10:19:55 +1000
Subject: [PATCH 114/282] Remove "go1.10" build tag (#1016)

Some IDEs and editors refuse to acknowledge the "go1.10" build tag when
autocompleting & compiling. Removing said tag increases usibility of the
library for those stuck with these editors.
---
 AUTHORS              |   1 +
 driver.go            |  22 +++++
 driver_go110.go      |  37 ---------
 driver_go110_test.go | 190 -------------------------------------------
 driver_test.go       | 169 ++++++++++++++++++++++++++++++++++++++
 5 files changed, 192 insertions(+), 227 deletions(-)
 delete mode 100644 driver_go110.go
 delete mode 100644 driver_go110_test.go

diff --git a/AUTHORS b/AUTHORS
index 7d647012d..ad5989800 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -63,6 +63,7 @@ Lucas Liu <extrafliu at gmail.com>
 Luke Scott <luke at webconnex.com>
 Maciej Zimnoch <maciej.zimnoch at codilime.com>
 Michael Woolnough <michael.woolnough at gmail.com>
+Nathanial Murphy <nathanial.murphy at gmail.com>
 Nicola Peduzzi <thenikso at gmail.com>
 Olivier Mengué <dolmen at cpan.org>
 oscarzhao <oscarzhaosl at gmail.com>
diff --git a/driver.go b/driver.go
index 1f9decf80..c1bdf1199 100644
--- a/driver.go
+++ b/driver.go
@@ -83,3 +83,25 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
 func init() {
 	sql.Register("mysql", &MySQLDriver{})
 }
+
+// NewConnector returns new driver.Connector.
+func NewConnector(cfg *Config) (driver.Connector, error) {
+	cfg = cfg.Clone()
+	// normalize the contents of cfg so calls to NewConnector have the same
+	// behavior as MySQLDriver.OpenConnector
+	if err := cfg.normalize(); err != nil {
+		return nil, err
+	}
+	return &connector{cfg: cfg}, nil
+}
+
+// OpenConnector implements driver.DriverContext.
+func (d MySQLDriver) OpenConnector(dsn string) (driver.Connector, error) {
+	cfg, err := ParseDSN(dsn)
+	if err != nil {
+		return nil, err
+	}
+	return &connector{
+		cfg: cfg,
+	}, nil
+}
diff --git a/driver_go110.go b/driver_go110.go
deleted file mode 100644
index eb5a8fe9b..000000000
--- a/driver_go110.go
+++ /dev/null
@@ -1,37 +0,0 @@
-// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
-//
-// Copyright 2018 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/.
-
-// +build go1.10
-
-package mysql
-
-import (
-	"database/sql/driver"
-)
-
-// NewConnector returns new driver.Connector.
-func NewConnector(cfg *Config) (driver.Connector, error) {
-	cfg = cfg.Clone()
-	// normalize the contents of cfg so calls to NewConnector have the same
-	// behavior as MySQLDriver.OpenConnector
-	if err := cfg.normalize(); err != nil {
-		return nil, err
-	}
-	return &connector{cfg: cfg}, nil
-}
-
-// OpenConnector implements driver.DriverContext.
-func (d MySQLDriver) OpenConnector(dsn string) (driver.Connector, error) {
-	cfg, err := ParseDSN(dsn)
-	if err != nil {
-		return nil, err
-	}
-	return &connector{
-		cfg: cfg,
-	}, nil
-}
diff --git a/driver_go110_test.go b/driver_go110_test.go
deleted file mode 100644
index fd8df8975..000000000
--- a/driver_go110_test.go
+++ /dev/null
@@ -1,190 +0,0 @@
-// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
-//
-// Copyright 2018 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/.
-
-// +build go1.10
-
-package mysql
-
-import (
-	"context"
-	"database/sql"
-	"database/sql/driver"
-	"fmt"
-	"net"
-	"testing"
-	"time"
-)
-
-var _ driver.DriverContext = &MySQLDriver{}
-
-type dialCtxKey struct{}
-
-func TestConnectorObeysDialTimeouts(t *testing.T) {
-	if !available {
-		t.Skipf("MySQL server not running on %s", netAddr)
-	}
-
-	RegisterDialContext("dialctxtest", func(ctx context.Context, addr string) (net.Conn, error) {
-		var d net.Dialer
-		if !ctx.Value(dialCtxKey{}).(bool) {
-			return nil, fmt.Errorf("test error: query context is not propagated to our dialer")
-		}
-		return d.DialContext(ctx, prot, addr)
-	})
-
-	db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@dialctxtest(%s)/%s?timeout=30s", user, pass, addr, dbname))
-	if err != nil {
-		t.Fatalf("error connecting: %s", err.Error())
-	}
-	defer db.Close()
-
-	ctx := context.WithValue(context.Background(), dialCtxKey{}, true)
-
-	_, err = db.ExecContext(ctx, "DO 1")
-	if err != nil {
-		t.Fatal(err)
-	}
-}
-
-func configForTests(t *testing.T) *Config {
-	if !available {
-		t.Skipf("MySQL server not running on %s", netAddr)
-	}
-
-	mycnf := NewConfig()
-	mycnf.User = user
-	mycnf.Passwd = pass
-	mycnf.Addr = addr
-	mycnf.Net = prot
-	mycnf.DBName = dbname
-	return mycnf
-}
-
-func TestNewConnector(t *testing.T) {
-	mycnf := configForTests(t)
-	conn, err := NewConnector(mycnf)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	db := sql.OpenDB(conn)
-	defer db.Close()
-
-	if err := db.Ping(); err != nil {
-		t.Fatal(err)
-	}
-}
-
-type slowConnection struct {
-	net.Conn
-	slowdown time.Duration
-}
-
-func (sc *slowConnection) Read(b []byte) (int, error) {
-	time.Sleep(sc.slowdown)
-	return sc.Conn.Read(b)
-}
-
-type connectorHijack struct {
-	driver.Connector
-	connErr error
-}
-
-func (cw *connectorHijack) Connect(ctx context.Context) (driver.Conn, error) {
-	var conn driver.Conn
-	conn, cw.connErr = cw.Connector.Connect(ctx)
-	return conn, cw.connErr
-}
-
-func TestConnectorTimeoutsDuringOpen(t *testing.T) {
-	RegisterDialContext("slowconn", func(ctx context.Context, addr string) (net.Conn, error) {
-		var d net.Dialer
-		conn, err := d.DialContext(ctx, prot, addr)
-		if err != nil {
-			return nil, err
-		}
-		return &slowConnection{Conn: conn, slowdown: 100 * time.Millisecond}, nil
-	})
-
-	mycnf := configForTests(t)
-	mycnf.Net = "slowconn"
-
-	conn, err := NewConnector(mycnf)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	hijack := &connectorHijack{Connector: conn}
-
-	db := sql.OpenDB(hijack)
-	defer db.Close()
-
-	ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
-	defer cancel()
-
-	_, err = db.ExecContext(ctx, "DO 1")
-	if err != context.DeadlineExceeded {
-		t.Fatalf("ExecContext should have timed out")
-	}
-	if hijack.connErr != context.DeadlineExceeded {
-		t.Fatalf("(*Connector).Connect should have timed out")
-	}
-}
-
-// A connection which can only be closed.
-type dummyConnection struct {
-	net.Conn
-	closed bool
-}
-
-func (d *dummyConnection) Close() error {
-	d.closed = true
-	return nil
-}
-
-func TestConnectorTimeoutsWatchCancel(t *testing.T) {
-	var (
-		cancel  func()           // Used to cancel the context just after connecting.
-		created *dummyConnection // The created connection.
-	)
-
-	RegisterDialContext("TestConnectorTimeoutsWatchCancel", func(ctx context.Context, addr string) (net.Conn, error) {
-		// Canceling at this time triggers the watchCancel error branch in Connect().
-		cancel()
-		created = &dummyConnection{}
-		return created, nil
-	})
-
-	mycnf := NewConfig()
-	mycnf.User = "root"
-	mycnf.Addr = "foo"
-	mycnf.Net = "TestConnectorTimeoutsWatchCancel"
-
-	conn, err := NewConnector(mycnf)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	db := sql.OpenDB(conn)
-	defer db.Close()
-
-	var ctx context.Context
-	ctx, cancel = context.WithCancel(context.Background())
-	defer cancel()
-
-	if _, err := db.Conn(ctx); err != context.Canceled {
-		t.Errorf("got %v, want context.Canceled", err)
-	}
-
-	if created == nil {
-		t.Fatal("no connection created")
-	}
-	if !created.closed {
-		t.Errorf("connection not closed")
-	}
-}
diff --git a/driver_test.go b/driver_test.go
index df7353dbb..ace083dfc 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -2994,3 +2994,172 @@ func TestRawBytesAreNotModified(t *testing.T) {
 		}
 	})
 }
+
+var _ driver.DriverContext = &MySQLDriver{}
+
+type dialCtxKey struct{}
+
+func TestConnectorObeysDialTimeouts(t *testing.T) {
+	if !available {
+		t.Skipf("MySQL server not running on %s", netAddr)
+	}
+
+	RegisterDialContext("dialctxtest", func(ctx context.Context, addr string) (net.Conn, error) {
+		var d net.Dialer
+		if !ctx.Value(dialCtxKey{}).(bool) {
+			return nil, fmt.Errorf("test error: query context is not propagated to our dialer")
+		}
+		return d.DialContext(ctx, prot, addr)
+	})
+
+	db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@dialctxtest(%s)/%s?timeout=30s", user, pass, addr, dbname))
+	if err != nil {
+		t.Fatalf("error connecting: %s", err.Error())
+	}
+	defer db.Close()
+
+	ctx := context.WithValue(context.Background(), dialCtxKey{}, true)
+
+	_, err = db.ExecContext(ctx, "DO 1")
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func configForTests(t *testing.T) *Config {
+	if !available {
+		t.Skipf("MySQL server not running on %s", netAddr)
+	}
+
+	mycnf := NewConfig()
+	mycnf.User = user
+	mycnf.Passwd = pass
+	mycnf.Addr = addr
+	mycnf.Net = prot
+	mycnf.DBName = dbname
+	return mycnf
+}
+
+func TestNewConnector(t *testing.T) {
+	mycnf := configForTests(t)
+	conn, err := NewConnector(mycnf)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	db := sql.OpenDB(conn)
+	defer db.Close()
+
+	if err := db.Ping(); err != nil {
+		t.Fatal(err)
+	}
+}
+
+type slowConnection struct {
+	net.Conn
+	slowdown time.Duration
+}
+
+func (sc *slowConnection) Read(b []byte) (int, error) {
+	time.Sleep(sc.slowdown)
+	return sc.Conn.Read(b)
+}
+
+type connectorHijack struct {
+	driver.Connector
+	connErr error
+}
+
+func (cw *connectorHijack) Connect(ctx context.Context) (driver.Conn, error) {
+	var conn driver.Conn
+	conn, cw.connErr = cw.Connector.Connect(ctx)
+	return conn, cw.connErr
+}
+
+func TestConnectorTimeoutsDuringOpen(t *testing.T) {
+	RegisterDialContext("slowconn", func(ctx context.Context, addr string) (net.Conn, error) {
+		var d net.Dialer
+		conn, err := d.DialContext(ctx, prot, addr)
+		if err != nil {
+			return nil, err
+		}
+		return &slowConnection{Conn: conn, slowdown: 100 * time.Millisecond}, nil
+	})
+
+	mycnf := configForTests(t)
+	mycnf.Net = "slowconn"
+
+	conn, err := NewConnector(mycnf)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	hijack := &connectorHijack{Connector: conn}
+
+	db := sql.OpenDB(hijack)
+	defer db.Close()
+
+	ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
+	defer cancel()
+
+	_, err = db.ExecContext(ctx, "DO 1")
+	if err != context.DeadlineExceeded {
+		t.Fatalf("ExecContext should have timed out")
+	}
+	if hijack.connErr != context.DeadlineExceeded {
+		t.Fatalf("(*Connector).Connect should have timed out")
+	}
+}
+
+// A connection which can only be closed.
+type dummyConnection struct {
+	net.Conn
+	closed bool
+}
+
+func (d *dummyConnection) Close() error {
+	d.closed = true
+	return nil
+}
+
+func TestConnectorTimeoutsWatchCancel(t *testing.T) {
+	var (
+		cancel  func()           // Used to cancel the context just after connecting.
+		created *dummyConnection // The created connection.
+	)
+
+	RegisterDialContext("TestConnectorTimeoutsWatchCancel", func(ctx context.Context, addr string) (net.Conn, error) {
+		// Canceling at this time triggers the watchCancel error branch in Connect().
+		cancel()
+		created = &dummyConnection{}
+		return created, nil
+	})
+
+	mycnf := NewConfig()
+	mycnf.User = "root"
+	mycnf.Addr = "foo"
+	mycnf.Net = "TestConnectorTimeoutsWatchCancel"
+
+	conn, err := NewConnector(mycnf)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	db := sql.OpenDB(conn)
+	defer db.Close()
+
+	var ctx context.Context
+	ctx, cancel = context.WithCancel(context.Background())
+	defer cancel()
+
+	if _, err := db.Conn(ctx); err != context.Canceled {
+		t.Errorf("got %v, want context.Canceled", err)
+	}
+
+	if created == nil {
+		t.Fatal("no connection created")
+	}
+	if !created.closed {
+		t.Errorf("connection not closed")
+	}
+}

From 87239404061139bfbd9a61b167c5ed54539e6086 Mon Sep 17 00:00:00 2001
From: Ichinose Shogo <shogo82148@gmail.com>
Date: Sun, 5 Jan 2020 05:18:52 +0000
Subject: [PATCH 115/282] fix compile error of connCheck.go (#1048)

* fix compile error of connCheck.go

* remove "appengine" build tag
---
 conncheck.go       | 2 +-
 conncheck_dummy.go | 2 +-
 conncheck_test.go  | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/conncheck.go b/conncheck.go
index 70e9925f6..3dd115485 100644
--- a/conncheck.go
+++ b/conncheck.go
@@ -6,7 +6,7 @@
 // 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/.
 
-// +build !windows,!appengine
+// +build linux darwin
 
 package mysql
 
diff --git a/conncheck_dummy.go b/conncheck_dummy.go
index 4888288aa..d042811c1 100644
--- a/conncheck_dummy.go
+++ b/conncheck_dummy.go
@@ -6,7 +6,7 @@
 // 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/.
 
-// +build windows appengine
+// +build !linux,!darwin
 
 package mysql
 
diff --git a/conncheck_test.go b/conncheck_test.go
index b7234b0f5..8cd81223a 100644
--- a/conncheck_test.go
+++ b/conncheck_test.go
@@ -6,7 +6,7 @@
 // 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/.
 
-// +build go1.10,!windows
+// +build linux darwin
 
 package mysql
 

From 4bdaef4517f923802be30b39b8c12d97fee32466 Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Tue, 7 Jan 2020 08:34:00 +0100
Subject: [PATCH 116/282] conncheck: build on more supported platforms (#1051)

---
 conncheck.go       | 2 +-
 conncheck_dummy.go | 2 +-
 conncheck_test.go  | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/conncheck.go b/conncheck.go
index 3dd115485..024eb2858 100644
--- a/conncheck.go
+++ b/conncheck.go
@@ -6,7 +6,7 @@
 // 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/.
 
-// +build linux darwin
+// +build linux darwin dragonfly freebsd netbsd openbsd solaris illumos
 
 package mysql
 
diff --git a/conncheck_dummy.go b/conncheck_dummy.go
index d042811c1..ea7fb607a 100644
--- a/conncheck_dummy.go
+++ b/conncheck_dummy.go
@@ -6,7 +6,7 @@
 // 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/.
 
-// +build !linux,!darwin
+// +build !linux,!darwin,!dragonfly,!freebsd,!netbsd,!openbsd,!solaris,!illumos
 
 package mysql
 
diff --git a/conncheck_test.go b/conncheck_test.go
index 8cd81223a..53995517b 100644
--- a/conncheck_test.go
+++ b/conncheck_test.go
@@ -6,7 +6,7 @@
 // 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/.
 
-// +build linux darwin
+// +build linux darwin dragonfly freebsd netbsd openbsd solaris illumos
 
 package mysql
 

From 6844171c7b652e0a8271a909fc48f6a32492d04f Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Tue, 7 Jan 2020 12:54:56 +0100
Subject: [PATCH 117/282] conncheck: allow to disable via config (#1052)

* conncheck: allow to disable via config

* dsn: refactor writing of params in FormatDSN
---
 README.md   |  11 ++++
 dsn.go      | 166 ++++++++++++++--------------------------------------
 dsn_test.go |  36 ++++++------
 packets.go  |   2 +-
 4 files changed, 75 insertions(+), 140 deletions(-)

diff --git a/README.md b/README.md
index 2d15ffda3..239de7b6b 100644
--- a/README.md
+++ b/README.md
@@ -166,6 +166,17 @@ Sets the charset used for client-server interaction (`"SET NAMES <value>"`). If
 Usage of the `charset` parameter is discouraged because it issues additional queries to the server.
 Unless you need the fallback behavior, please use `collation` instead.
 
+##### `checkConnLiveness`
+
+```
+Type:           bool
+Valid Values:   true, false
+Default:        true
+```
+
+On supported platforms connections retrieved from the connection pool are checked for liveness before using them. If the check fails, the respective connection is marked as bad and the query retried with another connection.
+`checkConnLiveness=false` disables this liveness check of connections.
+
 ##### `collation`
 
 ```
diff --git a/dsn.go b/dsn.go
index 1d9b4ab0a..75c8c2489 100644
--- a/dsn.go
+++ b/dsn.go
@@ -55,6 +55,7 @@ type Config struct {
 	AllowCleartextPasswords bool // Allows the cleartext client side plugin
 	AllowNativePasswords    bool // Allows the native password authentication method
 	AllowOldPasswords       bool // Allows the old insecure password method
+	CheckConnLiveness       bool // Check connections for liveness before using them
 	ClientFoundRows         bool // Return number of matching rows instead of rows changed
 	ColumnsWithAlias        bool // Prepend table alias to column names
 	InterpolateParams       bool // Interpolate placeholders into query string
@@ -70,6 +71,7 @@ func NewConfig() *Config {
 		Loc:                  time.UTC,
 		MaxAllowedPacket:     defaultMaxAllowedPacket,
 		AllowNativePasswords: true,
+		CheckConnLiveness:    true,
 	}
 }
 
@@ -148,6 +150,19 @@ func (cfg *Config) normalize() error {
 	return nil
 }
 
+func writeDSNParam(buf *bytes.Buffer, hasParam *bool, name, value string) {
+	buf.Grow(1 + len(name) + 1 + len(value))
+	if !*hasParam {
+		*hasParam = true
+		buf.WriteByte('?')
+	} else {
+		buf.WriteByte('&')
+	}
+	buf.WriteString(name)
+	buf.WriteByte('=')
+	buf.WriteString(value)
+}
+
 // FormatDSN formats the given Config into a DSN string which can be passed to
 // the driver.
 func (cfg *Config) FormatDSN() string {
@@ -186,165 +201,75 @@ func (cfg *Config) FormatDSN() string {
 	}
 
 	if cfg.AllowCleartextPasswords {
-		if hasParam {
-			buf.WriteString("&allowCleartextPasswords=true")
-		} else {
-			hasParam = true
-			buf.WriteString("?allowCleartextPasswords=true")
-		}
+		writeDSNParam(&buf, &hasParam, "allowCleartextPasswords", "true")
 	}
 
 	if !cfg.AllowNativePasswords {
-		if hasParam {
-			buf.WriteString("&allowNativePasswords=false")
-		} else {
-			hasParam = true
-			buf.WriteString("?allowNativePasswords=false")
-		}
+		writeDSNParam(&buf, &hasParam, "allowNativePasswords", "false")
 	}
 
 	if cfg.AllowOldPasswords {
-		if hasParam {
-			buf.WriteString("&allowOldPasswords=true")
-		} else {
-			hasParam = true
-			buf.WriteString("?allowOldPasswords=true")
-		}
+		writeDSNParam(&buf, &hasParam, "allowOldPasswords", "true")
+	}
+
+	if !cfg.CheckConnLiveness {
+		writeDSNParam(&buf, &hasParam, "checkConnLiveness", "false")
 	}
 
 	if cfg.ClientFoundRows {
-		if hasParam {
-			buf.WriteString("&clientFoundRows=true")
-		} else {
-			hasParam = true
-			buf.WriteString("?clientFoundRows=true")
-		}
+		writeDSNParam(&buf, &hasParam, "clientFoundRows", "true")
 	}
 
 	if col := cfg.Collation; col != defaultCollation && len(col) > 0 {
-		if hasParam {
-			buf.WriteString("&collation=")
-		} else {
-			hasParam = true
-			buf.WriteString("?collation=")
-		}
-		buf.WriteString(col)
+		writeDSNParam(&buf, &hasParam, "collation", col)
 	}
 
 	if cfg.ColumnsWithAlias {
-		if hasParam {
-			buf.WriteString("&columnsWithAlias=true")
-		} else {
-			hasParam = true
-			buf.WriteString("?columnsWithAlias=true")
-		}
+		writeDSNParam(&buf, &hasParam, "columnsWithAlias", "true")
 	}
 
 	if cfg.InterpolateParams {
-		if hasParam {
-			buf.WriteString("&interpolateParams=true")
-		} else {
-			hasParam = true
-			buf.WriteString("?interpolateParams=true")
-		}
+		writeDSNParam(&buf, &hasParam, "interpolateParams", "true")
 	}
 
 	if cfg.Loc != time.UTC && cfg.Loc != nil {
-		if hasParam {
-			buf.WriteString("&loc=")
-		} else {
-			hasParam = true
-			buf.WriteString("?loc=")
-		}
-		buf.WriteString(url.QueryEscape(cfg.Loc.String()))
+		writeDSNParam(&buf, &hasParam, "loc", url.QueryEscape(cfg.Loc.String()))
 	}
 
 	if cfg.MultiStatements {
-		if hasParam {
-			buf.WriteString("&multiStatements=true")
-		} else {
-			hasParam = true
-			buf.WriteString("?multiStatements=true")
-		}
+		writeDSNParam(&buf, &hasParam, "multiStatements", "true")
 	}
 
 	if cfg.ParseTime {
-		if hasParam {
-			buf.WriteString("&parseTime=true")
-		} else {
-			hasParam = true
-			buf.WriteString("?parseTime=true")
-		}
+		writeDSNParam(&buf, &hasParam, "parseTime", "true")
 	}
 
 	if cfg.ReadTimeout > 0 {
-		if hasParam {
-			buf.WriteString("&readTimeout=")
-		} else {
-			hasParam = true
-			buf.WriteString("?readTimeout=")
-		}
-		buf.WriteString(cfg.ReadTimeout.String())
+		writeDSNParam(&buf, &hasParam, "readTimeout", cfg.ReadTimeout.String())
 	}
 
 	if cfg.RejectReadOnly {
-		if hasParam {
-			buf.WriteString("&rejectReadOnly=true")
-		} else {
-			hasParam = true
-			buf.WriteString("?rejectReadOnly=true")
-		}
+		writeDSNParam(&buf, &hasParam, "rejectReadOnly", "true")
 	}
 
 	if len(cfg.ServerPubKey) > 0 {
-		if hasParam {
-			buf.WriteString("&serverPubKey=")
-		} else {
-			hasParam = true
-			buf.WriteString("?serverPubKey=")
-		}
-		buf.WriteString(url.QueryEscape(cfg.ServerPubKey))
+		writeDSNParam(&buf, &hasParam, "serverPubKey", url.QueryEscape(cfg.ServerPubKey))
 	}
 
 	if cfg.Timeout > 0 {
-		if hasParam {
-			buf.WriteString("&timeout=")
-		} else {
-			hasParam = true
-			buf.WriteString("?timeout=")
-		}
-		buf.WriteString(cfg.Timeout.String())
+		writeDSNParam(&buf, &hasParam, "timeout", cfg.Timeout.String())
 	}
 
 	if len(cfg.TLSConfig) > 0 {
-		if hasParam {
-			buf.WriteString("&tls=")
-		} else {
-			hasParam = true
-			buf.WriteString("?tls=")
-		}
-		buf.WriteString(url.QueryEscape(cfg.TLSConfig))
+		writeDSNParam(&buf, &hasParam, "tls", url.QueryEscape(cfg.TLSConfig))
 	}
 
 	if cfg.WriteTimeout > 0 {
-		if hasParam {
-			buf.WriteString("&writeTimeout=")
-		} else {
-			hasParam = true
-			buf.WriteString("?writeTimeout=")
-		}
-		buf.WriteString(cfg.WriteTimeout.String())
+		writeDSNParam(&buf, &hasParam, "writeTimeout", cfg.WriteTimeout.String())
 	}
 
 	if cfg.MaxAllowedPacket != defaultMaxAllowedPacket {
-		if hasParam {
-			buf.WriteString("&maxAllowedPacket=")
-		} else {
-			hasParam = true
-			buf.WriteString("?maxAllowedPacket=")
-		}
-		buf.WriteString(strconv.Itoa(cfg.MaxAllowedPacket))
-
+		writeDSNParam(&buf, &hasParam, "maxAllowedPacket", strconv.Itoa(cfg.MaxAllowedPacket))
 	}
 
 	// other params
@@ -355,16 +280,7 @@ func (cfg *Config) FormatDSN() string {
 		}
 		sort.Strings(params)
 		for _, param := range params {
-			if hasParam {
-				buf.WriteByte('&')
-			} else {
-				hasParam = true
-				buf.WriteByte('?')
-			}
-
-			buf.WriteString(param)
-			buf.WriteByte('=')
-			buf.WriteString(url.QueryEscape(cfg.Params[param]))
+			writeDSNParam(&buf, &hasParam, param, url.QueryEscape(cfg.Params[param]))
 		}
 	}
 
@@ -491,6 +407,14 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 				return errors.New("invalid bool value: " + value)
 			}
 
+		// Check connections for Liveness before using them
+		case "checkConnLiveness":
+			var isBool bool
+			cfg.CheckConnLiveness, isBool = readBool(value)
+			if !isBool {
+				return errors.New("invalid bool value: " + value)
+			}
+
 		// Switch "rowsAffected" mode
 		case "clientFoundRows":
 			var isBool bool
diff --git a/dsn_test.go b/dsn_test.go
index 50dc2932c..89815b341 100644
--- a/dsn_test.go
+++ b/dsn_test.go
@@ -22,55 +22,55 @@ var testDSNs = []struct {
 	out *Config
 }{{
 	"username:password@protocol(address)/dbname?param=value",
-	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
+	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true",
-	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, ColumnsWithAlias: true},
+	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true, ColumnsWithAlias: true},
 }, {
 	"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true&multiStatements=true",
-	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, ColumnsWithAlias: true, MultiStatements: true},
+	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true, ColumnsWithAlias: true, MultiStatements: true},
 }, {
 	"user@unix(/path/to/socket)/dbname?charset=utf8",
-	&Config{User: "user", Net: "unix", Addr: "/path/to/socket", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
+	&Config{User: "user", Net: "unix", Addr: "/path/to/socket", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true",
-	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, TLSConfig: "true"},
+	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true, TLSConfig: "true"},
 }, {
 	"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify",
-	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8mb4,utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, TLSConfig: "skip-verify"},
+	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8mb4,utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true, TLSConfig: "skip-verify"},
 }, {
 	"user:password@/dbname?loc=UTC&timeout=30s&readTimeout=1s&writeTimeout=1s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci&maxAllowedPacket=16777216&tls=false&allowCleartextPasswords=true&parseTime=true&rejectReadOnly=true",
-	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_unicode_ci", Loc: time.UTC, TLSConfig: "false", AllowCleartextPasswords: true, AllowNativePasswords: true, Timeout: 30 * time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, AllowAllFiles: true, AllowOldPasswords: true, ClientFoundRows: true, MaxAllowedPacket: 16777216, ParseTime: true, RejectReadOnly: true},
+	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_unicode_ci", Loc: time.UTC, TLSConfig: "false", AllowCleartextPasswords: true, AllowNativePasswords: true, Timeout: 30 * time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, AllowAllFiles: true, AllowOldPasswords: true, CheckConnLiveness: true, ClientFoundRows: true, MaxAllowedPacket: 16777216, ParseTime: true, RejectReadOnly: true},
 }, {
-	"user:password@/dbname?allowNativePasswords=false&maxAllowedPacket=0",
-	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: 0, AllowNativePasswords: false},
+	"user:password@/dbname?allowNativePasswords=false&checkConnLiveness=false&maxAllowedPacket=0",
+	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: 0, AllowNativePasswords: false, CheckConnLiveness: false},
 }, {
 	"user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local",
-	&Config{User: "user", Passwd: "p@ss(word)", Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:80", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.Local, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
+	&Config{User: "user", Passwd: "p@ss(word)", Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:80", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.Local, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"/dbname",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"@/",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"/",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"user:p@/ssword@/",
-	&Config{User: "user", Passwd: "p@/ssword", Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
+	&Config{User: "user", Passwd: "p@/ssword", Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"unix/?arg=%2Fsome%2Fpath.ext",
-	&Config{Net: "unix", Addr: "/tmp/mysql.sock", Params: map[string]string{"arg": "/some/path.ext"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
+	&Config{Net: "unix", Addr: "/tmp/mysql.sock", Params: map[string]string{"arg": "/some/path.ext"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"tcp(127.0.0.1)/dbname",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"tcp(de:ad:be:ef::ca:fe)/dbname",
-	&Config{Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
+	&Config{Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
 },
 }
 
diff --git a/packets.go b/packets.go
index 30b3352c2..82ad7a200 100644
--- a/packets.go
+++ b/packets.go
@@ -115,7 +115,7 @@ func (mc *mysqlConn) writePacket(data []byte) error {
 		if mc.cfg.ReadTimeout != 0 {
 			err = conn.SetReadDeadline(time.Time{})
 		}
-		if err == nil {
+		if err == nil && mc.cfg.CheckConnLiveness {
 			err = connCheck(conn)
 		}
 		if err != nil {

From 2898b563649417674caafd1a7ee2acf21d06a3ce Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Tue, 7 Jan 2020 17:19:51 +0100
Subject: [PATCH 118/282] README: update default collation to
 utf8mb4_general_ci (#1054)

---
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 239de7b6b..d2627a41a 100644
--- a/README.md
+++ b/README.md
@@ -463,13 +463,13 @@ Alternatively you can use the [`NullTime`](https://godoc.org/github.com/go-sql-d
 
 
 ### Unicode support
-Since version 1.1 Go-MySQL-Driver automatically uses the collation `utf8_general_ci` by default.
+Since version 1.5 Go-MySQL-Driver automatically uses the collation ` utf8mb4_general_ci` by default.
 
 Other collations / charsets can be set using the [`collation`](#collation) DSN parameter.
 
 Version 1.0 of the driver recommended adding `&charset=utf8` (alias for `SET NAMES utf8`) to the DSN to enable proper UTF-8 support. This is not necessary anymore. The [`collation`](#collation) parameter should be preferred to set another collation / charset than the default.
 
-See http://dev.mysql.com/doc/refman/5.7/en/charset-unicode.html for more details on MySQL's Unicode support.
+See http://dev.mysql.com/doc/refman/8.0/en/charset-unicode.html for more details on MySQL's Unicode support.
 
 ## 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.

From 17ef3dd9d98b69acec3e85878995ada9533a9370 Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Tue, 7 Jan 2020 17:33:55 +0100
Subject: [PATCH 119/282] Release v1.5.0 (#1047)

* CHANGELOG: include v1.4.1

* Release v1.5.0
---
 CHANGELOG.md | 39 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 39 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2d87d74c9..9cb97b38d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,42 @@
+## Version 1.5 (2020-01-07)
+
+Changes:
+
+  - Dropped support Go 1.9 and lower (#823, #829, #886, #1016, #1017)
+  - Improve buffer handling (#890)
+  - Document potentially insecure TLS configs (#901)
+  - Use a double-buffering scheme to prevent data races (#943)
+  - Pass uint64 values without converting them to string (#838, #955)
+  - Update collations and make utf8mb4 default (#877, #1054)
+  - Make NullTime compatible with sql.NullTime in Go 1.13+ (#995)
+  - Removed CloudSQL support (#993, #1007)
+  - Add Go Module support (#1003)
+
+New Features:
+
+  - Implement support of optional TLS (#900)
+  - Check connection liveness (#934, #964, #997, #1048, #1051, #1052)
+  - Implement Connector Interface (#941, #958, #1020, #1035)
+
+Bugfixes:
+
+  - Mark connections as bad on error during ping (#875)
+  - Mark connections as bad on error during dial (#867)
+  - Fix connection leak caused by rapid context cancellation (#1024)
+  - Mark connections as bad on error during Conn.Prepare (#1030)
+
+
+## Version 1.4.1 (2018-11-14)
+
+Bugfixes:
+
+ - Fix TIME format for binary columns (#818)
+ - Fix handling of empty auth plugin names (#835)
+ - Fix caching_sha2_password with empty password (#826)
+ - Fix canceled context broke mysqlConn (#862)
+ - Fix OldAuthSwitchRequest support (#870)
+ - Fix Auth Response packet for cleartext password (#887)
+
 ## Version 1.4 (2018-06-03)
 
 Changes:

From c4f1976e433a0e5f4a0d612ee45c85f689076ae7 Mon Sep 17 00:00:00 2001
From: Alex Snast <alexsn@fb.com>
Date: Sun, 9 Feb 2020 16:41:49 +0200
Subject: [PATCH 120/282] connection: interpolate json.RawMessage as string
 (#1058)

json encoded data is represented as bytes however it should be interpolated as a string

Fixes #819
---
 AUTHORS            |  1 +
 connection.go      |  9 +++++++++
 connection_test.go | 28 ++++++++++++++++++++++++++++
 3 files changed, 38 insertions(+)

diff --git a/AUTHORS b/AUTHORS
index ad5989800..0896ba1bc 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -13,6 +13,7 @@
 
 Aaron Hopkins <go-sql-driver at die.net>
 Achille Roussel <achille.roussel at gmail.com>
+Alex Snast <alexsn at fb.com>
 Alexey Palazhchenko <alexey.palazhchenko at gmail.com>
 Andrew Reid <andrew.reid at tixtrack.com>
 Arne Hormann <arnehormann at gmail.com>
diff --git a/connection.go b/connection.go
index e4bb59e67..b07cd7651 100644
--- a/connection.go
+++ b/connection.go
@@ -12,6 +12,7 @@ import (
 	"context"
 	"database/sql"
 	"database/sql/driver"
+	"encoding/json"
 	"io"
 	"net"
 	"strconv"
@@ -271,6 +272,14 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
 				}
 				buf = append(buf, '\'')
 			}
+		case json.RawMessage:
+			buf = append(buf, '\'')
+			if mc.status&statusNoBackslashEscapes == 0 {
+				buf = escapeBytesBackslash(buf, v)
+			} else {
+				buf = escapeBytesQuotes(buf, v)
+			}
+			buf = append(buf, '\'')
 		case []byte:
 			if v == nil {
 				buf = append(buf, "NULL"...)
diff --git a/connection_test.go b/connection_test.go
index 19c17ff8b..a6d677308 100644
--- a/connection_test.go
+++ b/connection_test.go
@@ -11,6 +11,7 @@ package mysql
 import (
 	"context"
 	"database/sql/driver"
+	"encoding/json"
 	"errors"
 	"net"
 	"testing"
@@ -36,6 +37,33 @@ func TestInterpolateParams(t *testing.T) {
 	}
 }
 
+func TestInterpolateParamsJSONRawMessage(t *testing.T) {
+	mc := &mysqlConn{
+		buf:              newBuffer(nil),
+		maxAllowedPacket: maxPacketSize,
+		cfg: &Config{
+			InterpolateParams: true,
+		},
+	}
+
+	buf, err := json.Marshal(struct {
+		Value int `json:"value"`
+	}{Value: 42})
+	if err != nil {
+		t.Errorf("Expected err=nil, got %#v", err)
+		return
+	}
+	q, err := mc.interpolateParams("SELECT ?", []driver.Value{json.RawMessage(buf)})
+	if err != nil {
+		t.Errorf("Expected err=nil, got %#v", err)
+		return
+	}
+	expected := `SELECT '{\"value\":42}'`
+	if q != expected {
+		t.Errorf("Expected: %q\nGot: %q", expected, q)
+	}
+}
+
 func TestInterpolateParamsTooManyPlaceholders(t *testing.T) {
 	mc := &mysqlConn{
 		buf:              newBuffer(nil),

From 5a8a207333b3cbdd6f50a31da2d448658343637e Mon Sep 17 00:00:00 2001
From: Noboru Saito <noborusai@gmail.com>
Date: Fri, 14 Feb 2020 07:49:22 +0900
Subject: [PATCH 121/282] Fix link(#1061) (#1062)

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index d2627a41a..6ac81d1e4 100644
--- a/README.md
+++ b/README.md
@@ -477,7 +477,7 @@ To run the driver tests you may need to adjust the configuration. See the [Testi
 Go-MySQL-Driver is not feature-complete yet. Your help is very appreciated.
 If you want to contribute, you can work on an [open issue](https://github.com/go-sql-driver/mysql/issues?state=open) or review a [pull request](https://github.com/go-sql-driver/mysql/pulls).
 
-See the [Contribution Guidelines](https://github.com/go-sql-driver/mysql/blob/master/CONTRIBUTING.md) for details.
+See the [Contribution Guidelines](https://github.com/go-sql-driver/mysql/blob/master/.github/CONTRIBUTING.md) for details.
 
 ---------------------------------------
 

From 3d8a0293423afe714a98d549f0a8015b2d0930b7 Mon Sep 17 00:00:00 2001
From: Ariel Mashraki <7413593+a8m@users.noreply.github.com>
Date: Tue, 18 Feb 2020 17:16:20 +0200
Subject: [PATCH 122/282] stmt: add json.RawMessage for converter and prepared
 statement (#1059)

Following #1058, in order for the driver.Value to get as a json.RawMessage,
the converter should accept it as a valid value, and handle it as bytes in
case where interpolation is disabled
---
 AUTHORS           |  1 +
 driver_test.go    | 24 ++++++++++++++++++++++++
 packets.go        |  4 ++++
 statement.go      | 13 +++++++++----
 statement_test.go | 15 +++++++++++++++
 5 files changed, 53 insertions(+), 4 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 0896ba1bc..7dafbe2ec 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -17,6 +17,7 @@ Alex Snast <alexsn at fb.com>
 Alexey Palazhchenko <alexey.palazhchenko at gmail.com>
 Andrew Reid <andrew.reid at tixtrack.com>
 Arne Hormann <arnehormann at gmail.com>
+Ariel Mashraki <ariel at mashraki.co.il>
 Asta Xie <xiemengjun at gmail.com>
 Bulat Gaifullin <gaifullinbf at gmail.com>
 Carlos Nieto <jose.carlos at menteslibres.net>
diff --git a/driver_test.go b/driver_test.go
index ace083dfc..8edd17c47 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -14,6 +14,7 @@ import (
 	"crypto/tls"
 	"database/sql"
 	"database/sql/driver"
+	"encoding/json"
 	"fmt"
 	"io"
 	"io/ioutil"
@@ -559,6 +560,29 @@ func TestRawBytes(t *testing.T) {
 	})
 }
 
+func TestRawMessage(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		v1 := json.RawMessage("{}")
+		v2 := json.RawMessage("[]")
+		rows := dbt.mustQuery("SELECT ?, ?", v1, v2)
+		defer rows.Close()
+		if rows.Next() {
+			var o1, o2 json.RawMessage
+			if err := rows.Scan(&o1, &o2); err != nil {
+				dbt.Errorf("Got error: %v", err)
+			}
+			if !bytes.Equal(v1, o1) {
+				dbt.Errorf("expected %v, got %v", v1, o1)
+			}
+			if !bytes.Equal(v2, o2) {
+				dbt.Errorf("expected %v, got %v", v2, o2)
+			}
+		} else {
+			dbt.Errorf("no data")
+		}
+	})
+}
+
 type testValuer struct {
 	value string
 }
diff --git a/packets.go b/packets.go
index 82ad7a200..575202ea3 100644
--- a/packets.go
+++ b/packets.go
@@ -13,6 +13,7 @@ import (
 	"crypto/tls"
 	"database/sql/driver"
 	"encoding/binary"
+	"encoding/json"
 	"errors"
 	"fmt"
 	"io"
@@ -1003,6 +1004,9 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 				continue
 			}
 
+			if v, ok := arg.(json.RawMessage); ok {
+				arg = []byte(v)
+			}
 			// cache types and values
 			switch v := arg.(type) {
 			case int64:
diff --git a/statement.go b/statement.go
index f7e370939..7c6dc1367 100644
--- a/statement.go
+++ b/statement.go
@@ -10,6 +10,7 @@ package mysql
 
 import (
 	"database/sql/driver"
+	"encoding/json"
 	"fmt"
 	"io"
 	"reflect"
@@ -129,6 +130,8 @@ func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) {
 	return rows, err
 }
 
+var jsonType = reflect.TypeOf(json.RawMessage{})
+
 type converter struct{}
 
 // ConvertValue mirrors the reference/default converter in database/sql/driver
@@ -151,7 +154,6 @@ func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
 		}
 		return sv, nil
 	}
-
 	rv := reflect.ValueOf(v)
 	switch rv.Kind() {
 	case reflect.Ptr:
@@ -170,11 +172,14 @@ func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
 	case reflect.Bool:
 		return rv.Bool(), nil
 	case reflect.Slice:
-		ek := rv.Type().Elem().Kind()
-		if ek == reflect.Uint8 {
+		switch t := rv.Type(); {
+		case t == jsonType:
+			return v, nil
+		case t.Elem().Kind() == reflect.Uint8:
 			return rv.Bytes(), nil
+		default:
+			return nil, fmt.Errorf("unsupported type %T, a slice of %s", v, t.Elem().Kind())
 		}
-		return nil, fmt.Errorf("unsupported type %T, a slice of %s", v, ek)
 	case reflect.String:
 		return rv.String(), nil
 	}
diff --git a/statement_test.go b/statement_test.go
index 4b9914f8e..2cc022bf5 100644
--- a/statement_test.go
+++ b/statement_test.go
@@ -10,6 +10,7 @@ package mysql
 
 import (
 	"bytes"
+	"encoding/json"
 	"testing"
 )
 
@@ -124,3 +125,17 @@ func TestConvertUnsignedIntegers(t *testing.T) {
 		t.Fatalf("uint64 high-bit converted, got %#v %T", output, output)
 	}
 }
+
+func TestConvertJSON(t *testing.T) {
+	raw := json.RawMessage("{}")
+
+	out, err := converter{}.ConvertValue(raw)
+
+	if err != nil {
+		t.Fatal("json.RawMessage was failed in convert", err)
+	}
+
+	if _, ok := out.(json.RawMessage); !ok {
+		t.Fatalf("json.RawMessage converted, got %#v %T", out, out)
+	}
+}

From dd9d356b496cd5c37543d3dd0ffbef75714b88ec Mon Sep 17 00:00:00 2001
From: Pavel Ivanov <paivanof@gmail.com>
Date: Tue, 25 Feb 2020 07:24:38 -0800
Subject: [PATCH 123/282] Put zero filler into the SSL handshake packet.
 (#1066)

According to the linked documentation at
http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::SSLRequest
SSLRequest packet should have zero filler similar to the regular handshake request,
but now the driver puts zeros only in the regular request. Luckily vanilla MySQL
doesn't rely on this zero filler and doesn't verify its presence, thus the driver worked
fine so far. But MySQL can change to rely on zeros at any point.

The problem was discovered while testing against a customized MySQL.
---
 packets.go | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/packets.go b/packets.go
index 575202ea3..5cbd53298 100644
--- a/packets.go
+++ b/packets.go
@@ -349,6 +349,12 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 		return errors.New("unknown collation")
 	}
 
+	// Filler [23 bytes] (all 0x00)
+	pos := 13
+	for ; pos < 13+23; pos++ {
+		data[pos] = 0
+	}
+
 	// SSL Connection Request Packet
 	// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::SSLRequest
 	if mc.cfg.tls != nil {
@@ -367,12 +373,6 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 		mc.buf.nc = tlsConn
 	}
 
-	// Filler [23 bytes] (all 0x00)
-	pos := 13
-	for ; pos < 13+23; pos++ {
-		data[pos] = 0
-	}
-
 	// User [null terminated string]
 	if len(mc.cfg.User) > 0 {
 		pos += copy(data[pos:], mc.cfg.User)

From 681ffa848bae8d0d7b1a452963c0705b132308f7 Mon Sep 17 00:00:00 2001
From: Animesh Ray <5434024+rayanimesh@users.noreply.github.com>
Date: Wed, 11 Mar 2020 17:02:36 +0530
Subject: [PATCH 124/282] travis: Add compile check for all supported platforms
 (#1070)

Implements a Travis CI task that checks if the driver compiles on all platforms supported by Go.

Fixes #1050
---
 .travis.yml              |  1 +
 .travis/complie_check.sh | 17 +++++++++++++++++
 AUTHORS                  |  1 +
 3 files changed, 19 insertions(+)
 create mode 100755 .travis/complie_check.sh

diff --git a/.travis.yml b/.travis.yml
index 56fcf25f2..40c230105 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -125,5 +125,6 @@ script:
   - go test -v -covermode=count -coverprofile=coverage.out
   - go vet ./...
   - .travis/gofmt.sh
+  - .travis/complie_check.sh
 after_script:
   - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci
diff --git a/.travis/complie_check.sh b/.travis/complie_check.sh
new file mode 100755
index 000000000..3bb3ed49d
--- /dev/null
+++ b/.travis/complie_check.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+set -e 
+dist_list=$(go tool dist list)
+
+for dist in ${dist_list}; do
+    GOOS=$(echo ${dist} | cut  -d "/" -f 1)
+    GOARCH=$(echo ${dist} | cut -d "/" -f 2)
+    set +e
+    GOOS=${GOOS} GOARCH=${GOARCH} go tool compile -V > /dev/null 2>&1 
+    if [[ $? -ne 0 ]]; then
+        echo "Compile support for ${GOOS}/${GOARCH} is not provided; skipping"
+        continue
+    fi
+    set -e
+    echo "Building  ${GOOS}/${GOARCH}"
+    GOOS=${GOOS} GOARCH=${GOARCH} go build  -o /dev/null
+ done
diff --git a/AUTHORS b/AUTHORS
index 7dafbe2ec..5473a8df9 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -16,6 +16,7 @@ Achille Roussel <achille.roussel at gmail.com>
 Alex Snast <alexsn at fb.com>
 Alexey Palazhchenko <alexey.palazhchenko at gmail.com>
 Andrew Reid <andrew.reid at tixtrack.com>
+Animesh Ray <mail.rayanimesh at gmail.com>
 Arne Hormann <arnehormann at gmail.com>
 Ariel Mashraki <ariel at mashraki.co.il>
 Asta Xie <xiemengjun at gmail.com>

From f070e56d1471e44025607168e66be68f9aa54ed6 Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Fri, 8 May 2020 10:52:21 +0200
Subject: [PATCH 125/282] Travis allow Go master to fail (#1092)

* travis: allow master branch to fail

* travis: matrix is an alias for jobs

* travis: remove obsolete sudo key

* travis: remove obsolete sudo keys in jobs matrix
---
 .travis.yml | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 40c230105..506b4f4fb 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,4 +1,3 @@
-sudo: false
 language: go
 go:
   - 1.10.x
@@ -17,10 +16,12 @@ before_script:
   - .travis/wait_mysql.sh
   - mysql -e 'create database gotest;'
 
-matrix:
+jobs:
+  allow_failures:
+  - go: master
+
   include:
     - env: DB=MYSQL8
-      sudo: required
       dist: trusty
       go: 1.10.x
       services:
@@ -40,7 +41,6 @@ matrix:
         - export MYSQL_TEST_CONCURRENT=1
 
     - env: DB=MYSQL57
-      sudo: required
       dist: trusty
       go: 1.10.x
       services:
@@ -60,7 +60,6 @@ matrix:
         - export MYSQL_TEST_CONCURRENT=1
 
     - env: DB=MARIA55
-      sudo: required
       dist: trusty
       go: 1.10.x
       services:
@@ -80,7 +79,6 @@ matrix:
         - export MYSQL_TEST_CONCURRENT=1
 
     - env: DB=MARIA10_1
-      sudo: required
       dist: trusty
       go: 1.10.x
       services:

From 343c8030d2cbdf55d368a16ec3030981f7e755f2 Mon Sep 17 00:00:00 2001
From: Zhixin Wen <john.wenzhixin@hotmail.com>
Date: Sat, 9 May 2020 06:02:55 -0700
Subject: [PATCH 126/282] mysqlStmt Implements CheckNamedValue (#1090)

* Add CheckNamedValue for mysqlStmt

* Update AUTHORS

Co-authored-by: Zhixin Wen <zwen@nuro.ai>
---
 AUTHORS      | 1 +
 statement.go | 5 +++++
 2 files changed, 6 insertions(+)

diff --git a/AUTHORS b/AUTHORS
index 5473a8df9..221f4a395 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -91,6 +91,7 @@ Xiangyu Hu <xiangyu.hu at outlook.com>
 Xiaobing Jiang <s7v7nislands at gmail.com>
 Xiuming Chen <cc at cxm.cc>
 Zhenye Xie <xiezhenye at gmail.com>
+Zhixin Wen <john.wenzhixin at gmail.com>
 
 # Organizations
 
diff --git a/statement.go b/statement.go
index 7c6dc1367..d3e68112f 100644
--- a/statement.go
+++ b/statement.go
@@ -44,6 +44,11 @@ func (stmt *mysqlStmt) ColumnConverter(idx int) driver.ValueConverter {
 	return converter{}
 }
 
+func (stmt *mysqlStmt) CheckNamedValue(nv *driver.NamedValue) (err error) {
+	nv.Value, err = converter{}.ConvertValue(nv.Value)
+	return
+}
+
 func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
 	if stmt.mc.closed.IsSet() {
 		errLog.Print(ErrInvalidConn)

From 6313f20fe43bffaa8d750d5e840ff30706a5cddc Mon Sep 17 00:00:00 2001
From: Konstantinos Tsanaktsidis <ktsanaktsidis@zendesk.com>
Date: Tue, 12 May 2020 14:12:06 +1000
Subject: [PATCH 127/282] Fix checking cancelled connections back into the
 connection pool (#1095)

If
    * BeginTx is called with a non-default isolation level,
    * The context is canceled before SET TRANSACTION ISOLATION LEVEL
    completes,
then the connection:
    * has the cancelled property set to "context cancelled",
    * has the closed property set to true,
and,
    * BeginTx returns "context canceled"

Because of this, the connection gets put back into the connection pool.
When it is checked out again, if BeginTx is called on it again _without_
an isolation level,
    * then we fall into the mc.closed.IsSet() check in begin(),
    * so we return ErrBadConn,
    * so the driver kicks the broken connection out of the pool
    * (and transparently retries to get a new connection that isn't
    broken too).

However, if BeginTx is called on the connection _with_ an isolation
level, then we return a context canceled error from the SET TRANSACTION
ISOLATION LEVEL call.

That means the broken connection will stick around in the pool forever
(or until it's checked out for some other operation that correctly
returns ErrBadConn).

The fix is to check for the connection being closed before executing SET
TRANSACTION ISOLATION LEVEL.
---
 AUTHORS        |  1 +
 connection.go  |  4 ++++
 driver_test.go | 19 +++++++++++++++++--
 3 files changed, 22 insertions(+), 2 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 221f4a395..98cb1e66f 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -107,3 +107,4 @@ Multiplay Ltd.
 Percona LLC
 Pivotal Inc.
 Stripe Inc.
+Zendesk Inc.
diff --git a/connection.go b/connection.go
index b07cd7651..6769e3ce1 100644
--- a/connection.go
+++ b/connection.go
@@ -489,6 +489,10 @@ func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
 
 // BeginTx implements driver.ConnBeginTx interface
 func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
+	if mc.closed.IsSet() {
+		return nil, driver.ErrBadConn
+	}
+
 	if err := mc.watchCancel(ctx); err != nil {
 		return nil, err
 	}
diff --git a/driver_test.go b/driver_test.go
index 8edd17c47..34b476ed3 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -2608,7 +2608,12 @@ func TestContextCancelBegin(t *testing.T) {
 	runTests(t, dsn, func(dbt *DBTest) {
 		dbt.mustExec("CREATE TABLE test (v INTEGER)")
 		ctx, cancel := context.WithCancel(context.Background())
-		tx, err := dbt.db.BeginTx(ctx, nil)
+		conn, err := dbt.db.Conn(ctx)
+		if err != nil {
+			dbt.Fatal(err)
+		}
+		defer conn.Close()
+		tx, err := conn.BeginTx(ctx, nil)
 		if err != nil {
 			dbt.Fatal(err)
 		}
@@ -2638,7 +2643,17 @@ func TestContextCancelBegin(t *testing.T) {
 			dbt.Errorf("expected sql.ErrTxDone or context.Canceled, got %v", err)
 		}
 
-		// Context is canceled, so cannot begin a transaction.
+		// The connection is now in an inoperable state - so performing other
+		// operations should fail with ErrBadConn
+		// Important to exercise isolation level too - it runs SET TRANSACTION ISOLATION
+		// LEVEL XXX first, which needs to return ErrBadConn if the connection's context
+		// is cancelled
+		_, err = conn.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelReadCommitted})
+		if err != driver.ErrBadConn {
+			dbt.Errorf("expected driver.ErrBadConn, got %v", err)
+		}
+
+		// cannot begin a transaction (on a different conn) with a canceled context
 		if _, err := dbt.db.BeginTx(ctx, nil); err != context.Canceled {
 			dbt.Errorf("expected context.Canceled, got %v", err)
 		}

From f378f59f67100fe0079a06c4c5588a1f4df06227 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Olivier=20Mengu=C3=A9?= <dolmen@cpan.org>
Date: Thu, 14 May 2020 01:43:51 +0200
Subject: [PATCH 128/282] Update travis: use Go 1.14 for testing (#1100)

* Travis-CI: reverse order of Go version

Reverse order of Go versions so the recent ones are tested first.

* Travis-CI: add Go 1.14

* Travis-CI: move 'tip' just below the latest Go release

* Travis-CI: upgrade Go to 1.14 to run tests
---
 .travis.yml | 22 +++++++++++++---------
 1 file changed, 13 insertions(+), 9 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 506b4f4fb..74d9cd022 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,10 +1,14 @@
 language: go
 go:
-  - 1.10.x
-  - 1.11.x
-  - 1.12.x
-  - 1.13.x
+  # Keep the most recent production release at the top
+  - 1.14.x
+  # Go development version
   - master
+  # Older production releases
+  - 1.13.x
+  - 1.12.x
+  - 1.11.x
+  - 1.10.x
 
 before_install:
   - go get golang.org/x/tools/cmd/cover
@@ -23,7 +27,7 @@ jobs:
   include:
     - env: DB=MYSQL8
       dist: trusty
-      go: 1.10.x
+      go: 1.14.x
       services:
         - docker
       before_install:
@@ -42,7 +46,7 @@ jobs:
 
     - env: DB=MYSQL57
       dist: trusty
-      go: 1.10.x
+      go: 1.14.x
       services:
         - docker
       before_install:
@@ -61,7 +65,7 @@ jobs:
 
     - env: DB=MARIA55
       dist: trusty
-      go: 1.10.x
+      go: 1.14.x
       services:
         - docker
       before_install:
@@ -80,7 +84,7 @@ jobs:
 
     - env: DB=MARIA10_1
       dist: trusty
-      go: 1.10.x
+      go: 1.14.x
       services:
         - docker
       before_install:
@@ -104,7 +108,7 @@ jobs:
           packages:
             - mysql
           update: true
-      go: 1.12.x
+      go: 1.14.x
       before_install:
         - go get golang.org/x/tools/cmd/cover
         - go get github.com/mattn/goveralls

From 096feaaf8e9ffa009d7aa1c1b7eb57367b84d40c Mon Sep 17 00:00:00 2001
From: chanxuehong <chanxuehong@gmail.com>
Date: Sun, 17 May 2020 23:48:53 +0800
Subject: [PATCH 129/282] performance improvement for parseDateTime (#1098)

---
 AUTHORS       |  1 +
 utils.go      | 14 ++++----------
 utils_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 48 insertions(+), 10 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 98cb1e66f..e24231d81 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -90,6 +90,7 @@ Vladimir Kovpak <cn007b at gmail.com>
 Xiangyu Hu <xiangyu.hu at outlook.com>
 Xiaobing Jiang <s7v7nislands at gmail.com>
 Xiuming Chen <cc at cxm.cc>
+Xuehong Chan <chanxuehong at gmail.com>
 Zhenye Xie <xiezhenye at gmail.com>
 Zhixin Wen <john.wenzhixin at gmail.com>
 
diff --git a/utils.go b/utils.go
index 9552e80b5..154ecc337 100644
--- a/utils.go
+++ b/utils.go
@@ -113,20 +113,14 @@ func parseDateTime(str string, loc *time.Location) (t time.Time, err error) {
 		if str == base[:len(str)] {
 			return
 		}
-		t, err = time.Parse(timeFormat[:len(str)], str)
+		if loc == time.UTC {
+			return time.Parse(timeFormat[:len(str)], str)
+		}
+		return time.ParseInLocation(timeFormat[:len(str)], str, loc)
 	default:
 		err = fmt.Errorf("invalid time string: %s", str)
 		return
 	}
-
-	// Adjust location
-	if err == nil && loc != time.UTC {
-		y, mo, d := t.Date()
-		h, mi, s := t.Clock()
-		t, err = time.Date(y, mo, d, h, mi, s, t.Nanosecond(), loc), nil
-	}
-
-	return
 }
 
 func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Value, error) {
diff --git a/utils_test.go b/utils_test.go
index 10a60c2d0..ab29cad78 100644
--- a/utils_test.go
+++ b/utils_test.go
@@ -14,6 +14,7 @@ import (
 	"database/sql/driver"
 	"encoding/binary"
 	"testing"
+	"time"
 )
 
 func TestLengthEncodedInteger(t *testing.T) {
@@ -291,3 +292,45 @@ func TestIsolationLevelMapping(t *testing.T) {
 		t.Fatalf("Expected error to be %q, got %q", expectedErr, err)
 	}
 }
+
+func TestParseDateTime(t *testing.T) {
+	// UTC loc
+	{
+		str := "2020-05-13 21:30:45"
+		t1, err := parseDateTime(str, time.UTC)
+		if err != nil {
+			t.Error(err)
+			return
+		}
+		t2 := time.Date(2020, 5, 13,
+			21, 30, 45, 0, time.UTC)
+		if !t1.Equal(t2) {
+			t.Errorf("want equal, have: %v, want: %v", t1, t2)
+			return
+		}
+	}
+	// non-UTC loc
+	{
+		str := "2020-05-13 21:30:45"
+		loc := time.FixedZone("test", 8*60*60)
+		t1, err := parseDateTime(str, loc)
+		if err != nil {
+			t.Error(err)
+			return
+		}
+		t2 := time.Date(2020, 5, 13,
+			21, 30, 45, 0, loc)
+		if !t1.Equal(t2) {
+			t.Errorf("want equal, have: %v, want: %v", t1, t2)
+			return
+		}
+	}
+}
+
+func BenchmarkParseDateTime(b *testing.B) {
+	str := "2020-05-13 21:30:45"
+	loc := time.FixedZone("test", 8*60*60)
+	for i := 0; i < b.N; i++ {
+		_, _ = parseDateTime(str, loc)
+	}
+}

From 3f51e4ed70f7313fd12889ebc93fb42ab67d4941 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Olivier=20Mengu=C3=A9?= <dolmen@cpan.org>
Date: Tue, 19 May 2020 14:52:07 +0200
Subject: [PATCH 130/282] On connect, set all variables in a single SET
 statement (#1099)

When opening a connection, instead of iterating on all variables and
calling "SET <variable>=<value>" for each, we now use a single SET statement
with all pairs using a comma as separator:
  SET <variable1>=<value1>,<variable2>=<value2>,...
---
 connection.go | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/connection.go b/connection.go
index 6769e3ce1..0c280443d 100644
--- a/connection.go
+++ b/connection.go
@@ -47,9 +47,10 @@ type mysqlConn struct {
 
 // Handles parameters set in DSN after the connection is established
 func (mc *mysqlConn) handleParams() (err error) {
+	var params []string
 	for param, val := range mc.cfg.Params {
 		switch param {
-		// Charset
+		// Charset: character_set_connection, character_set_client, character_set_results
 		case "charset":
 			charsets := strings.Split(val, ",")
 			for i := range charsets {
@@ -63,12 +64,16 @@ func (mc *mysqlConn) handleParams() (err error) {
 				return
 			}
 
-		// System Vars
+		// Other system vars
 		default:
-			err = mc.exec("SET " + param + "=" + val + "")
-			if err != nil {
-				return
-			}
+			params = append(params, param+"="+val)
+		}
+	}
+
+	if len(params) > 0 {
+		err = mc.exec("SET " + strings.Join(params, ","))
+		if err != nil {
+			return
 		}
 	}
 

From 128a6737a20278b625f8de647998fec7b7a2dc3e Mon Sep 17 00:00:00 2001
From: AdamKorcz <44787359+AdamKorcz@users.noreply.github.com>
Date: Thu, 21 May 2020 14:24:19 +0100
Subject: [PATCH 131/282] Add fuzzer (#1097)

The fuzzer targets the `Open` function, which can be run locally as well as through oss-fuzz's infrastructure.
---
 fuzz.go | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)
 create mode 100644 fuzz.go

diff --git a/fuzz.go b/fuzz.go
new file mode 100644
index 000000000..fa75adf6a
--- /dev/null
+++ b/fuzz.go
@@ -0,0 +1,24 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package.
+//
+// Copyright 2020 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/.
+
+// +build gofuzz
+
+package mysql
+
+import (
+	"database/sql"
+)
+
+func Fuzz(data []byte) int {
+	db, err := sql.Open("mysql", string(data))
+	if err != nil {
+		return 0
+	}
+	db.Close()
+	return 1
+}

From db7a5f2f6f8d399089e805f53a41014e988d365f Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Mon, 25 May 2020 12:28:14 +0200
Subject: [PATCH 132/282] travis: do not run compile check in matrix builds
 (#1104)

---
 .travis.yml                                    | 7 ++++++-
 .travis/{complie_check.sh => compile_check.sh} | 0
 2 files changed, 6 insertions(+), 1 deletion(-)
 rename .travis/{complie_check.sh => compile_check.sh} (100%)

diff --git a/.travis.yml b/.travis.yml
index 74d9cd022..5639e4fd2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -39,6 +39,7 @@ jobs:
         - cp .travis/docker.cnf ~/.my.cnf
         - .travis/wait_mysql.sh
       before_script:
+        - export cross_compile=false
         - export MYSQL_TEST_USER=gotest
         - export MYSQL_TEST_PASS=secret
         - export MYSQL_TEST_ADDR=127.0.0.1:3307
@@ -58,6 +59,7 @@ jobs:
         - cp .travis/docker.cnf ~/.my.cnf
         - .travis/wait_mysql.sh
       before_script:
+        - export cross_compile=false
         - export MYSQL_TEST_USER=gotest
         - export MYSQL_TEST_PASS=secret
         - export MYSQL_TEST_ADDR=127.0.0.1:3307
@@ -77,6 +79,7 @@ jobs:
         - cp .travis/docker.cnf ~/.my.cnf
         - .travis/wait_mysql.sh
       before_script:
+        - export cross_compile=false
         - export MYSQL_TEST_USER=gotest
         - export MYSQL_TEST_PASS=secret
         - export MYSQL_TEST_ADDR=127.0.0.1:3307
@@ -96,6 +99,7 @@ jobs:
         - cp .travis/docker.cnf ~/.my.cnf
         - .travis/wait_mysql.sh
       before_script:
+        - export cross_compile=false
         - export MYSQL_TEST_USER=gotest
         - export MYSQL_TEST_PASS=secret
         - export MYSQL_TEST_ADDR=127.0.0.1:3307
@@ -113,6 +117,7 @@ jobs:
         - go get golang.org/x/tools/cmd/cover
         - go get github.com/mattn/goveralls
       before_script:
+        - export cross_compile=false
         - echo -e "[server]\ninnodb_log_file_size=256MB\ninnodb_buffer_pool_size=512MB\nmax_allowed_packet=16MB\nlocal_infile=1" >> /usr/local/etc/my.cnf
         - mysql.server start
         - mysql -uroot -e 'CREATE USER gotest IDENTIFIED BY "secret"'
@@ -127,6 +132,6 @@ script:
   - go test -v -covermode=count -coverprofile=coverage.out
   - go vet ./...
   - .travis/gofmt.sh
-  - .travis/complie_check.sh
+  - if [ "$cross_compile" != "false" ]; then .travis/compile_check.sh; fi
 after_script:
   - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci
diff --git a/.travis/complie_check.sh b/.travis/compile_check.sh
similarity index 100%
rename from .travis/complie_check.sh
rename to .travis/compile_check.sh

From 8c3a2d9049b4fc1a914673c22bdef3741b8bea7e Mon Sep 17 00:00:00 2001
From: Julien Schmidt <julienschmidt@users.noreply.github.com>
Date: Mon, 25 May 2020 12:28:40 +0200
Subject: [PATCH 133/282] travis: update image versions (#1103)

* travis: update image versions

* travis: use mysql client inside the docker container for polling

* travis: force mysql client to connect via tcp

* travis: use root pass in mysql client

* travis: use empty root password
---
 .travis.yml           | 18 +++++++++---------
 .travis/wait_mysql.sh |  8 +++++++-
 2 files changed, 16 insertions(+), 10 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 5639e4fd2..21638db0e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -26,7 +26,7 @@ jobs:
 
   include:
     - env: DB=MYSQL8
-      dist: trusty
+      dist: xenial
       go: 1.14.x
       services:
         - docker
@@ -34,7 +34,7 @@ jobs:
         - go get golang.org/x/tools/cmd/cover
         - go get github.com/mattn/goveralls
         - docker pull mysql:8.0
-        - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
+        - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ALLOW_EMPTY_PASSWORD=yes
           mysql:8.0 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
         - cp .travis/docker.cnf ~/.my.cnf
         - .travis/wait_mysql.sh
@@ -46,7 +46,7 @@ jobs:
         - export MYSQL_TEST_CONCURRENT=1
 
     - env: DB=MYSQL57
-      dist: trusty
+      dist: xenial
       go: 1.14.x
       services:
         - docker
@@ -54,7 +54,7 @@ jobs:
         - go get golang.org/x/tools/cmd/cover
         - go get github.com/mattn/goveralls
         - docker pull mysql:5.7
-        - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
+        - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ALLOW_EMPTY_PASSWORD=yes
           mysql:5.7 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
         - cp .travis/docker.cnf ~/.my.cnf
         - .travis/wait_mysql.sh
@@ -66,7 +66,7 @@ jobs:
         - export MYSQL_TEST_CONCURRENT=1
 
     - env: DB=MARIA55
-      dist: trusty
+      dist: xenial
       go: 1.14.x
       services:
         - docker
@@ -74,7 +74,7 @@ jobs:
         - go get golang.org/x/tools/cmd/cover
         - go get github.com/mattn/goveralls
         - docker pull mariadb:5.5
-        - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
+        - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ALLOW_EMPTY_PASSWORD=yes
           mariadb:5.5 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
         - cp .travis/docker.cnf ~/.my.cnf
         - .travis/wait_mysql.sh
@@ -86,7 +86,7 @@ jobs:
         - export MYSQL_TEST_CONCURRENT=1
 
     - env: DB=MARIA10_1
-      dist: trusty
+      dist: xenial
       go: 1.14.x
       services:
         - docker
@@ -94,7 +94,7 @@ jobs:
         - go get golang.org/x/tools/cmd/cover
         - go get github.com/mattn/goveralls
         - docker pull mariadb:10.1
-        - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
+        - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ALLOW_EMPTY_PASSWORD=yes
           mariadb:10.1 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
         - cp .travis/docker.cnf ~/.my.cnf
         - .travis/wait_mysql.sh
@@ -106,7 +106,7 @@ jobs:
         - export MYSQL_TEST_CONCURRENT=1
 
     - os: osx
-      osx_image: xcode10.1
+      osx_image: xcode11.4
       addons:
         homebrew:
           packages:
diff --git a/.travis/wait_mysql.sh b/.travis/wait_mysql.sh
index e87993e57..fa2054ff1 100755
--- a/.travis/wait_mysql.sh
+++ b/.travis/wait_mysql.sh
@@ -1,7 +1,13 @@
 #!/bin/sh
+
+# use the mysql client inside the docker container if docker is running
+[ "$(docker inspect -f '{{.State.Running}}' mysqld 2>/dev/null)" = "true" ] && mysql() {
+    docker exec mysqld mysql "${@}"
+}
+
 while :
 do
-    if mysql -e 'select version()' 2>&1 | grep 'version()\|ERROR 2059 (HY000):'; then
+    if mysql --protocol=tcp -e 'select version()'; then
         break
     fi
     sleep 3

From 26060e1824b523760ce8c6a6794b34b20c3f220d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Olivier=20Mengu=C3=A9?= <dolmen@cpan.org>
Date: Fri, 29 May 2020 10:48:22 +0200
Subject: [PATCH 134/282] connect: reduce allocations when building SET command
 (#1111)

* connect: reduce allocations when building SET command

* handleParams: use strings.Builder instead of direct []byte
---
 connection.go | 19 ++++++++++++++-----
 1 file changed, 14 insertions(+), 5 deletions(-)

diff --git a/connection.go b/connection.go
index 0c280443d..d1d8b29fe 100644
--- a/connection.go
+++ b/connection.go
@@ -47,7 +47,7 @@ type mysqlConn struct {
 
 // Handles parameters set in DSN after the connection is established
 func (mc *mysqlConn) handleParams() (err error) {
-	var params []string
+	var cmdSet strings.Builder
 	for param, val := range mc.cfg.Params {
 		switch param {
 		// Charset: character_set_connection, character_set_client, character_set_results
@@ -64,14 +64,23 @@ func (mc *mysqlConn) handleParams() (err error) {
 				return
 			}
 
-		// Other system vars
+		// Other system vars accumulated in a single SET command
 		default:
-			params = append(params, param+"="+val)
+			if cmdSet.Len() == 0 {
+				// Heuristic: 29 chars for each other key=value to reduce reallocations
+				cmdSet.Grow(4 + len(param) + 1 + len(val) + 30*(len(mc.cfg.Params)-1))
+				cmdSet.WriteString("SET ")
+			} else {
+				cmdSet.WriteByte(',')
+			}
+			cmdSet.WriteString(param)
+			cmdSet.WriteByte('=')
+			cmdSet.WriteString(val)
 		}
 	}
 
-	if len(params) > 0 {
-		err = mc.exec("SET " + strings.Join(params, ","))
+	if cmdSet.Len() > 0 {
+		err = mc.exec(cmdSet.String())
 		if err != nil {
 			return
 		}

From d2e52fca0b6562a2e2cb8252fbcdbab6a6e8e2d6 Mon Sep 17 00:00:00 2001
From: Caine Jette <cainejette@github.com>
Date: Fri, 29 May 2020 01:50:06 -0700
Subject: [PATCH 135/282] Replace whitelist/blacklist terminology with
 allowlist/denylist (#1116)

* Replace whitelist/blacklist terminology with allowlist/denylist

* Add myself to AUTHORS

* PR feedback

* Denylist --> denied

* Update denied --> rejected
---
 AUTHORS       | 1 +
 README.md     | 8 ++++----
 collations.go | 2 +-
 dsn.go        | 2 +-
 infile.go     | 4 ++--
 5 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index e24231d81..acc842daf 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -21,6 +21,7 @@ Arne Hormann <arnehormann at gmail.com>
 Ariel Mashraki <ariel at mashraki.co.il>
 Asta Xie <xiemengjun at gmail.com>
 Bulat Gaifullin <gaifullinbf at gmail.com>
+Caine Jette <jette at alum.mit.edu>
 Carlos Nieto <jose.carlos at menteslibres.net>
 Chris Moos <chris at tech9computers.com>
 Craig Wilson <craiggwilson at gmail.com>
diff --git a/README.md b/README.md
index 6ac81d1e4..1eeb9794a 100644
--- a/README.md
+++ b/README.md
@@ -35,7 +35,7 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
   * Supports queries larger than 16MB
   * Full [`sql.RawBytes`](https://golang.org/pkg/database/sql/#RawBytes) support.
   * Intelligent `LONG DATA` handling in prepared statements
-  * Secure `LOAD DATA LOCAL INFILE` support with file Whitelisting and `io.Reader` support
+  * Secure `LOAD DATA LOCAL INFILE` support with file allowlisting and `io.Reader` support
   * Optional `time.Time` parsing
   * Optional placeholder interpolation
 
@@ -122,7 +122,7 @@ Valid Values:   true, false
 Default:        false
 ```
 
-`allowAllFiles=true` disables the file Whitelist for `LOAD DATA LOCAL INFILE` and allows *all* files.
+`allowAllFiles=true` disables the file allowlist for `LOAD DATA LOCAL INFILE` and allows *all* files.
 [*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)
 
 ##### `allowCleartextPasswords`
@@ -230,7 +230,7 @@ Default:        false
 
 If `interpolateParams` is true, placeholders (`?`) in calls to `db.Query()` and `db.Exec()` are interpolated into a single query string with given parameters. This reduces the number of roundtrips, since the driver has to prepare a statement, execute it with given parameters and close the statement again with `interpolateParams=false`.
 
-*This can not be used together with the multibyte encodings BIG5, CP932, GB2312, GBK or SJIS. These are blacklisted as they may [introduce a SQL injection vulnerability](http://stackoverflow.com/a/12118602/3430118)!*
+*This can not be used together with the multibyte encodings BIG5, CP932, GB2312, GBK or SJIS. These are rejected as they may [introduce a SQL injection vulnerability](http://stackoverflow.com/a/12118602/3430118)!*
 
 ##### `loc`
 
@@ -445,7 +445,7 @@ For this feature you need direct access to the package. Therefore you must chang
 import "github.com/go-sql-driver/mysql"
 ```
 
-Files must be whitelisted by registering them with `mysql.RegisterLocalFile(filepath)` (recommended) or the Whitelist check must be deactivated by using the DSN parameter `allowAllFiles=true` ([*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)).
+Files must be explicitly allowed by registering them with `mysql.RegisterLocalFile(filepath)` (recommended) or the allowlist check must be deactivated by using the DSN parameter `allowAllFiles=true` ([*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)).
 
 To use a `io.Reader` a handler function must be registered with `mysql.RegisterReaderHandler(name, handler)` which returns a `io.Reader` or `io.ReadCloser`. The Reader is available with the filepath `Reader::<name>` then. Choose different names for different handlers and `DeregisterReaderHandler` when you don't need it anymore.
 
diff --git a/collations.go b/collations.go
index 8d2b55676..326a9f7fa 100644
--- a/collations.go
+++ b/collations.go
@@ -247,7 +247,7 @@ var collations = map[string]byte{
 	"utf8mb4_0900_ai_ci":       255,
 }
 
-// A blacklist of collations which is unsafe to interpolate parameters.
+// A denylist of collations which is unsafe to interpolate parameters.
 // These multibyte encodings may contains 0x5c (`\`) in their trailing bytes.
 var unsafeCollations = map[string]bool{
 	"big5_chinese_ci":        true,
diff --git a/dsn.go b/dsn.go
index 75c8c2489..93f3548cb 100644
--- a/dsn.go
+++ b/dsn.go
@@ -375,7 +375,7 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 
 		// cfg params
 		switch value := param[1]; param[0] {
-		// Disable INFILE whitelist / enable all files
+		// Disable INFILE allowlist / enable all files
 		case "allowAllFiles":
 			var isBool bool
 			cfg.AllowAllFiles, isBool = readBool(value)
diff --git a/infile.go b/infile.go
index 273cb0ba5..60effdfc2 100644
--- a/infile.go
+++ b/infile.go
@@ -23,7 +23,7 @@ var (
 	readerRegisterLock sync.RWMutex
 )
 
-// RegisterLocalFile adds the given file to the file whitelist,
+// RegisterLocalFile adds the given file to the file allowlist,
 // so that it can be used by "LOAD DATA LOCAL INFILE <filepath>".
 // Alternatively you can allow the use of all local files with
 // the DSN parameter 'allowAllFiles=true'
@@ -45,7 +45,7 @@ func RegisterLocalFile(filePath string) {
 	fileRegisterLock.Unlock()
 }
 
-// DeregisterLocalFile removes the given filepath from the whitelist.
+// DeregisterLocalFile removes the given filepath from the allowlist.
 func DeregisterLocalFile(filePath string) {
 	fileRegisterLock.Lock()
 	delete(fileRegister, strings.Trim(filePath, `"`))

From 12508c83901b1a418e3dea51850f956f32506f4e Mon Sep 17 00:00:00 2001
From: Kei Kamikawa <Code-Hex@users.noreply.github.com>
Date: Sun, 31 May 2020 19:04:19 +0900
Subject: [PATCH 136/282] utils: parse using byteslice in parseDateTime (#1113)

* fixed the way of parsing datetime when byte slice string

The benchmark results

$ go test -benchmem . -bench "^BenchmarkParseByte"
goos: darwin
goarch: amd64
pkg: github.com/go-sql-driver/mysql
BenchmarkParseByteDateTime-4                    12023173               104 ns/op               0 B/op          0 allocs/op
BenchmarkParseByteDateTimeStringCast-4           3394239               355 ns/op              32 B/op          1 allocs/op

* added line to AUTHORS file

* fixed error handling

* fixed nanosec digits

* added more tests for error

* renamed parseByteDateTime to parseDateTime

* reverted base null time

* Update utils.go

Co-authored-by: Inada Naoki <songofacandy@gmail.com>

* Update utils.go

Co-authored-by: Inada Naoki <songofacandy@gmail.com>

* Update utils.go

Co-authored-by: Inada Naoki <songofacandy@gmail.com>

* removed deprecatedParseDateTime from test

Co-authored-by: Inada Naoki <songofacandy@gmail.com>
---
 AUTHORS       |   1 +
 nulltime.go   |   4 +-
 packets.go    |   2 +-
 utils.go      | 135 +++++++++++++++++++++++++++++++++++++---
 utils_test.go | 167 ++++++++++++++++++++++++++++++++++++++++----------
 5 files changed, 263 insertions(+), 46 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index acc842daf..8ba3db6b3 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -56,6 +56,7 @@ Julien Schmidt <go-sql-driver at julienschmidt.com>
 Justin Li <jli at j-li.net>
 Justin Nuß <nuss.justin at gmail.com>
 Kamil Dziedzic <kamil at klecza.pl>
+Kei Kamikawa <x00.x7f.x86 at gmail.com>
 Kevin Malachowski <kevin at chowski.com>
 Kieron Woodhouse <kieron.woodhouse at infosum.com>
 Lennart Rudolph <lrudolph at hmc.edu>
diff --git a/nulltime.go b/nulltime.go
index afa8a89e9..651723a96 100644
--- a/nulltime.go
+++ b/nulltime.go
@@ -28,11 +28,11 @@ func (nt *NullTime) Scan(value interface{}) (err error) {
 		nt.Time, nt.Valid = v, true
 		return
 	case []byte:
-		nt.Time, err = parseDateTime(string(v), time.UTC)
+		nt.Time, err = parseDateTime(v, time.UTC)
 		nt.Valid = (err == nil)
 		return
 	case string:
-		nt.Time, err = parseDateTime(v, time.UTC)
+		nt.Time, err = parseDateTime([]byte(v), time.UTC)
 		nt.Valid = (err == nil)
 		return
 	}
diff --git a/packets.go b/packets.go
index 5cbd53298..8e2f5e76f 100644
--- a/packets.go
+++ b/packets.go
@@ -778,7 +778,7 @@ func (rows *textRows) readRow(dest []driver.Value) error {
 					case fieldTypeTimestamp, fieldTypeDateTime,
 						fieldTypeDate, fieldTypeNewDate:
 						dest[i], err = parseDateTime(
-							string(dest[i].([]byte)),
+							dest[i].([]byte),
 							mc.cfg.Loc,
 						)
 						if err == nil {
diff --git a/utils.go b/utils.go
index 154ecc337..9dd3679c6 100644
--- a/utils.go
+++ b/utils.go
@@ -106,21 +106,136 @@ func readBool(input string) (value bool, valid bool) {
 *                           Time related utils                                *
 ******************************************************************************/
 
-func parseDateTime(str string, loc *time.Location) (t time.Time, err error) {
-	base := "0000-00-00 00:00:00.0000000"
-	switch len(str) {
+func parseDateTime(b []byte, loc *time.Location) (time.Time, error) {
+	const base = "0000-00-00 00:00:00.000000"
+	switch len(b) {
 	case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM"
-		if str == base[:len(str)] {
-			return
+		if string(b) == base[:len(b)] {
+			return time.Time{}, nil
 		}
-		if loc == time.UTC {
-			return time.Parse(timeFormat[:len(str)], str)
+
+		year, err := parseByteYear(b)
+		if err != nil {
+			return time.Time{}, err
+		}
+		if year <= 0 {
+			year = 1
+		}
+
+		if b[4] != '-' {
+			return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[4])
+		}
+
+		m, err := parseByte2Digits(b[5], b[6])
+		if err != nil {
+			return time.Time{}, err
+		}
+		if m <= 0 {
+			m = 1
+		}
+		month := time.Month(m)
+
+		if b[7] != '-' {
+			return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[7])
+		}
+
+		day, err := parseByte2Digits(b[8], b[9])
+		if err != nil {
+			return time.Time{}, err
+		}
+		if day <= 0 {
+			day = 1
+		}
+		if len(b) == 10 {
+			return time.Date(year, month, day, 0, 0, 0, 0, loc), nil
+		}
+
+		if b[10] != ' ' {
+			return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[10])
+		}
+
+		hour, err := parseByte2Digits(b[11], b[12])
+		if err != nil {
+			return time.Time{}, err
+		}
+		if b[13] != ':' {
+			return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[13])
+		}
+
+		min, err := parseByte2Digits(b[14], b[15])
+		if err != nil {
+			return time.Time{}, err
+		}
+		if b[16] != ':' {
+			return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[16])
 		}
-		return time.ParseInLocation(timeFormat[:len(str)], str, loc)
+
+		sec, err := parseByte2Digits(b[17], b[18])
+		if err != nil {
+			return time.Time{}, err
+		}
+		if len(b) == 19 {
+			return time.Date(year, month, day, hour, min, sec, 0, loc), nil
+		}
+
+		if b[19] != '.' {
+			return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[19])
+		}
+		nsec, err := parseByteNanoSec(b[20:])
+		if err != nil {
+			return time.Time{}, err
+		}
+		return time.Date(year, month, day, hour, min, sec, nsec, loc), nil
 	default:
-		err = fmt.Errorf("invalid time string: %s", str)
-		return
+		return time.Time{}, fmt.Errorf("invalid time bytes: %s", b)
+	}
+}
+
+func parseByteYear(b []byte) (int, error) {
+	year, n := 0, 1000
+	for i := 0; i < 4; i++ {
+		v, err := bToi(b[i])
+		if err != nil {
+			return 0, err
+		}
+		year += v * n
+		n = n / 10
+	}
+	return year, nil
+}
+
+func parseByte2Digits(b1, b2 byte) (int, error) {
+	d1, err := bToi(b1)
+	if err != nil {
+		return 0, err
+	}
+	d2, err := bToi(b2)
+	if err != nil {
+		return 0, err
+	}
+	return d1*10 + d2, nil
+}
+
+func parseByteNanoSec(b []byte) (int, error) {
+	ns, digit := 0, 100000 // max is 6-digits
+	for i := 0; i < len(b); i++ {
+		v, err := bToi(b[i])
+		if err != nil {
+			return 0, err
+		}
+		ns += v * digit
+		digit /= 10
+	}
+	// nanoseconds has 10-digits. (needs to scale digits)
+	// 10 - 6 = 4, so we have to multiple 1000.
+	return ns * 1000, nil
+}
+
+func bToi(b byte) (int, error) {
+	if b < '0' || b > '9' {
+		return 0, errors.New("not [0-9]")
 	}
+	return int(b - '0'), nil
 }
 
 func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Value, error) {
diff --git a/utils_test.go b/utils_test.go
index ab29cad78..114f4b3da 100644
--- a/utils_test.go
+++ b/utils_test.go
@@ -294,43 +294,144 @@ func TestIsolationLevelMapping(t *testing.T) {
 }
 
 func TestParseDateTime(t *testing.T) {
-	// UTC loc
-	{
-		str := "2020-05-13 21:30:45"
-		t1, err := parseDateTime(str, time.UTC)
-		if err != nil {
-			t.Error(err)
-			return
-		}
-		t2 := time.Date(2020, 5, 13,
-			21, 30, 45, 0, time.UTC)
-		if !t1.Equal(t2) {
-			t.Errorf("want equal, have: %v, want: %v", t1, t2)
-			return
-		}
+	cases := []struct {
+		name string
+		str  string
+	}{
+		{
+			name: "parse date",
+			str:  "2020-05-13",
+		},
+		{
+			name: "parse null date",
+			str:  sDate0,
+		},
+		{
+			name: "parse datetime",
+			str:  "2020-05-13 21:30:45",
+		},
+		{
+			name: "parse null datetime",
+			str:  sDateTime0,
+		},
+		{
+			name: "parse datetime nanosec 1-digit",
+			str:  "2020-05-25 23:22:01.1",
+		},
+		{
+			name: "parse datetime nanosec 2-digits",
+			str:  "2020-05-25 23:22:01.15",
+		},
+		{
+			name: "parse datetime nanosec 3-digits",
+			str:  "2020-05-25 23:22:01.159",
+		},
+		{
+			name: "parse datetime nanosec 4-digits",
+			str:  "2020-05-25 23:22:01.1594",
+		},
+		{
+			name: "parse datetime nanosec 5-digits",
+			str:  "2020-05-25 23:22:01.15949",
+		},
+		{
+			name: "parse datetime nanosec 6-digits",
+			str:  "2020-05-25 23:22:01.159491",
+		},
 	}
-	// non-UTC loc
-	{
-		str := "2020-05-13 21:30:45"
-		loc := time.FixedZone("test", 8*60*60)
-		t1, err := parseDateTime(str, loc)
-		if err != nil {
-			t.Error(err)
-			return
-		}
-		t2 := time.Date(2020, 5, 13,
-			21, 30, 45, 0, loc)
-		if !t1.Equal(t2) {
-			t.Errorf("want equal, have: %v, want: %v", t1, t2)
-			return
+
+	for _, loc := range []*time.Location{
+		time.UTC,
+		time.FixedZone("test", 8*60*60),
+	} {
+		for _, cc := range cases {
+			t.Run(cc.name+"-"+loc.String(), func(t *testing.T) {
+				var want time.Time
+				if cc.str != sDate0 && cc.str != sDateTime0 {
+					var err error
+					want, err = time.ParseInLocation(timeFormat[:len(cc.str)], cc.str, loc)
+					if err != nil {
+						t.Fatal(err)
+					}
+				}
+				got, err := parseDateTime([]byte(cc.str), loc)
+				if err != nil {
+					t.Fatal(err)
+				}
+
+				if !want.Equal(got) {
+					t.Fatalf("want: %v, but got %v", want, got)
+				}
+			})
 		}
 	}
 }
 
-func BenchmarkParseDateTime(b *testing.B) {
-	str := "2020-05-13 21:30:45"
-	loc := time.FixedZone("test", 8*60*60)
-	for i := 0; i < b.N; i++ {
-		_, _ = parseDateTime(str, loc)
+func TestParseDateTimeFail(t *testing.T) {
+	cases := []struct {
+		name    string
+		str     string
+		wantErr string
+	}{
+		{
+			name:    "parse invalid time",
+			str:     "hello",
+			wantErr: "invalid time bytes: hello",
+		},
+		{
+			name:    "parse year",
+			str:     "000!-00-00 00:00:00.000000",
+			wantErr: "not [0-9]",
+		},
+		{
+			name:    "parse month",
+			str:     "0000-!0-00 00:00:00.000000",
+			wantErr: "not [0-9]",
+		},
+		{
+			name:    `parse "-" after parsed year`,
+			str:     "0000:00-00 00:00:00.000000",
+			wantErr: "bad value for field: `:`",
+		},
+		{
+			name:    `parse "-" after parsed month`,
+			str:     "0000-00:00 00:00:00.000000",
+			wantErr: "bad value for field: `:`",
+		},
+		{
+			name:    `parse " " after parsed date`,
+			str:     "0000-00-00+00:00:00.000000",
+			wantErr: "bad value for field: `+`",
+		},
+		{
+			name:    `parse ":" after parsed date`,
+			str:     "0000-00-00 00-00:00.000000",
+			wantErr: "bad value for field: `-`",
+		},
+		{
+			name:    `parse ":" after parsed hour`,
+			str:     "0000-00-00 00:00-00.000000",
+			wantErr: "bad value for field: `-`",
+		},
+		{
+			name:    `parse "." after parsed sec`,
+			str:     "0000-00-00 00:00:00?000000",
+			wantErr: "bad value for field: `?`",
+		},
+	}
+
+	for _, cc := range cases {
+		t.Run(cc.name, func(t *testing.T) {
+			got, err := parseDateTime([]byte(cc.str), time.UTC)
+			if err == nil {
+				t.Fatal("want error")
+			}
+			if cc.wantErr != err.Error() {
+				t.Fatalf("want `%s`, but got `%s`", cc.wantErr, err)
+			}
+			if !got.IsZero() {
+				t.Fatal("want zero time")
+			}
+		})
 	}
 }

From 73dc904a9ece5c074295a77abb7a135797a351bf Mon Sep 17 00:00:00 2001
From: nozawana44 <62693119+nozawana44@users.noreply.github.com>
Date: Mon, 20 Jul 2020 16:11:43 +0900
Subject: [PATCH 137/282] README: replace tx_isolation with
 transaction_isolation (#1077)

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 1eeb9794a..505d2a7e1 100644
--- a/README.md
+++ b/README.md
@@ -376,7 +376,7 @@ Rules:
 Examples:
   * `autocommit=1`: `SET autocommit=1`
   * [`time_zone=%27Europe%2FParis%27`](https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html): `SET time_zone='Europe/Paris'`
-  * [`tx_isolation=%27REPEATABLE-READ%27`](https://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_tx_isolation): `SET tx_isolation='REPEATABLE-READ'`
+  * [`transaction_isolation=%27REPEATABLE-READ%27`](https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_transaction_isolation): `SET transaction_isolation='REPEATABLE-READ'`
 
 
 #### Examples

From 076901a81ee91628a5c11d46584053047c10d1d5 Mon Sep 17 00:00:00 2001
From: IKEDA Sho <suicaicoca@gmail.com>
Date: Thu, 13 Aug 2020 13:40:09 +0900
Subject: [PATCH 138/282] Support returning uint64 from Valuer in ConvertValue
 (#1143)

https://golang.org/pkg/database/sql/driver/#Value says:

> Value is a value that drivers must be able to handle. It is either nil, a type handled by a database driver's NamedValueChecker interface, or an instance of one of these types:
---
 AUTHORS           |  1 +
 statement.go      | 12 +++++++++---
 statement_test.go | 10 ++++++++++
 3 files changed, 20 insertions(+), 3 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 8ba3db6b3..ccf2f466c 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -79,6 +79,7 @@ Reed Allman <rdallman10 at gmail.com>
 Richard Wilkes <wilkes at me.com>
 Robert Russell <robert at rrbrussell.com>
 Runrioter Wung <runrioter at gmail.com>
+Sho Ikeda <suicaicoca at gmail.com>
 Shuode Li <elemount at qq.com>
 Simon J Mudd <sjmudd at pobox.com>
 Soroush Pour <me at soroushjp.com>
diff --git a/statement.go b/statement.go
index d3e68112f..18a3ae498 100644
--- a/statement.go
+++ b/statement.go
@@ -154,10 +154,16 @@ func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
 		if err != nil {
 			return nil, err
 		}
-		if !driver.IsValue(sv) {
-			return nil, fmt.Errorf("non-Value type %T returned from Value", sv)
+		if driver.IsValue(sv) {
+			return sv, nil
 		}
-		return sv, nil
+		// A value returend from the Valuer interface can be "a type handled by
+		// a database driver's NamedValueChecker interface" so we should accept
+		// uint64 here as well.
+		if u, ok := sv.(uint64); ok {
+			return u, nil
+		}
+		return nil, fmt.Errorf("non-Value type %T returned from Value", sv)
 	}
 	rv := reflect.ValueOf(v)
 	switch rv.Kind() {
diff --git a/statement_test.go b/statement_test.go
index 2cc022bf5..ac6b92de9 100644
--- a/statement_test.go
+++ b/statement_test.go
@@ -10,6 +10,7 @@ package mysql
 
 import (
 	"bytes"
+	"database/sql/driver"
 	"encoding/json"
 	"testing"
 )
@@ -96,6 +97,14 @@ func TestConvertSignedIntegers(t *testing.T) {
 	}
 }
 
+type myUint64 struct {
+	value uint64
+}
+
+func (u myUint64) Value() (driver.Value, error) {
+	return u.value, nil
+}
+
 func TestConvertUnsignedIntegers(t *testing.T) {
 	values := []interface{}{
 		uint8(42),
@@ -103,6 +112,7 @@ func TestConvertUnsignedIntegers(t *testing.T) {
 		uint32(42),
 		uint64(42),
 		uint(42),
+		myUint64{uint64(42)},
 	}
 
 	for _, value := range values {

From 3b935426341bc5d229eafd936e4f4240da027ccd Mon Sep 17 00:00:00 2001
From: chanxuehong <chanxuehong@gmail.com>
Date: Thu, 13 Aug 2020 12:43:20 +0800
Subject: [PATCH 139/282] performance improvement for time format (#1118)

---
 connection.go | 42 +++---------------------------
 packets.go    |  5 +++-
 utils.go      | 49 +++++++++++++++++++++++++++++++++++
 utils_test.go | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 129 insertions(+), 39 deletions(-)

diff --git a/connection.go b/connection.go
index d1d8b29fe..90aec6439 100644
--- a/connection.go
+++ b/connection.go
@@ -245,44 +245,10 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
 			if v.IsZero() {
 				buf = append(buf, "'0000-00-00'"...)
 			} else {
-				v := v.In(mc.cfg.Loc)
-				v = v.Add(time.Nanosecond * 500) // To round under microsecond
-				year := v.Year()
-				year100 := year / 100
-				year1 := year % 100
-				month := v.Month()
-				day := v.Day()
-				hour := v.Hour()
-				minute := v.Minute()
-				second := v.Second()
-				micro := v.Nanosecond() / 1000
-
-				buf = append(buf, []byte{
-					'\'',
-					digits10[year100], digits01[year100],
-					digits10[year1], digits01[year1],
-					'-',
-					digits10[month], digits01[month],
-					'-',
-					digits10[day], digits01[day],
-					' ',
-					digits10[hour], digits01[hour],
-					':',
-					digits10[minute], digits01[minute],
-					':',
-					digits10[second], digits01[second],
-				}...)
-
-				if micro != 0 {
-					micro10000 := micro / 10000
-					micro100 := micro / 100 % 100
-					micro1 := micro % 100
-					buf = append(buf, []byte{
-						'.',
-						digits10[micro10000], digits01[micro10000],
-						digits10[micro100], digits01[micro100],
-						digits10[micro1], digits01[micro1],
-					}...)
+				buf = append(buf, '\'')
+				buf, err = appendDateTime(buf, v.In(mc.cfg.Loc))
+				if err != nil {
+					return "", err
 				}
 				buf = append(buf, '\'')
 			}
diff --git a/packets.go b/packets.go
index 8e2f5e76f..6664e5ae5 100644
--- a/packets.go
+++ b/packets.go
@@ -1116,7 +1116,10 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 				if v.IsZero() {
 					b = append(b, "0000-00-00"...)
 				} else {
-					b = v.In(mc.cfg.Loc).AppendFormat(b, timeFormat)
+					b, err = appendDateTime(b, v.In(mc.cfg.Loc))
+					if err != nil {
+						return err
+					}
 				}
 
 				paramValues = appendLengthEncodedInteger(paramValues,
diff --git a/utils.go b/utils.go
index 9dd3679c6..b0c6e9ca3 100644
--- a/utils.go
+++ b/utils.go
@@ -276,6 +276,55 @@ func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Va
 	return nil, fmt.Errorf("invalid DATETIME packet length %d", num)
 }
 
+func appendDateTime(buf []byte, t time.Time) ([]byte, error) {
+	nsec := t.Nanosecond()
+	// to round under microsecond
+	if nsec%1000 >= 500 { // save half of time.Time.Add calls
+		t = t.Add(500 * time.Nanosecond)
+		nsec = t.Nanosecond()
+	}
+	year, month, day := t.Date()
+	hour, min, sec := t.Clock()
+	micro := nsec / 1000
+
+	if year < 1 || year > 9999 {
+		return buf, errors.New("year is not in the range [1, 9999]: " + strconv.Itoa(year)) // use errors.New instead of fmt.Errorf to avoid year escape to heap
+	}
+	year100 := year / 100
+	year1 := year % 100
+
+	var localBuf [26]byte // does not escape
+	localBuf[0], localBuf[1], localBuf[2], localBuf[3] = digits10[year100], digits01[year100], digits10[year1], digits01[year1]
+	localBuf[4] = '-'
+	localBuf[5], localBuf[6] = digits10[month], digits01[month]
+	localBuf[7] = '-'
+	localBuf[8], localBuf[9] = digits10[day], digits01[day]
+
+	if hour == 0 && min == 0 && sec == 0 && micro == 0 {
+		return append(buf, localBuf[:10]...), nil
+	}
+
+	localBuf[10] = ' '
+	localBuf[11], localBuf[12] = digits10[hour], digits01[hour]
+	localBuf[13] = ':'
+	localBuf[14], localBuf[15] = digits10[min], digits01[min]
+	localBuf[16] = ':'
+	localBuf[17], localBuf[18] = digits10[sec], digits01[sec]
+
+	if micro == 0 {
+		return append(buf, localBuf[:19]...), nil
+	}
+
+	micro10000 := micro / 10000
+	micro100 := (micro / 100) % 100
+	micro1 := micro % 100
+	localBuf[19] = '.'
+	localBuf[20], localBuf[21], localBuf[22], localBuf[23], localBuf[24], localBuf[25] =
+		digits10[micro10000], digits01[micro10000], digits10[micro100], digits01[micro100], digits10[micro1], digits01[micro1]
+
+	return append(buf, localBuf[:]...), nil
+}
+
 // zeroDateTime is used in formatBinaryDateTime to avoid an allocation
 // if the DATE or DATETIME has the zero value.
 // It must never be changed.
diff --git a/utils_test.go b/utils_test.go
index 114f4b3da..e3619e7a7 100644
--- a/utils_test.go
+++ b/utils_test.go
@@ -293,6 +293,78 @@ func TestIsolationLevelMapping(t *testing.T) {
 	}
 }
 
+func TestAppendDateTime(t *testing.T) {
+	tests := []struct {
+		t   time.Time
+		str string
+	}{
+		{
+			t:   time.Date(2020, 05, 30, 0, 0, 0, 0, time.UTC),
+			str: "2020-05-30",
+		},
+		{
+			t:   time.Date(2020, 05, 30, 22, 0, 0, 0, time.UTC),
+			str: "2020-05-30 22:00:00",
+		},
+		{
+			t:   time.Date(2020, 05, 30, 22, 33, 0, 0, time.UTC),
+			str: "2020-05-30 22:33:00",
+		},
+		{
+			t:   time.Date(2020, 05, 30, 22, 33, 44, 0, time.UTC),
+			str: "2020-05-30 22:33:44",
+		},
+		{
+			t:   time.Date(2020, 05, 30, 22, 33, 44, 550000000, time.UTC),
+			str: "2020-05-30 22:33:44.550000",
+		},
+		{
+			t:   time.Date(2020, 05, 30, 22, 33, 44, 550000499, time.UTC),
+			str: "2020-05-30 22:33:44.550000",
+		},
+		{
+			t:   time.Date(2020, 05, 30, 22, 33, 44, 550000500, time.UTC),
+			str: "2020-05-30 22:33:44.550001",
+		},
+		{
+			t:   time.Date(2020, 05, 30, 22, 33, 44, 550000567, time.UTC),
+			str: "2020-05-30 22:33:44.550001",
+		},
+		{
+			t:   time.Date(2020, 05, 30, 22, 33, 44, 999999567, time.UTC),
+			str: "2020-05-30 22:33:45",
+		},
+	}
+	for _, v := range tests {
+		buf := make([]byte, 0, 32)
+		buf, _ = appendDateTime(buf, v.t)
+		if str := string(buf); str != v.str {
+			t.Errorf("appendDateTime(%v), have: %s, want: %s", v.t, str, v.str)
+			return
+		}
+	}
+
+	// year out of range
+	{
+		v := time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC)
+		buf := make([]byte, 0, 32)
+		_, err := appendDateTime(buf, v)
+		if err == nil {
+			t.Error("want an error")
+			return
+		}
+	}
+	{
+		v := time.Date(10000, 1, 1, 0, 0, 0, 0, time.UTC)
+		buf := make([]byte, 0, 32)
+		_, err := appendDateTime(buf, v)
+		if err == nil {
+			t.Error("want an error")
+			return
+		}
+	}
+}
+
 func TestParseDateTime(t *testing.T) {
 	cases := []struct {
 		name string

From f26d659cdb79eef0c396c764896dfaaa2fe93991 Mon Sep 17 00:00:00 2001
From: Ichinose Shogo <shogo82148@gmail.com>
Date: Thu, 13 Aug 2020 13:52:13 +0900
Subject: [PATCH 140/282] Add Go 1.15 to the build matrix (#1146)

---
 .travis.yml | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 21638db0e..2926bdd11 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,10 +1,11 @@
 language: go
 go:
   # Keep the most recent production release at the top
-  - 1.14.x
+  - 1.15.x
   # Go development version
   - master
   # Older production releases
+  - 1.14.x
   - 1.13.x
   - 1.12.x
   - 1.11.x
@@ -22,12 +23,12 @@ before_script:
 
 jobs:
   allow_failures:
-  - go: master
+    - go: master
 
   include:
     - env: DB=MYSQL8
       dist: xenial
-      go: 1.14.x
+      go: 1.15.x
       services:
         - docker
       before_install:
@@ -47,7 +48,7 @@ jobs:
 
     - env: DB=MYSQL57
       dist: xenial
-      go: 1.14.x
+      go: 1.15.x
       services:
         - docker
       before_install:
@@ -67,7 +68,7 @@ jobs:
 
     - env: DB=MARIA55
       dist: xenial
-      go: 1.14.x
+      go: 1.15.x
       services:
         - docker
       before_install:
@@ -87,7 +88,7 @@ jobs:
 
     - env: DB=MARIA10_1
       dist: xenial
-      go: 1.14.x
+      go: 1.15.x
       services:
         - docker
       before_install:
@@ -112,7 +113,7 @@ jobs:
           packages:
             - mysql
           update: true
-      go: 1.14.x
+      go: 1.15.x
       before_install:
         - go get golang.org/x/tools/cmd/cover
         - go get github.com/mattn/goveralls

From b5b0ea55e9c131dcc4a3d5ccb70f06be52f72c9e Mon Sep 17 00:00:00 2001
From: Ichinose Shogo <shogo82148@gmail.com>
Date: Sun, 16 Aug 2020 11:32:30 +0900
Subject: [PATCH 141/282] Add deprecated message to NullTime (#1144)

* Add deprecated message to NullTime
* remove NullTime from README
---
 README.md         | 2 --
 nulltime_go113.go | 4 ++++
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 505d2a7e1..834cf10f5 100644
--- a/README.md
+++ b/README.md
@@ -459,8 +459,6 @@ However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` v
 
 **Caution:** As of Go 1.1, this makes `time.Time` the only variable type you can scan `DATE` and `DATETIME` values into. This breaks for example [`sql.RawBytes` support](https://github.com/go-sql-driver/mysql/wiki/Examples#rawbytes).
 
-Alternatively you can use the [`NullTime`](https://godoc.org/github.com/go-sql-driver/mysql#NullTime) type as the scan destination, which works with both `time.Time` and `string` / `[]byte`.
-
 
 ### Unicode support
 Since version 1.5 Go-MySQL-Driver automatically uses the collation ` utf8mb4_general_ci` by default.
diff --git a/nulltime_go113.go b/nulltime_go113.go
index c392594dd..cb9bcae4a 100644
--- a/nulltime_go113.go
+++ b/nulltime_go113.go
@@ -28,4 +28,8 @@ import (
 //  }
 //
 // This NullTime implementation is not driver-specific
+//
+// Deprecated: NullTime doesn't honor the loc DSN parameter.
+// NullTime.Scan interprets a time as UTC, not the loc DSN parameter.
+// Use sql.NullTime instead.
 type NullTime sql.NullTime

From 84241ad3f47266ef3ee7cdbc13975c48ce66fa83 Mon Sep 17 00:00:00 2001
From: Ichinose Shogo <shogo82148@gmail.com>
Date: Sun, 16 Aug 2020 11:33:32 +0900
Subject: [PATCH 142/282] return sql.NullTime if it available (#1145)

* return sql.NullTime if it available

* NullTime should be used with parseTime=true option
---
 driver_test.go     | 222 ++++++++++++++++++++++-----------------------
 fields.go          |   2 +-
 nulltime_go113.go  |   5 +
 nulltime_legacy.go |   5 +
 4 files changed, 119 insertions(+), 115 deletions(-)

diff --git a/driver_test.go b/driver_test.go
index 34b476ed3..aa55d2f55 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -2758,13 +2758,13 @@ 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, 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}
-	nd1 := NullTime{Time: time.Date(2006, 01, 02, 0, 0, 0, 0, time.UTC), Valid: true}
-	nd2 := NullTime{Time: time.Date(2006, 03, 04, 0, 0, 0, 0, time.UTC), Valid: true}
-	ndNULL := NullTime{Time: time.Time{}, Valid: false}
+	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, 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}
+	nd1 := nullTime{Time: time.Date(2006, 01, 02, 0, 0, 0, 0, time.UTC), Valid: true}
+	nd2 := nullTime{Time: time.Date(2006, 03, 04, 0, 0, 0, 0, time.UTC), Valid: true}
+	ndNULL := nullTime{Time: time.Time{}, Valid: false}
 	rbNULL := sql.RawBytes(nil)
 	rb0 := sql.RawBytes("0")
 	rb42 := sql.RawBytes("42")
@@ -2844,131 +2844,125 @@ func TestRowsColumnTypes(t *testing.T) {
 	values2 = values2[:len(values2)-2]
 	values3 = values3[:len(values3)-2]
 
-	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 + ")")
+	runTests(t, dsn+"&parseTime=true", 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)
-			}
+		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)
-			}
+		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))
-			}
+		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]
+		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
-				}
+			// 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
-				}
+			// 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
+			// 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[i] = scanType
-
-				// Nullable
-				nullable, ok := tp.Nullable()
+				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 and Scale
+			precision, scale, ok := tp.DecimalSize()
+			if precision != column.precision {
 				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)
+					t.Errorf("precision not ok for column %q", name)
+				} else {
+					t.Errorf("precision mismatch for column %q: %d != %d", name, precision, column.precision)
 				}
-
-				// 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 and Scale
-				precision, scale, 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
-				}
-				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
+				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))
-			for i := range values {
-				values[i] = reflect.New(types[i]).Interface()
+		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)
 			}
-			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]) {
-						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])
-						}
+			for j := range values {
+				value := reflect.ValueOf(values[j]).Elem().Interface()
+				if !reflect.DeepEqual(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++
-			}
-			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)
+		}
+	})
 }
 
 func TestValuerWithValueReceiverGivenNilValue(t *testing.T) {
diff --git a/fields.go b/fields.go
index e1e2ece4b..ed6c7a37d 100644
--- a/fields.go
+++ b/fields.go
@@ -106,7 +106,7 @@ var (
 	scanTypeInt64     = reflect.TypeOf(int64(0))
 	scanTypeNullFloat = reflect.TypeOf(sql.NullFloat64{})
 	scanTypeNullInt   = reflect.TypeOf(sql.NullInt64{})
-	scanTypeNullTime  = reflect.TypeOf(NullTime{})
+	scanTypeNullTime  = reflect.TypeOf(nullTime{})
 	scanTypeUint8     = reflect.TypeOf(uint8(0))
 	scanTypeUint16    = reflect.TypeOf(uint16(0))
 	scanTypeUint32    = reflect.TypeOf(uint32(0))
diff --git a/nulltime_go113.go b/nulltime_go113.go
index cb9bcae4a..453b4b394 100644
--- a/nulltime_go113.go
+++ b/nulltime_go113.go
@@ -33,3 +33,8 @@ import (
 // NullTime.Scan interprets a time as UTC, not the loc DSN parameter.
 // Use sql.NullTime instead.
 type NullTime sql.NullTime
+
+// for internal use.
+// the mysql package uses sql.NullTime if it is available.
+// if not, the package uses mysql.NullTime.
+type nullTime = sql.NullTime // sql.NullTime is available
diff --git a/nulltime_legacy.go b/nulltime_legacy.go
index 86d159d44..9f7ae27a8 100644
--- a/nulltime_legacy.go
+++ b/nulltime_legacy.go
@@ -32,3 +32,8 @@ type NullTime struct {
 	Time  time.Time
 	Valid bool // Valid is true if Time is not NULL
 }
+
+// for internal use.
+// the mysql package uses sql.NullTime if it is available.
+// if not, the package uses mysql.NullTime.
+type nullTime = NullTime // sql.NullTime is not available

From e2ee2f35c7a3d5a37ddcbaff7f6b679ffd9e457c Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Sun, 16 Aug 2020 17:27:32 +0900
Subject: [PATCH 143/282] README: Recommend some DB settings (#1141)

---
 README.md | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 834cf10f5..969aec554 100644
--- a/README.md
+++ b/README.md
@@ -56,15 +56,31 @@ Make sure [Git is installed](https://git-scm.com/downloads) on your machine and
 _Go MySQL Driver_ is an implementation of Go's `database/sql/driver` interface. You only need to import the driver and can use the full [`database/sql`](https://golang.org/pkg/database/sql/) API then.
 
 Use `mysql` as `driverName` and a valid [DSN](#dsn-data-source-name)  as `dataSourceName`:
+
 ```go
 import "database/sql"
 import _ "github.com/go-sql-driver/mysql"
 
 db, err := sql.Open("mysql", "user:password@/dbname")
+if err != nil {
+	panic(err)
+}
+// See "Important settings" section.
+db.SetConnMaxLifetime(time.Minute * 3)
+db.SetMaxOpenConns(10)
+db.SetMaxIdleConns(10)
 ```
 
 [Examples are available in our Wiki](https://github.com/go-sql-driver/mysql/wiki/Examples "Go-MySQL-Driver Examples").
 
+### Important settings
+
+`db.SetConnMaxLifetime()` is required to ensure connections are closed by the driver safely before connection is closed by MySQL server, OS, or other middlewares. Since some middlewares close idle connections by 5 minutes, we recommend timeout shorter than 5 minutes. This setting helps load balancing and changing system variables too.
+
+`db.SetMaxOpenConns()` is highly recommended to limit the number of connection used by the application. There is no recommended limit number because it depends on application and MySQL server.
+
+`db.SetMaxIdleConns()` is recommended to be set same to (or greater than) `db.SetMaxOpenConns()`. When it is smaller than `SetMaxOpenConns()`, connections can be opened and closed very frequently than you expect. Idle connections can be closed by the `db.SetConnMaxLifetime()`. If you want to close idle connections more rapidly, you can use `db.SetConnMaxIdleTime()` since Go 1.15.
+
 
 ### DSN (Data Source Name)
 
@@ -496,4 +512,3 @@ Please read the [MPL 2.0 FAQ](https://www.mozilla.org/en-US/MPL/2.0/FAQ/) if you
 You can read the full terms here: [LICENSE](https://raw.github.com/go-sql-driver/mysql/master/LICENSE).
 
 ![Go Gopher and MySQL Dolphin](https://raw.github.com/wiki/go-sql-driver/mysql/go-mysql-driver_m.jpg "Golang Gopher transporting the MySQL Dolphin in a wheelbarrow")
-

From 46351a8892976898935f653f5333782579a96fa5 Mon Sep 17 00:00:00 2001
From: Jinhua Tan <312841925@qq.com>
Date: Tue, 18 Aug 2020 19:12:13 +0800
Subject: [PATCH 144/282] auth: do not send 0 byte for mysql_old_password when
 password is empty (#1133)

---
 AUTHORS      | 1 +
 auth.go      | 7 +++----
 auth_test.go | 4 ++--
 3 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index ccf2f466c..112e07524 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -86,6 +86,7 @@ Soroush Pour <me at soroushjp.com>
 Stan Putrya <root.vagner at gmail.com>
 Stanley Gunawan <gunawan.stanley at gmail.com>
 Steven Hartland <steven.hartland at multiplay.co.uk>
+Tan Jinhua <312841925 at qq.com>
 Thomas Wodarek <wodarekwebpage at gmail.com>
 Tim Ruffles <timruffles at gmail.com>
 Tom Jenkinson <tom at tjenkinson.me>
diff --git a/auth.go b/auth.go
index fec7040d4..1f9ceb059 100644
--- a/auth.go
+++ b/auth.go
@@ -136,10 +136,6 @@ func pwHash(password []byte) (result [2]uint32) {
 
 // Hash password using insecure pre 4.1 method
 func scrambleOldPassword(scramble []byte, password string) []byte {
-	if len(password) == 0 {
-		return nil
-	}
-
 	scramble = scramble[:8]
 
 	hashPw := pwHash([]byte(password))
@@ -247,6 +243,9 @@ func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, error) {
 		if !mc.cfg.AllowOldPasswords {
 			return nil, ErrOldPassword
 		}
+		if len(mc.cfg.Passwd) == 0 {
+			return nil, nil
+		}
 		// Note: there are edge cases where this should work but doesn't;
 		// this is currently "wontfix":
 		// https://github.com/go-sql-driver/mysql/issues/184
diff --git a/auth_test.go b/auth_test.go
index 1920ef39f..3bce7fe22 100644
--- a/auth_test.go
+++ b/auth_test.go
@@ -1157,7 +1157,7 @@ func TestAuthSwitchOldPasswordEmpty(t *testing.T) {
 		t.Errorf("got error: %v", err)
 	}
 
-	expectedReply := []byte{1, 0, 0, 3, 0}
+	expectedReply := []byte{0, 0, 0, 3}
 	if !bytes.Equal(conn.written, expectedReply) {
 		t.Errorf("got unexpected data: %v", conn.written)
 	}
@@ -1184,7 +1184,7 @@ func TestOldAuthSwitchPasswordEmpty(t *testing.T) {
 		t.Errorf("got error: %v", err)
 	}
 
-	expectedReply := []byte{1, 0, 0, 3, 0}
+	expectedReply := []byte{0, 0, 0, 3}
 	if !bytes.Equal(conn.written, expectedReply) {
 		t.Errorf("got unexpected data: %v", conn.written)
 	}

From cf72fd249522a0cc5e1ce727dfa15189dcb6411f Mon Sep 17 00:00:00 2001
From: Sho IIZUKA <sho.i518@gmail.com>
Date: Sat, 14 Nov 2020 10:11:01 +0900
Subject: [PATCH 145/282] Fix a broken link to cleartext client side plugin
 (#1165)

---
 AUTHORS   | 1 +
 README.md | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/AUTHORS b/AUTHORS
index 112e07524..3460c2e06 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -79,6 +79,7 @@ Reed Allman <rdallman10 at gmail.com>
 Richard Wilkes <wilkes at me.com>
 Robert Russell <robert at rrbrussell.com>
 Runrioter Wung <runrioter at gmail.com>
+Sho Iizuka <sho.i518 at gmail.com>
 Sho Ikeda <suicaicoca at gmail.com>
 Shuode Li <elemount at qq.com>
 Simon J Mudd <sjmudd at pobox.com>
diff --git a/README.md b/README.md
index 969aec554..8f4c6fb04 100644
--- a/README.md
+++ b/README.md
@@ -149,7 +149,7 @@ Valid Values:   true, false
 Default:        false
 ```
 
-`allowCleartextPasswords=true` allows using the [cleartext client side plugin](http://dev.mysql.com/doc/en/cleartext-authentication-plugin.html) if required by an account, such as one defined with the [PAM authentication plugin](http://dev.mysql.com/doc/en/pam-authentication-plugin.html). Sending passwords in clear text may be a security problem in some configurations. To avoid problems if there is any possibility that the password would be intercepted, clients should connect to MySQL Server using a method that protects the password. Possibilities include [TLS / SSL](#tls), IPsec, or a private network.
+`allowCleartextPasswords=true` allows using the [cleartext client side plugin](https://dev.mysql.com/doc/en/cleartext-pluggable-authentication.html) if required by an account, such as one defined with the [PAM authentication plugin](http://dev.mysql.com/doc/en/pam-authentication-plugin.html). Sending passwords in clear text may be a security problem in some configurations. To avoid problems if there is any possibility that the password would be intercepted, clients should connect to MySQL Server using a method that protects the password. Possibilities include [TLS / SSL](#tls), IPsec, or a private network.
 
 ##### `allowNativePasswords`
 

From aae55f736d467f81dced6ab1d37d4128209f0962 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Thu, 26 Nov 2020 11:36:41 +0900
Subject: [PATCH 146/282] README: Make usage code more friendly (#1170)

---
 README.md | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 8f4c6fb04..0b13154fc 100644
--- a/README.md
+++ b/README.md
@@ -58,8 +58,14 @@ _Go MySQL Driver_ is an implementation of Go's `database/sql/driver` interface.
 Use `mysql` as `driverName` and a valid [DSN](#dsn-data-source-name)  as `dataSourceName`:
 
 ```go
-import "database/sql"
-import _ "github.com/go-sql-driver/mysql"
+import (
+	"database/sql"
+	"time"
+
+	_ "github.com/go-sql-driver/mysql"
+)
+
+// ...
 
 db, err := sql.Open("mysql", "user:password@/dbname")
 if err != nil {

From f6dcc3d870a826fb4212f2f124acd2e4658632c5 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Fri, 27 Nov 2020 22:54:36 +0900
Subject: [PATCH 147/282] Fix go vet error (#1173)

---
 benchmark_test.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/benchmark_test.go b/benchmark_test.go
index 3e25a3bf2..1030ddc52 100644
--- a/benchmark_test.go
+++ b/benchmark_test.go
@@ -127,7 +127,8 @@ func BenchmarkExec(b *testing.B) {
 				}
 
 				if _, err := stmt.Exec(); err != nil {
-					b.Fatal(err.Error())
+					b.Logf("stmt.Exec failed: %v", err)
+					b.Fail()
 				}
 			}
 		}()

From a341cd15c45e4cdae93421b02834fdadf61e5e18 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Tue, 29 Dec 2020 00:21:29 +0900
Subject: [PATCH 148/282] Move tests from Travis to Actions (#1183)

* Run macOS CI on Github Actions

* no cover

* Fix workflow name

* Actions: Add linux test

* fix

* fix

* fix go 1.10

* Remove .travis.yml
---
 .github/workflows/linux.yaml |  50 +++++++++++++
 .github/workflows/mac.yaml   |  36 +++++++++
 .travis.yml                  | 138 -----------------------------------
 3 files changed, 86 insertions(+), 138 deletions(-)
 create mode 100644 .github/workflows/linux.yaml
 create mode 100644 .github/workflows/mac.yaml
 delete mode 100644 .travis.yml

diff --git a/.github/workflows/linux.yaml b/.github/workflows/linux.yaml
new file mode 100644
index 000000000..c00bd72c4
--- /dev/null
+++ b/.github/workflows/linux.yaml
@@ -0,0 +1,50 @@
+name: Test on Linux
+on:
+  push:
+  pull_request:
+
+jobs:
+  test:
+    runs-on: ubuntu-20.04
+    strategy:
+      matrix:
+        go: [1.15, 1.14, 1.13, 1.12, 1.11, "1.10"]
+        mysql: ["mysql:8.0"]
+        include:
+          - go: 1.15
+            mysql: "mysql:5.7"
+          - go: 1.15
+            mysql: "mysql:5.5"
+          - go: 1.15
+            mysql: "mariadb:5.5"
+          - go: 1.15
+            mysql: "mariadb:10.1"
+
+    services:
+      mysql:
+        image: ${{ matrix.mysql }}
+        ports:
+          - 3306:3306
+        env:
+          MYSQL_DATABASE: gotest
+          MYSQL_USER: gotest
+          MYSQL_PASSWORD: secret
+          MYSQL_ALLOW_EMPTY_PASSWORD: yes
+        # How can we add these options?
+        #options: --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
+
+    steps:
+      - uses: actions/setup-go@v2
+        with:
+          go-version: ${{ matrix.go }}
+      - uses: actions/checkout@v2
+      - name: Run test
+        env:
+          MYSQL_TEST_USER: gotest
+          MYSQL_TEST_PASS: secret
+          MYSQL_TEST_ADDR: 127.0.0.1:3307
+          MYSQL_TEST_CONCURRENT: 1
+        run: |
+          go test -v -covermode=count -coverprofile=coverage.out
+          go vet ./...
+          gofmt -d -s .
diff --git a/.github/workflows/mac.yaml b/.github/workflows/mac.yaml
new file mode 100644
index 000000000..f80a90b6e
--- /dev/null
+++ b/.github/workflows/mac.yaml
@@ -0,0 +1,36 @@
+name: Test on macOS
+on:
+  push:
+  pull_request:
+
+jobs:
+  build:
+    runs-on: macos-latest
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+
+      - name: Set up MySQL
+        run: |
+          brew install mysql
+          echo -e "[server]\ninnodb_log_file_size=256MB\ninnodb_buffer_pool_size=512MB\nmax_allowed_packet=16MB\nlocal_infile=1" >> /usr/local/etc/my.cnf
+          mysql.server start
+          mysql -uroot -e 'CREATE USER gotest IDENTIFIED BY "secret"'
+          mysql -uroot -e 'GRANT ALL ON *.* TO gotest'
+          mysql -uroot -e 'create database gotest;'
+
+      - name: Set up Go
+        run: |
+          go install golang.org/x/tools/cmd/cover
+
+      - name: Run tests
+        env:
+          MYSQL_TEST_USER: gotest
+          MYSQL_TEST_PASS: secret
+          MYSQL_TEST_ADDR: 127.0.0.1:3306
+          MYSQL_TEST_CONCURRENT: 1
+        run: |
+          go test -v -covermode=count -coverprofile=coverage.out
+          go vet ./...
+          gofmt -d -s .
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 2926bdd11..000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,138 +0,0 @@
-language: go
-go:
-  # Keep the most recent production release at the top
-  - 1.15.x
-  # Go development version
-  - master
-  # Older production releases
-  - 1.14.x
-  - 1.13.x
-  - 1.12.x
-  - 1.11.x
-  - 1.10.x
-
-before_install:
-  - go get golang.org/x/tools/cmd/cover
-  - go get github.com/mattn/goveralls
-
-before_script:
-  - echo -e "[server]\ninnodb_log_file_size=256MB\ninnodb_buffer_pool_size=512MB\nmax_allowed_packet=16MB" | sudo tee -a /etc/mysql/my.cnf
-  - sudo service mysql restart
-  - .travis/wait_mysql.sh
-  - mysql -e 'create database gotest;'
-
-jobs:
-  allow_failures:
-    - go: master
-
-  include:
-    - env: DB=MYSQL8
-      dist: xenial
-      go: 1.15.x
-      services:
-        - docker
-      before_install:
-        - go get golang.org/x/tools/cmd/cover
-        - go get github.com/mattn/goveralls
-        - docker pull mysql:8.0
-        - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ALLOW_EMPTY_PASSWORD=yes
-          mysql:8.0 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
-        - cp .travis/docker.cnf ~/.my.cnf
-        - .travis/wait_mysql.sh
-      before_script:
-        - export cross_compile=false
-        - export MYSQL_TEST_USER=gotest
-        - export MYSQL_TEST_PASS=secret
-        - export MYSQL_TEST_ADDR=127.0.0.1:3307
-        - export MYSQL_TEST_CONCURRENT=1
-
-    - env: DB=MYSQL57
-      dist: xenial
-      go: 1.15.x
-      services:
-        - docker
-      before_install:
-        - go get golang.org/x/tools/cmd/cover
-        - go get github.com/mattn/goveralls
-        - docker pull mysql:5.7
-        - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ALLOW_EMPTY_PASSWORD=yes
-          mysql:5.7 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
-        - cp .travis/docker.cnf ~/.my.cnf
-        - .travis/wait_mysql.sh
-      before_script:
-        - export cross_compile=false
-        - export MYSQL_TEST_USER=gotest
-        - export MYSQL_TEST_PASS=secret
-        - export MYSQL_TEST_ADDR=127.0.0.1:3307
-        - export MYSQL_TEST_CONCURRENT=1
-
-    - env: DB=MARIA55
-      dist: xenial
-      go: 1.15.x
-      services:
-        - docker
-      before_install:
-        - go get golang.org/x/tools/cmd/cover
-        - go get github.com/mattn/goveralls
-        - docker pull mariadb:5.5
-        - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ALLOW_EMPTY_PASSWORD=yes
-          mariadb:5.5 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
-        - cp .travis/docker.cnf ~/.my.cnf
-        - .travis/wait_mysql.sh
-      before_script:
-        - export cross_compile=false
-        - export MYSQL_TEST_USER=gotest
-        - export MYSQL_TEST_PASS=secret
-        - export MYSQL_TEST_ADDR=127.0.0.1:3307
-        - export MYSQL_TEST_CONCURRENT=1
-
-    - env: DB=MARIA10_1
-      dist: xenial
-      go: 1.15.x
-      services:
-        - docker
-      before_install:
-        - go get golang.org/x/tools/cmd/cover
-        - go get github.com/mattn/goveralls
-        - docker pull mariadb:10.1
-        - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ALLOW_EMPTY_PASSWORD=yes
-          mariadb:10.1 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
-        - cp .travis/docker.cnf ~/.my.cnf
-        - .travis/wait_mysql.sh
-      before_script:
-        - export cross_compile=false
-        - export MYSQL_TEST_USER=gotest
-        - export MYSQL_TEST_PASS=secret
-        - export MYSQL_TEST_ADDR=127.0.0.1:3307
-        - export MYSQL_TEST_CONCURRENT=1
-
-    - os: osx
-      osx_image: xcode11.4
-      addons:
-        homebrew:
-          packages:
-            - mysql
-          update: true
-      go: 1.15.x
-      before_install:
-        - go get golang.org/x/tools/cmd/cover
-        - go get github.com/mattn/goveralls
-      before_script:
-        - export cross_compile=false
-        - echo -e "[server]\ninnodb_log_file_size=256MB\ninnodb_buffer_pool_size=512MB\nmax_allowed_packet=16MB\nlocal_infile=1" >> /usr/local/etc/my.cnf
-        - mysql.server start
-        - mysql -uroot -e 'CREATE USER gotest IDENTIFIED BY "secret"'
-        - mysql -uroot -e 'GRANT ALL ON *.* TO gotest'
-        - mysql -uroot -e 'create database gotest;'
-        - export MYSQL_TEST_USER=gotest
-        - export MYSQL_TEST_PASS=secret
-        - export MYSQL_TEST_ADDR=127.0.0.1:3306
-        - export MYSQL_TEST_CONCURRENT=1
-
-script:
-  - go test -v -covermode=count -coverprofile=coverage.out
-  - go vet ./...
-  - .travis/gofmt.sh
-  - if [ "$cross_compile" != "false" ]; then .travis/compile_check.sh; fi
-after_script:
-  - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci

From 4d5208afde31873cb7e2c02c78cb58dd2b3cb6e0 Mon Sep 17 00:00:00 2001
From: Ichinose Shogo <shogo82148@gmail.com>
Date: Mon, 1 Feb 2021 23:46:06 +0900
Subject: [PATCH 149/282] improve GitHub Actions workflows (#1190)

* improve GitHub Actions workflows

- add windows to build matrix
- use newer versions of MySQL and MariaDB

* remove branch restriction
---
 .github/workflows/linux.yaml |  50 -----------------
 .github/workflows/mac.yaml   |  36 ------------
 .github/workflows/test.yml   | 104 +++++++++++++++++++++++++++++++++++
 .travis/compile_check.sh     |  17 ------
 .travis/docker.cnf           |   5 --
 .travis/gofmt.sh             |   7 ---
 .travis/wait_mysql.sh        |  14 -----
 driver_test.go               |  13 +++++
 8 files changed, 117 insertions(+), 129 deletions(-)
 delete mode 100644 .github/workflows/linux.yaml
 delete mode 100644 .github/workflows/mac.yaml
 create mode 100644 .github/workflows/test.yml
 delete mode 100755 .travis/compile_check.sh
 delete mode 100644 .travis/docker.cnf
 delete mode 100755 .travis/gofmt.sh
 delete mode 100755 .travis/wait_mysql.sh

diff --git a/.github/workflows/linux.yaml b/.github/workflows/linux.yaml
deleted file mode 100644
index c00bd72c4..000000000
--- a/.github/workflows/linux.yaml
+++ /dev/null
@@ -1,50 +0,0 @@
-name: Test on Linux
-on:
-  push:
-  pull_request:
-
-jobs:
-  test:
-    runs-on: ubuntu-20.04
-    strategy:
-      matrix:
-        go: [1.15, 1.14, 1.13, 1.12, 1.11, "1.10"]
-        mysql: ["mysql:8.0"]
-        include:
-          - go: 1.15
-            mysql: "mysql:5.7"
-          - go: 1.15
-            mysql: "mysql:5.5"
-          - go: 1.15
-            mysql: "mariadb:5.5"
-          - go: 1.15
-            mysql: "mariadb:10.1"
-
-    services:
-      mysql:
-        image: ${{ matrix.mysql }}
-        ports:
-          - 3306:3306
-        env:
-          MYSQL_DATABASE: gotest
-          MYSQL_USER: gotest
-          MYSQL_PASSWORD: secret
-          MYSQL_ALLOW_EMPTY_PASSWORD: yes
-        # How can we add these options?
-        #options: --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
-
-    steps:
-      - uses: actions/setup-go@v2
-        with:
-          go-version: ${{ matrix.go }}
-      - uses: actions/checkout@v2
-      - name: Run test
-        env:
-          MYSQL_TEST_USER: gotest
-          MYSQL_TEST_PASS: secret
-          MYSQL_TEST_ADDR: 127.0.0.1:3307
-          MYSQL_TEST_CONCURRENT: 1
-        run: |
-          go test -v -covermode=count -coverprofile=coverage.out
-          go vet ./...
-          gofmt -d -s .
diff --git a/.github/workflows/mac.yaml b/.github/workflows/mac.yaml
deleted file mode 100644
index f80a90b6e..000000000
--- a/.github/workflows/mac.yaml
+++ /dev/null
@@ -1,36 +0,0 @@
-name: Test on macOS
-on:
-  push:
-  pull_request:
-
-jobs:
-  build:
-    runs-on: macos-latest
-
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v2
-
-      - name: Set up MySQL
-        run: |
-          brew install mysql
-          echo -e "[server]\ninnodb_log_file_size=256MB\ninnodb_buffer_pool_size=512MB\nmax_allowed_packet=16MB\nlocal_infile=1" >> /usr/local/etc/my.cnf
-          mysql.server start
-          mysql -uroot -e 'CREATE USER gotest IDENTIFIED BY "secret"'
-          mysql -uroot -e 'GRANT ALL ON *.* TO gotest'
-          mysql -uroot -e 'create database gotest;'
-
-      - name: Set up Go
-        run: |
-          go install golang.org/x/tools/cmd/cover
-
-      - name: Run tests
-        env:
-          MYSQL_TEST_USER: gotest
-          MYSQL_TEST_PASS: secret
-          MYSQL_TEST_ADDR: 127.0.0.1:3306
-          MYSQL_TEST_CONCURRENT: 1
-        run: |
-          go test -v -covermode=count -coverprofile=coverage.out
-          go vet ./...
-          gofmt -d -s .
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 000000000..99cf896a9
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,104 @@
+name: test
+on:
+  pull_request:
+  push:
+  workflow_dispatch:
+
+env:
+  MYSQL_TEST_USER: gotest
+  MYSQL_TEST_PASS: secret
+  MYSQL_TEST_ADDR: 127.0.0.1:3306
+  MYSQL_TEST_CONCURRENT: 1
+
+jobs:
+  list:
+    runs-on: ubuntu-latest
+    outputs:
+      matrix: ${{ steps.set-matrix.outputs.matrix }}
+    steps:
+      - name: list
+        id: set-matrix
+        run: |
+          from __future__ import print_function
+          import json
+          go = [
+              # Keep the most recent production release at the top
+              '1.15',
+              # Older production releases
+              '1.14',
+              '1.13',
+              '1.12',
+              '1.11',
+          ]
+          mysql = [
+              '8.0',
+              '5.7',
+              '5.6',
+              'mariadb-10.5',
+              'mariadb-10.4',
+              'mariadb-10.3',
+          ]
+
+          includes = []
+          # Go versions compatibility check
+          for v in go[1:]:
+                  includes.append({'os': 'ubuntu-latest', 'go': v, 'mysql': mysql[0]})
+
+          matrix = {
+              # OS vs MySQL versions
+              'os': [ 'ubuntu-latest', 'macos-latest', 'windows-latest' ],
+              'go': [ go[0] ],
+              'mysql': mysql,
+
+              'include': includes
+          }
+          output = json.dumps(matrix, separators=(',', ':'))
+          print('::set-output name=matrix::{0}'.format(output))
+        shell: python
+  test:
+    needs: list
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix: ${{ fromJSON(needs.list.outputs.matrix) }}
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions/setup-go@v2
+        with:
+          go-version: ${{ matrix.go }}
+      - uses: shogo82148/actions-setup-mysql@v1
+        with:
+          mysql-version: ${{ matrix.mysql }}
+          user: ${{ env.MYSQL_TEST_USER }}
+          password: ${{ env.MYSQL_TEST_PASS }}
+          my-cnf: |
+            innodb_log_file_size=256MB
+            innodb_buffer_pool_size=512MB
+            max_allowed_packet=16MB
+            ; TestConcurrent fails if max_connections is too large
+            max_connections=50
+            local_infile=1
+      - name: setup database
+        run: |
+          mysql --user 'root' --host '127.0.0.1' -e 'create database gotest;'
+
+      - name: test
+        run: |
+          go test -v '-covermode=count' '-coverprofile=coverage.out'
+
+      - name: Send coverage
+        uses: shogo82148/actions-goveralls@v1
+        with:
+          path-to-profile: coverage.out
+          flag-name: ${{ runner.os }}-Go-${{ matrix.go }}-DB-${{ matrix.mysql }}
+          parallel: true
+
+  # notifies that all test jobs are finished.
+  finish:
+    needs: test
+    if: always()
+    runs-on: ubuntu-latest
+    steps:
+      - uses: shogo82148/actions-goveralls@v1
+        with:
+          parallel-finished: true
diff --git a/.travis/compile_check.sh b/.travis/compile_check.sh
deleted file mode 100755
index 3bb3ed49d..000000000
--- a/.travis/compile_check.sh
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-set -e 
-dist_list=$(go tool dist list)
-
-for dist in ${dist_list}; do
-    GOOS=$(echo ${dist} | cut  -d "/" -f 1)
-    GOARCH=$(echo ${dist} | cut -d "/" -f 2)
-    set +e
-    GOOS=${GOOS} GOARCH=${GOARCH} go tool compile -V > /dev/null 2>&1 
-    if [[ $? -ne 0 ]]; then
-        echo "Compile support for ${GOOS}/${GOARCH} is not provided; skipping"
-        continue
-    fi
-    set -e
-    echo "Building  ${GOOS}/${GOARCH}"
-    GOOS=${GOOS} GOARCH=${GOARCH} go build  -o /dev/null
- done
diff --git a/.travis/docker.cnf b/.travis/docker.cnf
deleted file mode 100644
index e57754e5a..000000000
--- a/.travis/docker.cnf
+++ /dev/null
@@ -1,5 +0,0 @@
-[client]
-user = gotest
-password = secret
-host = 127.0.0.1
-port = 3307
diff --git a/.travis/gofmt.sh b/.travis/gofmt.sh
deleted file mode 100755
index 9bf0d1684..000000000
--- a/.travis/gofmt.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/bash
-set -ev
-
-# Only check for go1.10+ since the gofmt style changed
-if [[ $(go version) =~ go1\.([0-9]+) ]] && ((${BASH_REMATCH[1]} >= 10)); then
-    test -z "$(gofmt -d -s . | tee /dev/stderr)"
-fi
diff --git a/.travis/wait_mysql.sh b/.travis/wait_mysql.sh
deleted file mode 100755
index fa2054ff1..000000000
--- a/.travis/wait_mysql.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/bin/sh
-
-# use the mysql client inside the docker container if docker is running
-[ "$(docker inspect -f '{{.State.Running}}' mysqld 2>/dev/null)" = "true" ] && mysql() {
-    docker exec mysqld mysql "${@}"
-}
-
-while :
-do
-    if mysql --protocol=tcp -e 'select version()'; then
-        break
-    fi
-    sleep 3
-done
diff --git a/driver_test.go b/driver_test.go
index aa55d2f55..54f7cd10c 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -24,6 +24,7 @@ import (
 	"net/url"
 	"os"
 	"reflect"
+	"runtime"
 	"strings"
 	"sync"
 	"sync/atomic"
@@ -1806,6 +1807,14 @@ func TestConcurrent(t *testing.T) {
 	}
 
 	runTests(t, dsn, func(dbt *DBTest) {
+		var version string
+		if err := dbt.db.QueryRow("SELECT @@version").Scan(&version); err != nil {
+			dbt.Fatalf("%s", err.Error())
+		}
+		if strings.Contains(strings.ToLower(version), "mariadb") {
+			t.Skip(`TODO: "fix commands out of sync. Did you run multiple statements at once?" on MariaDB`)
+		}
+
 		var max int
 		err := dbt.db.QueryRow("SELECT @@max_connections").Scan(&max)
 		if err != nil {
@@ -2605,6 +2614,10 @@ func TestContextCancelStmtQuery(t *testing.T) {
 }
 
 func TestContextCancelBegin(t *testing.T) {
+	if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
+		t.Skip(`FIXME: it sometime fails with "expected driver.ErrBadConn, got sql: connection is already closed" on windows and macOS`)
+	}
+
 	runTests(t, dsn, func(dbt *DBTest) {
 		dbt.mustExec("CREATE TABLE test (v INTEGER)")
 		ctx, cancel := context.WithCancel(context.Background())

From fe2230a8b20cee1e48f7b75a9b363def5f950ba0 Mon Sep 17 00:00:00 2001
From: Ichinose Shogo <shogo82148@gmail.com>
Date: Tue, 2 Feb 2021 13:30:19 +0900
Subject: [PATCH 150/282] stop rounding times (#1172)

* stop rounding times

fixes https://github.com/go-sql-driver/mysql/issues/1121

* trim trailing zeros
---
 utils.go      | 43 ++++++++++++++++++++++++++-----------------
 utils_test.go | 37 ++++++++++++++++++-------------------
 2 files changed, 44 insertions(+), 36 deletions(-)

diff --git a/utils.go b/utils.go
index b0c6e9ca3..d6545f5be 100644
--- a/utils.go
+++ b/utils.go
@@ -277,15 +277,9 @@ func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Va
 }
 
 func appendDateTime(buf []byte, t time.Time) ([]byte, error) {
-	nsec := t.Nanosecond()
-	// to round under microsecond
-	if nsec%1000 >= 500 { // save half of time.Time.Add calls
-		t = t.Add(500 * time.Nanosecond)
-		nsec = t.Nanosecond()
-	}
 	year, month, day := t.Date()
 	hour, min, sec := t.Clock()
-	micro := nsec / 1000
+	nsec := t.Nanosecond()
 
 	if year < 1 || year > 9999 {
 		return buf, errors.New("year is not in the range [1, 9999]: " + strconv.Itoa(year)) // use errors.New instead of fmt.Errorf to avoid year escape to heap
@@ -293,14 +287,14 @@ func appendDateTime(buf []byte, t time.Time) ([]byte, error) {
 	year100 := year / 100
 	year1 := year % 100
 
-	var localBuf [26]byte // does not escape
+	var localBuf [len("2006-01-02T15:04:05.999999999")]byte // does not escape
 	localBuf[0], localBuf[1], localBuf[2], localBuf[3] = digits10[year100], digits01[year100], digits10[year1], digits01[year1]
 	localBuf[4] = '-'
 	localBuf[5], localBuf[6] = digits10[month], digits01[month]
 	localBuf[7] = '-'
 	localBuf[8], localBuf[9] = digits10[day], digits01[day]
 
-	if hour == 0 && min == 0 && sec == 0 && micro == 0 {
+	if hour == 0 && min == 0 && sec == 0 && nsec == 0 {
 		return append(buf, localBuf[:10]...), nil
 	}
 
@@ -311,18 +305,33 @@ func appendDateTime(buf []byte, t time.Time) ([]byte, error) {
 	localBuf[16] = ':'
 	localBuf[17], localBuf[18] = digits10[sec], digits01[sec]
 
-	if micro == 0 {
+	if nsec == 0 {
 		return append(buf, localBuf[:19]...), nil
 	}
-
-	micro10000 := micro / 10000
-	micro100 := (micro / 100) % 100
-	micro1 := micro % 100
+	nsec100000000 := nsec / 100000000
+	nsec1000000 := (nsec / 1000000) % 100
+	nsec10000 := (nsec / 10000) % 100
+	nsec100 := (nsec / 100) % 100
+	nsec1 := nsec % 100
 	localBuf[19] = '.'
-	localBuf[20], localBuf[21], localBuf[22], localBuf[23], localBuf[24], localBuf[25] =
-		digits10[micro10000], digits01[micro10000], digits10[micro100], digits01[micro100], digits10[micro1], digits01[micro1]
 
-	return append(buf, localBuf[:]...), nil
+	// milli second
+	localBuf[20], localBuf[21], localBuf[22] =
+		digits01[nsec100000000], digits10[nsec1000000], digits01[nsec1000000]
+	// micro second
+	localBuf[23], localBuf[24], localBuf[25] =
+		digits10[nsec10000], digits01[nsec10000], digits10[nsec100]
+	// nano second
+	localBuf[26], localBuf[27], localBuf[28] =
+		digits01[nsec100], digits10[nsec1], digits01[nsec1]
+
+	// trim trailing zeros
+	n := len(localBuf)
+	for n > 0 && localBuf[n-1] == '0' {
+		n--
+	}
+
+	return append(buf, localBuf[:n]...), nil
 }
 
 // zeroDateTime is used in formatBinaryDateTime to avoid an allocation
diff --git a/utils_test.go b/utils_test.go
index e3619e7a7..67b132d2b 100644
--- a/utils_test.go
+++ b/utils_test.go
@@ -299,40 +299,40 @@ func TestAppendDateTime(t *testing.T) {
 		str string
 	}{
 		{
-			t:   time.Date(2020, 05, 30, 0, 0, 0, 0, time.UTC),
-			str: "2020-05-30",
+			t:   time.Date(1234, 5, 6, 0, 0, 0, 0, time.UTC),
+			str: "1234-05-06",
 		},
 		{
-			t:   time.Date(2020, 05, 30, 22, 0, 0, 0, time.UTC),
-			str: "2020-05-30 22:00:00",
+			t:   time.Date(4567, 12, 31, 12, 0, 0, 0, time.UTC),
+			str: "4567-12-31 12:00:00",
 		},
 		{
-			t:   time.Date(2020, 05, 30, 22, 33, 0, 0, time.UTC),
-			str: "2020-05-30 22:33:00",
+			t:   time.Date(2020, 5, 30, 12, 34, 0, 0, time.UTC),
+			str: "2020-05-30 12:34:00",
 		},
 		{
-			t:   time.Date(2020, 05, 30, 22, 33, 44, 0, time.UTC),
-			str: "2020-05-30 22:33:44",
+			t:   time.Date(2020, 5, 30, 12, 34, 56, 0, time.UTC),
+			str: "2020-05-30 12:34:56",
 		},
 		{
-			t:   time.Date(2020, 05, 30, 22, 33, 44, 550000000, time.UTC),
-			str: "2020-05-30 22:33:44.550000",
+			t:   time.Date(2020, 5, 30, 22, 33, 44, 123000000, time.UTC),
+			str: "2020-05-30 22:33:44.123",
 		},
 		{
-			t:   time.Date(2020, 05, 30, 22, 33, 44, 550000499, time.UTC),
-			str: "2020-05-30 22:33:44.550000",
+			t:   time.Date(2020, 5, 30, 22, 33, 44, 123456000, time.UTC),
+			str: "2020-05-30 22:33:44.123456",
 		},
 		{
-			t:   time.Date(2020, 05, 30, 22, 33, 44, 550000500, time.UTC),
-			str: "2020-05-30 22:33:44.550001",
+			t:   time.Date(2020, 5, 30, 22, 33, 44, 123456789, time.UTC),
+			str: "2020-05-30 22:33:44.123456789",
 		},
 		{
-			t:   time.Date(2020, 05, 30, 22, 33, 44, 550000567, time.UTC),
-			str: "2020-05-30 22:33:44.550001",
+			t:   time.Date(9999, 12, 31, 23, 59, 59, 999999999, time.UTC),
+			str: "9999-12-31 23:59:59.999999999",
 		},
 		{
-			t:   time.Date(2020, 05, 30, 22, 33, 44, 999999567, time.UTC),
-			str: "2020-05-30 22:33:45",
+			t:   time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC),
+			str: "0001-01-01",
 		},
 	}
 	for _, v := range tests {
@@ -340,7 +340,6 @@ func TestAppendDateTime(t *testing.T) {
 		buf, _ = appendDateTime(buf, v.t)
 		if str := string(buf); str != v.str {
 			t.Errorf("appendDateTime(%v), have: %s, want: %s", v.t, str, v.str)
-			return
 		}
 	}
 

From 7b629a987258dcaba84d662edb5f408d11878fef Mon Sep 17 00:00:00 2001
From: MrBTTF <zhvladi@gmail.com>
Date: Mon, 1 Mar 2021 14:19:29 +0100
Subject: [PATCH 151/282] handling decoding pem error (#1192)

---
 AUTHORS | 1 +
 auth.go | 6 +++++-
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/AUTHORS b/AUTHORS
index 3460c2e06..50afa2c85 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -92,6 +92,7 @@ Thomas Wodarek <wodarekwebpage at gmail.com>
 Tim Ruffles <timruffles at gmail.com>
 Tom Jenkinson <tom at tjenkinson.me>
 Vladimir Kovpak <cn007b at gmail.com>
+Vladyslav Zhelezniak <zhvladi at gmail.com>
 Xiangyu Hu <xiangyu.hu at outlook.com>
 Xiaobing Jiang <s7v7nislands at gmail.com>
 Xiuming Chen <cc at cxm.cc>
diff --git a/auth.go b/auth.go
index 1f9ceb059..b2f19e8f0 100644
--- a/auth.go
+++ b/auth.go
@@ -15,6 +15,7 @@ import (
 	"crypto/sha256"
 	"crypto/x509"
 	"encoding/pem"
+	"fmt"
 	"sync"
 )
 
@@ -371,7 +372,10 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
 							return err
 						}
 
-						block, _ := pem.Decode(data[1:])
+						block, rest := pem.Decode(data[1:])
+						if block == nil {
+							return fmt.Errorf("No Pem data found, data: %s", rest)
+						}
 						pkix, err := x509.ParsePKIXPublicKey(block.Bytes)
 						if err != nil {
 							return err

From 33267428f77eb99023f936a2b9b330416053d67a Mon Sep 17 00:00:00 2001
From: Bui Quoc Trong <trongbq@users.noreply.github.com>
Date: Tue, 2 Mar 2021 17:56:02 +0700
Subject: [PATCH 152/282] Implement driver.Validator interface (#1174)

---
 connection.go | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/connection.go b/connection.go
index 90aec6439..835f89729 100644
--- a/connection.go
+++ b/connection.go
@@ -642,3 +642,9 @@ func (mc *mysqlConn) ResetSession(ctx context.Context) error {
 	mc.reset = true
 	return nil
 }
+
+// IsValid implements driver.Validator interface
+// (From Go 1.15)
+func (mc *mysqlConn) IsValid() bool {
+	return !mc.closed.IsSet()
+}

From e246959aa61ad88a4d5d459aae5e73d04faf8f85 Mon Sep 17 00:00:00 2001
From: Ichinose Shogo <shogo82148@gmail.com>
Date: Tue, 30 Mar 2021 08:00:02 +0900
Subject: [PATCH 153/282] add Go 1.16 to the build matrix (#1198)

---
 .github/workflows/test.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 99cf896a9..886002143 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -19,12 +19,12 @@ jobs:
       - name: list
         id: set-matrix
         run: |
-          from __future__ import print_function
           import json
           go = [
               # Keep the most recent production release at the top
-              '1.15',
+              '1.16',
               # Older production releases
+              '1.15',
               '1.14',
               '1.13',
               '1.12',

From bcc459a906419e2890a50fc2c99ea6dd927a88f2 Mon Sep 17 00:00:00 2001
From: Ichinose Shogo <shogo82148@gmail.com>
Date: Thu, 1 Apr 2021 10:56:49 +0900
Subject: [PATCH 154/282] Release v1.6.0 (#1197)

* Release v1.6.0

* v1.6.0 is released on 2021-04-01
---
 CHANGELOG.md | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9cb97b38d..72a738ed5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,29 @@
+## Version 1.6 (2021-04-01)
+
+Changes:
+
+  - Migrate the CI service from travis-ci to GitHub Actions (#1176, #1183, #1190)
+  - `NullTime` is deprecated (#960, #1144)
+  - Reduce allocations when building SET command (#1111)
+  - Performance improvement for time formatting (#1118)
+  - Performance improvement for time parsing (#1098, #1113)
+
+New Features:
+
+  - Implement `driver.Validator` interface (#1106, #1174)
+  - Support returning `uint64` from `Valuer` in `ConvertValue` (#1143)
+  - Add `json.RawMessage` for converter and prepared statement (#1059)
+  - Interpolate `json.RawMessage` as `string` (#1058)
+  - Implements `CheckNamedValue` (#1090)
+
+Bugfixes:
+
+  - Stop rounding times (#1121, #1172)
+  - Put zero filler into the SSL handshake packet (#1066)
+  - Fix checking cancelled connections back into the connection pool (#1095)
+  - Fix remove last 0 byte for mysql_old_password when password is empty (#1133)
+
+
 ## Version 1.5 (2020-01-07)
 
 Changes:

From b36cd86ebcb680d317f962679780f5877a0b91e1 Mon Sep 17 00:00:00 2001
From: Ichinose Shogo <shogo82148@gmail.com>
Date: Tue, 18 May 2021 10:33:08 +0900
Subject: [PATCH 155/282] Drop support of Go 1.12 (#1211)

* Drop support of Go 1.12

* bump Go version in go.mod

* remove nulltime_legacy
---
 .github/workflows/test.yml |  2 --
 README.md                  |  2 +-
 driver_test.go             | 14 ++++++-------
 fields.go                  |  2 +-
 go.mod                     |  2 +-
 nulltime.go                | 21 ++++++++++++++++++++
 nulltime_go113.go          | 40 --------------------------------------
 nulltime_legacy.go         | 39 -------------------------------------
 8 files changed, 31 insertions(+), 91 deletions(-)
 delete mode 100644 nulltime_go113.go
 delete mode 100644 nulltime_legacy.go

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 886002143..fce4cf670 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -27,8 +27,6 @@ jobs:
               '1.15',
               '1.14',
               '1.13',
-              '1.12',
-              '1.11',
           ]
           mysql = [
               '8.0',
diff --git a/README.md b/README.md
index 0b13154fc..9c8284cd1 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
   * Optional placeholder interpolation
 
 ## Requirements
-  * Go 1.10 or higher. We aim to support the 3 latest versions of Go.
+  * Go 1.13 or higher. We aim to support the 3 latest versions of Go.
   * MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
 
 ---------------------------------------
diff --git a/driver_test.go b/driver_test.go
index 54f7cd10c..3ae379b26 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -2771,13 +2771,13 @@ 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, 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}
-	nd1 := nullTime{Time: time.Date(2006, 01, 02, 0, 0, 0, 0, time.UTC), Valid: true}
-	nd2 := nullTime{Time: time.Date(2006, 03, 04, 0, 0, 0, 0, time.UTC), Valid: true}
-	ndNULL := nullTime{Time: time.Time{}, Valid: false}
+	nt0 := sql.NullTime{Time: time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC), Valid: true}
+	nt1 := sql.NullTime{Time: time.Date(2006, 01, 02, 15, 04, 05, 100000000, time.UTC), Valid: true}
+	nt2 := sql.NullTime{Time: time.Date(2006, 01, 02, 15, 04, 05, 110000000, time.UTC), Valid: true}
+	nt6 := sql.NullTime{Time: time.Date(2006, 01, 02, 15, 04, 05, 111111000, time.UTC), Valid: true}
+	nd1 := sql.NullTime{Time: time.Date(2006, 01, 02, 0, 0, 0, 0, time.UTC), Valid: true}
+	nd2 := sql.NullTime{Time: time.Date(2006, 03, 04, 0, 0, 0, 0, time.UTC), Valid: true}
+	ndNULL := sql.NullTime{Time: time.Time{}, Valid: false}
 	rbNULL := sql.RawBytes(nil)
 	rb0 := sql.RawBytes("0")
 	rb42 := sql.RawBytes("42")
diff --git a/fields.go b/fields.go
index ed6c7a37d..d82154ce8 100644
--- a/fields.go
+++ b/fields.go
@@ -106,7 +106,7 @@ var (
 	scanTypeInt64     = reflect.TypeOf(int64(0))
 	scanTypeNullFloat = reflect.TypeOf(sql.NullFloat64{})
 	scanTypeNullInt   = reflect.TypeOf(sql.NullInt64{})
-	scanTypeNullTime  = reflect.TypeOf(nullTime{})
+	scanTypeNullTime  = reflect.TypeOf(sql.NullTime{})
 	scanTypeUint8     = reflect.TypeOf(uint8(0))
 	scanTypeUint16    = reflect.TypeOf(uint16(0))
 	scanTypeUint32    = reflect.TypeOf(uint32(0))
diff --git a/go.mod b/go.mod
index fffbf6a90..251110478 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,3 @@
 module github.com/go-sql-driver/mysql
 
-go 1.10
+go 1.13
diff --git a/nulltime.go b/nulltime.go
index 651723a96..17af92ddc 100644
--- a/nulltime.go
+++ b/nulltime.go
@@ -9,11 +9,32 @@
 package mysql
 
 import (
+	"database/sql"
 	"database/sql/driver"
 	"fmt"
 	"time"
 )
 
+// NullTime represents a time.Time that may be NULL.
+// NullTime implements the Scanner interface so
+// it can be used as a scan destination:
+//
+//  var nt NullTime
+//  err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
+//  ...
+//  if nt.Valid {
+//     // use nt.Time
+//  } else {
+//     // NULL value
+//  }
+//
+// This NullTime implementation is not driver-specific
+//
+// Deprecated: NullTime doesn't honor the loc DSN parameter.
+// NullTime.Scan interprets a time as UTC, not the loc DSN parameter.
+// Use sql.NullTime instead.
+type NullTime sql.NullTime
+
 // Scan implements the Scanner interface.
 // The value type must be time.Time or string / []byte (formatted time-string),
 // otherwise Scan fails.
diff --git a/nulltime_go113.go b/nulltime_go113.go
deleted file mode 100644
index 453b4b394..000000000
--- a/nulltime_go113.go
+++ /dev/null
@@ -1,40 +0,0 @@
-// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
-//
-// Copyright 2013 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/.
-
-// +build go1.13
-
-package mysql
-
-import (
-	"database/sql"
-)
-
-// NullTime represents a time.Time that may be NULL.
-// NullTime implements the Scanner interface so
-// it can be used as a scan destination:
-//
-//  var nt NullTime
-//  err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
-//  ...
-//  if nt.Valid {
-//     // use nt.Time
-//  } else {
-//     // NULL value
-//  }
-//
-// This NullTime implementation is not driver-specific
-//
-// Deprecated: NullTime doesn't honor the loc DSN parameter.
-// NullTime.Scan interprets a time as UTC, not the loc DSN parameter.
-// Use sql.NullTime instead.
-type NullTime sql.NullTime
-
-// for internal use.
-// the mysql package uses sql.NullTime if it is available.
-// if not, the package uses mysql.NullTime.
-type nullTime = sql.NullTime // sql.NullTime is available
diff --git a/nulltime_legacy.go b/nulltime_legacy.go
deleted file mode 100644
index 9f7ae27a8..000000000
--- a/nulltime_legacy.go
+++ /dev/null
@@ -1,39 +0,0 @@
-// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
-//
-// Copyright 2013 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/.
-
-// +build !go1.13
-
-package mysql
-
-import (
-	"time"
-)
-
-// NullTime represents a time.Time that may be NULL.
-// NullTime implements the Scanner interface so
-// it can be used as a scan destination:
-//
-//  var nt NullTime
-//  err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
-//  ...
-//  if nt.Valid {
-//     // use nt.Time
-//  } else {
-//     // NULL value
-//  }
-//
-// This NullTime implementation is not driver-specific
-type NullTime struct {
-	Time  time.Time
-	Valid bool // Valid is true if Time is not NULL
-}
-
-// for internal use.
-// the mysql package uses sql.NullTime if it is available.
-// if not, the package uses mysql.NullTime.
-type nullTime = NullTime // sql.NullTime is not available

From 9942e21775f58b4a6339c3a814f7c1865d2007d9 Mon Sep 17 00:00:00 2001
From: lowang-bh <lhui_wang@163.com>
Date: Wed, 26 May 2021 13:13:55 +0800
Subject: [PATCH 156/282] Fix readme: MaxIdle is same or less than MaxOpen
 (#1215)

Co-authored-by: wanglonghui7 <wanglonghui7@jd.com>
---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 9c8284cd1..eb9614cbd 100644
--- a/README.md
+++ b/README.md
@@ -85,7 +85,7 @@ db.SetMaxIdleConns(10)
 
 `db.SetMaxOpenConns()` is highly recommended to limit the number of connection used by the application. There is no recommended limit number because it depends on application and MySQL server.
 
-`db.SetMaxIdleConns()` is recommended to be set same to (or greater than) `db.SetMaxOpenConns()`. When it is smaller than `SetMaxOpenConns()`, connections can be opened and closed very frequently than you expect. Idle connections can be closed by the `db.SetConnMaxLifetime()`. If you want to close idle connections more rapidly, you can use `db.SetConnMaxIdleTime()` since Go 1.15.
+`db.SetMaxIdleConns()` is recommended to be set same to `db.SetMaxOpenConns()`. When it is smaller than `SetMaxOpenConns()`, connections can be opened and closed very frequently than you expect. Idle connections can be closed by the `db.SetConnMaxLifetime()`. If you want to close idle connections more rapidly, you can use `db.SetConnMaxIdleTime()` since Go 1.15.
 
 
 ### DSN (Data Source Name)

From 4b653727060f5799e4e4bd96ad693046885b375e Mon Sep 17 00:00:00 2001
From: Ichinose Shogo <shogo82148@gmail.com>
Date: Wed, 26 May 2021 20:46:06 +0900
Subject: [PATCH 157/282] noCopy implements sync.Locker (#1216)

noCopy is used by -copylocks checker from `go vet`.
see https://github.com/golang/go/issues/8005#issuecomment-190753527 for details.
but it doesn't work from Go 1.11, because of https://github.com/golang/go/commit/c2eba53e7f80df21d51285879d51ab81bcfbf6bc and https://github.com/golang/go/issues/26165
-copylock now works with structs that implement sync.Locker.
So, noCopy should implement sync.Locker.
---
 utils.go      | 6 ++++++
 utils_test.go | 4 +++-
 2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/utils.go b/utils.go
index d6545f5be..b400cf99f 100644
--- a/utils.go
+++ b/utils.go
@@ -790,6 +790,12 @@ type noCopy struct{}
 // Lock is a no-op used by -copylocks checker from `go vet`.
 func (*noCopy) Lock() {}
 
+// Unlock is a no-op used by -copylocks checker from `go vet`.
+// noCopy should implement sync.Locker from Go 1.11
+// https://github.com/golang/go/commit/c2eba53e7f80df21d51285879d51ab81bcfbf6bc
+// https://github.com/golang/go/issues/26165
+func (*noCopy) Unlock() {}
+
 // atomicBool is a wrapper around uint32 for usage as a boolean value with
 // atomic access.
 type atomicBool struct {
diff --git a/utils_test.go b/utils_test.go
index 67b132d2b..b0069251e 100644
--- a/utils_test.go
+++ b/utils_test.go
@@ -228,7 +228,9 @@ func TestAtomicBool(t *testing.T) {
 		t.Fatal("Expected value to be false")
 	}
 
-	ab._noCopy.Lock() // we've "tested" it ¯\_(ツ)_/¯
+	// we've "tested" them ¯\_(ツ)_/¯
+	ab._noCopy.Lock()
+	defer ab._noCopy.Unlock()
 }
 
 func TestAtomicError(t *testing.T) {

From e74ba5c13fd26850019c51afb1c4acf71f688aec Mon Sep 17 00:00:00 2001
From: Janek Vedock <83283832+jvedock@users.noreply.github.com>
Date: Thu, 3 Jun 2021 04:45:34 -0400
Subject: [PATCH 158/282] Wording correction in README (#1218)

* fixed grammatical error

* Update AUTHORS
---
 AUTHORS   | 1 +
 README.md | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/AUTHORS b/AUTHORS
index 50afa2c85..900dfec06 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -45,6 +45,7 @@ Ilia Cimpoes <ichimpoesh at gmail.com>
 INADA Naoki <songofacandy at gmail.com>
 Jacek Szwec <szwec.jacek at gmail.com>
 James Harr <james.harr at gmail.com>
+Janek Vedock <janekvedock at comcast.net>
 Jeff Hodges <jeff at somethingsimilar.com>
 Jeffrey Charles <jeffreycharles at gmail.com>
 Jerome Meyer <jxmeyer at gmail.com>
diff --git a/README.md b/README.md
index eb9614cbd..f056e614b 100644
--- a/README.md
+++ b/README.md
@@ -85,7 +85,7 @@ db.SetMaxIdleConns(10)
 
 `db.SetMaxOpenConns()` is highly recommended to limit the number of connection used by the application. There is no recommended limit number because it depends on application and MySQL server.
 
-`db.SetMaxIdleConns()` is recommended to be set same to `db.SetMaxOpenConns()`. When it is smaller than `SetMaxOpenConns()`, connections can be opened and closed very frequently than you expect. Idle connections can be closed by the `db.SetConnMaxLifetime()`. If you want to close idle connections more rapidly, you can use `db.SetConnMaxIdleTime()` since Go 1.15.
+`db.SetMaxIdleConns()` is recommended to be set same to `db.SetMaxOpenConns()`. When it is smaller than `SetMaxOpenConns()`, connections can be opened and closed much more frequently than you expect. Idle connections can be closed by the `db.SetConnMaxLifetime()`. If you want to close idle connections more rapidly, you can use `db.SetConnMaxIdleTime()` since Go 1.15.
 
 
 ### DSN (Data Source Name)

From 417641ad42910e50863a0caf4740ce319262f2f9 Mon Sep 17 00:00:00 2001
From: Chris Kirkland <chriskirkland@github.com>
Date: Thu, 3 Jun 2021 18:34:57 -0500
Subject: [PATCH 159/282] support Is comparison on MySQLError (#1210)

* support Is comparison on MySQLError

* add myself to authors

* skip error tests for go 1.12

* remove test build tag
---
 AUTHORS        |  1 +
 errors.go      |  7 +++++++
 errors_test.go | 19 +++++++++++++++++++
 3 files changed, 27 insertions(+)

diff --git a/AUTHORS b/AUTHORS
index 900dfec06..e3370e025 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -23,6 +23,7 @@ Asta Xie <xiemengjun at gmail.com>
 Bulat Gaifullin <gaifullinbf at gmail.com>
 Caine Jette <jette at alum.mit.edu>
 Carlos Nieto <jose.carlos at menteslibres.net>
+Chris Kirkland <chriskirkland at github.com>
 Chris Moos <chris at tech9computers.com>
 Craig Wilson <craiggwilson at gmail.com>
 Daniel Montoya <dsmontoyam at gmail.com>
diff --git a/errors.go b/errors.go
index 760782ff2..92cc9a361 100644
--- a/errors.go
+++ b/errors.go
@@ -63,3 +63,10 @@ type MySQLError struct {
 func (me *MySQLError) Error() string {
 	return fmt.Sprintf("Error %d: %s", me.Number, me.Message)
 }
+
+func (me *MySQLError) Is(err error) bool {
+	if merr, ok := err.(*MySQLError); ok {
+		return merr.Number == me.Number
+	}
+	return false
+}
diff --git a/errors_test.go b/errors_test.go
index 96f9126d6..3a1aef74d 100644
--- a/errors_test.go
+++ b/errors_test.go
@@ -10,6 +10,7 @@ package mysql
 
 import (
 	"bytes"
+	"errors"
 	"log"
 	"testing"
 )
@@ -40,3 +41,21 @@ func TestErrorsStrictIgnoreNotes(t *testing.T) {
 		dbt.mustExec("DROP TABLE IF EXISTS does_not_exist")
 	})
 }
+
+func TestMySQLErrIs(t *testing.T) {
+	infraErr := &MySQLError{1234, "the server is on fire"}
+	otherInfraErr := &MySQLError{1234, "the datacenter is flooded"}
+	if !errors.Is(infraErr, otherInfraErr) {
+		t.Errorf("expected errors to be the same: %+v %+v", infraErr, otherInfraErr)
+	}
+
+	differentCodeErr := &MySQLError{5678, "the server is on fire"}
+	if errors.Is(infraErr, differentCodeErr) {
+		t.Fatalf("expected errors to be different: %+v %+v", infraErr, differentCodeErr)
+	}
+
+	nonMysqlErr := errors.New("not a mysql error")
+	if errors.Is(infraErr, nonMysqlErr) {
+		t.Fatalf("expected errors to be different: %+v %+v", infraErr, nonMysqlErr)
+	}
+}

From 21f789cd2353b7ac81538f41426e9cfd2b1fcc87 Mon Sep 17 00:00:00 2001
From: ziheng <zihenglv@gmail.com>
Date: Wed, 30 Jun 2021 08:27:49 +0800
Subject: [PATCH 160/282] improve readability follows go-staticcheck (#1227)

sign in AUTHORS
---
 AUTHORS           | 1 +
 dsn.go            | 1 -
 statement_test.go | 2 +-
 utils.go          | 2 +-
 4 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index e3370e025..fee2d5ccf 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -101,6 +101,7 @@ Xiuming Chen <cc at cxm.cc>
 Xuehong Chan <chanxuehong at gmail.com>
 Zhenye Xie <xiezhenye at gmail.com>
 Zhixin Wen <john.wenzhixin at gmail.com>
+Ziheng Lyu <zihenglv at gmail.com>
 
 # Organizations
 
diff --git a/dsn.go b/dsn.go
index 93f3548cb..a306d66a3 100644
--- a/dsn.go
+++ b/dsn.go
@@ -426,7 +426,6 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 		// Collation
 		case "collation":
 			cfg.Collation = value
-			break
 
 		case "columnsWithAlias":
 			var isBool bool
diff --git a/statement_test.go b/statement_test.go
index ac6b92de9..2563ece55 100644
--- a/statement_test.go
+++ b/statement_test.go
@@ -36,7 +36,7 @@ func TestConvertDerivedByteSlice(t *testing.T) {
 		t.Fatal("Byte slice not convertible", err)
 	}
 
-	if bytes.Compare(output.([]byte), []byte("value")) != 0 {
+	if !bytes.Equal(output.([]byte), []byte("value")) {
 		t.Fatalf("Byte slice not converted, got %#v %T", output, output)
 	}
 }
diff --git a/utils.go b/utils.go
index b400cf99f..bcdee1b46 100644
--- a/utils.go
+++ b/utils.go
@@ -199,7 +199,7 @@ func parseByteYear(b []byte) (int, error) {
 			return 0, err
 		}
 		year += v * n
-		n = n / 10
+		n /= 10
 	}
 	return year, nil
 }

From a34e090a4648ec0ec682e87966cdcd4e43006a79 Mon Sep 17 00:00:00 2001
From: Ichinose Shogo <shogo82148@gmail.com>
Date: Mon, 12 Jul 2021 13:36:52 +0900
Subject: [PATCH 161/282] use utf8mb4 instead of utf8 in TestCharset (#1228)

From MySQL 8.0.24, `SELECT @@character_set_connection` reports utf8mb3 or utf8mb4 instead of utf8.
Because utf8 is currently an alias for utf8mb3, however at some point utf8 is expected to become a reference to utf8mb4.

> ref. https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-24.html#mysqld-8-0-24-bug
> Important Note: When a utf8mb3 collation was specified in a CREATE TABLE statement, SHOW CREATE TABLE, DEFAULT CHARSET,
> the values of system variables containing character set names,
> and the binary log all subsequently displayed the character set as utf8 which is becoming a synonym for utf8mb4.
> Now in such cases, utf8mb3 is shown instead, and CREATE TABLE raises the warning 'collation_name' is a collation of the deprecated character set UTF8MB3.
> Please consider using UTF8MB4 with an appropriate collation instead. (Bug #27225287, Bug #32085357, Bug #32122844)
>
> References: See also: Bug #30624990.

The document says that we should use utf8mb4 instead of utf8, so we should follow it.
---
 driver_test.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/driver_test.go b/driver_test.go
index 3ae379b26..f1e4ad71e 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -1450,11 +1450,11 @@ func TestCharset(t *testing.T) {
 	mustSetCharset("charset=ascii", "ascii")
 
 	// when the first charset is invalid, use the second
-	mustSetCharset("charset=none,utf8", "utf8")
+	mustSetCharset("charset=none,utf8mb4", "utf8mb4")
 
 	// when the first charset is valid, use it
-	mustSetCharset("charset=ascii,utf8", "ascii")
-	mustSetCharset("charset=utf8,ascii", "utf8")
+	mustSetCharset("charset=ascii,utf8mb4", "ascii")
+	mustSetCharset("charset=utf8mb4,ascii", "utf8mb4")
 }
 
 func TestFailingCharset(t *testing.T) {

From 75d09acc46ea1a7074058d31da50293052248047 Mon Sep 17 00:00:00 2001
From: ziheng <zihenglv@gmail.com>
Date: Mon, 12 Jul 2021 17:58:03 +0800
Subject: [PATCH 162/282] refactoring (*textRows).readRow in a more clear way
 (#1230)

---
 packets.go | 52 ++++++++++++++++++++++++++--------------------------
 1 file changed, 26 insertions(+), 26 deletions(-)

diff --git a/packets.go b/packets.go
index 6664e5ae5..1867ecab2 100644
--- a/packets.go
+++ b/packets.go
@@ -761,40 +761,40 @@ func (rows *textRows) readRow(dest []driver.Value) error {
 	}
 
 	// RowSet Packet
-	var n int
-	var isNull bool
-	pos := 0
+	var (
+		n      int
+		isNull bool
+		pos    int = 0
+	)
 
 	for i := range dest {
 		// Read bytes and convert to string
 		dest[i], isNull, n, err = readLengthEncodedString(data[pos:])
 		pos += n
-		if err == nil {
-			if !isNull {
-				if !mc.parseTime {
-					continue
-				} else {
-					switch rows.rs.columns[i].fieldType {
-					case fieldTypeTimestamp, fieldTypeDateTime,
-						fieldTypeDate, fieldTypeNewDate:
-						dest[i], err = parseDateTime(
-							dest[i].([]byte),
-							mc.cfg.Loc,
-						)
-						if err == nil {
-							continue
-						}
-					default:
-						continue
-					}
-				}
 
-			} else {
-				dest[i] = nil
-				continue
+		if err != nil {
+			return err
+		}
+
+		if isNull {
+			dest[i] = nil
+			continue
+		}
+
+		if !mc.parseTime {
+			continue
+		}
+
+		// Parse time field
+		switch rows.rs.columns[i].fieldType {
+		case fieldTypeTimestamp,
+			fieldTypeDateTime,
+			fieldTypeDate,
+			fieldTypeNewDate:
+			if dest[i], err = parseDateTime(dest[i].([]byte), mc.cfg.Loc); err != nil {
+				return err
 			}
 		}
-		return err // err != nil
 	}
 
 	return nil

From 6a88ab97c64c79be016f34d11e3295b8d291d50b Mon Sep 17 00:00:00 2001
From: ziheng <zihenglv@gmail.com>
Date: Mon, 19 Jul 2021 07:00:59 +0800
Subject: [PATCH 163/282] add an invalid DSN test case (#1235)

---
 dsn_test.go | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/dsn_test.go b/dsn_test.go
index 89815b341..fc6eea9c8 100644
--- a/dsn_test.go
+++ b/dsn_test.go
@@ -92,13 +92,14 @@ func TestDSNParser(t *testing.T) {
 
 func TestDSNParserInvalid(t *testing.T) {
 	var invalidDSNs = []string{
-		"@net(addr/",                  // no closing brace
-		"@tcp(/",                      // no closing brace
-		"tcp(/",                       // no closing brace
-		"(/",                          // no closing brace
-		"net(addr)//",                 // unescaped
-		"User:pass@tcp(1.2.3.4:3306)", // no trailing slash
-		"net()/",                      // unknown default addr
+		"@net(addr/",                            // no closing brace
+		"@tcp(/",                                // no closing brace
+		"tcp(/",                                 // no closing brace
+		"(/",                                    // no closing brace
+		"net(addr)//",                           // unescaped
+		"User:pass@tcp(1.2.3.4:3306)",           // no trailing slash
+		"net()/",                                // unknown default addr
+		"user:pass@tcp(127.0.0.1:3306)/db/name", // invalid dbname
 		//"/dbname?arg=/some/unescaped/path",
 	}
 

From e8f8fcdbd3e06fc74b6cf745c13eaebedff68c49 Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Mon, 19 Jul 2021 07:01:45 +0800
Subject: [PATCH 164/282] return unsigned in database type name when necessary
 (#1238)

* return unsigned in database type name when necessary

* Fix test

* Update README

* Add myself in AUTHORS
---
 AUTHORS        |  1 +
 README.md      |  2 +-
 driver_test.go |  8 ++++----
 fields.go      | 12 ++++++++++++
 4 files changed, 18 insertions(+), 5 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index fee2d5ccf..80199cf2c 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -67,6 +67,7 @@ Linh Tran Tuan <linhduonggnu at gmail.com>
 Lion Yang <lion at aosc.xyz>
 Luca Looz <luca.looz92 at gmail.com>
 Lucas Liu <extrafliu at gmail.com>
+Lunny Xiao <xiaolunwen at gmail.com>
 Luke Scott <luke at webconnex.com>
 Maciej Zimnoch <maciej.zimnoch at codilime.com>
 Michael Woolnough <michael.woolnough at gmail.com>
diff --git a/README.md b/README.md
index f056e614b..ded6e3b16 100644
--- a/README.md
+++ b/README.md
@@ -454,7 +454,7 @@ user:password@/
 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.
+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. All Unsigned database type names will be returned `UNSIGNED ` with `INT`, `TINYINT`, `SMALLINT`, `BIGINT`.
 
 ## `context.Context` Support
 Go 1.8 added `database/sql` support for `context.Context`. This driver supports query timeouts and cancellation via contexts.
diff --git a/driver_test.go b/driver_test.go
index f1e4ad71e..7b4ab84f3 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -2808,10 +2808,10 @@ func TestRowsColumnTypes(t *testing.T) {
 		{"mediumintnull", "MEDIUMINT", "MEDIUMINT", scanTypeNullInt, true, 0, 0, [3]string{"0", "42", "NULL"}, [3]interface{}{ni0, ni42, niNULL}},
 		{"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)}},
+		{"tinyuint", "TINYINT UNSIGNED NOT NULL", "UNSIGNED TINYINT", scanTypeUint8, false, 0, 0, [3]string{"0", "255", "42"}, [3]interface{}{uint8(0), uint8(255), uint8(42)}},
+		{"smalluint", "SMALLINT UNSIGNED NOT NULL", "UNSIGNED SMALLINT", scanTypeUint16, false, 0, 0, [3]string{"0", "65535", "42"}, [3]interface{}{uint16(0), uint16(65535), uint16(42)}},
+		{"biguint", "BIGINT UNSIGNED NOT NULL", "UNSIGNED BIGINT", scanTypeUint64, false, 0, 0, [3]string{"0", "65535", "42"}, [3]interface{}{uint64(0), uint64(65535), uint64(42)}},
+		{"uint13", "INT(13) UNSIGNED NOT NULL", "UNSIGNED 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}},
diff --git a/fields.go b/fields.go
index d82154ce8..e0654a83d 100644
--- a/fields.go
+++ b/fields.go
@@ -41,6 +41,9 @@ func (mf *mysqlField) typeDatabaseName() string {
 	case fieldTypeJSON:
 		return "JSON"
 	case fieldTypeLong:
+		if mf.flags&flagUnsigned != 0 {
+			return "UNSIGNED INT"
+		}
 		return "INT"
 	case fieldTypeLongBLOB:
 		if mf.charSet != collations[binaryCollation] {
@@ -48,6 +51,9 @@ func (mf *mysqlField) typeDatabaseName() string {
 		}
 		return "LONGBLOB"
 	case fieldTypeLongLong:
+		if mf.flags&flagUnsigned != 0 {
+			return "UNSIGNED BIGINT"
+		}
 		return "BIGINT"
 	case fieldTypeMediumBLOB:
 		if mf.charSet != collations[binaryCollation] {
@@ -63,6 +69,9 @@ func (mf *mysqlField) typeDatabaseName() string {
 	case fieldTypeSet:
 		return "SET"
 	case fieldTypeShort:
+		if mf.flags&flagUnsigned != 0 {
+			return "UNSIGNED SMALLINT"
+		}
 		return "SMALLINT"
 	case fieldTypeString:
 		if mf.charSet == collations[binaryCollation] {
@@ -74,6 +83,9 @@ func (mf *mysqlField) typeDatabaseName() string {
 	case fieldTypeTimestamp:
 		return "TIMESTAMP"
 	case fieldTypeTiny:
+		if mf.flags&flagUnsigned != 0 {
+			return "UNSIGNED TINYINT"
+		}
 		return "TINYINT"
 	case fieldTypeTinyBLOB:
 		if mf.charSet != collations[binaryCollation] {

From a5bb8074f2718b6f49530d588c5ee8649cf0a58a Mon Sep 17 00:00:00 2001
From: Santhosh Kumar Tekuri <santhosh.tekuri@gmail.com>
Date: Tue, 24 Aug 2021 10:20:01 +0530
Subject: [PATCH 165/282] Fix sha256_password auth plugin on unix transport
 (#1246)

caching_sha2_password uses cleartext password when
the connection is Unix or sharedmemory protocol.
but sha256_password do not.
---
 AUTHORS | 1 +
 auth.go | 4 +++-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/AUTHORS b/AUTHORS
index 80199cf2c..876b2964a 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -82,6 +82,7 @@ Reed Allman <rdallman10 at gmail.com>
 Richard Wilkes <wilkes at me.com>
 Robert Russell <robert at rrbrussell.com>
 Runrioter Wung <runrioter at gmail.com>
+Santhosh Kumar Tekuri <santhosh.tekuri at gmail.com>
 Sho Iizuka <sho.i518 at gmail.com>
 Sho Ikeda <suicaicoca at gmail.com>
 Shuode Li <elemount at qq.com>
diff --git a/auth.go b/auth.go
index b2f19e8f0..f610b5f49 100644
--- a/auth.go
+++ b/auth.go
@@ -274,7 +274,9 @@ func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, error) {
 		if len(mc.cfg.Passwd) == 0 {
 			return []byte{0}, nil
 		}
-		if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
+		// unlike caching_sha2_password, sha256_password does not accept
+		// cleartext password on unix transport.
+		if mc.cfg.tls != nil {
 			// write cleartext auth packet
 			return append([]byte(mc.cfg.Passwd), 0), nil
 		}

From 6cf3092b0e12f6e197de3ed6aa2acfeac322a9bb Mon Sep 17 00:00:00 2001
From: Ichinose Shogo <shogo82148@gmail.com>
Date: Wed, 25 Aug 2021 17:32:50 +0900
Subject: [PATCH 166/282] Go 1.17 and MariaDB 10.6 are released (#1253)

* Go 1.17 and MariaDB 10.6 are released

* use `utf8mb4_unicode_ci` instead of `utf8_unicode_ci`

https://mariadb.com/kb/en/changes-improvements-in-mariadb-106/#character-sets

> The utf8 character set (and related collations) is now by default an alias for utf8mb3 rather than the other way around.
---
 .github/workflows/test.yml | 4 +++-
 driver_test.go             | 2 +-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index fce4cf670..6bf3524d4 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -22,8 +22,9 @@ jobs:
           import json
           go = [
               # Keep the most recent production release at the top
-              '1.16',
+              '1.17',
               # Older production releases
+              '1.16',
               '1.15',
               '1.14',
               '1.13',
@@ -32,6 +33,7 @@ jobs:
               '8.0',
               '5.7',
               '5.6',
+              'mariadb-10.6',
               'mariadb-10.5',
               'mariadb-10.4',
               'mariadb-10.3',
diff --git a/driver_test.go b/driver_test.go
index 7b4ab84f3..4850498d0 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -1479,7 +1479,7 @@ func TestCollation(t *testing.T) {
 		defaultCollation, // driver default
 		"latin1_general_ci",
 		"binary",
-		"utf8_unicode_ci",
+		"utf8mb4_unicode_ci",
 		"cp1257_bin",
 	}
 

From 0d7b91a8e9b24de9f7fa55d475b8b43f0bccfe48 Mon Sep 17 00:00:00 2001
From: zjj <zhong2plus@gmail.com>
Date: Mon, 10 Jan 2022 16:54:06 +0800
Subject: [PATCH 167/282] Fixed SetReadDeadline before connCheck (#1299)

there's no need to set conn's readline if CheckConnLiveness is false,
and the ReadTimeout shall work with rawConn.Read inside conncheck.
buffer.fill will do SetReadDeadline if needed
---
 packets.go | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/packets.go b/packets.go
index 1867ecab2..ab30601ae 100644
--- a/packets.go
+++ b/packets.go
@@ -110,14 +110,13 @@ func (mc *mysqlConn) writePacket(data []byte) error {
 			conn = mc.rawConn
 		}
 		var err error
-		// If this connection has a ReadTimeout which we've been setting on
-		// reads, reset it to its default value before we attempt a non-blocking
-		// read, otherwise the scheduler will just time us out before we can read
-		if mc.cfg.ReadTimeout != 0 {
-			err = conn.SetReadDeadline(time.Time{})
-		}
-		if err == nil && mc.cfg.CheckConnLiveness {
-			err = connCheck(conn)
+		if mc.cfg.CheckConnLiveness {
+			if mc.cfg.ReadTimeout != 0 {
+				err = conn.SetReadDeadline(time.Now().Add(mc.cfg.ReadTimeout))
+			}
+			if err == nil {
+				err = connCheck(conn)
+			}
 		}
 		if err != nil {
 			errLog.Print("closing bad idle connection: ", err)

From 217d05049e5a88d529b9a2d5fe5675120831efab Mon Sep 17 00:00:00 2001
From: zjj <zhong2plus@gmail.com>
Date: Wed, 19 Jan 2022 16:52:20 +0800
Subject: [PATCH 168/282] enhancement for mysqlConn handleAuthResult (#1250)

---
 auth.go | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/auth.go b/auth.go
index f610b5f49..a25353429 100644
--- a/auth.go
+++ b/auth.go
@@ -367,13 +367,20 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
 							return err
 						}
 						data[4] = cachingSha2PasswordRequestPublicKey
-						mc.writePacket(data)
+						err = mc.writePacket(data)
+						if err != nil {
+							return err
+						}
 
-						// parse public key
 						if data, err = mc.readPacket(); err != nil {
 							return err
 						}
 
+						if data[0] != iAuthMoreData {
+							return fmt.Errorf("unexpect resp from server for caching_sha2_password perform full authentication")
+						}
+
+						// parse public key
 						block, rest := pem.Decode(data[1:])
 						if block == nil {
 							return fmt.Errorf("No Pem data found, data: %s", rest)
@@ -406,6 +413,10 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
 			return nil // auth successful
 		default:
 			block, _ := pem.Decode(authData)
+			if block == nil {
+				return fmt.Errorf("no Pem data found, data: %s", authData)
+			}
+
 			pub, err := x509.ParsePKIXPublicKey(block.Bytes)
 			if err != nil {
 				return err

From c1aa6812e4756a364458fbef765f098421090372 Mon Sep 17 00:00:00 2001
From: cui fliter <imcusg@gmail.com>
Date: Thu, 3 Mar 2022 08:13:32 +0800
Subject: [PATCH 169/282] utils: fix typo (#1312)

Signed-off-by: cuishuang <imcusg@gmail.com>
---
 utils.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/utils.go b/utils.go
index bcdee1b46..1176d743b 100644
--- a/utils.go
+++ b/utils.go
@@ -542,7 +542,7 @@ func stringToInt(b []byte) int {
 	return val
 }
 
-// returns the string read as a bytes slice, wheter the value is NULL,
+// returns the string read as a bytes slice, whether the value is NULL,
 // the number of bytes read and an error, in case the string is longer than
 // the input slice
 func readLengthEncodedString(b []byte) ([]byte, bool, int, error) {

From ce134bfccee8f827a591ba127ee4b004e0ee0a5c Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Fri, 11 Mar 2022 21:47:08 +0900
Subject: [PATCH 170/282] util: Reduce boundery check in escape functions.
 (#1316)

---
 utils.go | 32 ++++++++++++++++----------------
 1 file changed, 16 insertions(+), 16 deletions(-)

diff --git a/utils.go b/utils.go
index 1176d743b..5a024aa0a 100644
--- a/utils.go
+++ b/utils.go
@@ -652,32 +652,32 @@ func escapeBytesBackslash(buf, v []byte) []byte {
 	for _, c := range v {
 		switch c {
 		case '\x00':
-			buf[pos] = '\\'
 			buf[pos+1] = '0'
+			buf[pos] = '\\'
 			pos += 2
 		case '\n':
-			buf[pos] = '\\'
 			buf[pos+1] = 'n'
+			buf[pos] = '\\'
 			pos += 2
 		case '\r':
-			buf[pos] = '\\'
 			buf[pos+1] = 'r'
+			buf[pos] = '\\'
 			pos += 2
 		case '\x1a':
-			buf[pos] = '\\'
 			buf[pos+1] = 'Z'
+			buf[pos] = '\\'
 			pos += 2
 		case '\'':
-			buf[pos] = '\\'
 			buf[pos+1] = '\''
+			buf[pos] = '\\'
 			pos += 2
 		case '"':
-			buf[pos] = '\\'
 			buf[pos+1] = '"'
+			buf[pos] = '\\'
 			pos += 2
 		case '\\':
-			buf[pos] = '\\'
 			buf[pos+1] = '\\'
+			buf[pos] = '\\'
 			pos += 2
 		default:
 			buf[pos] = c
@@ -697,32 +697,32 @@ func escapeStringBackslash(buf []byte, v string) []byte {
 		c := v[i]
 		switch c {
 		case '\x00':
-			buf[pos] = '\\'
 			buf[pos+1] = '0'
+			buf[pos] = '\\'
 			pos += 2
 		case '\n':
-			buf[pos] = '\\'
 			buf[pos+1] = 'n'
+			buf[pos] = '\\'
 			pos += 2
 		case '\r':
-			buf[pos] = '\\'
 			buf[pos+1] = 'r'
+			buf[pos] = '\\'
 			pos += 2
 		case '\x1a':
-			buf[pos] = '\\'
 			buf[pos+1] = 'Z'
+			buf[pos] = '\\'
 			pos += 2
 		case '\'':
-			buf[pos] = '\\'
 			buf[pos+1] = '\''
+			buf[pos] = '\\'
 			pos += 2
 		case '"':
-			buf[pos] = '\\'
 			buf[pos+1] = '"'
+			buf[pos] = '\\'
 			pos += 2
 		case '\\':
-			buf[pos] = '\\'
 			buf[pos+1] = '\\'
+			buf[pos] = '\\'
 			pos += 2
 		default:
 			buf[pos] = c
@@ -744,8 +744,8 @@ func escapeBytesQuotes(buf, v []byte) []byte {
 
 	for _, c := range v {
 		if c == '\'' {
-			buf[pos] = '\''
 			buf[pos+1] = '\''
+			buf[pos] = '\''
 			pos += 2
 		} else {
 			buf[pos] = c
@@ -764,8 +764,8 @@ func escapeStringQuotes(buf []byte, v string) []byte {
 	for i := 0; i < len(v); i++ {
 		c := v[i]
 		if c == '\'' {
-			buf[pos] = '\''
 			buf[pos+1] = '\''
+			buf[pos] = '\''
 			pos += 2
 		} else {
 			buf[pos] = c

From 90e813fe43edc87a66650b570e8362da44041a4c Mon Sep 17 00:00:00 2001
From: Mikhail Faraponov <11322032+moredure@users.noreply.github.com>
Date: Fri, 18 Mar 2022 06:23:21 +0200
Subject: [PATCH 171/282] Add go1.18 to test matrix (#1317)

---
 .github/workflows/test.yml | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 6bf3524d4..f8c472832 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -22,8 +22,9 @@ jobs:
           import json
           go = [
               # Keep the most recent production release at the top
-              '1.17',
+              '1.18',
               # Older production releases
+              '1.17',
               '1.16',
               '1.15',
               '1.14',

From eff3908a822d24d8952cc3cd547eeb36f2affa7f Mon Sep 17 00:00:00 2001
From: Mikhail Faraponov <11322032+moredure@users.noreply.github.com>
Date: Fri, 25 Mar 2022 02:07:47 +0200
Subject: [PATCH 172/282] Move default packet size to constant (#1319)

---
 infile.go | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/infile.go b/infile.go
index 60effdfc2..e6323aea4 100644
--- a/infile.go
+++ b/infile.go
@@ -93,10 +93,12 @@ func deferredClose(err *error, closer io.Closer) {
 	}
 }
 
+const defaultPacketSize = 16 * 1024 // 16KB is small enough for disk readahead and large enough for TCP
+
 func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
 	var rdr io.Reader
 	var data []byte
-	packetSize := 16 * 1024 // 16KB is small enough for disk readahead and large enough for TCP
+	packetSize := defaultPacketSize
 	if mc.maxWriteSize < packetSize {
 		packetSize = mc.maxWriteSize
 	}

From ad9fa14acdcf7d0533e7fbe58728f3d216213ade Mon Sep 17 00:00:00 2001
From: Thomas Posch <55388669+thopos@users.noreply.github.com>
Date: Wed, 13 Apr 2022 09:25:45 +0200
Subject: [PATCH 173/282] Add SQLState to MySQLError (#1321)

Report SQLState in MySQLError to allow library users to distinguish user-defined from client / server errors.
---
 AUTHORS        |  1 +
 errors.go      |  9 +++++++--
 errors_test.go |  6 +++---
 packets.go     | 11 ++++++-----
 4 files changed, 17 insertions(+), 10 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 876b2964a..50b9593f0 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -110,6 +110,7 @@ Ziheng Lyu <zihenglv at gmail.com>
 Barracuda Networks, Inc.
 Counting Ltd.
 DigitalOcean Inc.
+dyves labs AG
 Facebook Inc.
 GitHub Inc.
 Google Inc.
diff --git a/errors.go b/errors.go
index 92cc9a361..7c037e7d6 100644
--- a/errors.go
+++ b/errors.go
@@ -56,11 +56,16 @@ func SetLogger(logger Logger) error {
 
 // MySQLError is an error type which represents a single MySQL error
 type MySQLError struct {
-	Number  uint16
-	Message string
+	Number   uint16
+	SQLState [5]byte
+	Message  string
 }
 
 func (me *MySQLError) Error() string {
+	if me.SQLState != [5]byte{} {
+		return fmt.Sprintf("Error %d (%s): %s", me.Number, me.SQLState, me.Message)
+	}
+
 	return fmt.Sprintf("Error %d: %s", me.Number, me.Message)
 }
 
diff --git a/errors_test.go b/errors_test.go
index 3a1aef74d..43213f98e 100644
--- a/errors_test.go
+++ b/errors_test.go
@@ -43,13 +43,13 @@ func TestErrorsStrictIgnoreNotes(t *testing.T) {
 }
 
 func TestMySQLErrIs(t *testing.T) {
-	infraErr := &MySQLError{1234, "the server is on fire"}
-	otherInfraErr := &MySQLError{1234, "the datacenter is flooded"}
+	infraErr := &MySQLError{Number: 1234, Message: "the server is on fire"}
+	otherInfraErr := &MySQLError{Number: 1234, Message: "the datacenter is flooded"}
 	if !errors.Is(infraErr, otherInfraErr) {
 		t.Errorf("expected errors to be the same: %+v %+v", infraErr, otherInfraErr)
 	}
 
-	differentCodeErr := &MySQLError{5678, "the server is on fire"}
+	differentCodeErr := &MySQLError{Number: 5678, Message: "the server is on fire"}
 	if errors.Is(infraErr, differentCodeErr) {
 		t.Fatalf("expected errors to be different: %+v %+v", infraErr, differentCodeErr)
 	}
diff --git a/packets.go b/packets.go
index ab30601ae..003584c25 100644
--- a/packets.go
+++ b/packets.go
@@ -587,19 +587,20 @@ func (mc *mysqlConn) handleErrorPacket(data []byte) error {
 		return driver.ErrBadConn
 	}
 
+	me := &MySQLError{Number: errno}
+
 	pos := 3
 
 	// SQL State [optional: # + 5bytes string]
 	if data[3] == 0x23 {
-		//sqlstate := string(data[4 : 4+5])
+		copy(me.SQLState[:], data[4:4+5])
 		pos = 9
 	}
 
 	// Error Message [string]
-	return &MySQLError{
-		Number:  errno,
-		Message: string(data[pos:]),
-	}
+	me.Message = string(data[pos:])
+
+	return me
 }
 
 func readStatus(b []byte) statusFlag {

From 0c62bb2791485d4260371bcc6017321de93b2430 Mon Sep 17 00:00:00 2001
From: ICHINOSE Shogo <shogo82148@gmail.com>
Date: Fri, 19 Aug 2022 16:29:14 +0900
Subject: [PATCH 174/282] Go1.19 is released (#1350)

---
 .github/workflows/test.yml |  3 +-
 atomic_bool.go             | 19 ++++++++++
 atomic_bool_go118.go       | 47 +++++++++++++++++++++++++
 atomic_bool_test.go        | 71 +++++++++++++++++++++++++++++++++++++
 auth.go                    | 35 +++++++++---------
 collations.go              |  3 +-
 conncheck.go               |  1 +
 conncheck_dummy.go         |  1 +
 conncheck_test.go          |  1 +
 connection.go              | 22 ++++++------
 connection_test.go         |  2 +-
 driver.go                  |  6 ++--
 fuzz.go                    |  1 +
 infile.go                  | 28 +++++++--------
 nulltime.go                | 18 +++++-----
 statement.go               |  8 ++---
 transaction.go             |  4 +--
 utils.go                   | 72 +++++++++++---------------------------
 utils_test.go              | 60 -------------------------------
 19 files changed, 226 insertions(+), 176 deletions(-)
 create mode 100644 atomic_bool.go
 create mode 100644 atomic_bool_go118.go
 create mode 100644 atomic_bool_test.go

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index f8c472832..b558eba28 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -22,8 +22,9 @@ jobs:
           import json
           go = [
               # Keep the most recent production release at the top
-              '1.18',
+              '1.19',
               # Older production releases
+              '1.18',
               '1.17',
               '1.16',
               '1.15',
diff --git a/atomic_bool.go b/atomic_bool.go
new file mode 100644
index 000000000..1b7e19f3e
--- /dev/null
+++ b/atomic_bool.go
@@ -0,0 +1,19 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package.
+//
+// Copyright 2022 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/.
+//go:build go1.19
+// +build go1.19
+
+package mysql
+
+import "sync/atomic"
+
+/******************************************************************************
+*                               Sync utils                                    *
+******************************************************************************/
+
+type atomicBool = atomic.Bool
diff --git a/atomic_bool_go118.go b/atomic_bool_go118.go
new file mode 100644
index 000000000..2e9a7f0b6
--- /dev/null
+++ b/atomic_bool_go118.go
@@ -0,0 +1,47 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package.
+//
+// Copyright 2022 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/.
+//go:build !go1.19
+// +build !go1.19
+
+package mysql
+
+import "sync/atomic"
+
+/******************************************************************************
+*                               Sync utils                                    *
+******************************************************************************/
+
+// atomicBool is an implementation of atomic.Bool for older version of Go.
+// it is a wrapper around uint32 for usage as a boolean value with
+// atomic access.
+type atomicBool struct {
+	_     noCopy
+	value uint32
+}
+
+// Load returns whether the current boolean value is true
+func (ab *atomicBool) Load() bool {
+	return atomic.LoadUint32(&ab.value) > 0
+}
+
+// Store sets the value of the bool regardless of the previous value
+func (ab *atomicBool) Store(value bool) {
+	if value {
+		atomic.StoreUint32(&ab.value, 1)
+	} else {
+		atomic.StoreUint32(&ab.value, 0)
+	}
+}
+
+// Swap sets the value of the bool and returns the old value.
+func (ab *atomicBool) Swap(value bool) bool {
+	if value {
+		return atomic.SwapUint32(&ab.value, 1) > 0
+	}
+	return atomic.SwapUint32(&ab.value, 0) > 0
+}
diff --git a/atomic_bool_test.go b/atomic_bool_test.go
new file mode 100644
index 000000000..a3b4ea0e8
--- /dev/null
+++ b/atomic_bool_test.go
@@ -0,0 +1,71 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package.
+//
+// Copyright 2022 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/.
+//go:build !go1.19
+// +build !go1.19
+
+package mysql
+
+import (
+	"testing"
+)
+
+func TestAtomicBool(t *testing.T) {
+	var ab atomicBool
+	if ab.Load() {
+		t.Fatal("Expected value to be false")
+	}
+
+	ab.Store(true)
+	if ab.value != 1 {
+		t.Fatal("Set(true) did not set value to 1")
+	}
+	if !ab.Load() {
+		t.Fatal("Expected value to be true")
+	}
+
+	ab.Store(true)
+	if !ab.Load() {
+		t.Fatal("Expected value to be true")
+	}
+
+	ab.Store(false)
+	if ab.value != 0 {
+		t.Fatal("Set(false) did not set value to 0")
+	}
+	if ab.Load() {
+		t.Fatal("Expected value to be false")
+	}
+
+	ab.Store(false)
+	if ab.Load() {
+		t.Fatal("Expected value to be false")
+	}
+	if ab.Swap(false) {
+		t.Fatal("Expected the old value to be false")
+	}
+	if ab.Swap(true) {
+		t.Fatal("Expected the old value to be false")
+	}
+	if !ab.Load() {
+		t.Fatal("Expected value to be true")
+	}
+
+	ab.Store(true)
+	if !ab.Load() {
+		t.Fatal("Expected value to be true")
+	}
+	if !ab.Swap(true) {
+		t.Fatal("Expected the old value to be true")
+	}
+	if !ab.Swap(false) {
+		t.Fatal("Expected the old value to be true")
+	}
+	if ab.Load() {
+		t.Fatal("Expected value to be false")
+	}
+}
diff --git a/auth.go b/auth.go
index a25353429..26f8723f5 100644
--- a/auth.go
+++ b/auth.go
@@ -33,27 +33,26 @@ var (
 // Note: The provided rsa.PublicKey instance is exclusively owned by the driver
 // after registering it and may not be modified.
 //
-//  data, err := ioutil.ReadFile("mykey.pem")
-//  if err != nil {
-//  	log.Fatal(err)
-//  }
+//	data, err := ioutil.ReadFile("mykey.pem")
+//	if err != nil {
+//		log.Fatal(err)
+//	}
 //
-//  block, _ := pem.Decode(data)
-//  if block == nil || block.Type != "PUBLIC KEY" {
-//  	log.Fatal("failed to decode PEM block containing public key")
-//  }
+//	block, _ := pem.Decode(data)
+//	if block == nil || block.Type != "PUBLIC KEY" {
+//		log.Fatal("failed to decode PEM block containing public key")
+//	}
 //
-//  pub, err := x509.ParsePKIXPublicKey(block.Bytes)
-//  if err != nil {
-//  	log.Fatal(err)
-//  }
-//
-//  if rsaPubKey, ok := pub.(*rsa.PublicKey); ok {
-//  	mysql.RegisterServerPubKey("mykey", rsaPubKey)
-//  } else {
-//  	log.Fatal("not a RSA public key")
-//  }
+//	pub, err := x509.ParsePKIXPublicKey(block.Bytes)
+//	if err != nil {
+//		log.Fatal(err)
+//	}
 //
+//	if rsaPubKey, ok := pub.(*rsa.PublicKey); ok {
+//		mysql.RegisterServerPubKey("mykey", rsaPubKey)
+//	} else {
+//		log.Fatal("not a RSA public key")
+//	}
 func RegisterServerPubKey(name string, pubKey *rsa.PublicKey) {
 	serverPubKeyLock.Lock()
 	if serverPubKeyRegistry == nil {
diff --git a/collations.go b/collations.go
index 326a9f7fa..295bfbe52 100644
--- a/collations.go
+++ b/collations.go
@@ -13,7 +13,8 @@ const binaryCollation = "binary"
 
 // A list of available collations mapped to the internal ID.
 // To update this map use the following MySQL query:
-//     SELECT COLLATION_NAME, ID FROM information_schema.COLLATIONS WHERE ID<256 ORDER BY ID
+//
+//	SELECT COLLATION_NAME, ID FROM information_schema.COLLATIONS WHERE ID<256 ORDER BY ID
 //
 // Handshake packet have only 1 byte for collation_id.  So we can't use collations with ID > 255.
 //
diff --git a/conncheck.go b/conncheck.go
index 024eb2858..0ea721720 100644
--- a/conncheck.go
+++ b/conncheck.go
@@ -6,6 +6,7 @@
 // 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/.
 
+//go:build linux || darwin || dragonfly || freebsd || netbsd || openbsd || solaris || illumos
 // +build linux darwin dragonfly freebsd netbsd openbsd solaris illumos
 
 package mysql
diff --git a/conncheck_dummy.go b/conncheck_dummy.go
index ea7fb607a..a56c138f2 100644
--- a/conncheck_dummy.go
+++ b/conncheck_dummy.go
@@ -6,6 +6,7 @@
 // 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/.
 
+//go:build !linux && !darwin && !dragonfly && !freebsd && !netbsd && !openbsd && !solaris && !illumos
 // +build !linux,!darwin,!dragonfly,!freebsd,!netbsd,!openbsd,!solaris,!illumos
 
 package mysql
diff --git a/conncheck_test.go b/conncheck_test.go
index 53995517b..f7e025680 100644
--- a/conncheck_test.go
+++ b/conncheck_test.go
@@ -6,6 +6,7 @@
 // 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/.
 
+//go:build linux || darwin || dragonfly || freebsd || netbsd || openbsd || solaris || illumos
 // +build linux darwin dragonfly freebsd netbsd openbsd solaris illumos
 
 package mysql
diff --git a/connection.go b/connection.go
index 835f89729..9539077cb 100644
--- a/connection.go
+++ b/connection.go
@@ -104,7 +104,7 @@ func (mc *mysqlConn) Begin() (driver.Tx, error) {
 }
 
 func (mc *mysqlConn) begin(readOnly bool) (driver.Tx, error) {
-	if mc.closed.IsSet() {
+	if mc.closed.Load() {
 		errLog.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
@@ -123,7 +123,7 @@ func (mc *mysqlConn) begin(readOnly bool) (driver.Tx, error) {
 
 func (mc *mysqlConn) Close() (err error) {
 	// Makes Close idempotent
-	if !mc.closed.IsSet() {
+	if !mc.closed.Load() {
 		err = mc.writeCommandPacket(comQuit)
 	}
 
@@ -137,7 +137,7 @@ func (mc *mysqlConn) Close() (err error) {
 // is called before auth or on auth failure because MySQL will have already
 // closed the network connection.
 func (mc *mysqlConn) cleanup() {
-	if !mc.closed.TrySet(true) {
+	if mc.closed.Swap(true) {
 		return
 	}
 
@@ -152,7 +152,7 @@ func (mc *mysqlConn) cleanup() {
 }
 
 func (mc *mysqlConn) error() error {
-	if mc.closed.IsSet() {
+	if mc.closed.Load() {
 		if err := mc.canceled.Value(); err != nil {
 			return err
 		}
@@ -162,7 +162,7 @@ func (mc *mysqlConn) error() error {
 }
 
 func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
-	if mc.closed.IsSet() {
+	if mc.closed.Load() {
 		errLog.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
@@ -295,7 +295,7 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
 }
 
 func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
-	if mc.closed.IsSet() {
+	if mc.closed.Load() {
 		errLog.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
@@ -356,7 +356,7 @@ func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, erro
 }
 
 func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error) {
-	if mc.closed.IsSet() {
+	if mc.closed.Load() {
 		errLog.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
@@ -450,7 +450,7 @@ func (mc *mysqlConn) finish() {
 
 // Ping implements driver.Pinger interface
 func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
-	if mc.closed.IsSet() {
+	if mc.closed.Load() {
 		errLog.Print(ErrInvalidConn)
 		return driver.ErrBadConn
 	}
@@ -469,7 +469,7 @@ func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
 
 // BeginTx implements driver.ConnBeginTx interface
 func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
-	if mc.closed.IsSet() {
+	if mc.closed.Load() {
 		return nil, driver.ErrBadConn
 	}
 
@@ -636,7 +636,7 @@ func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) {
 // ResetSession implements driver.SessionResetter.
 // (From Go 1.10)
 func (mc *mysqlConn) ResetSession(ctx context.Context) error {
-	if mc.closed.IsSet() {
+	if mc.closed.Load() {
 		return driver.ErrBadConn
 	}
 	mc.reset = true
@@ -646,5 +646,5 @@ func (mc *mysqlConn) ResetSession(ctx context.Context) error {
 // IsValid implements driver.Validator interface
 // (From Go 1.15)
 func (mc *mysqlConn) IsValid() bool {
-	return !mc.closed.IsSet()
+	return !mc.closed.Load()
 }
diff --git a/connection_test.go b/connection_test.go
index a6d677308..b6764a2f6 100644
--- a/connection_test.go
+++ b/connection_test.go
@@ -147,7 +147,7 @@ func TestCleanCancel(t *testing.T) {
 			t.Errorf("expected context.Canceled, got %#v", err)
 		}
 
-		if mc.closed.IsSet() {
+		if mc.closed.Load() {
 			t.Error("expected mc is not closed, closed actually")
 		}
 
diff --git a/driver.go b/driver.go
index c1bdf1199..ad7aec215 100644
--- a/driver.go
+++ b/driver.go
@@ -8,10 +8,10 @@
 //
 // The driver should be used via the database/sql package:
 //
-//  import "database/sql"
-//  import _ "github.com/go-sql-driver/mysql"
+//	import "database/sql"
+//	import _ "github.com/go-sql-driver/mysql"
 //
-//  db, err := sql.Open("mysql", "user:password@/dbname")
+//	db, err := sql.Open("mysql", "user:password@/dbname")
 //
 // See https://github.com/go-sql-driver/mysql#usage for details
 package mysql
diff --git a/fuzz.go b/fuzz.go
index fa75adf6a..3a4ec25a9 100644
--- a/fuzz.go
+++ b/fuzz.go
@@ -6,6 +6,7 @@
 // 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/.
 
+//go:build gofuzz
 // +build gofuzz
 
 package mysql
diff --git a/infile.go b/infile.go
index e6323aea4..3279dcffd 100644
--- a/infile.go
+++ b/infile.go
@@ -28,12 +28,11 @@ var (
 // Alternatively you can allow the use of all local files with
 // the DSN parameter 'allowAllFiles=true'
 //
-//  filePath := "/home/gopher/data.csv"
-//  mysql.RegisterLocalFile(filePath)
-//  err := db.Exec("LOAD DATA LOCAL INFILE '" + filePath + "' INTO TABLE foo")
-//  if err != nil {
-//  ...
-//
+//	filePath := "/home/gopher/data.csv"
+//	mysql.RegisterLocalFile(filePath)
+//	err := db.Exec("LOAD DATA LOCAL INFILE '" + filePath + "' INTO TABLE foo")
+//	if err != nil {
+//	...
 func RegisterLocalFile(filePath string) {
 	fileRegisterLock.Lock()
 	// lazy map init
@@ -58,15 +57,14 @@ func DeregisterLocalFile(filePath string) {
 // If the handler returns a io.ReadCloser Close() is called when the
 // request is finished.
 //
-//  mysql.RegisterReaderHandler("data", func() io.Reader {
-//  	var csvReader io.Reader // Some Reader that returns CSV data
-//  	... // Open Reader here
-//  	return csvReader
-//  })
-//  err := db.Exec("LOAD DATA LOCAL INFILE 'Reader::data' INTO TABLE foo")
-//  if err != nil {
-//  ...
-//
+//	mysql.RegisterReaderHandler("data", func() io.Reader {
+//		var csvReader io.Reader // Some Reader that returns CSV data
+//		... // Open Reader here
+//		return csvReader
+//	})
+//	err := db.Exec("LOAD DATA LOCAL INFILE 'Reader::data' INTO TABLE foo")
+//	if err != nil {
+//	...
 func RegisterReaderHandler(name string, handler func() io.Reader) {
 	readerRegisterLock.Lock()
 	// lazy map init
diff --git a/nulltime.go b/nulltime.go
index 17af92ddc..36c8a42c5 100644
--- a/nulltime.go
+++ b/nulltime.go
@@ -19,16 +19,16 @@ import (
 // NullTime implements the Scanner interface so
 // it can be used as a scan destination:
 //
-//  var nt NullTime
-//  err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
-//  ...
-//  if nt.Valid {
-//     // use nt.Time
-//  } else {
-//     // NULL value
-//  }
+//	var nt NullTime
+//	err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
+//	...
+//	if nt.Valid {
+//	   // use nt.Time
+//	} else {
+//	   // NULL value
+//	}
 //
-// This NullTime implementation is not driver-specific
+// # This NullTime implementation is not driver-specific
 //
 // Deprecated: NullTime doesn't honor the loc DSN parameter.
 // NullTime.Scan interprets a time as UTC, not the loc DSN parameter.
diff --git a/statement.go b/statement.go
index 18a3ae498..10ece8bd6 100644
--- a/statement.go
+++ b/statement.go
@@ -23,7 +23,7 @@ type mysqlStmt struct {
 }
 
 func (stmt *mysqlStmt) Close() error {
-	if stmt.mc == nil || stmt.mc.closed.IsSet() {
+	if stmt.mc == nil || stmt.mc.closed.Load() {
 		// driver.Stmt.Close can be called more than once, thus this function
 		// has to be idempotent.
 		// See also Issue #450 and golang/go#16019.
@@ -50,7 +50,7 @@ func (stmt *mysqlStmt) CheckNamedValue(nv *driver.NamedValue) (err error) {
 }
 
 func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
-	if stmt.mc.closed.IsSet() {
+	if stmt.mc.closed.Load() {
 		errLog.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
@@ -98,7 +98,7 @@ func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
 }
 
 func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) {
-	if stmt.mc.closed.IsSet() {
+	if stmt.mc.closed.Load() {
 		errLog.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
@@ -157,7 +157,7 @@ func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
 		if driver.IsValue(sv) {
 			return sv, nil
 		}
-		// A value returend from the Valuer interface can be "a type handled by
+		// A value returned from the Valuer interface can be "a type handled by
 		// a database driver's NamedValueChecker interface" so we should accept
 		// uint64 here as well.
 		if u, ok := sv.(uint64); ok {
diff --git a/transaction.go b/transaction.go
index 417d72793..4a4b61001 100644
--- a/transaction.go
+++ b/transaction.go
@@ -13,7 +13,7 @@ type mysqlTx struct {
 }
 
 func (tx *mysqlTx) Commit() (err error) {
-	if tx.mc == nil || tx.mc.closed.IsSet() {
+	if tx.mc == nil || tx.mc.closed.Load() {
 		return ErrInvalidConn
 	}
 	err = tx.mc.exec("COMMIT")
@@ -22,7 +22,7 @@ func (tx *mysqlTx) Commit() (err error) {
 }
 
 func (tx *mysqlTx) Rollback() (err error) {
-	if tx.mc == nil || tx.mc.closed.IsSet() {
+	if tx.mc == nil || tx.mc.closed.Load() {
 		return ErrInvalidConn
 	}
 	err = tx.mc.exec("ROLLBACK")
diff --git a/utils.go b/utils.go
index 5a024aa0a..60f1a91c6 100644
--- a/utils.go
+++ b/utils.go
@@ -35,26 +35,25 @@ var (
 // Note: The provided tls.Config is exclusively owned by the driver after
 // registering it.
 //
-//  rootCertPool := x509.NewCertPool()
-//  pem, err := ioutil.ReadFile("/path/ca-cert.pem")
-//  if err != nil {
-//      log.Fatal(err)
-//  }
-//  if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
-//      log.Fatal("Failed to append PEM.")
-//  }
-//  clientCert := make([]tls.Certificate, 0, 1)
-//  certs, err := tls.LoadX509KeyPair("/path/client-cert.pem", "/path/client-key.pem")
-//  if err != nil {
-//      log.Fatal(err)
-//  }
-//  clientCert = append(clientCert, certs)
-//  mysql.RegisterTLSConfig("custom", &tls.Config{
-//      RootCAs: rootCertPool,
-//      Certificates: clientCert,
-//  })
-//  db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom")
-//
+//	rootCertPool := x509.NewCertPool()
+//	pem, err := ioutil.ReadFile("/path/ca-cert.pem")
+//	if err != nil {
+//	    log.Fatal(err)
+//	}
+//	if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
+//	    log.Fatal("Failed to append PEM.")
+//	}
+//	clientCert := make([]tls.Certificate, 0, 1)
+//	certs, err := tls.LoadX509KeyPair("/path/client-cert.pem", "/path/client-key.pem")
+//	if err != nil {
+//	    log.Fatal(err)
+//	}
+//	clientCert = append(clientCert, certs)
+//	mysql.RegisterTLSConfig("custom", &tls.Config{
+//	    RootCAs: rootCertPool,
+//	    Certificates: clientCert,
+//	})
+//	db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom")
 func RegisterTLSConfig(key string, config *tls.Config) error {
 	if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" || strings.ToLower(key) == "preferred" {
 		return fmt.Errorf("key '%s' is reserved", key)
@@ -796,39 +795,10 @@ func (*noCopy) Lock() {}
 // https://github.com/golang/go/issues/26165
 func (*noCopy) Unlock() {}
 
-// atomicBool is a wrapper around uint32 for usage as a boolean value with
-// atomic access.
-type atomicBool struct {
-	_noCopy noCopy
-	value   uint32
-}
-
-// IsSet returns whether the current boolean value is true
-func (ab *atomicBool) IsSet() bool {
-	return atomic.LoadUint32(&ab.value) > 0
-}
-
-// Set sets the value of the bool regardless of the previous value
-func (ab *atomicBool) Set(value bool) {
-	if value {
-		atomic.StoreUint32(&ab.value, 1)
-	} else {
-		atomic.StoreUint32(&ab.value, 0)
-	}
-}
-
-// TrySet sets the value of the bool and returns whether the value changed
-func (ab *atomicBool) TrySet(value bool) bool {
-	if value {
-		return atomic.SwapUint32(&ab.value, 1) == 0
-	}
-	return atomic.SwapUint32(&ab.value, 0) > 0
-}
-
 // atomicError is a wrapper for atomically accessed error values
 type atomicError struct {
-	_noCopy noCopy
-	value   atomic.Value
+	_     noCopy
+	value atomic.Value
 }
 
 // Set sets the error value regardless of the previous value.
diff --git a/utils_test.go b/utils_test.go
index b0069251e..8296ac2aa 100644
--- a/utils_test.go
+++ b/utils_test.go
@@ -173,66 +173,6 @@ func TestEscapeQuotes(t *testing.T) {
 	expect("foo\"bar", "foo\"bar")     // not affected
 }
 
-func TestAtomicBool(t *testing.T) {
-	var ab atomicBool
-	if ab.IsSet() {
-		t.Fatal("Expected value to be false")
-	}
-
-	ab.Set(true)
-	if ab.value != 1 {
-		t.Fatal("Set(true) did not set value to 1")
-	}
-	if !ab.IsSet() {
-		t.Fatal("Expected value to be true")
-	}
-
-	ab.Set(true)
-	if !ab.IsSet() {
-		t.Fatal("Expected value to be true")
-	}
-
-	ab.Set(false)
-	if ab.value != 0 {
-		t.Fatal("Set(false) did not set value to 0")
-	}
-	if ab.IsSet() {
-		t.Fatal("Expected value to be false")
-	}
-
-	ab.Set(false)
-	if ab.IsSet() {
-		t.Fatal("Expected value to be false")
-	}
-	if ab.TrySet(false) {
-		t.Fatal("Expected TrySet(false) to fail")
-	}
-	if !ab.TrySet(true) {
-		t.Fatal("Expected TrySet(true) to succeed")
-	}
-	if !ab.IsSet() {
-		t.Fatal("Expected value to be true")
-	}
-
-	ab.Set(true)
-	if !ab.IsSet() {
-		t.Fatal("Expected value to be true")
-	}
-	if ab.TrySet(true) {
-		t.Fatal("Expected TrySet(true) to fail")
-	}
-	if !ab.TrySet(false) {
-		t.Fatal("Expected TrySet(false) to succeed")
-	}
-	if ab.IsSet() {
-		t.Fatal("Expected value to be false")
-	}
-
-	// we've "tested" them ¯\_(ツ)_/¯
-	ab._noCopy.Lock()
-	defer ab._noCopy.Unlock()
-}
-
 func TestAtomicError(t *testing.T) {
 	var ae atomicError
 	if ae.Value() != nil {

From a477f69f3c2abaf4646680bdc3a65d5172a6566e Mon Sep 17 00:00:00 2001
From: ICHINOSE Shogo <shogo82148@gmail.com>
Date: Sat, 20 Aug 2022 00:19:54 +0900
Subject: [PATCH 175/282] fix: benchmarkExecContext is unused (#1351)

---
 benchmark_test.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/benchmark_test.go b/benchmark_test.go
index 1030ddc52..97ed781f8 100644
--- a/benchmark_test.go
+++ b/benchmark_test.go
@@ -314,7 +314,7 @@ func BenchmarkExecContext(b *testing.B) {
 	defer db.Close()
 	for _, p := range []int{1, 2, 3, 4} {
 		b.Run(fmt.Sprintf("%d", p), func(b *testing.B) {
-			benchmarkQueryContext(b, db, p)
+			benchmarkExecContext(b, db, p)
 		})
 	}
 }

From 803c0e06f2b703d30b18e168e7349ffc66e7fa86 Mon Sep 17 00:00:00 2001
From: ICHINOSE Shogo <shogo82148@gmail.com>
Date: Mon, 7 Nov 2022 21:26:59 +0900
Subject: [PATCH 176/282] migrate set-output to the environment file (#1368)

---
 .github/workflows/test.yml | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index b558eba28..703203258 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -20,6 +20,7 @@ jobs:
         id: set-matrix
         run: |
           import json
+          import os
           go = [
               # Keep the most recent production release at the top
               '1.19',
@@ -55,7 +56,8 @@ jobs:
               'include': includes
           }
           output = json.dumps(matrix, separators=(',', ':'))
-          print('::set-output name=matrix::{0}'.format(output))
+          with open(os.environ["GITHUB_OUTPUT"], 'a', encoding="utf-8") as f:
+              f.write('matrix={0}\n'.format(output))
         shell: python
   test:
     needs: list

From 05bed834d054b8361595c6544146567d70713dc1 Mon Sep 17 00:00:00 2001
From: "lgtm-com[bot]" <43144390+lgtm-com[bot]@users.noreply.github.com>
Date: Thu, 10 Nov 2022 06:19:39 +0900
Subject: [PATCH 177/282] Add CodeQL workflow for GitHub code scanning (#1369)

Co-authored-by: LGTM Migrator <lgtm-migrator@users.noreply.github.com>
---
 .github/workflows/codeql.yml | 41 ++++++++++++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)
 create mode 100644 .github/workflows/codeql.yml

diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 000000000..d9d29a8b7
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,41 @@
+name: "CodeQL"
+
+on:
+  push:
+    branches: [ "master" ]
+  pull_request:
+    branches: [ "master" ]
+  schedule:
+    - cron: "18 19 * * 1"
+
+jobs:
+  analyze:
+    name: Analyze
+    runs-on: ubuntu-latest
+    permissions:
+      actions: read
+      contents: read
+      security-events: write
+
+    strategy:
+      fail-fast: false
+      matrix:
+        language: [ go ]
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+
+      - name: Initialize CodeQL
+        uses: github/codeql-action/init@v2
+        with:
+          languages: ${{ matrix.language }}
+          queries: +security-and-quality
+
+      - name: Autobuild
+        uses: github/codeql-action/autobuild@v2
+
+      - name: Perform CodeQL Analysis
+        uses: github/codeql-action/analyze@v2
+        with:
+          category: "/language:${{ matrix.language }}"

From fa1e4ed592daa59bcd70003263b5fc72e3de0137 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Thu, 10 Nov 2022 15:01:30 +0900
Subject: [PATCH 178/282] Fix parsing 0 year. (#1257)

Fix #1252
---
 utils.go      | 10 ----------
 utils_test.go | 27 +++++++++++++++++++++++++++
 2 files changed, 27 insertions(+), 10 deletions(-)

diff --git a/utils.go b/utils.go
index 60f1a91c6..15dbd8d16 100644
--- a/utils.go
+++ b/utils.go
@@ -117,10 +117,6 @@ func parseDateTime(b []byte, loc *time.Location) (time.Time, error) {
 		if err != nil {
 			return time.Time{}, err
 		}
-		if year <= 0 {
-			year = 1
-		}
-
 		if b[4] != '-' {
 			return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[4])
 		}
@@ -129,9 +125,6 @@ func parseDateTime(b []byte, loc *time.Location) (time.Time, error) {
 		if err != nil {
 			return time.Time{}, err
 		}
-		if m <= 0 {
-			m = 1
-		}
 		month := time.Month(m)
 
 		if b[7] != '-' {
@@ -142,9 +135,6 @@ func parseDateTime(b []byte, loc *time.Location) (time.Time, error) {
 		if err != nil {
 			return time.Time{}, err
 		}
-		if day <= 0 {
-			day = 1
-		}
 		if len(b) == 10 {
 			return time.Date(year, month, day, 0, 0, 0, 0, loc), nil
 		}
diff --git a/utils_test.go b/utils_test.go
index 8296ac2aa..4e5fc3cb7 100644
--- a/utils_test.go
+++ b/utils_test.go
@@ -380,6 +380,33 @@ func TestParseDateTime(t *testing.T) {
 	}
 }
 
+func TestInvalidDateTime(t *testing.T) {
+	cases := []struct {
+		name string
+		str  string
+		want time.Time
+	}{
+		{
+			name: "parse datetime without day",
+			str:  "0000-00-00 21:30:45",
+			want: time.Date(0, 0, 0, 21, 30, 45, 0, time.UTC),
+		},
+	}
+
+	for _, cc := range cases {
+		t.Run(cc.name, func(t *testing.T) {
+			got, err := parseDateTime([]byte(cc.str), time.UTC)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			if !cc.want.Equal(got) {
+				t.Fatalf("want: %v, but got %v", cc.want, got)
+			}
+		})
+	}
+}
+
 func TestParseDateTimeFail(t *testing.T) {
 	cases := []struct {
 		name    string

From 41dd159e6ec9afad00d2b90144bbc083ea860db1 Mon Sep 17 00:00:00 2001
From: lance6716 <lance6716@gmail.com>
Date: Mon, 28 Nov 2022 14:26:20 +0800
Subject: [PATCH 179/282] Add `AllowFallbackToPlaintext` and `TLS` to config
 (#1370)

---
 AUTHORS      |  1 +
 README.md    | 11 ++++++++
 auth.go      |  4 +--
 auth_test.go | 10 ++++----
 dsn.go       | 72 ++++++++++++++++++++++++++++++++--------------------
 dsn_test.go  | 47 +++++++++++++++++-----------------
 packets.go   | 12 ++++-----
 7 files changed, 94 insertions(+), 63 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 50b9593f0..051327519 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -61,6 +61,7 @@ Kamil Dziedzic <kamil at klecza.pl>
 Kei Kamikawa <x00.x7f.x86 at gmail.com>
 Kevin Malachowski <kevin at chowski.com>
 Kieron Woodhouse <kieron.woodhouse at infosum.com>
+Lance Tian <lance6716 at gmail.com>
 Lennart Rudolph <lrudolph at hmc.edu>
 Leonardo YongUk Kim <dalinaum at gmail.com>
 Linh Tran Tuan <linhduonggnu at gmail.com>
diff --git a/README.md b/README.md
index ded6e3b16..25de2e5aa 100644
--- a/README.md
+++ b/README.md
@@ -157,6 +157,17 @@ Default:        false
 
 `allowCleartextPasswords=true` allows using the [cleartext client side plugin](https://dev.mysql.com/doc/en/cleartext-pluggable-authentication.html) if required by an account, such as one defined with the [PAM authentication plugin](http://dev.mysql.com/doc/en/pam-authentication-plugin.html). Sending passwords in clear text may be a security problem in some configurations. To avoid problems if there is any possibility that the password would be intercepted, clients should connect to MySQL Server using a method that protects the password. Possibilities include [TLS / SSL](#tls), IPsec, or a private network.
 
+
+##### `allowFallbackToPlaintext`
+
+```
+Type:           bool
+Valid Values:   true, false
+Default:        false
+```
+
+`allowFallbackToPlaintext=true` acts like a `--ssl-mode=PREFERRED` MySQL client as described in [Command Options for Connecting to the Server](https://dev.mysql.com/doc/refman/5.7/en/connection-options.html#option_general_ssl-mode)
+
 ##### `allowNativePasswords`
 
 ```
diff --git a/auth.go b/auth.go
index 26f8723f5..1ff203e57 100644
--- a/auth.go
+++ b/auth.go
@@ -275,7 +275,7 @@ func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, error) {
 		}
 		// unlike caching_sha2_password, sha256_password does not accept
 		// cleartext password on unix transport.
-		if mc.cfg.tls != nil {
+		if mc.cfg.TLS != nil {
 			// write cleartext auth packet
 			return append([]byte(mc.cfg.Passwd), 0), nil
 		}
@@ -351,7 +351,7 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
 				}
 
 			case cachingSha2PasswordPerformFullAuthentication:
-				if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
+				if mc.cfg.TLS != nil || mc.cfg.Net == "unix" {
 					// write cleartext auth packet
 					err = mc.writeAuthSwitchPacket(append([]byte(mc.cfg.Passwd), 0))
 					if err != nil {
diff --git a/auth_test.go b/auth_test.go
index 3bce7fe22..3ce0ea6e0 100644
--- a/auth_test.go
+++ b/auth_test.go
@@ -291,7 +291,7 @@ func TestAuthFastCachingSHA256PasswordFullSecure(t *testing.T) {
 
 	// Hack to make the caching_sha2_password plugin believe that the connection
 	// is secure
-	mc.cfg.tls = &tls.Config{InsecureSkipVerify: true}
+	mc.cfg.TLS = &tls.Config{InsecureSkipVerify: true}
 
 	// check written auth response
 	authRespStart := 4 + 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1
@@ -663,7 +663,7 @@ func TestAuthFastSHA256PasswordSecure(t *testing.T) {
 
 	// hack to make the caching_sha2_password plugin believe that the connection
 	// is secure
-	mc.cfg.tls = &tls.Config{InsecureSkipVerify: true}
+	mc.cfg.TLS = &tls.Config{InsecureSkipVerify: true}
 
 	authData := []byte{6, 81, 96, 114, 14, 42, 50, 30, 76, 47, 1, 95, 126, 81,
 		62, 94, 83, 80, 52, 85}
@@ -676,7 +676,7 @@ func TestAuthFastSHA256PasswordSecure(t *testing.T) {
 	}
 
 	// unset TLS config to prevent the actual establishment of a TLS wrapper
-	mc.cfg.tls = nil
+	mc.cfg.TLS = nil
 
 	err = mc.writeHandshakeResponsePacket(authResp, plugin)
 	if err != nil {
@@ -866,7 +866,7 @@ func TestAuthSwitchCachingSHA256PasswordFullSecure(t *testing.T) {
 
 	// Hack to make the caching_sha2_password plugin believe that the connection
 	// is secure
-	mc.cfg.tls = &tls.Config{InsecureSkipVerify: true}
+	mc.cfg.TLS = &tls.Config{InsecureSkipVerify: true}
 
 	// auth switch request
 	conn.data = []byte{44, 0, 0, 2, 254, 99, 97, 99, 104, 105, 110, 103, 95,
@@ -1299,7 +1299,7 @@ func TestAuthSwitchSHA256PasswordSecure(t *testing.T) {
 
 	// Hack to make the caching_sha2_password plugin believe that the connection
 	// is secure
-	mc.cfg.tls = &tls.Config{InsecureSkipVerify: true}
+	mc.cfg.TLS = &tls.Config{InsecureSkipVerify: true}
 
 	// auth switch request
 	conn.data = []byte{38, 0, 0, 2, 254, 115, 104, 97, 50, 53, 54, 95, 112, 97,
diff --git a/dsn.go b/dsn.go
index a306d66a3..4b71aaab0 100644
--- a/dsn.go
+++ b/dsn.go
@@ -46,22 +46,23 @@ type Config struct {
 	ServerPubKey     string            // Server public key name
 	pubKey           *rsa.PublicKey    // Server public key
 	TLSConfig        string            // TLS configuration name
-	tls              *tls.Config       // TLS configuration
+	TLS              *tls.Config       // TLS configuration, its priority is higher than TLSConfig
 	Timeout          time.Duration     // Dial timeout
 	ReadTimeout      time.Duration     // I/O read timeout
 	WriteTimeout     time.Duration     // I/O write timeout
 
-	AllowAllFiles           bool // Allow all files to be used with LOAD DATA LOCAL INFILE
-	AllowCleartextPasswords bool // Allows the cleartext client side plugin
-	AllowNativePasswords    bool // Allows the native password authentication method
-	AllowOldPasswords       bool // Allows the old insecure password method
-	CheckConnLiveness       bool // Check connections for liveness before using them
-	ClientFoundRows         bool // Return number of matching rows instead of rows changed
-	ColumnsWithAlias        bool // Prepend table alias to column names
-	InterpolateParams       bool // Interpolate placeholders into query string
-	MultiStatements         bool // Allow multiple statements in one query
-	ParseTime               bool // Parse time values to time.Time
-	RejectReadOnly          bool // Reject read-only connections
+	AllowAllFiles            bool // Allow all files to be used with LOAD DATA LOCAL INFILE
+	AllowCleartextPasswords  bool // Allows the cleartext client side plugin
+	AllowFallbackToPlaintext bool // Allows fallback to unencrypted connection if server does not support TLS
+	AllowNativePasswords     bool // Allows the native password authentication method
+	AllowOldPasswords        bool // Allows the old insecure password method
+	CheckConnLiveness        bool // Check connections for liveness before using them
+	ClientFoundRows          bool // Return number of matching rows instead of rows changed
+	ColumnsWithAlias         bool // Prepend table alias to column names
+	InterpolateParams        bool // Interpolate placeholders into query string
+	MultiStatements          bool // Allow multiple statements in one query
+	ParseTime                bool // Parse time values to time.Time
+	RejectReadOnly           bool // Reject read-only connections
 }
 
 // NewConfig creates a new Config and sets default values.
@@ -77,8 +78,8 @@ func NewConfig() *Config {
 
 func (cfg *Config) Clone() *Config {
 	cp := *cfg
-	if cp.tls != nil {
-		cp.tls = cfg.tls.Clone()
+	if cp.TLS != nil {
+		cp.TLS = cfg.TLS.Clone()
 	}
 	if len(cp.Params) > 0 {
 		cp.Params = make(map[string]string, len(cfg.Params))
@@ -119,24 +120,29 @@ func (cfg *Config) normalize() error {
 		cfg.Addr = ensureHavePort(cfg.Addr)
 	}
 
-	switch cfg.TLSConfig {
-	case "false", "":
-		// don't set anything
-	case "true":
-		cfg.tls = &tls.Config{}
-	case "skip-verify", "preferred":
-		cfg.tls = &tls.Config{InsecureSkipVerify: true}
-	default:
-		cfg.tls = getTLSConfigClone(cfg.TLSConfig)
-		if cfg.tls == nil {
-			return errors.New("invalid value / unknown config name: " + cfg.TLSConfig)
+	if cfg.TLS == nil {
+		switch cfg.TLSConfig {
+		case "false", "":
+			// don't set anything
+		case "true":
+			cfg.TLS = &tls.Config{}
+		case "skip-verify":
+			cfg.TLS = &tls.Config{InsecureSkipVerify: true}
+		case "preferred":
+			cfg.TLS = &tls.Config{InsecureSkipVerify: true}
+			cfg.AllowFallbackToPlaintext = true
+		default:
+			cfg.TLS = getTLSConfigClone(cfg.TLSConfig)
+			if cfg.TLS == nil {
+				return errors.New("invalid value / unknown config name: " + cfg.TLSConfig)
+			}
 		}
 	}
 
-	if cfg.tls != nil && cfg.tls.ServerName == "" && !cfg.tls.InsecureSkipVerify {
+	if cfg.TLS != nil && cfg.TLS.ServerName == "" && !cfg.TLS.InsecureSkipVerify {
 		host, _, err := net.SplitHostPort(cfg.Addr)
 		if err == nil {
-			cfg.tls.ServerName = host
+			cfg.TLS.ServerName = host
 		}
 	}
 
@@ -204,6 +210,10 @@ func (cfg *Config) FormatDSN() string {
 		writeDSNParam(&buf, &hasParam, "allowCleartextPasswords", "true")
 	}
 
+	if cfg.AllowFallbackToPlaintext {
+		writeDSNParam(&buf, &hasParam, "allowFallbackToPlaintext", "true")
+	}
+
 	if !cfg.AllowNativePasswords {
 		writeDSNParam(&buf, &hasParam, "allowNativePasswords", "false")
 	}
@@ -391,6 +401,14 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 				return errors.New("invalid bool value: " + value)
 			}
 
+		// Allow fallback to unencrypted connection if server does not support TLS
+		case "allowFallbackToPlaintext":
+			var isBool bool
+			cfg.AllowFallbackToPlaintext, isBool = readBool(value)
+			if !isBool {
+				return errors.New("invalid bool value: " + value)
+			}
+
 		// Use native password authentication
 		case "allowNativePasswords":
 			var isBool bool
diff --git a/dsn_test.go b/dsn_test.go
index fc6eea9c8..41a6a29fa 100644
--- a/dsn_test.go
+++ b/dsn_test.go
@@ -42,8 +42,8 @@ var testDSNs = []struct {
 	"user:password@/dbname?loc=UTC&timeout=30s&readTimeout=1s&writeTimeout=1s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci&maxAllowedPacket=16777216&tls=false&allowCleartextPasswords=true&parseTime=true&rejectReadOnly=true",
 	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_unicode_ci", Loc: time.UTC, TLSConfig: "false", AllowCleartextPasswords: true, AllowNativePasswords: true, Timeout: 30 * time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, AllowAllFiles: true, AllowOldPasswords: true, CheckConnLiveness: true, ClientFoundRows: true, MaxAllowedPacket: 16777216, ParseTime: true, RejectReadOnly: true},
 }, {
-	"user:password@/dbname?allowNativePasswords=false&checkConnLiveness=false&maxAllowedPacket=0",
-	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: 0, AllowNativePasswords: false, CheckConnLiveness: false},
+	"user:password@/dbname?allowNativePasswords=false&checkConnLiveness=false&maxAllowedPacket=0&allowFallbackToPlaintext=true",
+	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: 0, AllowFallbackToPlaintext: true, AllowNativePasswords: false, CheckConnLiveness: false},
 }, {
 	"user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local",
 	&Config{User: "user", Passwd: "p@ss(word)", Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:80", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.Local, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
@@ -82,7 +82,7 @@ func TestDSNParser(t *testing.T) {
 		}
 
 		// pointer not static
-		cfg.tls = nil
+		cfg.TLS = nil
 
 		if !reflect.DeepEqual(cfg, tst.out) {
 			t.Errorf("%d. ParseDSN(%q) mismatch:\ngot  %+v\nwant %+v", i, tst.in, cfg, tst.out)
@@ -100,6 +100,7 @@ func TestDSNParserInvalid(t *testing.T) {
 		"User:pass@tcp(1.2.3.4:3306)",           // no trailing slash
 		"net()/",                                // unknown default addr
 		"user:pass@tcp(127.0.0.1:3306)/db/name", // invalid dbname
+		"user:password@/dbname?allowFallbackToPlaintext=PREFERRED", // wrong bool flag
 		//"/dbname?arg=/some/unescaped/path",
 	}
 
@@ -118,7 +119,7 @@ func TestDSNReformat(t *testing.T) {
 			t.Error(err.Error())
 			continue
 		}
-		cfg1.tls = nil // pointer not static
+		cfg1.TLS = nil // pointer not static
 		res1 := fmt.Sprintf("%+v", cfg1)
 
 		dsn2 := cfg1.FormatDSN()
@@ -127,7 +128,7 @@ func TestDSNReformat(t *testing.T) {
 			t.Error(err.Error())
 			continue
 		}
-		cfg2.tls = nil // pointer not static
+		cfg2.TLS = nil // pointer not static
 		res2 := fmt.Sprintf("%+v", cfg2)
 
 		if res1 != res2 {
@@ -203,7 +204,7 @@ func TestDSNWithCustomTLS(t *testing.T) {
 
 	if err != nil {
 		t.Error(err.Error())
-	} else if cfg.tls.ServerName != name {
+	} else if cfg.TLS.ServerName != name {
 		t.Errorf("did not get the correct TLS ServerName (%s) parsing DSN (%s).", name, tst)
 	}
 
@@ -214,7 +215,7 @@ func TestDSNWithCustomTLS(t *testing.T) {
 
 	if err != nil {
 		t.Error(err.Error())
-	} else if cfg.tls.ServerName != name {
+	} else if cfg.TLS.ServerName != name {
 		t.Errorf("did not get the correct ServerName (%s) parsing DSN (%s).", name, tst)
 	} else if tlsCfg.ServerName != "" {
 		t.Errorf("tlsCfg was mutated ServerName (%s) should be empty parsing DSN (%s).", name, tst)
@@ -229,11 +230,11 @@ func TestDSNTLSConfig(t *testing.T) {
 	if err != nil {
 		t.Error(err.Error())
 	}
-	if cfg.tls == nil {
+	if cfg.TLS == nil {
 		t.Error("cfg.tls should not be nil")
 	}
-	if cfg.tls.ServerName != expectedServerName {
-		t.Errorf("cfg.tls.ServerName should be %q, got %q (host with port)", expectedServerName, cfg.tls.ServerName)
+	if cfg.TLS.ServerName != expectedServerName {
+		t.Errorf("cfg.tls.ServerName should be %q, got %q (host with port)", expectedServerName, cfg.TLS.ServerName)
 	}
 
 	dsn = "tcp(example.com)/?tls=true"
@@ -241,11 +242,11 @@ func TestDSNTLSConfig(t *testing.T) {
 	if err != nil {
 		t.Error(err.Error())
 	}
-	if cfg.tls == nil {
+	if cfg.TLS == nil {
 		t.Error("cfg.tls should not be nil")
 	}
-	if cfg.tls.ServerName != expectedServerName {
-		t.Errorf("cfg.tls.ServerName should be %q, got %q (host without port)", expectedServerName, cfg.tls.ServerName)
+	if cfg.TLS.ServerName != expectedServerName {
+		t.Errorf("cfg.tls.ServerName should be %q, got %q (host without port)", expectedServerName, cfg.TLS.ServerName)
 	}
 }
 
@@ -262,7 +263,7 @@ func TestDSNWithCustomTLSQueryEscape(t *testing.T) {
 
 	if err != nil {
 		t.Error(err.Error())
-	} else if cfg.tls.ServerName != name {
+	} else if cfg.TLS.ServerName != name {
 		t.Errorf("did not get the correct TLS ServerName (%s) parsing DSN (%s).", name, dsn)
 	}
 }
@@ -335,12 +336,12 @@ func TestCloneConfig(t *testing.T) {
 		t.Errorf("Config.Clone did not create a separate config struct")
 	}
 
-	if cfg2.tls.ServerName != expectedServerName {
-		t.Errorf("cfg.tls.ServerName should be %q, got %q (host with port)", expectedServerName, cfg.tls.ServerName)
+	if cfg2.TLS.ServerName != expectedServerName {
+		t.Errorf("cfg.tls.ServerName should be %q, got %q (host with port)", expectedServerName, cfg.TLS.ServerName)
 	}
 
-	cfg2.tls.ServerName = "example2.com"
-	if cfg.tls.ServerName == cfg2.tls.ServerName {
+	cfg2.TLS.ServerName = "example2.com"
+	if cfg.TLS.ServerName == cfg2.TLS.ServerName {
 		t.Errorf("changed cfg.tls.Server name should not propagate to original Config")
 	}
 
@@ -384,20 +385,20 @@ func TestNormalizeTLSConfig(t *testing.T) {
 
 			cfg.normalize()
 
-			if cfg.tls == nil {
+			if cfg.TLS == nil {
 				if tc.want != nil {
 					t.Fatal("wanted a tls config but got nil instead")
 				}
 				return
 			}
 
-			if cfg.tls.ServerName != tc.want.ServerName {
+			if cfg.TLS.ServerName != tc.want.ServerName {
 				t.Errorf("tls.ServerName doesn't match (want: '%s', got: '%s')",
-					tc.want.ServerName, cfg.tls.ServerName)
+					tc.want.ServerName, cfg.TLS.ServerName)
 			}
-			if cfg.tls.InsecureSkipVerify != tc.want.InsecureSkipVerify {
+			if cfg.TLS.InsecureSkipVerify != tc.want.InsecureSkipVerify {
 				t.Errorf("tls.InsecureSkipVerify doesn't match (want: %T, got :%T)",
-					tc.want.InsecureSkipVerify, cfg.tls.InsecureSkipVerify)
+					tc.want.InsecureSkipVerify, cfg.TLS.InsecureSkipVerify)
 			}
 		})
 	}
diff --git a/packets.go b/packets.go
index 003584c25..ee05c95a8 100644
--- a/packets.go
+++ b/packets.go
@@ -222,9 +222,9 @@ func (mc *mysqlConn) readHandshakePacket() (data []byte, plugin string, err erro
 	if mc.flags&clientProtocol41 == 0 {
 		return nil, "", ErrOldProtocol
 	}
-	if mc.flags&clientSSL == 0 && mc.cfg.tls != nil {
-		if mc.cfg.TLSConfig == "preferred" {
-			mc.cfg.tls = nil
+	if mc.flags&clientSSL == 0 && mc.cfg.TLS != nil {
+		if mc.cfg.AllowFallbackToPlaintext {
+			mc.cfg.TLS = nil
 		} else {
 			return nil, "", ErrNoTLS
 		}
@@ -292,7 +292,7 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 	}
 
 	// To enable TLS / SSL
-	if mc.cfg.tls != nil {
+	if mc.cfg.TLS != nil {
 		clientFlags |= clientSSL
 	}
 
@@ -356,14 +356,14 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 
 	// SSL Connection Request Packet
 	// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::SSLRequest
-	if mc.cfg.tls != nil {
+	if mc.cfg.TLS != nil {
 		// Send TLS / SSL request packet
 		if err := mc.writePacket(data[:(4+4+1+23)+4]); err != nil {
 			return err
 		}
 
 		// Switch to TLS
-		tlsConn := tls.Client(mc.netConn, mc.cfg.tls)
+		tlsConn := tls.Client(mc.netConn, mc.cfg.TLS)
 		if err := tlsConn.Handshake(); err != nil {
 			return err
 		}

From 5cee457661043566c72c86b89aadbab7b88cce7a Mon Sep 17 00:00:00 2001
From: ICHINOSE Shogo <shogo82148@gmail.com>
Date: Fri, 2 Dec 2022 20:40:24 +0900
Subject: [PATCH 180/282] update changelog for Version 1.7 (#1376)

---
 CHANGELOG.md | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 72a738ed5..77024a820 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,24 @@
+## Version 1.7 (2022-11-29)
+
+Changes:
+
+  - Drop support of Go 1.12 (#1211)
+  - Refactoring `(*textRows).readRow` in a more clear way (#1230)
+  - util: Reduce boundary check in escape functions. (#1316)
+  - enhancement for mysqlConn handleAuthResult (#1250)
+
+New Features:
+
+  - support Is comparison on MySQLError (#1210)
+  - return unsigned in database type name when necessary (#1238)
+  - Add API to express like a --ssl-mode=PREFERRED MySQL client (#1370)
+  - Add SQLState to MySQLError (#1321)
+
+Bugfixes:
+
+  -  Fix parsing 0 year. (#1257)
+
+
 ## Version 1.6 (2021-04-01)
 
 Changes:

From 4591e42e65cf483147a7c7a4f4cfeac81b21c917 Mon Sep 17 00:00:00 2001
From: ICHINOSE Shogo <shogo82148@gmail.com>
Date: Fri, 9 Dec 2022 20:51:20 +0900
Subject: [PATCH 181/282] bump actions/checkout@v3 and actions/setup-go@v3
 (#1375)

* bump actions/checkout@v3 and actions/setup-go@v3

* enable cache of actions/setup-go

* Revert "enable cache of actions/setup-go"

I don't know why, but some jobs fail with "Could not get cache folder paths".
This reverts commit 185228e0e3110b182759332193ebd75ed7054477.
---
 .github/workflows/test.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 703203258..f5ba6b99c 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -66,8 +66,8 @@ jobs:
       fail-fast: false
       matrix: ${{ fromJSON(needs.list.outputs.matrix) }}
     steps:
-      - uses: actions/checkout@v2
-      - uses: actions/setup-go@v2
+      - uses: actions/checkout@v3
+      - uses: actions/setup-go@v3
         with:
           go-version: ${{ matrix.go }}
       - uses: shogo82148/actions-setup-mysql@v1

From af380e92cd245f1815fa16f42ea65472a7cb06ee Mon Sep 17 00:00:00 2001
From: Samantha <hello@entropy.cat>
Date: Wed, 8 Mar 2023 03:16:29 -0500
Subject: [PATCH 182/282] Use SET syntax as specified in the MySQL
 documentation (#1402)

---
 AUTHORS       | 1 +
 connection.go | 4 ++--
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 051327519..6f7041c7a 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -83,6 +83,7 @@ Reed Allman <rdallman10 at gmail.com>
 Richard Wilkes <wilkes at me.com>
 Robert Russell <robert at rrbrussell.com>
 Runrioter Wung <runrioter at gmail.com>
+Samantha Frank <hello at entropy.cat>
 Santhosh Kumar Tekuri <santhosh.tekuri at gmail.com>
 Sho Iizuka <sho.i518 at gmail.com>
 Sho Ikeda <suicaicoca at gmail.com>
diff --git a/connection.go b/connection.go
index 9539077cb..947a883e3 100644
--- a/connection.go
+++ b/connection.go
@@ -71,10 +71,10 @@ func (mc *mysqlConn) handleParams() (err error) {
 				cmdSet.Grow(4 + len(param) + 1 + len(val) + 30*(len(mc.cfg.Params)-1))
 				cmdSet.WriteString("SET ")
 			} else {
-				cmdSet.WriteByte(',')
+				cmdSet.WriteString(", ")
 			}
 			cmdSet.WriteString(param)
-			cmdSet.WriteByte('=')
+			cmdSet.WriteString(" = ")
 			cmdSet.WriteString(val)
 		}
 	}

From d83ecdc268ff92fa198c0bf64356a1479cc83438 Mon Sep 17 00:00:00 2001
From: Phil Porada <pgporada@users.noreply.github.com>
Date: Wed, 29 Mar 2023 21:34:18 -0400
Subject: [PATCH 183/282] Add go1.20 and mariadb10.11 to the testing matrix
 (#1403)

* Add go1.20 and mariadb10.11 to the testing matrix

* Use latest upstream actions-setup-mysql which has support for mariadb 10.11

* Update authors file
---
 .github/workflows/test.yml | 6 ++++--
 AUTHORS                    | 1 +
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index f5ba6b99c..d45ed0fa9 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -23,8 +23,9 @@ jobs:
           import os
           go = [
               # Keep the most recent production release at the top
-              '1.19',
+              '1.20',
               # Older production releases
+              '1.19',
               '1.18',
               '1.17',
               '1.16',
@@ -36,6 +37,7 @@ jobs:
               '8.0',
               '5.7',
               '5.6',
+              'mariadb-10.11',
               'mariadb-10.6',
               'mariadb-10.5',
               'mariadb-10.4',
@@ -70,7 +72,7 @@ jobs:
       - uses: actions/setup-go@v3
         with:
           go-version: ${{ matrix.go }}
-      - uses: shogo82148/actions-setup-mysql@v1
+      - uses: shogo82148/actions-setup-mysql@v1.15.0
         with:
           mysql-version: ${{ matrix.mysql }}
           user: ${{ env.MYSQL_TEST_USER }}
diff --git a/AUTHORS b/AUTHORS
index 6f7041c7a..fb1478c3b 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -78,6 +78,7 @@ Olivier Mengué <dolmen at cpan.org>
 oscarzhao <oscarzhaosl at gmail.com>
 Paul Bonser <misterpib at gmail.com>
 Peter Schultz <peter.schultz at classmarkets.com>
+Phil Porada <philporada at gmail.com>
 Rebecca Chin <rchin at pivotal.io>
 Reed Allman <rdallman10 at gmail.com>
 Richard Wilkes <wilkes at me.com>

From f0e16c6977aae7045c058989971467759e470e99 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Fri, 14 Apr 2023 19:00:15 +0900
Subject: [PATCH 184/282] Increase default maxAllowedPacket size. (#1411)

64MiB is same to MySQL 8.0.
---
 README.md | 2 +-
 const.go  | 2 +-
 errors.go | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index 25de2e5aa..252bbefdf 100644
--- a/README.md
+++ b/README.md
@@ -282,7 +282,7 @@ Please keep in mind, that param values must be [url.QueryEscape](https://golang.
 ##### `maxAllowedPacket`
 ```
 Type:          decimal number
-Default:       4194304
+Default:       64*1024*1024
 ```
 
 Max packet size allowed in bytes. The default value is 4 MiB and should be adjusted to match the server settings. `maxAllowedPacket=0` can be used to automatically fetch the `max_allowed_packet` variable from server *on every connection*.
diff --git a/const.go b/const.go
index b1e6b85ef..64e2bced6 100644
--- a/const.go
+++ b/const.go
@@ -10,7 +10,7 @@ package mysql
 
 const (
 	defaultAuthPlugin       = "mysql_native_password"
-	defaultMaxAllowedPacket = 4 << 20 // 4 MiB
+	defaultMaxAllowedPacket = 64 << 20 // 64 MiB. See https://github.com/go-sql-driver/mysql/issues/1355
 	minProtocolVersion      = 10
 	maxPacketSize           = 1<<24 - 1
 	timeFormat              = "2006-01-02 15:04:05.999999"
diff --git a/errors.go b/errors.go
index 7c037e7d6..ff9a8f088 100644
--- a/errors.go
+++ b/errors.go
@@ -27,7 +27,7 @@ var (
 	ErrOldProtocol       = errors.New("MySQL server does not support required protocol 41+")
 	ErrPktSync           = errors.New("commands out of sync. You can't run this command now")
 	ErrPktSyncMul        = errors.New("commands out of sync. Did you run multiple statements at once?")
-	ErrPktTooLarge       = errors.New("packet for query is too large. Try adjusting the 'max_allowed_packet' variable on the server")
+	ErrPktTooLarge       = errors.New("packet for query is too large. Try adjusting the `Config.MaxAllowedPacket`")
 	ErrBusyBuffer        = errors.New("busy buffer")
 
 	// errBadConnNoWrite is used for connection errors where nothing was sent to the database yet.

From faedeff6d3187aad8fa1d08a206d082f8c371656 Mon Sep 17 00:00:00 2001
From: Simon J Mudd <sjmudd@pobox.com>
Date: Sat, 15 Apr 2023 15:38:33 +0200
Subject: [PATCH 185/282] Correct maxAllowedPacket default value mentioned in
 docs to match the new setting (#1412)

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 252bbefdf..3b5d229aa 100644
--- a/README.md
+++ b/README.md
@@ -285,7 +285,7 @@ Type:          decimal number
 Default:       64*1024*1024
 ```
 
-Max packet size allowed in bytes. The default value is 4 MiB and should be adjusted to match the server settings. `maxAllowedPacket=0` can be used to automatically fetch the `max_allowed_packet` variable from server *on every connection*.
+Max packet size allowed in bytes. The default value is 64 MiB and should be adjusted to match the server settings. `maxAllowedPacket=0` can be used to automatically fetch the `max_allowed_packet` variable from server *on every connection*.
 
 ##### `multiStatements`
 

From 8503110d880a06b863c5a93c8214068c71f16e9d Mon Sep 17 00:00:00 2001
From: cui fliter <imcusg@gmail.com>
Date: Tue, 25 Apr 2023 13:46:24 +0800
Subject: [PATCH 186/282] fix some comments (#1417)

Signed-off-by: cui fliter <imcusg@gmail.com>
---
 driver_test.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/driver_test.go b/driver_test.go
index 4850498d0..a1c776728 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -2703,7 +2703,7 @@ func TestContextBeginIsolationLevel(t *testing.T) {
 		if err := row.Scan(&v); err != nil {
 			dbt.Fatal(err)
 		}
-		// Because writer transaction wasn't commited yet, it should be available
+		// Because writer transaction wasn't committed yet, it should be available
 		if v != 0 {
 			dbt.Errorf("expected val to be 0, got %d", v)
 		}
@@ -2717,7 +2717,7 @@ func TestContextBeginIsolationLevel(t *testing.T) {
 		if err := row.Scan(&v); err != nil {
 			dbt.Fatal(err)
 		}
-		// Data written by writer transaction is already commited, it should be selectable
+		// Data written by writer transaction is already committed, it should be selectable
 		if v != 1 {
 			dbt.Errorf("expected val to be 1, got %d", v)
 		}

From f20b2863636093e5fbf1481b59bdaff3b0fbb779 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Tue, 25 Apr 2023 19:02:15 +0900
Subject: [PATCH 187/282] Update changelog for version 1.7.1 (#1418)

---
 CHANGELOG.md | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 77024a820..5166e4adb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,16 @@
+## Version 1.7.1 (2023-04-25)
+
+Changes:
+
+  - bump actions/checkout@v3 and actions/setup-go@v3 (#1375)
+  - Add go1.20 and mariadb10.11 to the testing matrix (#1403)
+  - Increase default maxAllowedPacket size. (#1411)
+
+Bugfixes:
+
+  - Use SET syntax as specified in the MySQL documentation (#1402)
+
+
 ## Version 1.7 (2022-11-29)
 
 Changes:

From aa0194dbeccdb9e79d5775f0a8903c3cdbb4e753 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Tue, 25 Apr 2023 20:36:57 +0900
Subject: [PATCH 188/282] Drop Go 1.13-17 support (#1420)

Start v1.8 development
---
 .github/workflows/test.yml | 11 +++--------
 README.md                  |  4 ++--
 go.mod                     |  2 +-
 3 files changed, 6 insertions(+), 11 deletions(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index d45ed0fa9..cd474767b 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -27,11 +27,6 @@ jobs:
               # Older production releases
               '1.19',
               '1.18',
-              '1.17',
-              '1.16',
-              '1.15',
-              '1.14',
-              '1.13',
           ]
           mysql = [
               '8.0',
@@ -47,7 +42,7 @@ jobs:
           includes = []
           # Go versions compatibility check
           for v in go[1:]:
-                  includes.append({'os': 'ubuntu-latest', 'go': v, 'mysql': mysql[0]})
+              includes.append({'os': 'ubuntu-latest', 'go': v, 'mysql': mysql[0]})
 
           matrix = {
               # OS vs MySQL versions
@@ -69,10 +64,10 @@ jobs:
       matrix: ${{ fromJSON(needs.list.outputs.matrix) }}
     steps:
       - uses: actions/checkout@v3
-      - uses: actions/setup-go@v3
+      - uses: actions/setup-go@v4
         with:
           go-version: ${{ matrix.go }}
-      - uses: shogo82148/actions-setup-mysql@v1.15.0
+      - uses: shogo82148/actions-setup-mysql@v1.16.0
         with:
           mysql-version: ${{ matrix.mysql }}
           user: ${{ env.MYSQL_TEST_USER }}
diff --git a/README.md b/README.md
index 3b5d229aa..5a242e9d7 100644
--- a/README.md
+++ b/README.md
@@ -40,8 +40,8 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
   * Optional placeholder interpolation
 
 ## Requirements
-  * Go 1.13 or higher. We aim to support the 3 latest versions of Go.
-  * MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
+  * Go 1.18 or higher. We aim to support the 3 latest versions of Go.
+  * MySQL (5.6+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
 
 ---------------------------------------
 
diff --git a/go.mod b/go.mod
index 251110478..77bbb8dbf 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,3 @@
 module github.com/go-sql-driver/mysql
 
-go 1.13
+go 1.18

From cffc85ce9efe406a98c1d82749a237cc0338a8b2 Mon Sep 17 00:00:00 2001
From: Evil Puncker <EPuncker@users.noreply.github.com>
Date: Tue, 25 Apr 2023 19:10:42 -0300
Subject: [PATCH 189/282] Reduced allocation on connection.go (#1421)

reduces allocations when there is only one param because current calculation is off by 2
---
 connection.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/connection.go b/connection.go
index 947a883e3..0aeef207b 100644
--- a/connection.go
+++ b/connection.go
@@ -68,7 +68,7 @@ func (mc *mysqlConn) handleParams() (err error) {
 		default:
 			if cmdSet.Len() == 0 {
 				// Heuristic: 29 chars for each other key=value to reduce reallocations
-				cmdSet.Grow(4 + len(param) + 1 + len(val) + 30*(len(mc.cfg.Params)-1))
+				cmdSet.Grow(4 + len(param) + 3 + len(val) + 30*(len(mc.cfg.Params)-1))
 				cmdSet.WriteString("SET ")
 			} else {
 				cmdSet.WriteString(", ")

From fbfb3f6a34bd0d4e73e1569831e054ec36b38ce9 Mon Sep 17 00:00:00 2001
From: jypelle <52546084+jypelle@users.noreply.github.com>
Date: Mon, 1 May 2023 17:52:55 +0200
Subject: [PATCH 190/282] Adding DeregisterDialContext (#1422)

Co-authored-by: jypelle <jy@fabriks.eu>
---
 AUTHORS   |  1 +
 driver.go | 11 +++++++++++
 2 files changed, 12 insertions(+)

diff --git a/AUTHORS b/AUTHORS
index fb1478c3b..ea9b96789 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -47,6 +47,7 @@ INADA Naoki <songofacandy at gmail.com>
 Jacek Szwec <szwec.jacek at gmail.com>
 James Harr <james.harr at gmail.com>
 Janek Vedock <janekvedock at comcast.net>
+Jean-Yves Pellé <jy at pelle.link>
 Jeff Hodges <jeff at somethingsimilar.com>
 Jeffrey Charles <jeffreycharles at gmail.com>
 Jerome Meyer <jxmeyer at gmail.com>
diff --git a/driver.go b/driver.go
index ad7aec215..8b0c3ec0a 100644
--- a/driver.go
+++ b/driver.go
@@ -55,6 +55,17 @@ func RegisterDialContext(net string, dial DialContextFunc) {
 	dials[net] = dial
 }
 
+// DeregisterDialContext removes the custom dial function registered with the given net.
+func DeregisterDialContext(net string) {
+	dialsLock.Lock()
+	defer dialsLock.Unlock()
+	if dials != nil {
+		if _, ok := dials[net]; ok {
+			delete(dials, net)
+		}
+	}
+}
+
 // RegisterDial registers a custom dial function. It can then be used by the
 // network address mynet(addr), where mynet is the registered new network.
 // addr is passed as a parameter to the dial function.

From 191a7c4c519ef60cf3e8656fde8728eee9194308 Mon Sep 17 00:00:00 2001
From: frozenbonito <t.aoki1130@gmail.com>
Date: Thu, 4 May 2023 23:30:22 +0900
Subject: [PATCH 191/282] Make logger configurable per Connector (#1408)

---
 AUTHORS            |  1 +
 auth.go            |  2 +-
 connection.go      | 16 ++++++++--------
 connection_test.go |  1 +
 connector.go       |  2 +-
 driver_test.go     |  2 +-
 dsn.go             |  6 ++++++
 dsn_test.go        | 34 +++++++++++++++++-----------------
 errors.go          | 12 +++++++++---
 errors_test.go     |  6 +++---
 packets.go         | 26 +++++++++++++-------------
 packets_test.go    |  1 +
 statement.go       |  4 ++--
 13 files changed, 64 insertions(+), 49 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index ea9b96789..129ca665a 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -96,6 +96,7 @@ Stan Putrya <root.vagner at gmail.com>
 Stanley Gunawan <gunawan.stanley at gmail.com>
 Steven Hartland <steven.hartland at multiplay.co.uk>
 Tan Jinhua <312841925 at qq.com>
+Tetsuro Aoki <t.aoki1130 at gmail.com>
 Thomas Wodarek <wodarekwebpage at gmail.com>
 Tim Ruffles <timruffles at gmail.com>
 Tom Jenkinson <tom at tjenkinson.me>
diff --git a/auth.go b/auth.go
index 1ff203e57..b591e7b8a 100644
--- a/auth.go
+++ b/auth.go
@@ -291,7 +291,7 @@ func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, error) {
 		return enc, err
 
 	default:
-		errLog.Print("unknown auth plugin:", plugin)
+		mc.cfg.Logger.Print("unknown auth plugin:", plugin)
 		return nil, ErrUnknownPlugin
 	}
 }
diff --git a/connection.go b/connection.go
index 0aeef207b..a7da9e7e2 100644
--- a/connection.go
+++ b/connection.go
@@ -105,7 +105,7 @@ func (mc *mysqlConn) Begin() (driver.Tx, error) {
 
 func (mc *mysqlConn) begin(readOnly bool) (driver.Tx, error) {
 	if mc.closed.Load() {
-		errLog.Print(ErrInvalidConn)
+		mc.cfg.Logger.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
 	var q string
@@ -147,7 +147,7 @@ func (mc *mysqlConn) cleanup() {
 		return
 	}
 	if err := mc.netConn.Close(); err != nil {
-		errLog.Print(err)
+		mc.cfg.Logger.Print(err)
 	}
 }
 
@@ -163,14 +163,14 @@ func (mc *mysqlConn) error() error {
 
 func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
 	if mc.closed.Load() {
-		errLog.Print(ErrInvalidConn)
+		mc.cfg.Logger.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
 	// Send command
 	err := mc.writeCommandPacketStr(comStmtPrepare, query)
 	if err != nil {
 		// STMT_PREPARE is safe to retry.  So we can return ErrBadConn here.
-		errLog.Print(err)
+		mc.cfg.Logger.Print(err)
 		return nil, driver.ErrBadConn
 	}
 
@@ -204,7 +204,7 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
 	buf, err := mc.buf.takeCompleteBuffer()
 	if err != nil {
 		// can not take the buffer. Something must be wrong with the connection
-		errLog.Print(err)
+		mc.cfg.Logger.Print(err)
 		return "", ErrInvalidConn
 	}
 	buf = buf[:0]
@@ -296,7 +296,7 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
 
 func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
 	if mc.closed.Load() {
-		errLog.Print(ErrInvalidConn)
+		mc.cfg.Logger.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
 	if len(args) != 0 {
@@ -357,7 +357,7 @@ func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, erro
 
 func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error) {
 	if mc.closed.Load() {
-		errLog.Print(ErrInvalidConn)
+		mc.cfg.Logger.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
 	if len(args) != 0 {
@@ -451,7 +451,7 @@ func (mc *mysqlConn) finish() {
 // Ping implements driver.Pinger interface
 func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
 	if mc.closed.Load() {
-		errLog.Print(ErrInvalidConn)
+		mc.cfg.Logger.Print(ErrInvalidConn)
 		return driver.ErrBadConn
 	}
 
diff --git a/connection_test.go b/connection_test.go
index b6764a2f6..98c985ae1 100644
--- a/connection_test.go
+++ b/connection_test.go
@@ -179,6 +179,7 @@ func TestPingErrInvalidConn(t *testing.T) {
 		buf:              newBuffer(nc),
 		maxAllowedPacket: defaultMaxAllowedPacket,
 		closech:          make(chan struct{}),
+		cfg:              NewConfig(),
 	}
 
 	err := ms.Ping(context.Background())
diff --git a/connector.go b/connector.go
index d567b4e4f..a5c988e13 100644
--- a/connector.go
+++ b/connector.go
@@ -92,7 +92,7 @@ func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
 	authResp, err := mc.auth(authData, plugin)
 	if err != nil {
 		// try the default auth plugin, if using the requested plugin failed
-		errLog.Print("could not use requested auth plugin '"+plugin+"': ", err.Error())
+		c.cfg.Logger.Print("could not use requested auth plugin '"+plugin+"': ", err.Error())
 		plugin = defaultAuthPlugin
 		authResp, err = mc.auth(authData, plugin)
 		if err != nil {
diff --git a/driver_test.go b/driver_test.go
index a1c776728..1741a13ef 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -1995,7 +1995,7 @@ func TestInsertRetrieveEscapedData(t *testing.T) {
 func TestUnixSocketAuthFail(t *testing.T) {
 	runTests(t, dsn, func(dbt *DBTest) {
 		// Save the current logger so we can restore it.
-		oldLogger := errLog
+		oldLogger := defaultLogger
 
 		// Set a new logger so we can capture its output.
 		buffer := bytes.NewBuffer(make([]byte, 0, 64))
diff --git a/dsn.go b/dsn.go
index 4b71aaab0..ded459c94 100644
--- a/dsn.go
+++ b/dsn.go
@@ -50,6 +50,7 @@ type Config struct {
 	Timeout          time.Duration     // Dial timeout
 	ReadTimeout      time.Duration     // I/O read timeout
 	WriteTimeout     time.Duration     // I/O write timeout
+	Logger           Logger            // Logger
 
 	AllowAllFiles            bool // Allow all files to be used with LOAD DATA LOCAL INFILE
 	AllowCleartextPasswords  bool // Allows the cleartext client side plugin
@@ -71,6 +72,7 @@ func NewConfig() *Config {
 		Collation:            defaultCollation,
 		Loc:                  time.UTC,
 		MaxAllowedPacket:     defaultMaxAllowedPacket,
+		Logger:               defaultLogger,
 		AllowNativePasswords: true,
 		CheckConnLiveness:    true,
 	}
@@ -153,6 +155,10 @@ func (cfg *Config) normalize() error {
 		}
 	}
 
+	if cfg.Logger == nil {
+		cfg.Logger = defaultLogger
+	}
+
 	return nil
 }
 
diff --git a/dsn_test.go b/dsn_test.go
index 41a6a29fa..cb97d557e 100644
--- a/dsn_test.go
+++ b/dsn_test.go
@@ -22,55 +22,55 @@ var testDSNs = []struct {
 	out *Config
 }{{
 	"username:password@protocol(address)/dbname?param=value",
-	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
+	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true",
-	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true, ColumnsWithAlias: true},
+	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, ColumnsWithAlias: true},
 }, {
 	"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true&multiStatements=true",
-	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true, ColumnsWithAlias: true, MultiStatements: true},
+	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, ColumnsWithAlias: true, MultiStatements: true},
 }, {
 	"user@unix(/path/to/socket)/dbname?charset=utf8",
-	&Config{User: "user", Net: "unix", Addr: "/path/to/socket", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
+	&Config{User: "user", Net: "unix", Addr: "/path/to/socket", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true",
-	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true, TLSConfig: "true"},
+	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, TLSConfig: "true"},
 }, {
 	"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify",
-	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8mb4,utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true, TLSConfig: "skip-verify"},
+	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8mb4,utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, TLSConfig: "skip-verify"},
 }, {
 	"user:password@/dbname?loc=UTC&timeout=30s&readTimeout=1s&writeTimeout=1s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci&maxAllowedPacket=16777216&tls=false&allowCleartextPasswords=true&parseTime=true&rejectReadOnly=true",
-	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_unicode_ci", Loc: time.UTC, TLSConfig: "false", AllowCleartextPasswords: true, AllowNativePasswords: true, Timeout: 30 * time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, AllowAllFiles: true, AllowOldPasswords: true, CheckConnLiveness: true, ClientFoundRows: true, MaxAllowedPacket: 16777216, ParseTime: true, RejectReadOnly: true},
+	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_unicode_ci", Loc: time.UTC, TLSConfig: "false", AllowCleartextPasswords: true, AllowNativePasswords: true, Timeout: 30 * time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, Logger: defaultLogger, AllowAllFiles: true, AllowOldPasswords: true, CheckConnLiveness: true, ClientFoundRows: true, MaxAllowedPacket: 16777216, ParseTime: true, RejectReadOnly: true},
 }, {
 	"user:password@/dbname?allowNativePasswords=false&checkConnLiveness=false&maxAllowedPacket=0&allowFallbackToPlaintext=true",
-	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: 0, AllowFallbackToPlaintext: true, AllowNativePasswords: false, CheckConnLiveness: false},
+	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: 0, Logger: defaultLogger, AllowFallbackToPlaintext: true, AllowNativePasswords: false, CheckConnLiveness: false},
 }, {
 	"user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local",
-	&Config{User: "user", Passwd: "p@ss(word)", Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:80", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.Local, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
+	&Config{User: "user", Passwd: "p@ss(word)", Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:80", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.Local, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"/dbname",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"@/",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"/",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"user:p@/ssword@/",
-	&Config{User: "user", Passwd: "p@/ssword", Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
+	&Config{User: "user", Passwd: "p@/ssword", Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"unix/?arg=%2Fsome%2Fpath.ext",
-	&Config{Net: "unix", Addr: "/tmp/mysql.sock", Params: map[string]string{"arg": "/some/path.ext"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
+	&Config{Net: "unix", Addr: "/tmp/mysql.sock", Params: map[string]string{"arg": "/some/path.ext"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"tcp(127.0.0.1)/dbname",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"tcp(de:ad:be:ef::ca:fe)/dbname",
-	&Config{Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
+	&Config{Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
 },
 }
 
diff --git a/errors.go b/errors.go
index ff9a8f088..5680b6c05 100644
--- a/errors.go
+++ b/errors.go
@@ -37,20 +37,26 @@ var (
 	errBadConnNoWrite = errors.New("bad connection")
 )
 
-var errLog = Logger(log.New(os.Stderr, "[mysql] ", log.Ldate|log.Ltime|log.Lshortfile))
+var defaultLogger = Logger(log.New(os.Stderr, "[mysql] ", log.Ldate|log.Ltime|log.Lshortfile))
 
 // Logger is used to log critical error messages.
 type Logger interface {
 	Print(v ...interface{})
 }
 
-// SetLogger is used to set the logger for critical errors.
+// NopLogger is a nop implementation of the Logger interface.
+type NopLogger struct{}
+
+// Print implements Logger interface.
+func (nl *NopLogger) Print(_ ...interface{}) {}
+
+// SetLogger is used to set the default logger for critical errors.
 // The initial logger is os.Stderr.
 func SetLogger(logger Logger) error {
 	if logger == nil {
 		return errors.New("logger is nil")
 	}
-	errLog = logger
+	defaultLogger = logger
 	return nil
 }
 
diff --git a/errors_test.go b/errors_test.go
index 43213f98e..53d634454 100644
--- a/errors_test.go
+++ b/errors_test.go
@@ -16,9 +16,9 @@ import (
 )
 
 func TestErrorsSetLogger(t *testing.T) {
-	previous := errLog
+	previous := defaultLogger
 	defer func() {
-		errLog = previous
+		defaultLogger = previous
 	}()
 
 	// set up logger
@@ -28,7 +28,7 @@ func TestErrorsSetLogger(t *testing.T) {
 
 	// print
 	SetLogger(logger)
-	errLog.Print("test")
+	defaultLogger.Print("test")
 
 	// check result
 	if actual := buffer.String(); actual != expected {
diff --git a/packets.go b/packets.go
index ee05c95a8..8fd67997b 100644
--- a/packets.go
+++ b/packets.go
@@ -34,7 +34,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
 			if cerr := mc.canceled.Value(); cerr != nil {
 				return nil, cerr
 			}
-			errLog.Print(err)
+			mc.cfg.Logger.Print(err)
 			mc.Close()
 			return nil, ErrInvalidConn
 		}
@@ -56,7 +56,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
 		if pktLen == 0 {
 			// there was no previous packet
 			if prevData == nil {
-				errLog.Print(ErrMalformPkt)
+				mc.cfg.Logger.Print(ErrMalformPkt)
 				mc.Close()
 				return nil, ErrInvalidConn
 			}
@@ -70,7 +70,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
 			if cerr := mc.canceled.Value(); cerr != nil {
 				return nil, cerr
 			}
-			errLog.Print(err)
+			mc.cfg.Logger.Print(err)
 			mc.Close()
 			return nil, ErrInvalidConn
 		}
@@ -119,7 +119,7 @@ func (mc *mysqlConn) writePacket(data []byte) error {
 			}
 		}
 		if err != nil {
-			errLog.Print("closing bad idle connection: ", err)
+			mc.cfg.Logger.Print("closing bad idle connection: ", err)
 			mc.Close()
 			return driver.ErrBadConn
 		}
@@ -161,7 +161,7 @@ func (mc *mysqlConn) writePacket(data []byte) error {
 		// Handle error
 		if err == nil { // n != len(data)
 			mc.cleanup()
-			errLog.Print(ErrMalformPkt)
+			mc.cfg.Logger.Print(ErrMalformPkt)
 		} else {
 			if cerr := mc.canceled.Value(); cerr != nil {
 				return cerr
@@ -171,7 +171,7 @@ func (mc *mysqlConn) writePacket(data []byte) error {
 				return errBadConnNoWrite
 			}
 			mc.cleanup()
-			errLog.Print(err)
+			mc.cfg.Logger.Print(err)
 		}
 		return ErrInvalidConn
 	}
@@ -322,7 +322,7 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 	data, err := mc.buf.takeSmallBuffer(pktLen + 4)
 	if err != nil {
 		// cannot take the buffer. Something must be wrong with the connection
-		errLog.Print(err)
+		mc.cfg.Logger.Print(err)
 		return errBadConnNoWrite
 	}
 
@@ -404,7 +404,7 @@ func (mc *mysqlConn) writeAuthSwitchPacket(authData []byte) error {
 	data, err := mc.buf.takeSmallBuffer(pktLen)
 	if err != nil {
 		// cannot take the buffer. Something must be wrong with the connection
-		errLog.Print(err)
+		mc.cfg.Logger.Print(err)
 		return errBadConnNoWrite
 	}
 
@@ -424,7 +424,7 @@ func (mc *mysqlConn) writeCommandPacket(command byte) error {
 	data, err := mc.buf.takeSmallBuffer(4 + 1)
 	if err != nil {
 		// cannot take the buffer. Something must be wrong with the connection
-		errLog.Print(err)
+		mc.cfg.Logger.Print(err)
 		return errBadConnNoWrite
 	}
 
@@ -443,7 +443,7 @@ func (mc *mysqlConn) writeCommandPacketStr(command byte, arg string) error {
 	data, err := mc.buf.takeBuffer(pktLen + 4)
 	if err != nil {
 		// cannot take the buffer. Something must be wrong with the connection
-		errLog.Print(err)
+		mc.cfg.Logger.Print(err)
 		return errBadConnNoWrite
 	}
 
@@ -464,7 +464,7 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
 	data, err := mc.buf.takeSmallBuffer(4 + 1 + 4)
 	if err != nil {
 		// cannot take the buffer. Something must be wrong with the connection
-		errLog.Print(err)
+		mc.cfg.Logger.Print(err)
 		return errBadConnNoWrite
 	}
 
@@ -938,7 +938,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 	}
 	if err != nil {
 		// cannot take the buffer. Something must be wrong with the connection
-		errLog.Print(err)
+		mc.cfg.Logger.Print(err)
 		return errBadConnNoWrite
 	}
 
@@ -1137,7 +1137,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 		if valuesCap != cap(paramValues) {
 			data = append(data[:pos], paramValues...)
 			if err = mc.buf.store(data); err != nil {
-				errLog.Print(err)
+				mc.cfg.Logger.Print(err)
 				return errBadConnNoWrite
 			}
 		}
diff --git a/packets_test.go b/packets_test.go
index b61e4dbf7..cacec1c68 100644
--- a/packets_test.go
+++ b/packets_test.go
@@ -265,6 +265,7 @@ func TestReadPacketFail(t *testing.T) {
 	mc := &mysqlConn{
 		buf:     newBuffer(conn),
 		closech: make(chan struct{}),
+		cfg:     NewConfig(),
 	}
 
 	// illegal empty (stand-alone) packet
diff --git a/statement.go b/statement.go
index 10ece8bd6..d8b3975a5 100644
--- a/statement.go
+++ b/statement.go
@@ -51,7 +51,7 @@ func (stmt *mysqlStmt) CheckNamedValue(nv *driver.NamedValue) (err error) {
 
 func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
 	if stmt.mc.closed.Load() {
-		errLog.Print(ErrInvalidConn)
+		stmt.mc.cfg.Logger.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
 	// Send command
@@ -99,7 +99,7 @@ func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
 
 func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) {
 	if stmt.mc.closed.Load() {
-		errLog.Print(ErrInvalidConn)
+		stmt.mc.cfg.Logger.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
 	// Send command

From 0b40aee005dafcce42833210b672c1f0930008aa Mon Sep 17 00:00:00 2001
From: wayyoungboy <35394786+wayyoungboy@users.noreply.github.com>
Date: Sat, 6 May 2023 17:07:41 +0800
Subject: [PATCH 192/282] Avoid panic in TestRowsColumnTypes (#1426)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* optimized the execution flow of the TestRowsColumnTypes unit test

* Update driver_test.go

---------

Co-authored-by: 渠磊 <xuyan.wxy@oceanbase.com>
Co-authored-by: Inada Naoki <songofacandy@gmail.com>
---
 driver_test.go | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/driver_test.go b/driver_test.go
index 1741a13ef..d24488a82 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -2945,7 +2945,10 @@ func TestRowsColumnTypes(t *testing.T) {
 				continue
 			}
 		}
-
+		// Avoid panic caused by nil scantype.
+		if t.Failed() {
+			return
+		}
 		values := make([]interface{}, len(tt))
 		for i := range values {
 			values[i] = reflect.New(types[i]).Interface()

From 736b6faabe4947c9a0a7fef6407839dc72114011 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Sun, 7 May 2023 20:24:09 +0900
Subject: [PATCH 193/282] Stop `ColumnTypeScanType()` from returning
 `sql.RawBytes` (#1424)

ColumnTypeScanType() returns []byte, string, or sql.NullString.
It returned sql.RawBytes but it was dangoerous.

Fixes #1423
---
 driver_test.go | 67 +++++++++++++++++++++++++-------------------------
 fields.go      | 48 +++++++++++++++++++++---------------
 2 files changed, 62 insertions(+), 53 deletions(-)

diff --git a/driver_test.go b/driver_test.go
index d24488a82..50c617274 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -2778,13 +2778,18 @@ func TestRowsColumnTypes(t *testing.T) {
 	nd1 := sql.NullTime{Time: time.Date(2006, 01, 02, 0, 0, 0, 0, time.UTC), Valid: true}
 	nd2 := sql.NullTime{Time: time.Date(2006, 03, 04, 0, 0, 0, 0, time.UTC), Valid: true}
 	ndNULL := sql.NullTime{Time: time.Time{}, Valid: false}
-	rbNULL := sql.RawBytes(nil)
-	rb0 := sql.RawBytes("0")
-	rb42 := sql.RawBytes("42")
-	rbTest := sql.RawBytes("Test")
-	rb0pad4 := sql.RawBytes("0\x00\x00\x00") // BINARY right-pads values with 0x00
-	rbx0 := sql.RawBytes("\x00")
-	rbx42 := sql.RawBytes("\x42")
+	bNULL := []byte(nil)
+	nsNULL := sql.NullString{String: "", Valid: false}
+	// Helper function to build NullString from string literal.
+	ns := func(s string) sql.NullString { return sql.NullString{String: s, Valid: true} }
+	ns0 := ns("0")
+	b0 := []byte("0")
+	b42 := []byte("42")
+	nsTest := ns("Test")
+	bTest := []byte("Test")
+	b0pad4 := []byte("0\x00\x00\x00") // BINARY right-pads values with 0x00
+	bx0 := []byte("\x00")
+	bx42 := []byte("\x42")
 
 	var columns = []struct {
 		name             string
@@ -2797,7 +2802,7 @@ func TestRowsColumnTypes(t *testing.T) {
 		valuesIn         [3]string
 		valuesOut        [3]interface{}
 	}{
-		{"bit8null", "BIT(8)", "BIT", scanTypeRawBytes, true, 0, 0, [3]string{"0x0", "NULL", "0x42"}, [3]interface{}{rbx0, rbNULL, rbx42}},
+		{"bit8null", "BIT(8)", "BIT", scanTypeBytes, true, 0, 0, [3]string{"0x0", "NULL", "0x42"}, [3]interface{}{bx0, bNULL, bx42}},
 		{"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}},
@@ -2817,24 +2822,24 @@ func TestRowsColumnTypes(t *testing.T) {
 		{"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}},
-		{"binary4null", "BINARY(4)", "BINARY", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0pad4, rbNULL, rbTest}},
-		{"varbinary42", "VARBINARY(42) NOT NULL", "VARBINARY", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
-		{"tinyblobnull", "TINYBLOB", "BLOB", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
-		{"tinytextnull", "TINYTEXT", "TEXT", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
-		{"blobnull", "BLOB", "BLOB", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
-		{"textnull", "TEXT", "TEXT", scanTypeRawBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{rb0, rbNULL, rbTest}},
-		{"mediumblob", "MEDIUMBLOB NOT NULL", "BLOB", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
-		{"mediumtext", "MEDIUMTEXT NOT NULL", "TEXT", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
-		{"longblob", "LONGBLOB NOT NULL", "BLOB", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
-		{"longtext", "LONGTEXT NOT NULL", "TEXT", scanTypeRawBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{rb0, rbTest, rb42}},
+		{"decimal1", "DECIMAL(10,6) NOT NULL", "DECIMAL", scanTypeString, false, 10, 6, [3]string{"0", "13.37", "1234.123456"}, [3]interface{}{"0.000000", "13.370000", "1234.123456"}},
+		{"decimal1null", "DECIMAL(10,6)", "DECIMAL", scanTypeNullString, true, 10, 6, [3]string{"0", "NULL", "1234.123456"}, [3]interface{}{ns("0.000000"), nsNULL, ns("1234.123456")}},
+		{"decimal2", "DECIMAL(8,4) NOT NULL", "DECIMAL", scanTypeString, false, 8, 4, [3]string{"0", "13.37", "1234.123456"}, [3]interface{}{"0.0000", "13.3700", "1234.1235"}},
+		{"decimal2null", "DECIMAL(8,4)", "DECIMAL", scanTypeNullString, true, 8, 4, [3]string{"0", "NULL", "1234.123456"}, [3]interface{}{ns("0.0000"), nsNULL, ns("1234.1235")}},
+		{"decimal3", "DECIMAL(5,0) NOT NULL", "DECIMAL", scanTypeString, false, 5, 0, [3]string{"0", "13.37", "-12345.123456"}, [3]interface{}{"0", "13", "-12345"}},
+		{"decimal3null", "DECIMAL(5,0)", "DECIMAL", scanTypeNullString, true, 5, 0, [3]string{"0", "NULL", "-12345.123456"}, [3]interface{}{ns0, nsNULL, ns("-12345")}},
+		{"char25null", "CHAR(25)", "CHAR", scanTypeNullString, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{ns0, nsNULL, nsTest}},
+		{"varchar42", "VARCHAR(42) NOT NULL", "VARCHAR", scanTypeString, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{"0", "Test", "42"}},
+		{"binary4null", "BINARY(4)", "BINARY", scanTypeBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{b0pad4, bNULL, bTest}},
+		{"varbinary42", "VARBINARY(42) NOT NULL", "VARBINARY", scanTypeBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{b0, bTest, b42}},
+		{"tinyblobnull", "TINYBLOB", "BLOB", scanTypeBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{b0, bNULL, bTest}},
+		{"tinytextnull", "TINYTEXT", "TEXT", scanTypeNullString, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{ns0, nsNULL, nsTest}},
+		{"blobnull", "BLOB", "BLOB", scanTypeBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{b0, bNULL, bTest}},
+		{"textnull", "TEXT", "TEXT", scanTypeNullString, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{ns0, nsNULL, nsTest}},
+		{"mediumblob", "MEDIUMBLOB NOT NULL", "BLOB", scanTypeBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{b0, bTest, b42}},
+		{"mediumtext", "MEDIUMTEXT NOT NULL", "TEXT", scanTypeString, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{"0", "Test", "42"}},
+		{"longblob", "LONGBLOB NOT NULL", "BLOB", scanTypeBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{b0, bTest, b42}},
+		{"longtext", "LONGTEXT NOT NULL", "TEXT", scanTypeString, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{"0", "Test", "42"}},
 		{"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}},
@@ -2959,14 +2964,10 @@ func TestRowsColumnTypes(t *testing.T) {
 			if err != nil {
 				t.Fatalf("failed to scan values in %v", err)
 			}
-			for j := range values {
-				value := reflect.ValueOf(values[j]).Elem().Interface()
+			for j, value := range values {
+				value := reflect.ValueOf(value).Elem().Interface()
 				if !reflect.DeepEqual(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])
-					}
+					t.Errorf("row %d, column %d: %v != %v", i, j, value, columns[j].valuesOut[i])
 				}
 			}
 			i++
diff --git a/fields.go b/fields.go
index e0654a83d..18c23e0cb 100644
--- a/fields.go
+++ b/fields.go
@@ -110,21 +110,23 @@ func (mf *mysqlField) typeDatabaseName() string {
 }
 
 var (
-	scanTypeFloat32   = reflect.TypeOf(float32(0))
-	scanTypeFloat64   = reflect.TypeOf(float64(0))
-	scanTypeInt8      = reflect.TypeOf(int8(0))
-	scanTypeInt16     = reflect.TypeOf(int16(0))
-	scanTypeInt32     = reflect.TypeOf(int32(0))
-	scanTypeInt64     = reflect.TypeOf(int64(0))
-	scanTypeNullFloat = reflect.TypeOf(sql.NullFloat64{})
-	scanTypeNullInt   = reflect.TypeOf(sql.NullInt64{})
-	scanTypeNullTime  = reflect.TypeOf(sql.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{}))
+	scanTypeFloat32    = reflect.TypeOf(float32(0))
+	scanTypeFloat64    = reflect.TypeOf(float64(0))
+	scanTypeInt8       = reflect.TypeOf(int8(0))
+	scanTypeInt16      = reflect.TypeOf(int16(0))
+	scanTypeInt32      = reflect.TypeOf(int32(0))
+	scanTypeInt64      = reflect.TypeOf(int64(0))
+	scanTypeNullFloat  = reflect.TypeOf(sql.NullFloat64{})
+	scanTypeNullInt    = reflect.TypeOf(sql.NullInt64{})
+	scanTypeNullTime   = reflect.TypeOf(sql.NullTime{})
+	scanTypeUint8      = reflect.TypeOf(uint8(0))
+	scanTypeUint16     = reflect.TypeOf(uint16(0))
+	scanTypeUint32     = reflect.TypeOf(uint32(0))
+	scanTypeUint64     = reflect.TypeOf(uint64(0))
+	scanTypeString     = reflect.TypeOf("")
+	scanTypeNullString = reflect.TypeOf(sql.NullString{})
+	scanTypeBytes      = reflect.TypeOf([]byte{})
+	scanTypeUnknown    = reflect.TypeOf(new(interface{}))
 )
 
 type mysqlField struct {
@@ -187,12 +189,18 @@ func (mf *mysqlField) scanType() reflect.Type {
 		}
 		return scanTypeNullFloat
 
+	case fieldTypeBit, fieldTypeTinyBLOB, fieldTypeMediumBLOB, fieldTypeLongBLOB,
+		fieldTypeBLOB, fieldTypeVarString, fieldTypeString, fieldTypeGeometry:
+		if mf.charSet == 63 /* binary */ {
+			return scanTypeBytes
+		}
+		fallthrough
 	case fieldTypeDecimal, fieldTypeNewDecimal, fieldTypeVarChar,
-		fieldTypeBit, fieldTypeEnum, fieldTypeSet, fieldTypeTinyBLOB,
-		fieldTypeMediumBLOB, fieldTypeLongBLOB, fieldTypeBLOB,
-		fieldTypeVarString, fieldTypeString, fieldTypeGeometry, fieldTypeJSON,
-		fieldTypeTime:
-		return scanTypeRawBytes
+		fieldTypeEnum, fieldTypeSet, fieldTypeJSON, fieldTypeTime:
+		if mf.flags&flagNotNULL != 0 {
+			return scanTypeString
+		}
+		return scanTypeNullString
 
 	case fieldTypeDate, fieldTypeNewDate,
 		fieldTypeTimestamp, fieldTypeDateTime:

From 081308f66228fdc51224614d1cf414c918cc1596 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Sun, 7 May 2023 20:25:21 +0900
Subject: [PATCH 194/282] Add benchmark to receive massive rows. (#1415)

---
 benchmark_test.go | 56 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 56 insertions(+)

diff --git a/benchmark_test.go b/benchmark_test.go
index 97ed781f8..fc70df60d 100644
--- a/benchmark_test.go
+++ b/benchmark_test.go
@@ -372,3 +372,59 @@ func BenchmarkQueryRawBytes(b *testing.B) {
 		})
 	}
 }
+
+// BenchmarkReceiveMassiveRows measures performance of receiving large number of rows.
+func BenchmarkReceiveMassiveRows(b *testing.B) {
+	// Setup -- prepare 10000 rows.
+	db := initDB(b,
+		"DROP TABLE IF EXISTS foo",
+		"CREATE TABLE foo (id INT PRIMARY KEY, val TEXT)")
+	defer db.Close()
+
+	sval := strings.Repeat("x", 50)
+	stmt, err := db.Prepare(`INSERT INTO foo (id, val) VALUES (?, ?)` + strings.Repeat(",(?,?)", 99))
+	if err != nil {
+		b.Errorf("failed to prepare query: %v", err)
+		return
+	}
+	for i := 0; i < 10000; i += 100 {
+		args := make([]any, 200)
+		for j := 0; j < 100; j++ {
+			args[j*2] = i + j
+			args[j*2+1] = sval
+		}
+		_, err := stmt.Exec(args...)
+		if err != nil {
+			b.Error(err)
+			return
+		}
+	}
+	stmt.Close()
+
+	// Use b.Run() to skip expensive setup.
+	b.Run("query", func(b *testing.B) {
+		b.ReportAllocs()
+
+		for i := 0; i < b.N; i++ {
+			rows, err := db.Query(`SELECT id, val FROM foo`)
+			if err != nil {
+				b.Errorf("failed to select: %v", err)
+				return
+			}
+			for rows.Next() {
+				var i int
+				var s sql.RawBytes
+				err = rows.Scan(&i, &s)
+				if err != nil {
+					b.Errorf("failed to scan: %v", err)
+					_ = rows.Close()
+					return
+				}
+			}
+			if err = rows.Err(); err != nil {
+				b.Errorf("failed to read rows: %v", err)
+			}
+			_ = rows.Close()
+		}
+	})
+}

From a841e816042356288f94f7c5a586d83040cb63ea Mon Sep 17 00:00:00 2001
From: Evan Elias <evanjelias@gmail.com>
Date: Wed, 17 May 2023 14:28:03 -0400
Subject: [PATCH 195/282] Fix ColumnType.DatabaseTypeName for mediumint
 unsigned (#1428)

---
 AUTHORS        | 1 +
 README.md      | 2 +-
 driver_test.go | 1 +
 fields.go      | 3 +++
 4 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/AUTHORS b/AUTHORS
index 129ca665a..24dc43652 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -33,6 +33,7 @@ Dave Protasowski <dprotaso at gmail.com>
 DisposaBoy <disposaboy at dby.me>
 Egor Smolyakov <egorsmkv at gmail.com>
 Erwan Martin <hello at erwan.io>
+Evan Elias <evan at skeema.net>
 Evan Shaw <evan at vendhq.com>
 Frederick Mayle <frederickmayle at gmail.com>
 Gustavo Kristic <gkristic at gmail.com>
diff --git a/README.md b/README.md
index 5a242e9d7..ddb5cefc7 100644
--- a/README.md
+++ b/README.md
@@ -465,7 +465,7 @@ user:password@/
 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. All Unsigned database type names will be returned `UNSIGNED ` with `INT`, `TINYINT`, `SMALLINT`, `BIGINT`.
+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. All Unsigned database type names will be returned `UNSIGNED ` with `INT`, `TINYINT`, `SMALLINT`, `MEDIUMINT`, `BIGINT`.
 
 ## `context.Context` Support
 Go 1.8 added `database/sql` support for `context.Context`. This driver supports query timeouts and cancellation via contexts.
diff --git a/driver_test.go b/driver_test.go
index 50c617274..118c0d7ba 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -2816,6 +2816,7 @@ func TestRowsColumnTypes(t *testing.T) {
 		{"tinyuint", "TINYINT UNSIGNED NOT NULL", "UNSIGNED TINYINT", scanTypeUint8, false, 0, 0, [3]string{"0", "255", "42"}, [3]interface{}{uint8(0), uint8(255), uint8(42)}},
 		{"smalluint", "SMALLINT UNSIGNED NOT NULL", "UNSIGNED SMALLINT", scanTypeUint16, false, 0, 0, [3]string{"0", "65535", "42"}, [3]interface{}{uint16(0), uint16(65535), uint16(42)}},
 		{"biguint", "BIGINT UNSIGNED NOT NULL", "UNSIGNED BIGINT", scanTypeUint64, false, 0, 0, [3]string{"0", "65535", "42"}, [3]interface{}{uint64(0), uint64(65535), uint64(42)}},
+		{"mediumuint", "MEDIUMINT UNSIGNED NOT NULL", "UNSIGNED MEDIUMINT", scanTypeUint32, false, 0, 0, [3]string{"0", "16777215", "42"}, [3]interface{}{uint32(0), uint32(16777215), uint32(42)}},
 		{"uint13", "INT(13) UNSIGNED NOT NULL", "UNSIGNED 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}},
diff --git a/fields.go b/fields.go
index 18c23e0cb..ae709363f 100644
--- a/fields.go
+++ b/fields.go
@@ -37,6 +37,9 @@ func (mf *mysqlField) typeDatabaseName() string {
 	case fieldTypeGeometry:
 		return "GEOMETRY"
 	case fieldTypeInt24:
+		if mf.flags&flagUnsigned != 0 {
+			return "UNSIGNED MEDIUMINT"
+		}
 		return "MEDIUMINT"
 	case fieldTypeJSON:
 		return "JSON"

From 72e78ee26806a26405ee462c4cf82406f094a143 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Fri, 19 May 2023 23:04:35 +0900
Subject: [PATCH 196/282] README: Update multistatement (#1431)

---
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index ddb5cefc7..ad7ca718e 100644
--- a/README.md
+++ b/README.md
@@ -295,9 +295,9 @@ Valid Values:   true, false
 Default:        false
 ```
 
-Allow multiple statements in one query. While this allows batch queries, it also greatly increases the risk of SQL injections. Only the result of the first query is returned, all other results are silently discarded.
+Allow multiple statements in one query. This can be used to bach multiple queries. Use [Rows.NextResultSet()](https://pkg.go.dev/database/sql#Rows.NextResultSet) to get result of the second and subsequent queries.
 
-When `multiStatements` is used, `?` parameters must only be used in the first statement.
+When `multiStatements` is used, `?` parameters must only be used in the first statement. [interpolateParams](#interpolateparams) can be used to avoid this limitation unless prepared statement is used explicitly.
 
 ##### `parseTime`
 

From 924f8336da7226f4cd4bfac575d394ffa20aacb4 Mon Sep 17 00:00:00 2001
From: Daemonxiao <35677990+Daemonxiao@users.noreply.github.com>
Date: Wed, 24 May 2023 00:44:19 +0800
Subject: [PATCH 197/282] Send connection attributes (#1389)

Co-authored-by: Inada Naoki <songofacandy@gmail.com>
---
 .github/workflows/test.yml |  1 +
 README.md                  |  9 ++++++++
 connection.go              |  1 +
 connector.go               | 46 ++++++++++++++++++++++++++++++++++++-
 connector_test.go          |  9 +++++---
 const.go                   | 12 ++++++++++
 driver.go                  | 11 ++++-----
 driver_test.go             | 47 ++++++++++++++++++++++++++++++++++++++
 dsn.go                     | 40 ++++++++++++++++++--------------
 packets.go                 | 13 +++++++++++
 packets_test.go            |  7 +++++-
 utils.go                   |  5 ++++
 12 files changed, 173 insertions(+), 28 deletions(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index cd474767b..b2ab5e82a 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -79,6 +79,7 @@ jobs:
             ; TestConcurrent fails if max_connections is too large
             max_connections=50
             local_infile=1
+            performance_schema=on
       - name: setup database
         run: |
           mysql --user 'root' --host '127.0.0.1' -e 'create database gotest;'
diff --git a/README.md b/README.md
index ad7ca718e..5935afd0c 100644
--- a/README.md
+++ b/README.md
@@ -393,6 +393,15 @@ Default:        0
 
 I/O write timeout. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*.
 
+##### `connectionAttributes`
+
+```
+Type:           comma-delimited string of user-defined "key:value" pairs
+Valid Values:   (<name1>:<value1>,<name2>:<value2>,...)
+Default:        none
+```
+
+[Connection attributes](https://dev.mysql.com/doc/refman/8.0/en/performance-schema-connection-attribute-tables.html) are key-value pairs that application programs can pass to the server at connect time.
 
 ##### System Variables
 
diff --git a/connection.go b/connection.go
index a7da9e7e2..67cea1fcb 100644
--- a/connection.go
+++ b/connection.go
@@ -27,6 +27,7 @@ type mysqlConn struct {
 	affectedRows     uint64
 	insertId         uint64
 	cfg              *Config
+	connector        *connector
 	maxAllowedPacket int
 	maxWriteSize     int
 	writeTimeout     time.Duration
diff --git a/connector.go b/connector.go
index a5c988e13..6acf3dd50 100644
--- a/connector.go
+++ b/connector.go
@@ -11,11 +11,54 @@ package mysql
 import (
 	"context"
 	"database/sql/driver"
+	"fmt"
 	"net"
+	"os"
+	"strconv"
+	"strings"
 )
 
 type connector struct {
-	cfg *Config // immutable private copy.
+	cfg               *Config // immutable private copy.
+	encodedAttributes string  // Encoded connection attributes.
+}
+
+func encodeConnectionAttributes(textAttributes string) string {
+	connAttrsBuf := make([]byte, 0, 251)
+
+	// default connection attributes
+	connAttrsBuf = appendLengthEncodedString(connAttrsBuf, connAttrClientName)
+	connAttrsBuf = appendLengthEncodedString(connAttrsBuf, connAttrClientNameValue)
+	connAttrsBuf = appendLengthEncodedString(connAttrsBuf, connAttrOS)
+	connAttrsBuf = appendLengthEncodedString(connAttrsBuf, connAttrOSValue)
+	connAttrsBuf = appendLengthEncodedString(connAttrsBuf, connAttrPlatform)
+	connAttrsBuf = appendLengthEncodedString(connAttrsBuf, connAttrPlatformValue)
+	connAttrsBuf = appendLengthEncodedString(connAttrsBuf, connAttrPid)
+	connAttrsBuf = appendLengthEncodedString(connAttrsBuf, strconv.Itoa(os.Getpid()))
+
+	// user-defined connection attributes
+	for _, connAttr := range strings.Split(textAttributes, ",") {
+		attr := strings.SplitN(connAttr, ":", 2)
+		if len(attr) != 2 {
+			continue
+		}
+		for _, v := range attr {
+			connAttrsBuf = appendLengthEncodedString(connAttrsBuf, v)
+		}
+	}
+
+	return string(connAttrsBuf)
+}
+
+func newConnector(cfg *Config) (*connector, error) {
+	encodedAttributes := encodeConnectionAttributes(cfg.ConnectionAttributes)
+	if len(encodedAttributes) > 250 {
+		return nil, fmt.Errorf("connection attributes are longer than 250 bytes: %dbytes (%q)", len(encodedAttributes), cfg.ConnectionAttributes)
+	}
+	return &connector{
+		cfg:               cfg,
+		encodedAttributes: encodedAttributes,
+	}, nil
 }
 
 // Connect implements driver.Connector interface.
@@ -29,6 +72,7 @@ func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
 		maxWriteSize:     maxPacketSize - 1,
 		closech:          make(chan struct{}),
 		cfg:              c.cfg,
+		connector:        c,
 	}
 	mc.parseTime = mc.cfg.ParseTime
 
diff --git a/connector_test.go b/connector_test.go
index 976903c5b..bedb44ce2 100644
--- a/connector_test.go
+++ b/connector_test.go
@@ -8,13 +8,16 @@ import (
 )
 
 func TestConnectorReturnsTimeout(t *testing.T) {
-	connector := &connector{&Config{
+	connector, err := newConnector(&Config{
 		Net:     "tcp",
 		Addr:    "1.1.1.1:1234",
 		Timeout: 10 * time.Millisecond,
-	}}
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
 
-	_, err := connector.Connect(context.Background())
+	_, err = connector.Connect(context.Background())
 	if err == nil {
 		t.Fatal("error expected")
 	}
diff --git a/const.go b/const.go
index 64e2bced6..0f2621a6f 100644
--- a/const.go
+++ b/const.go
@@ -8,12 +8,24 @@
 
 package mysql
 
+import "runtime"
+
 const (
 	defaultAuthPlugin       = "mysql_native_password"
 	defaultMaxAllowedPacket = 64 << 20 // 64 MiB. See https://github.com/go-sql-driver/mysql/issues/1355
 	minProtocolVersion      = 10
 	maxPacketSize           = 1<<24 - 1
 	timeFormat              = "2006-01-02 15:04:05.999999"
+
+	// Connection attributes
+	// See https://dev.mysql.com/doc/refman/8.0/en/performance-schema-connection-attribute-tables.html#performance-schema-connection-attributes-available
+	connAttrClientName      = "_client_name"
+	connAttrClientNameValue = "Go-MySQL-Driver"
+	connAttrOS              = "_os"
+	connAttrOSValue         = runtime.GOOS
+	connAttrPlatform        = "_platform"
+	connAttrPlatformValue   = runtime.GOARCH
+	connAttrPid             = "_pid"
 )
 
 // MySQL constants documentation:
diff --git a/driver.go b/driver.go
index 8b0c3ec0a..c19e04207 100644
--- a/driver.go
+++ b/driver.go
@@ -85,8 +85,9 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
 	if err != nil {
 		return nil, err
 	}
-	c := &connector{
-		cfg: cfg,
+	c, err := newConnector(cfg)
+	if err != nil {
+		return nil, err
 	}
 	return c.Connect(context.Background())
 }
@@ -103,7 +104,7 @@ func NewConnector(cfg *Config) (driver.Connector, error) {
 	if err := cfg.normalize(); err != nil {
 		return nil, err
 	}
-	return &connector{cfg: cfg}, nil
+	return newConnector(cfg)
 }
 
 // OpenConnector implements driver.DriverContext.
@@ -112,7 +113,5 @@ func (d MySQLDriver) OpenConnector(dsn string) (driver.Connector, error) {
 	if err != nil {
 		return nil, err
 	}
-	return &connector{
-		cfg: cfg,
-	}, nil
+	return newConnector(cfg)
 }
diff --git a/driver_test.go b/driver_test.go
index 118c0d7ba..7c25aa905 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -3214,3 +3214,50 @@ func TestConnectorTimeoutsWatchCancel(t *testing.T) {
 		t.Errorf("connection not closed")
 	}
 }
+
+func TestConnectionAttributes(t *testing.T) {
+	if !available {
+		t.Skipf("MySQL server not running on %s", netAddr)
+	}
+
+	attr1 := "attr1"
+	value1 := "value1"
+	attr2 := "foo"
+	value2 := "boo"
+	dsn += fmt.Sprintf("&connectionAttributes=%s:%s,%s:%s", attr1, value1, attr2, value2)
+
+	var db *sql.DB
+	if _, err := ParseDSN(dsn); err != errInvalidDSNUnsafeCollation {
+		db, err = sql.Open("mysql", dsn)
+		if err != nil {
+			t.Fatalf("error connecting: %s", err.Error())
+		}
+		defer db.Close()
+	}
+
+	dbt := &DBTest{t, db}
+
+	var attrValue string
+	queryString := "SELECT ATTR_VALUE FROM performance_schema.session_account_connect_attrs WHERE PROCESSLIST_ID = CONNECTION_ID() and ATTR_NAME = ?"
+	rows := dbt.mustQuery(queryString, connAttrClientName)
+	if rows.Next() {
+		rows.Scan(&attrValue)
+		if attrValue != connAttrClientNameValue {
+			dbt.Errorf("expected %q, got %q", connAttrClientNameValue, attrValue)
+		}
+	} else {
+		dbt.Errorf("no data")
+	}
+	rows.Close()
+
+	rows = dbt.mustQuery(queryString, attr2)
+	if rows.Next() {
+		rows.Scan(&attrValue)
+		if attrValue != value2 {
+			dbt.Errorf("expected %q, got %q", value2, attrValue)
+		}
+	} else {
+		dbt.Errorf("no data")
+	}
+	rows.Close()
+}
diff --git a/dsn.go b/dsn.go
index ded459c94..7c788517c 100644
--- a/dsn.go
+++ b/dsn.go
@@ -34,23 +34,24 @@ var (
 // If a new Config is created instead of being parsed from a DSN string,
 // the NewConfig function should be used, which sets default values.
 type Config struct {
-	User             string            // Username
-	Passwd           string            // Password (requires User)
-	Net              string            // Network type
-	Addr             string            // Network address (requires Net)
-	DBName           string            // Database name
-	Params           map[string]string // Connection parameters
-	Collation        string            // Connection collation
-	Loc              *time.Location    // Location for time.Time values
-	MaxAllowedPacket int               // Max packet size allowed
-	ServerPubKey     string            // Server public key name
-	pubKey           *rsa.PublicKey    // Server public key
-	TLSConfig        string            // TLS configuration name
-	TLS              *tls.Config       // TLS configuration, its priority is higher than TLSConfig
-	Timeout          time.Duration     // Dial timeout
-	ReadTimeout      time.Duration     // I/O read timeout
-	WriteTimeout     time.Duration     // I/O write timeout
-	Logger           Logger            // Logger
+	User                 string            // Username
+	Passwd               string            // Password (requires User)
+	Net                  string            // Network type
+	Addr                 string            // Network address (requires Net)
+	DBName               string            // Database name
+	Params               map[string]string // Connection parameters
+	ConnectionAttributes string            // Connection Attributes, comma-delimited string of user-defined "key:value" pairs
+	Collation            string            // Connection collation
+	Loc                  *time.Location    // Location for time.Time values
+	MaxAllowedPacket     int               // Max packet size allowed
+	ServerPubKey         string            // Server public key name
+	pubKey               *rsa.PublicKey    // Server public key
+	TLSConfig            string            // TLS configuration name
+	TLS                  *tls.Config       // TLS configuration, its priority is higher than TLSConfig
+	Timeout              time.Duration     // Dial timeout
+	ReadTimeout          time.Duration     // I/O read timeout
+	WriteTimeout         time.Duration     // I/O write timeout
+	Logger               Logger            // Logger
 
 	AllowAllFiles            bool // Allow all files to be used with LOAD DATA LOCAL INFILE
 	AllowCleartextPasswords  bool // Allows the cleartext client side plugin
@@ -560,6 +561,11 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 			if err != nil {
 				return
 			}
+
+		// Connection attributes
+		case "connectionAttributes":
+			cfg.ConnectionAttributes = value
+
 		default:
 			// lazy init
 			if cfg.Params == nil {
diff --git a/packets.go b/packets.go
index 8fd67997b..d6a11fd21 100644
--- a/packets.go
+++ b/packets.go
@@ -285,6 +285,7 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 		clientLocalFiles |
 		clientPluginAuth |
 		clientMultiResults |
+		clientConnectAttrs |
 		mc.flags&clientLongFlag
 
 	if mc.cfg.ClientFoundRows {
@@ -318,6 +319,13 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 		pktLen += n + 1
 	}
 
+	// 1 byte to store length of all key-values
+	// NOTE: Actually, this is length encoded integer.
+	// But we support only len(connAttrBuf) < 251 for now because takeSmallBuffer
+	// doesn't support buffer size more than 4096 bytes.
+	// TODO(methane): Rewrite buffer management.
+	pktLen += 1 + len(mc.connector.encodedAttributes)
+
 	// Calculate packet length and get buffer with that size
 	data, err := mc.buf.takeSmallBuffer(pktLen + 4)
 	if err != nil {
@@ -394,6 +402,11 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 	data[pos] = 0x00
 	pos++
 
+	// Connection Attributes
+	data[pos] = byte(len(mc.connector.encodedAttributes))
+	pos++
+	pos += copy(data[pos:], []byte(mc.connector.encodedAttributes))
+
 	// Send Auth packet
 	return mc.writePacket(data[:pos])
 }
diff --git a/packets_test.go b/packets_test.go
index cacec1c68..f429087e9 100644
--- a/packets_test.go
+++ b/packets_test.go
@@ -96,9 +96,14 @@ var _ net.Conn = new(mockConn)
 
 func newRWMockConn(sequence uint8) (*mockConn, *mysqlConn) {
 	conn := new(mockConn)
+	connector, err := newConnector(NewConfig())
+	if err != nil {
+		panic(err)
+	}
 	mc := &mysqlConn{
 		buf:              newBuffer(conn),
-		cfg:              NewConfig(),
+		cfg:              connector.cfg,
+		connector:        connector,
 		netConn:          conn,
 		closech:          make(chan struct{}),
 		maxAllowedPacket: defaultMaxAllowedPacket,
diff --git a/utils.go b/utils.go
index 15dbd8d16..753ebd65c 100644
--- a/utils.go
+++ b/utils.go
@@ -616,6 +616,11 @@ func appendLengthEncodedInteger(b []byte, n uint64) []byte {
 		byte(n>>32), byte(n>>40), byte(n>>48), byte(n>>56))
 }
 
+func appendLengthEncodedString(b []byte, s string) []byte {
+	b = appendLengthEncodedInteger(b, uint64(len(s)))
+	return append(b, s...)
+}
+
 // reserveBuffer checks cap(buf) and expand buffer to len(buf) + appendSize.
 // If cap(buf) is not enough, reallocate new buffer.
 func reserveBuffer(buf []byte, appendSize int) []byte {

From d3e4fe64aaa1e99a19f711233dc682f2114ffbfd Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Thu, 25 May 2023 23:49:33 +0900
Subject: [PATCH 198/282] Use PathEscape for dbname in DSN. (#1432)

Support for slashes in database names via url escape codes.
On the other hand, '%' in DSN is now treated as percent-encoding.

Co-authored-by: Brian Hendriks <brian@dolthub.com>
---
 AUTHORS     |  2 ++
 README.md   |  6 +++++
 dsn.go      |  8 +++++--
 dsn_test.go | 66 ++++++++++++++++++++++++++++++-----------------------
 4 files changed, 51 insertions(+), 31 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 24dc43652..7e4fac5a1 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -110,6 +110,7 @@ Xuehong Chan <chanxuehong at gmail.com>
 Zhenye Xie <xiezhenye at gmail.com>
 Zhixin Wen <john.wenzhixin at gmail.com>
 Ziheng Lyu <zihenglv at gmail.com>
+Brian Hendriks <brian at dolthub.com>
 
 # Organizations
 
@@ -127,3 +128,4 @@ Percona LLC
 Pivotal Inc.
 Stripe Inc.
 Zendesk Inc.
+Dolthub Inc.
diff --git a/README.md b/README.md
index 5935afd0c..156aaa965 100644
--- a/README.md
+++ b/README.md
@@ -114,6 +114,12 @@ This has the same effect as an empty DSN string:
 
 ```
 
+`dbname` is escaped by [PathEscape()]()https://pkg.go.dev/net/url#PathEscape) since v1.8.0. If your database name is `dbname/withslash`, it becomes:
+
+```
+/dbname%2Fwithslash
+```
+
 Alternatively, [Config.FormatDSN](https://godoc.org/github.com/go-sql-driver/mysql#Config.FormatDSN) can be used to create a DSN string by filling a struct.
 
 #### Password
diff --git a/dsn.go b/dsn.go
index 7c788517c..3a6537e6c 100644
--- a/dsn.go
+++ b/dsn.go
@@ -203,7 +203,7 @@ func (cfg *Config) FormatDSN() string {
 
 	// /dbname
 	buf.WriteByte('/')
-	buf.WriteString(cfg.DBName)
+	buf.WriteString(url.PathEscape(cfg.DBName))
 
 	// [?param1=value1&...&paramN=valueN]
 	hasParam := false
@@ -365,7 +365,11 @@ func ParseDSN(dsn string) (cfg *Config, err error) {
 					break
 				}
 			}
-			cfg.DBName = dsn[i+1 : j]
+
+			dbname := dsn[i+1 : j]
+			if cfg.DBName, err = url.PathUnescape(dbname); err != nil {
+				return nil, fmt.Errorf("invalid dbname %q: %w", dbname, err)
+			}
 
 			break
 		}
diff --git a/dsn_test.go b/dsn_test.go
index cb97d557e..8b623df01 100644
--- a/dsn_test.go
+++ b/dsn_test.go
@@ -50,6 +50,9 @@ var testDSNs = []struct {
 }, {
 	"/dbname",
 	&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
+}, {
+	"/dbname%2Fwithslash",
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname/withslash", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"@/",
 	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
@@ -76,17 +79,20 @@ var testDSNs = []struct {
 
 func TestDSNParser(t *testing.T) {
 	for i, tst := range testDSNs {
-		cfg, err := ParseDSN(tst.in)
-		if err != nil {
-			t.Error(err.Error())
-		}
+		t.Run(tst.in, func(t *testing.T) {
+			cfg, err := ParseDSN(tst.in)
+			if err != nil {
+				t.Error(err.Error())
+				return
+			}
 
-		// pointer not static
-		cfg.TLS = nil
+			// pointer not static
+			cfg.TLS = nil
 
-		if !reflect.DeepEqual(cfg, tst.out) {
-			t.Errorf("%d. ParseDSN(%q) mismatch:\ngot  %+v\nwant %+v", i, tst.in, cfg, tst.out)
-		}
+			if !reflect.DeepEqual(cfg, tst.out) {
+				t.Errorf("%d. ParseDSN(%q) mismatch:\ngot  %+v\nwant %+v", i, tst.in, cfg, tst.out)
+			}
+		})
 	}
 }
 
@@ -113,27 +119,29 @@ func TestDSNParserInvalid(t *testing.T) {
 
 func TestDSNReformat(t *testing.T) {
 	for i, tst := range testDSNs {
-		dsn1 := tst.in
-		cfg1, err := ParseDSN(dsn1)
-		if err != nil {
-			t.Error(err.Error())
-			continue
-		}
-		cfg1.TLS = nil // pointer not static
-		res1 := fmt.Sprintf("%+v", cfg1)
-
-		dsn2 := cfg1.FormatDSN()
-		cfg2, err := ParseDSN(dsn2)
-		if err != nil {
-			t.Error(err.Error())
-			continue
-		}
-		cfg2.TLS = nil // pointer not static
-		res2 := fmt.Sprintf("%+v", cfg2)
+		t.Run(tst.in, func(t *testing.T) {
+			dsn1 := tst.in
+			cfg1, err := ParseDSN(dsn1)
+			if err != nil {
+				t.Error(err.Error())
+				return
+			}
+			cfg1.TLS = nil // pointer not static
+			res1 := fmt.Sprintf("%+v", cfg1)
 
-		if res1 != res2 {
-			t.Errorf("%d. %q does not match %q", i, res2, res1)
-		}
+			dsn2 := cfg1.FormatDSN()
+			cfg2, err := ParseDSN(dsn2)
+			if err != nil {
+				t.Error(err.Error())
+				return
+			}
+			cfg2.TLS = nil // pointer not static
+			res2 := fmt.Sprintf("%+v", cfg2)
+
+			if res1 != res2 {
+				t.Errorf("%d. %q does not match %q", i, res2, res1)
+			}
+		})
 	}
 }
 

From 7b4d7eb08bc4e705373ad835b2384df28676fb2f Mon Sep 17 00:00:00 2001
From: uji <49834542+uji@users.noreply.github.com>
Date: Fri, 26 May 2023 11:32:30 +0900
Subject: [PATCH 199/282] all: replace ioutil pkg to new package (#1438)

---
 auth.go        | 2 +-
 driver_test.go | 3 +--
 utils.go       | 2 +-
 3 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/auth.go b/auth.go
index b591e7b8a..e758e6d00 100644
--- a/auth.go
+++ b/auth.go
@@ -33,7 +33,7 @@ var (
 // Note: The provided rsa.PublicKey instance is exclusively owned by the driver
 // after registering it and may not be modified.
 //
-//	data, err := ioutil.ReadFile("mykey.pem")
+//	data, err := os.ReadFile("mykey.pem")
 //	if err != nil {
 //		log.Fatal(err)
 //	}
diff --git a/driver_test.go b/driver_test.go
index 7c25aa905..abf91a486 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -17,7 +17,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log"
 	"math"
 	"net"
@@ -1245,7 +1244,7 @@ func TestLoadData(t *testing.T) {
 		dbt.mustExec("CREATE TABLE test (id INT NOT NULL PRIMARY KEY, value TEXT NOT NULL) CHARACTER SET utf8")
 
 		// Local File
-		file, err := ioutil.TempFile("", "gotest")
+		file, err := os.CreateTemp("", "gotest")
 		defer os.Remove(file.Name())
 		if err != nil {
 			dbt.Fatal(err)
diff --git a/utils.go b/utils.go
index 753ebd65c..a24197b93 100644
--- a/utils.go
+++ b/utils.go
@@ -36,7 +36,7 @@ var (
 // registering it.
 //
 //	rootCertPool := x509.NewCertPool()
-//	pem, err := ioutil.ReadFile("/path/ca-cert.pem")
+//	pem, err := os.ReadFile("/path/ca-cert.pem")
 //	if err != nil {
 //	    log.Fatal(err)
 //	}

From 7b22099c7ea60190ef92f953ee62263a1808bd4b Mon Sep 17 00:00:00 2001
From: guangwu <guoguangwu@magic-shield.com>
Date: Fri, 26 May 2023 12:52:11 +0800
Subject: [PATCH 200/282] code optimization (#1439)

---
 driver.go | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/driver.go b/driver.go
index c19e04207..0ed8fa1c5 100644
--- a/driver.go
+++ b/driver.go
@@ -60,9 +60,7 @@ func DeregisterDialContext(net string) {
 	dialsLock.Lock()
 	defer dialsLock.Unlock()
 	if dials != nil {
-		if _, ok := dials[net]; ok {
-			delete(dials, net)
-		}
+		delete(dials, net)
 	}
 }
 

From 99976f4f587dd1a26900f6dd91ca96f6e3e2f724 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Sun, 28 May 2023 01:43:28 +0900
Subject: [PATCH 201/282] Use `SET NAMES charset COLLATE collation`. (#1437)

---
 README.md     | 10 ++++++----
 connection.go |  9 +++++++--
 dsn.go        |  5 ++---
 dsn_test.go   | 34 +++++++++++++++++-----------------
 packets.go    | 11 +++++++----
 5 files changed, 39 insertions(+), 30 deletions(-)

diff --git a/README.md b/README.md
index 156aaa965..d747a7446 100644
--- a/README.md
+++ b/README.md
@@ -202,8 +202,7 @@ Default:        none
 
 Sets the charset used for client-server interaction (`"SET NAMES <value>"`). If multiple charsets are set (separated by a comma), the following charset is used if setting the charset failes. This enables for example support for `utf8mb4` ([introduced in MySQL 5.5.3](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html)) with fallback to `utf8` for older servers (`charset=utf8mb4,utf8`).
 
-Usage of the `charset` parameter is discouraged because it issues additional queries to the server.
-Unless you need the fallback behavior, please use `collation` instead.
+See also [Unicode Support](#unicode-support).
 
 ##### `checkConnLiveness`
 
@@ -232,6 +231,7 @@ The default collation (`utf8mb4_general_ci`) is supported from MySQL 5.5.  You s
 
 Collations for charset "ucs2", "utf16", "utf16le", and "utf32" can not be used ([ref](https://dev.mysql.com/doc/refman/5.7/en/charset-connection.html#charset-connection-impermissible-client-charset)).
 
+See also [Unicode Support](#unicode-support).
 
 ##### `clientFoundRows`
 
@@ -511,9 +511,11 @@ However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` v
 ### Unicode support
 Since version 1.5 Go-MySQL-Driver automatically uses the collation ` utf8mb4_general_ci` by default.
 
-Other collations / charsets can be set using the [`collation`](#collation) DSN parameter.
+Other charsets / collations can be set using the [`charset`](#charset) or [`collation`](#collation) DSN parameter.
 
-Version 1.0 of the driver recommended adding `&charset=utf8` (alias for `SET NAMES utf8`) to the DSN to enable proper UTF-8 support. This is not necessary anymore. The [`collation`](#collation) parameter should be preferred to set another collation / charset than the default.
+- When only the `charset` is specified, the `SET NAMES <charset>` query is sent and the server's default collation is used.
+- When both the `charset` and `collation` are specified, the `SET NAMES <charset> COLLATE <collation>` query is sent.
+- When only the `collation` is specified, the collation is specified in the protocol handshake and the `SET NAMES` query is not sent. This can save one roundtrip, but note that the server may ignore the specified collation silently and use the server's default charset/collation instead.
 
 See http://dev.mysql.com/doc/refman/8.0/en/charset-unicode.html for more details on MySQL's Unicode support.
 
diff --git a/connection.go b/connection.go
index 67cea1fcb..14a972b40 100644
--- a/connection.go
+++ b/connection.go
@@ -49,14 +49,19 @@ type mysqlConn struct {
 // Handles parameters set in DSN after the connection is established
 func (mc *mysqlConn) handleParams() (err error) {
 	var cmdSet strings.Builder
+
 	for param, val := range mc.cfg.Params {
 		switch param {
 		// Charset: character_set_connection, character_set_client, character_set_results
 		case "charset":
 			charsets := strings.Split(val, ",")
-			for i := range charsets {
+			for _, cs := range charsets {
 				// ignore errors here - a charset may not exist
-				err = mc.exec("SET NAMES " + charsets[i])
+				if mc.cfg.Collation != "" {
+					err = mc.exec("SET NAMES " + cs + " COLLATE " + mc.cfg.Collation)
+				} else {
+					err = mc.exec("SET NAMES " + cs)
+				}
 				if err == nil {
 					break
 				}
diff --git a/dsn.go b/dsn.go
index 3a6537e6c..693aa4e5a 100644
--- a/dsn.go
+++ b/dsn.go
@@ -70,7 +70,6 @@ type Config struct {
 // NewConfig creates a new Config and sets default values.
 func NewConfig() *Config {
 	return &Config{
-		Collation:            defaultCollation,
 		Loc:                  time.UTC,
 		MaxAllowedPacket:     defaultMaxAllowedPacket,
 		Logger:               defaultLogger,
@@ -100,7 +99,7 @@ func (cfg *Config) Clone() *Config {
 }
 
 func (cfg *Config) normalize() error {
-	if cfg.InterpolateParams && unsafeCollations[cfg.Collation] {
+	if cfg.InterpolateParams && cfg.Collation != "" && unsafeCollations[cfg.Collation] {
 		return errInvalidDSNUnsafeCollation
 	}
 
@@ -237,7 +236,7 @@ func (cfg *Config) FormatDSN() string {
 		writeDSNParam(&buf, &hasParam, "clientFoundRows", "true")
 	}
 
-	if col := cfg.Collation; col != defaultCollation && len(col) > 0 {
+	if col := cfg.Collation; col != "" {
 		writeDSNParam(&buf, &hasParam, "collation", col)
 	}
 
diff --git a/dsn_test.go b/dsn_test.go
index 8b623df01..a729d0ef8 100644
--- a/dsn_test.go
+++ b/dsn_test.go
@@ -22,58 +22,58 @@ var testDSNs = []struct {
 	out *Config
 }{{
 	"username:password@protocol(address)/dbname?param=value",
-	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
+	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true",
-	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, ColumnsWithAlias: true},
+	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, ColumnsWithAlias: true},
 }, {
 	"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true&multiStatements=true",
-	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, ColumnsWithAlias: true, MultiStatements: true},
+	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, ColumnsWithAlias: true, MultiStatements: true},
 }, {
 	"user@unix(/path/to/socket)/dbname?charset=utf8",
-	&Config{User: "user", Net: "unix", Addr: "/path/to/socket", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
+	&Config{User: "user", Net: "unix", Addr: "/path/to/socket", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true",
-	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, TLSConfig: "true"},
+	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, TLSConfig: "true"},
 }, {
 	"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify",
-	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8mb4,utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, TLSConfig: "skip-verify"},
+	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8mb4,utf8"}, Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, TLSConfig: "skip-verify"},
 }, {
 	"user:password@/dbname?loc=UTC&timeout=30s&readTimeout=1s&writeTimeout=1s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci&maxAllowedPacket=16777216&tls=false&allowCleartextPasswords=true&parseTime=true&rejectReadOnly=true",
 	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_unicode_ci", Loc: time.UTC, TLSConfig: "false", AllowCleartextPasswords: true, AllowNativePasswords: true, Timeout: 30 * time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, Logger: defaultLogger, AllowAllFiles: true, AllowOldPasswords: true, CheckConnLiveness: true, ClientFoundRows: true, MaxAllowedPacket: 16777216, ParseTime: true, RejectReadOnly: true},
 }, {
 	"user:password@/dbname?allowNativePasswords=false&checkConnLiveness=false&maxAllowedPacket=0&allowFallbackToPlaintext=true",
-	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: 0, Logger: defaultLogger, AllowFallbackToPlaintext: true, AllowNativePasswords: false, CheckConnLiveness: false},
+	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Loc: time.UTC, MaxAllowedPacket: 0, Logger: defaultLogger, AllowFallbackToPlaintext: true, AllowNativePasswords: false, CheckConnLiveness: false},
 }, {
 	"user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local",
-	&Config{User: "user", Passwd: "p@ss(word)", Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:80", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.Local, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
+	&Config{User: "user", Passwd: "p@ss(word)", Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:80", DBName: "dbname", Loc: time.Local, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"/dbname",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"/dbname%2Fwithslash",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname/withslash", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname/withslash", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"@/",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"/",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"user:p@/ssword@/",
-	&Config{User: "user", Passwd: "p@/ssword", Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
+	&Config{User: "user", Passwd: "p@/ssword", Net: "tcp", Addr: "127.0.0.1:3306", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"unix/?arg=%2Fsome%2Fpath.ext",
-	&Config{Net: "unix", Addr: "/tmp/mysql.sock", Params: map[string]string{"arg": "/some/path.ext"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
+	&Config{Net: "unix", Addr: "/tmp/mysql.sock", Params: map[string]string{"arg": "/some/path.ext"}, Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"tcp(127.0.0.1)/dbname",
-	&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
+	&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"tcp(de:ad:be:ef::ca:fe)/dbname",
-	&Config{Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
+	&Config{Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:3306", DBName: "dbname", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
 },
 }
 
diff --git a/packets.go b/packets.go
index d6a11fd21..c10072c94 100644
--- a/packets.go
+++ b/packets.go
@@ -14,7 +14,6 @@ import (
 	"database/sql/driver"
 	"encoding/binary"
 	"encoding/json"
-	"errors"
 	"fmt"
 	"io"
 	"math"
@@ -346,14 +345,18 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 	data[10] = 0x00
 	data[11] = 0x00
 
-	// Charset [1 byte]
+	// Collation ID [1 byte]
+	cname := mc.cfg.Collation
+	if cname == "" {
+		cname = defaultCollation
+	}
 	var found bool
-	data[12], found = collations[mc.cfg.Collation]
+	data[12], found = collations[cname]
 	if !found {
 		// Note possibility for false negatives:
 		// could be triggered  although the collation is valid if the
 		// collations map does not contain entries the server supports.
-		return errors.New("unknown collation")
+		return fmt.Errorf("unknown collation: %q", cname)
 	}
 
 	// Filler [23 bytes] (all 0x00)

From f43effaa7c9271606b37b04a6235e5f7ed37c3e0 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Sun, 28 May 2023 02:07:46 +0900
Subject: [PATCH 202/282] Reduce map lookup in ColumnTypeDatabaseTypeName.
 (#1436)

---
 collations.go |  2 +-
 fields.go     | 16 ++++++++--------
 2 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/collations.go b/collations.go
index 295bfbe52..1cdf97b67 100644
--- a/collations.go
+++ b/collations.go
@@ -9,7 +9,7 @@
 package mysql
 
 const defaultCollation = "utf8mb4_general_ci"
-const binaryCollation = "binary"
+const binaryCollationID = 63
 
 // A list of available collations mapped to the internal ID.
 // To update this map use the following MySQL query:
diff --git a/fields.go b/fields.go
index ae709363f..30f31cbfb 100644
--- a/fields.go
+++ b/fields.go
@@ -18,7 +18,7 @@ func (mf *mysqlField) typeDatabaseName() string {
 	case fieldTypeBit:
 		return "BIT"
 	case fieldTypeBLOB:
-		if mf.charSet != collations[binaryCollation] {
+		if mf.charSet != binaryCollationID {
 			return "TEXT"
 		}
 		return "BLOB"
@@ -49,7 +49,7 @@ func (mf *mysqlField) typeDatabaseName() string {
 		}
 		return "INT"
 	case fieldTypeLongBLOB:
-		if mf.charSet != collations[binaryCollation] {
+		if mf.charSet != binaryCollationID {
 			return "LONGTEXT"
 		}
 		return "LONGBLOB"
@@ -59,7 +59,7 @@ func (mf *mysqlField) typeDatabaseName() string {
 		}
 		return "BIGINT"
 	case fieldTypeMediumBLOB:
-		if mf.charSet != collations[binaryCollation] {
+		if mf.charSet != binaryCollationID {
 			return "MEDIUMTEXT"
 		}
 		return "MEDIUMBLOB"
@@ -77,7 +77,7 @@ func (mf *mysqlField) typeDatabaseName() string {
 		}
 		return "SMALLINT"
 	case fieldTypeString:
-		if mf.charSet == collations[binaryCollation] {
+		if mf.charSet == binaryCollationID {
 			return "BINARY"
 		}
 		return "CHAR"
@@ -91,17 +91,17 @@ func (mf *mysqlField) typeDatabaseName() string {
 		}
 		return "TINYINT"
 	case fieldTypeTinyBLOB:
-		if mf.charSet != collations[binaryCollation] {
+		if mf.charSet != binaryCollationID {
 			return "TINYTEXT"
 		}
 		return "TINYBLOB"
 	case fieldTypeVarChar:
-		if mf.charSet == collations[binaryCollation] {
+		if mf.charSet == binaryCollationID {
 			return "VARBINARY"
 		}
 		return "VARCHAR"
 	case fieldTypeVarString:
-		if mf.charSet == collations[binaryCollation] {
+		if mf.charSet == binaryCollationID {
 			return "VARBINARY"
 		}
 		return "VARCHAR"
@@ -194,7 +194,7 @@ func (mf *mysqlField) scanType() reflect.Type {
 
 	case fieldTypeBit, fieldTypeTinyBLOB, fieldTypeMediumBLOB, fieldTypeLongBLOB,
 		fieldTypeBLOB, fieldTypeVarString, fieldTypeString, fieldTypeGeometry:
-		if mf.charSet == 63 /* binary */ {
+		if mf.charSet == binaryCollationID {
 			return scanTypeBytes
 		}
 		fallthrough

From 397e2f5323e1c03bc4513d6c9ab345dfd47108cd Mon Sep 17 00:00:00 2001
From: Matthew Herrmann <47012945+mherr-google@users.noreply.github.com>
Date: Mon, 29 May 2023 13:33:49 +1000
Subject: [PATCH 203/282] Exec() now provides access to status of multiple
 statements. (#1309)

It now reports the last inserted ID and affected row count for all statements,
not just the last one. This is useful to execute batches of statements such as
UPDATE with minimal roundtrips.

Co-authored-by: Inada Naoki <songofacandy@gmail.com>
---
 README.md      |  16 +++++++
 auth.go        |   6 +--
 connection.go  |  29 ++++++-------
 driver_test.go | 112 +++++++++++++++++++++++++++++++++++++++++++++++++
 infile.go      |   8 ++--
 packets.go     |  77 ++++++++++++++++++++++++++++------
 result.go      |  37 ++++++++++++++--
 rows.go        |   7 +++-
 statement.go   |  17 ++++----
 9 files changed, 259 insertions(+), 50 deletions(-)

diff --git a/README.md b/README.md
index d747a7446..4eade6853 100644
--- a/README.md
+++ b/README.md
@@ -305,6 +305,22 @@ Allow multiple statements in one query. This can be used to bach multiple querie
 
 When `multiStatements` is used, `?` parameters must only be used in the first statement. [interpolateParams](#interpolateparams) can be used to avoid this limitation unless prepared statement is used explicitly.
 
+It's possible to access the last inserted ID and number of affected rows for multiple statements by using `sql.Conn.Raw()` and the `mysql.Result`. For example:
+
+```go
+conn, _ := db.Conn(ctx)
+conn.Raw(func(conn interface{}) error {
+  ex := conn.(driver.Execer)
+  res, err := ex.Exec(`
+  UPDATE point SET x = 1 WHERE y = 2;
+  UPDATE point SET x = 2 WHERE y = 3;
+  `, nil)
+  // Both slices have 2 elements.
+  log.Print(res.(mysql.Result).AllRowsAffected())
+  log.Print(res.(mysql.Result).AllLastInsertIds())
+})
+```
+
 ##### `parseTime`
 
 ```
diff --git a/auth.go b/auth.go
index e758e6d00..f6b157a12 100644
--- a/auth.go
+++ b/auth.go
@@ -346,7 +346,7 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
 		case 1:
 			switch authData[0] {
 			case cachingSha2PasswordFastAuthSuccess:
-				if err = mc.readResultOK(); err == nil {
+				if err = mc.resultUnchanged().readResultOK(); err == nil {
 					return nil // auth successful
 				}
 
@@ -397,7 +397,7 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
 						return err
 					}
 				}
-				return mc.readResultOK()
+				return mc.resultUnchanged().readResultOK()
 
 			default:
 				return ErrMalformPkt
@@ -426,7 +426,7 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
 			if err != nil {
 				return err
 			}
-			return mc.readResultOK()
+			return mc.resultUnchanged().readResultOK()
 		}
 
 	default:
diff --git a/connection.go b/connection.go
index 14a972b40..631a1dc24 100644
--- a/connection.go
+++ b/connection.go
@@ -23,9 +23,8 @@ import (
 type mysqlConn struct {
 	buf              buffer
 	netConn          net.Conn
-	rawConn          net.Conn // underlying connection when netConn is TLS connection.
-	affectedRows     uint64
-	insertId         uint64
+	rawConn          net.Conn    // underlying connection when netConn is TLS connection.
+	result           mysqlResult // managed by clearResult() and handleOkPacket().
 	cfg              *Config
 	connector        *connector
 	maxAllowedPacket int
@@ -155,6 +154,7 @@ func (mc *mysqlConn) cleanup() {
 	if err := mc.netConn.Close(); err != nil {
 		mc.cfg.Logger.Print(err)
 	}
+	mc.clearResult()
 }
 
 func (mc *mysqlConn) error() error {
@@ -316,28 +316,25 @@ func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, err
 		}
 		query = prepared
 	}
-	mc.affectedRows = 0
-	mc.insertId = 0
 
 	err := mc.exec(query)
 	if err == nil {
-		return &mysqlResult{
-			affectedRows: int64(mc.affectedRows),
-			insertId:     int64(mc.insertId),
-		}, err
+		copied := mc.result
+		return &copied, err
 	}
 	return nil, mc.markBadConn(err)
 }
 
 // Internal function to execute commands
 func (mc *mysqlConn) exec(query string) error {
+	handleOk := mc.clearResult()
 	// Send command
 	if err := mc.writeCommandPacketStr(comQuery, query); err != nil {
 		return mc.markBadConn(err)
 	}
 
 	// Read Result
-	resLen, err := mc.readResultSetHeaderPacket()
+	resLen, err := handleOk.readResultSetHeaderPacket()
 	if err != nil {
 		return err
 	}
@@ -354,7 +351,7 @@ func (mc *mysqlConn) exec(query string) error {
 		}
 	}
 
-	return mc.discardResults()
+	return handleOk.discardResults()
 }
 
 func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) {
@@ -362,6 +359,8 @@ func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, erro
 }
 
 func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error) {
+	handleOk := mc.clearResult()
+
 	if mc.closed.Load() {
 		mc.cfg.Logger.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
@@ -382,7 +381,7 @@ func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error)
 	if err == nil {
 		// Read Result
 		var resLen int
-		resLen, err = mc.readResultSetHeaderPacket()
+		resLen, err = handleOk.readResultSetHeaderPacket()
 		if err == nil {
 			rows := new(textRows)
 			rows.mc = mc
@@ -410,12 +409,13 @@ func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error)
 // The returned byte slice is only valid until the next read
 func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
 	// Send command
+	handleOk := mc.clearResult()
 	if err := mc.writeCommandPacketStr(comQuery, "SELECT @@"+name); err != nil {
 		return nil, err
 	}
 
 	// Read Result
-	resLen, err := mc.readResultSetHeaderPacket()
+	resLen, err := handleOk.readResultSetHeaderPacket()
 	if err == nil {
 		rows := new(textRows)
 		rows.mc = mc
@@ -466,11 +466,12 @@ func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
 	}
 	defer mc.finish()
 
+	handleOk := mc.clearResult()
 	if err = mc.writeCommandPacket(comPing); err != nil {
 		return mc.markBadConn(err)
 	}
 
-	return mc.readResultOK()
+	return handleOk.readResultOK()
 }
 
 // BeginTx implements driver.ConnBeginTx interface
diff --git a/driver_test.go b/driver_test.go
index abf91a486..cd94c434e 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -2154,11 +2154,51 @@ func TestRejectReadOnly(t *testing.T) {
 }
 
 func TestPing(t *testing.T) {
+	ctx := context.Background()
 	runTests(t, dsn, func(dbt *DBTest) {
 		if err := dbt.db.Ping(); err != nil {
 			dbt.fail("Ping", "Ping", err)
 		}
 	})
+
+	runTests(t, dsn, func(dbt *DBTest) {
+		conn, err := dbt.db.Conn(ctx)
+		if err != nil {
+			dbt.fail("db", "Conn", err)
+		}
+
+		// Check that affectedRows and insertIds are cleared after each call.
+		conn.Raw(func(conn interface{}) error {
+			c := conn.(*mysqlConn)
+
+			// Issue a query that sets affectedRows and insertIds.
+			q, err := c.Query(`SELECT 1`, nil)
+			if err != nil {
+				dbt.fail("Conn", "Query", err)
+			}
+			if got, want := c.result.affectedRows, []int64{0}; !reflect.DeepEqual(got, want) {
+				dbt.Fatalf("bad affectedRows: got %v, want=%v", got, want)
+			}
+			if got, want := c.result.insertIds, []int64{0}; !reflect.DeepEqual(got, want) {
+				dbt.Fatalf("bad insertIds: got %v, want=%v", got, want)
+			}
+			q.Close()
+
+			// Verify that Ping() clears both fields.
+			for i := 0; i < 2; i++ {
+				if err := c.Ping(ctx); err != nil {
+					dbt.fail("Pinger", "Ping", err)
+				}
+				if got, want := c.result.affectedRows, []int64(nil); !reflect.DeepEqual(got, want) {
+					t.Errorf("bad affectedRows: got %v, want=%v", got, want)
+				}
+				if got, want := c.result.insertIds, []int64(nil); !reflect.DeepEqual(got, want) {
+					t.Errorf("bad affectedRows: got %v, want=%v", got, want)
+				}
+			}
+			return nil
+		})
+	})
 }
 
 // See Issue #799
@@ -2378,6 +2418,42 @@ func TestMultiResultSetNoSelect(t *testing.T) {
 	})
 }
 
+func TestExecMultipleResults(t *testing.T) {
+	ctx := context.Background()
+	runTestsWithMultiStatement(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec(`
+		CREATE TABLE test (
+			id INT NOT NULL AUTO_INCREMENT,
+			value VARCHAR(255),
+			PRIMARY KEY (id)
+		)`)
+		conn, err := dbt.db.Conn(ctx)
+		if err != nil {
+			t.Fatalf("failed to connect: %v", err)
+		}
+		conn.Raw(func(conn interface{}) error {
+			ex := conn.(driver.Execer)
+			res, err := ex.Exec(`
+			INSERT INTO test (value) VALUES ('a'), ('b');
+			INSERT INTO test (value) VALUES ('c'), ('d'), ('e');
+			`, nil)
+			if err != nil {
+				t.Fatalf("insert statements failed: %v", err)
+			}
+			mres := res.(Result)
+			if got, want := mres.AllRowsAffected(), []int64{2, 3}; !reflect.DeepEqual(got, want) {
+				t.Errorf("bad AllRowsAffected: got %v, want=%v", got, want)
+			}
+			// For INSERTs containing multiple rows, LAST_INSERT_ID() returns the
+			// first inserted ID, not the last.
+			if got, want := mres.AllLastInsertIds(), []int64{1, 3}; !reflect.DeepEqual(got, want) {
+				t.Errorf("bad AllLastInsertIds: got %v, want %v", got, want)
+			}
+			return nil
+		})
+	})
+}
+
 // tests if rows are set in a proper state if some results were ignored before
 // calling rows.NextResultSet.
 func TestSkipResults(t *testing.T) {
@@ -2399,6 +2475,42 @@ func TestSkipResults(t *testing.T) {
 	})
 }
 
+func TestQueryMultipleResults(t *testing.T) {
+	ctx := context.Background()
+	runTestsWithMultiStatement(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec(`
+		CREATE TABLE test (
+			id INT NOT NULL AUTO_INCREMENT,
+			value VARCHAR(255),
+			PRIMARY KEY (id)
+		)`)
+		conn, err := dbt.db.Conn(ctx)
+		if err != nil {
+			t.Fatalf("failed to connect: %v", err)
+		}
+		conn.Raw(func(conn interface{}) error {
+			qr := conn.(driver.Queryer)
+
+			c := conn.(*mysqlConn)
+
+			// Demonstrate that repeated queries reset the affectedRows
+			for i := 0; i < 2; i++ {
+				_, err := qr.Query(`
+				INSERT INTO test (value) VALUES ('a'), ('b');
+				INSERT INTO test (value) VALUES ('c'), ('d'), ('e');
+			`, nil)
+				if err != nil {
+					t.Fatalf("insert statements failed: %v", err)
+				}
+				if got, want := c.result.affectedRows, []int64{2, 3}; !reflect.DeepEqual(got, want) {
+					t.Errorf("bad affectedRows: got %v, want=%v", got, want)
+				}
+			}
+			return nil
+		})
+	})
+}
+
 func TestPingContext(t *testing.T) {
 	runTests(t, dsn, func(dbt *DBTest) {
 		ctx, cancel := context.WithCancel(context.Background())
diff --git a/infile.go b/infile.go
index 3279dcffd..cfd41914e 100644
--- a/infile.go
+++ b/infile.go
@@ -93,7 +93,7 @@ func deferredClose(err *error, closer io.Closer) {
 
 const defaultPacketSize = 16 * 1024 // 16KB is small enough for disk readahead and large enough for TCP
 
-func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
+func (mc *okHandler) handleInFileRequest(name string) (err error) {
 	var rdr io.Reader
 	var data []byte
 	packetSize := defaultPacketSize
@@ -154,7 +154,7 @@ func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
 		for err == nil {
 			n, err = rdr.Read(data[4:])
 			if n > 0 {
-				if ioErr := mc.writePacket(data[:4+n]); ioErr != nil {
+				if ioErr := mc.conn().writePacket(data[:4+n]); ioErr != nil {
 					return ioErr
 				}
 			}
@@ -168,7 +168,7 @@ func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
 	if data == nil {
 		data = make([]byte, 4)
 	}
-	if ioErr := mc.writePacket(data[:4]); ioErr != nil {
+	if ioErr := mc.conn().writePacket(data[:4]); ioErr != nil {
 		return ioErr
 	}
 
@@ -177,6 +177,6 @@ func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
 		return mc.readResultOK()
 	}
 
-	mc.readPacket()
+	mc.conn().readPacket()
 	return err
 }
diff --git a/packets.go b/packets.go
index c10072c94..1a7f2c376 100644
--- a/packets.go
+++ b/packets.go
@@ -511,7 +511,9 @@ func (mc *mysqlConn) readAuthResult() ([]byte, string, error) {
 	switch data[0] {
 
 	case iOK:
-		return nil, "", mc.handleOkPacket(data)
+		// resultUnchanged, since auth happens before any queries or
+		// commands have been executed.
+		return nil, "", mc.resultUnchanged().handleOkPacket(data)
 
 	case iAuthMoreData:
 		return data[1:], "", err
@@ -535,8 +537,8 @@ func (mc *mysqlConn) readAuthResult() ([]byte, string, error) {
 }
 
 // Returns error if Packet is not an 'Result OK'-Packet
-func (mc *mysqlConn) readResultOK() error {
-	data, err := mc.readPacket()
+func (mc *okHandler) readResultOK() error {
+	data, err := mc.conn().readPacket()
 	if err != nil {
 		return err
 	}
@@ -544,13 +546,17 @@ func (mc *mysqlConn) readResultOK() error {
 	if data[0] == iOK {
 		return mc.handleOkPacket(data)
 	}
-	return mc.handleErrorPacket(data)
+	return mc.conn().handleErrorPacket(data)
 }
 
 // Result Set Header Packet
 // http://dev.mysql.com/doc/internals/en/com-query-response.html#packet-ProtocolText::Resultset
-func (mc *mysqlConn) readResultSetHeaderPacket() (int, error) {
-	data, err := mc.readPacket()
+func (mc *okHandler) readResultSetHeaderPacket() (int, error) {
+	// handleOkPacket replaces both values; other cases leave the values unchanged.
+	mc.result.affectedRows = append(mc.result.affectedRows, 0)
+	mc.result.insertIds = append(mc.result.insertIds, 0)
+
+	data, err := mc.conn().readPacket()
 	if err == nil {
 		switch data[0] {
 
@@ -558,7 +564,7 @@ func (mc *mysqlConn) readResultSetHeaderPacket() (int, error) {
 			return 0, mc.handleOkPacket(data)
 
 		case iERR:
-			return 0, mc.handleErrorPacket(data)
+			return 0, mc.conn().handleErrorPacket(data)
 
 		case iLocalInFile:
 			return 0, mc.handleInFileRequest(string(data[1:]))
@@ -623,18 +629,61 @@ func readStatus(b []byte) statusFlag {
 	return statusFlag(b[0]) | statusFlag(b[1])<<8
 }
 
+// Returns an instance of okHandler for codepaths where mysqlConn.result doesn't
+// need to be cleared first (e.g. during authentication, or while additional
+// resultsets are being fetched.)
+func (mc *mysqlConn) resultUnchanged() *okHandler {
+	return (*okHandler)(mc)
+}
+
+// okHandler represents the state of the connection when mysqlConn.result has
+// been prepared for processing of OK packets.
+//
+// To correctly populate mysqlConn.result (updated by handleOkPacket()), all
+// callpaths must either:
+//
+// 1. first clear it using clearResult(), or
+// 2. confirm that they don't need to (by calling resultUnchanged()).
+//
+// Both return an instance of type *okHandler.
+type okHandler mysqlConn
+
+// Exposees the underlying type's methods.
+func (mc *okHandler) conn() *mysqlConn {
+	return (*mysqlConn)(mc)
+}
+
+// clearResult clears the connection's stored affectedRows and insertIds
+// fields.
+//
+// It returns a handler that can process OK responses.
+func (mc *mysqlConn) clearResult() *okHandler {
+	mc.result = mysqlResult{}
+	return (*okHandler)(mc)
+}
+
 // Ok Packet
 // http://dev.mysql.com/doc/internals/en/generic-response-packets.html#packet-OK_Packet
-func (mc *mysqlConn) handleOkPacket(data []byte) error {
+func (mc *okHandler) handleOkPacket(data []byte) error {
 	var n, m int
+	var affectedRows, insertId uint64
 
 	// 0x00 [1 byte]
 
 	// Affected rows [Length Coded Binary]
-	mc.affectedRows, _, n = readLengthEncodedInteger(data[1:])
+	affectedRows, _, n = readLengthEncodedInteger(data[1:])
 
 	// Insert id [Length Coded Binary]
-	mc.insertId, _, m = readLengthEncodedInteger(data[1+n:])
+	insertId, _, m = readLengthEncodedInteger(data[1+n:])
+
+	// Update for the current statement result (only used by
+	// readResultSetHeaderPacket).
+	if len(mc.result.affectedRows) > 0 {
+		mc.result.affectedRows[len(mc.result.affectedRows)-1] = int64(affectedRows)
+	}
+	if len(mc.result.insertIds) > 0 {
+		mc.result.insertIds[len(mc.result.insertIds)-1] = int64(insertId)
+	}
 
 	// server_status [2 bytes]
 	mc.status = readStatus(data[1+n+m : 1+n+m+2])
@@ -1165,7 +1214,9 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 	return mc.writePacket(data)
 }
 
-func (mc *mysqlConn) discardResults() error {
+// For each remaining resultset in the stream, discards its rows and updates
+// mc.affectedRows and mc.insertIds.
+func (mc *okHandler) discardResults() error {
 	for mc.status&statusMoreResultsExists != 0 {
 		resLen, err := mc.readResultSetHeaderPacket()
 		if err != nil {
@@ -1173,11 +1224,11 @@ func (mc *mysqlConn) discardResults() error {
 		}
 		if resLen > 0 {
 			// columns
-			if err := mc.readUntilEOF(); err != nil {
+			if err := mc.conn().readUntilEOF(); err != nil {
 				return err
 			}
 			// rows
-			if err := mc.readUntilEOF(); err != nil {
+			if err := mc.conn().readUntilEOF(); err != nil {
 				return err
 			}
 		}
diff --git a/result.go b/result.go
index c6438d034..36a432e81 100644
--- a/result.go
+++ b/result.go
@@ -8,15 +8,44 @@
 
 package mysql
 
+import "database/sql/driver"
+
+// Result exposes data not available through *connection.Result.
+//
+// This is accessible by executing statements using sql.Conn.Raw() and
+// downcasting the returned result:
+//
+//    res, err := rawConn.Exec(...)
+//    res.(mysql.Result).AllRowsAffected()
+//
+type Result interface {
+	driver.Result
+	// AllRowsAffected returns a slice containing the affected rows for each
+	// executed statement.
+	AllRowsAffected() []int64
+	// AllLastInsertIds returns a slice containing the last inserted ID for each
+	// executed statement.
+	AllLastInsertIds() []int64
+}
+
 type mysqlResult struct {
-	affectedRows int64
-	insertId     int64
+	// One entry in both slices is created for every executed statement result.
+	affectedRows []int64
+	insertIds    []int64
 }
 
 func (res *mysqlResult) LastInsertId() (int64, error) {
-	return res.insertId, nil
+	return res.insertIds[len(res.insertIds)-1], nil
 }
 
 func (res *mysqlResult) RowsAffected() (int64, error) {
-	return res.affectedRows, nil
+	return res.affectedRows[len(res.affectedRows)-1], nil
+}
+
+func (res *mysqlResult) AllLastInsertIds() []int64 {
+	return append([]int64{}, res.insertIds...) // defensive copy
+}
+
+func (res *mysqlResult) AllRowsAffected() []int64 {
+	return append([]int64{}, res.affectedRows...) // defensive copy
 }
diff --git a/rows.go b/rows.go
index 888bdb5f0..63d0ed2d5 100644
--- a/rows.go
+++ b/rows.go
@@ -123,7 +123,8 @@ func (rows *mysqlRows) Close() (err error) {
 		err = mc.readUntilEOF()
 	}
 	if err == nil {
-		if err = mc.discardResults(); err != nil {
+		handleOk := mc.clearResult()
+		if err = handleOk.discardResults(); err != nil {
 			return err
 		}
 	}
@@ -160,7 +161,9 @@ func (rows *mysqlRows) nextResultSet() (int, error) {
 		return 0, io.EOF
 	}
 	rows.rs = resultSet{}
-	return rows.mc.readResultSetHeaderPacket()
+	// rows.mc.affectedRows and rows.mc.insertIds accumulate on each call to
+	// nextResultSet.
+	return rows.mc.resultUnchanged().readResultSetHeaderPacket()
 }
 
 func (rows *mysqlRows) nextNotEmptyResultSet() (int, error) {
diff --git a/statement.go b/statement.go
index d8b3975a5..31e7799c4 100644
--- a/statement.go
+++ b/statement.go
@@ -61,12 +61,10 @@ func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
 	}
 
 	mc := stmt.mc
-
-	mc.affectedRows = 0
-	mc.insertId = 0
+	handleOk := stmt.mc.clearResult()
 
 	// Read Result
-	resLen, err := mc.readResultSetHeaderPacket()
+	resLen, err := handleOk.readResultSetHeaderPacket()
 	if err != nil {
 		return nil, err
 	}
@@ -83,14 +81,12 @@ func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
 		}
 	}
 
-	if err := mc.discardResults(); err != nil {
+	if err := handleOk.discardResults(); err != nil {
 		return nil, err
 	}
 
-	return &mysqlResult{
-		affectedRows: int64(mc.affectedRows),
-		insertId:     int64(mc.insertId),
-	}, nil
+	copied := mc.result
+	return &copied, nil
 }
 
 func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
@@ -111,7 +107,8 @@ func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) {
 	mc := stmt.mc
 
 	// Read Result
-	resLen, err := mc.readResultSetHeaderPacket()
+	handleOk := stmt.mc.clearResult()
+	resLen, err := handleOk.readResultSetHeaderPacket()
 	if err != nil {
 		return nil, err
 	}

From 8365b948403b6a9d0724518c2f722e09d4561794 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Olivier=20Mengu=C3=A9?= <dolmen@cpan.org>
Date: Fri, 2 Jun 2023 17:28:47 +0200
Subject: [PATCH 204/282] doc: add link to NewConnector from FormatDSN (#1442)

Advise to use NewConnector instead of FormatDSN because roundtripping is known to not work well.
See https://github.com/go-sql-driver/mysql/issues/1410#issuecomment-1510866931
---
 dsn.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/dsn.go b/dsn.go
index 693aa4e5a..380ca9570 100644
--- a/dsn.go
+++ b/dsn.go
@@ -177,6 +177,8 @@ func writeDSNParam(buf *bytes.Buffer, hasParam *bool, name, value string) {
 
 // FormatDSN formats the given Config into a DSN string which can be passed to
 // the driver.
+//
+// Note: use [NewConnector] and [database/sql.OpenDB] to open a connection from a [*Config].
 func (cfg *Config) FormatDSN() string {
 	var buf bytes.Buffer
 

From 65ed3c5d4007ad7ea74c33e78b953b82a9ed80ed Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Olivier=20Mengu=C3=A9?= <dolmen@cpan.org>
Date: Fri, 2 Jun 2023 17:30:26 +0200
Subject: [PATCH 205/282] Add fuzz test for FormatDSN (#1444)

Run (go 1.18+): go test -fuzz FuzzFormatDSN

Note: invalid host:addr values are currently ignored as they are known
to break (ParseDSN doesn't strictly check address format).
---
 dsn_fuzz_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 47 insertions(+)
 create mode 100644 dsn_fuzz_test.go

diff --git a/dsn_fuzz_test.go b/dsn_fuzz_test.go
new file mode 100644
index 000000000..04c56ad45
--- /dev/null
+++ b/dsn_fuzz_test.go
@@ -0,0 +1,47 @@
+//go:build go1.18
+// +build go1.18
+
+package mysql
+
+import (
+	"net"
+	"testing"
+)
+
+func FuzzFormatDSN(f *testing.F) {
+	for _, test := range testDSNs { // See dsn_test.go
+		f.Add(test.in)
+	}
+
+	f.Fuzz(func(t *testing.T, dsn1 string) {
+		// Do not waste resources
+		if len(dsn1) > 1000 {
+			t.Skip("ignore: too long")
+		}
+
+		cfg1, err := ParseDSN(dsn1)
+		if err != nil {
+			t.Skipf("invalid DSN: %v", err)
+		}
+
+		dsn2 := cfg1.FormatDSN()
+		if dsn2 == dsn1 {
+			return
+		}
+
+		// Skip known cases of bad config that are not strictly checked by ParseDSN
+		if _, _, err := net.SplitHostPort(cfg1.Addr); err != nil {
+			t.Skipf("invalid addr %q: %v", cfg1.Addr, err)
+		}
+
+		cfg2, err := ParseDSN(dsn2)
+		if err != nil {
+			t.Fatalf("%q rewritten as %q: %v", dsn1, dsn2, err)
+		}
+
+		dsn3 := cfg2.FormatDSN()
+		if dsn3 != dsn2 {
+			t.Errorf("%q rewritten as %q", dsn2, dsn3)
+		}
+	})
+}

From cf948e4a9df2e97c1b0e3d068a52b5e2d53485a5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Olivier=20Mengu=C3=A9?= <dolmen@cpan.org>
Date: Tue, 13 Jun 2023 06:24:06 +0200
Subject: [PATCH 206/282] TestDSNReformat: add more roundtrip checks (#1443)

Add more roundtrip checks for ParseDSN/FormatDSN.
---
 dsn_test.go | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/dsn_test.go b/dsn_test.go
index a729d0ef8..be50102de 100644
--- a/dsn_test.go
+++ b/dsn_test.go
@@ -130,6 +130,11 @@ func TestDSNReformat(t *testing.T) {
 			res1 := fmt.Sprintf("%+v", cfg1)
 
 			dsn2 := cfg1.FormatDSN()
+			if dsn2 != dsn1 {
+				// Just log
+				t.Logf("%d. %q reformated as %q", i, dsn1, dsn2)
+			}
+
 			cfg2, err := ParseDSN(dsn2)
 			if err != nil {
 				t.Error(err.Error())
@@ -141,6 +146,11 @@ func TestDSNReformat(t *testing.T) {
 			if res1 != res2 {
 				t.Errorf("%d. %q does not match %q", i, res2, res1)
 			}
+
+			dsn3 := cfg2.FormatDSN()
+			if dsn3 != dsn2 {
+				t.Errorf("%d. %q does not match %q", i, dsn2, dsn3)
+			}
 		})
 	}
 }

From 943264b76442d87ceea460ae7745208c8143f098 Mon Sep 17 00:00:00 2001
From: Achille <achille.roussel@gmail.com>
Date: Mon, 12 Jun 2023 23:39:30 -0700
Subject: [PATCH 207/282] ignore errors returned by SetKeepAlive (#1448)

Signed-off-by: Achille Roussel <achille.roussel@gmail.com>
---
 connector.go | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/connector.go b/connector.go
index 6acf3dd50..7e0b16734 100644
--- a/connector.go
+++ b/connector.go
@@ -100,10 +100,7 @@ func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
 	// Enable TCP Keepalives on TCP connections
 	if tc, ok := mc.netConn.(*net.TCPConn); ok {
 		if err := tc.SetKeepAlive(true); err != nil {
-			// Don't send COM_QUIT before handshake.
-			mc.netConn.Close()
-			mc.netConn = nil
-			return nil, err
+			c.cfg.Logger.Print(err)
 		}
 	}
 

From 564dee9b80ffc1e406b8b91e2215d29919730ae2 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Fri, 16 Jun 2023 10:33:09 +0900
Subject: [PATCH 208/282] CI: use staticcheck (#1449)

---
 .github/workflows/test.yml |  8 ++++++++
 auth.go                    |  2 +-
 driver_test.go             | 11 +++++++----
 errors.go                  |  2 +-
 infile.go                  |  4 ++--
 nulltime.go                |  2 +-
 6 files changed, 20 insertions(+), 9 deletions(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index b2ab5e82a..3122c0e17 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -11,6 +11,14 @@ env:
   MYSQL_TEST_CONCURRENT: 1
 
 jobs:
+  lint:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+      - uses: dominikh/staticcheck-action@v1.3.0
+        with:
+          version: "2023.1.3"
+
   list:
     runs-on: ubuntu-latest
     outputs:
diff --git a/auth.go b/auth.go
index f6b157a12..d2ab0103d 100644
--- a/auth.go
+++ b/auth.go
@@ -382,7 +382,7 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
 						// parse public key
 						block, rest := pem.Decode(data[1:])
 						if block == nil {
-							return fmt.Errorf("No Pem data found, data: %s", rest)
+							return fmt.Errorf("no pem data found, data: %s", rest)
 						}
 						pkix, err := x509.ParsePKIXPublicKey(block.Bytes)
 						if err != nil {
diff --git a/driver_test.go b/driver_test.go
index cd94c434e..c937b8416 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -346,8 +346,8 @@ func TestMultiQuery(t *testing.T) {
 		rows := dbt.mustQuery("SELECT value FROM test WHERE id=1;")
 		if rows.Next() {
 			rows.Scan(&out)
-			if 5 != out {
-				dbt.Errorf("5 != %d", out)
+			if out != 5 {
+				dbt.Errorf("expected 5, got %d", out)
 			}
 
 			if rows.Next() {
@@ -1293,7 +1293,7 @@ func TestLoadData(t *testing.T) {
 		_, err = dbt.db.Exec("LOAD DATA LOCAL INFILE 'Reader::doesnotexist' INTO TABLE test")
 		if err == nil {
 			dbt.Fatal("load non-existent Reader didn't fail")
-		} else if err.Error() != "Reader 'doesnotexist' is not registered" {
+		} else if err.Error() != "reader 'doesnotexist' is not registered" {
 			dbt.Fatal(err.Error())
 		}
 	})
@@ -1401,6 +1401,7 @@ func TestReuseClosedConnection(t *testing.T) {
 	if err != nil {
 		t.Fatalf("error preparing statement: %s", err.Error())
 	}
+	//lint:ignore SA1019 this is a test
 	_, err = stmt.Exec(nil)
 	if err != nil {
 		t.Fatalf("error executing statement: %s", err.Error())
@@ -1415,6 +1416,7 @@ func TestReuseClosedConnection(t *testing.T) {
 			t.Errorf("panic after reusing a closed connection: %v", err)
 		}
 	}()
+	//lint:ignore SA1019 this is a test
 	_, err = stmt.Exec(nil)
 	if err != nil && err != driver.ErrBadConn {
 		t.Errorf("unexpected error '%s', expected '%s'",
@@ -2432,6 +2434,7 @@ func TestExecMultipleResults(t *testing.T) {
 			t.Fatalf("failed to connect: %v", err)
 		}
 		conn.Raw(func(conn interface{}) error {
+			//lint:ignore SA1019 this is a test
 			ex := conn.(driver.Execer)
 			res, err := ex.Exec(`
 			INSERT INTO test (value) VALUES ('a'), ('b');
@@ -2489,8 +2492,8 @@ func TestQueryMultipleResults(t *testing.T) {
 			t.Fatalf("failed to connect: %v", err)
 		}
 		conn.Raw(func(conn interface{}) error {
+			//lint:ignore SA1019 this is a test
 			qr := conn.(driver.Queryer)
-
 			c := conn.(*mysqlConn)
 
 			// Demonstrate that repeated queries reset the affectedRows
diff --git a/errors.go b/errors.go
index 5680b6c05..a9a3060c9 100644
--- a/errors.go
+++ b/errors.go
@@ -21,7 +21,7 @@ var (
 	ErrMalformPkt        = errors.New("malformed packet")
 	ErrNoTLS             = errors.New("TLS requested but server does not support TLS")
 	ErrCleartextPassword = errors.New("this user requires clear text authentication. If you still want to use it, please add 'allowCleartextPasswords=1' to your DSN")
-	ErrNativePassword    = errors.New("this user requires mysql native password authentication.")
+	ErrNativePassword    = errors.New("this user requires mysql native password authentication")
 	ErrOldPassword       = errors.New("this user requires old password authentication. If you still want to use it, please add 'allowOldPasswords=1' to your DSN. See also https://github.com/go-sql-driver/mysql/wiki/old_passwords")
 	ErrUnknownPlugin     = errors.New("this authentication plugin is not supported")
 	ErrOldProtocol       = errors.New("MySQL server does not support required protocol 41+")
diff --git a/infile.go b/infile.go
index cfd41914e..0c8af9f11 100644
--- a/infile.go
+++ b/infile.go
@@ -116,10 +116,10 @@ func (mc *okHandler) handleInFileRequest(name string) (err error) {
 					defer deferredClose(&err, cl)
 				}
 			} else {
-				err = fmt.Errorf("Reader '%s' is <nil>", name)
+				err = fmt.Errorf("reader '%s' is <nil>", name)
 			}
 		} else {
-			err = fmt.Errorf("Reader '%s' is not registered", name)
+			err = fmt.Errorf("reader '%s' is not registered", name)
 		}
 	} else { // File
 		name = strings.Trim(name, `"`)
diff --git a/nulltime.go b/nulltime.go
index 36c8a42c5..7d381d5c2 100644
--- a/nulltime.go
+++ b/nulltime.go
@@ -59,7 +59,7 @@ func (nt *NullTime) Scan(value interface{}) (err error) {
 	}
 
 	nt.Valid = false
-	return fmt.Errorf("Can't convert %T to time.Time", value)
+	return fmt.Errorf("can't convert %T to time.Time", value)
 }
 
 // Value implements the driver Valuer interface.

From 5d4a83127cf18cadc447807c320666de5367cc4d Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Mon, 3 Jul 2023 15:50:22 +0900
Subject: [PATCH 209/282] Parse numbers on text protocol too (#1452)

---
 driver_test.go | 87 ++++++++++++++++++++++++++++++++++----------------
 packets.go     | 39 +++++++++++++++++-----
 2 files changed, 90 insertions(+), 36 deletions(-)

diff --git a/driver_test.go b/driver_test.go
index c937b8416..2748870b7 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -148,29 +148,18 @@ func runTests(t *testing.T, dsn string, tests ...func(dbt *DBTest)) {
 		defer db2.Close()
 	}
 
-	dsn3 := dsn + "&multiStatements=true"
-	var db3 *sql.DB
-	if _, err := ParseDSN(dsn3); err != errInvalidDSNUnsafeCollation {
-		db3, err = sql.Open("mysql", dsn3)
-		if err != nil {
-			t.Fatalf("error connecting: %s", err.Error())
-		}
-		defer db3.Close()
-	}
-
-	dbt := &DBTest{t, db}
-	dbt2 := &DBTest{t, db2}
-	dbt3 := &DBTest{t, db3}
 	for _, test := range tests {
-		test(dbt)
-		dbt.db.Exec("DROP TABLE IF EXISTS test")
+		t.Run("default", func(t *testing.T) {
+			dbt := &DBTest{t, db}
+			test(dbt)
+			dbt.db.Exec("DROP TABLE IF EXISTS test")
+		})
 		if db2 != nil {
-			test(dbt2)
-			dbt2.db.Exec("DROP TABLE IF EXISTS test")
-		}
-		if db3 != nil {
-			test(dbt3)
-			dbt3.db.Exec("DROP TABLE IF EXISTS test")
+			t.Run("interpolateParams", func(t *testing.T) {
+				dbt2 := &DBTest{t, db2}
+				test(dbt2)
+				dbt2.db.Exec("DROP TABLE IF EXISTS test")
+			})
 		}
 	}
 }
@@ -316,6 +305,48 @@ func TestCRUD(t *testing.T) {
 	})
 }
 
+// TestNumbers test that selecting numeric columns.
+// Both of textRows and binaryRows should return same type and value.
+func TestNumbersToAny(t *testing.T) {
+	runTests(t, dsn, func(dbt *DBTest) {
+		dbt.mustExec("CREATE TABLE `test` (id INT PRIMARY KEY, b BOOL, i8 TINYINT, " +
+			"i16 SMALLINT, i32 INT, i64 BIGINT, f32 FLOAT, f64 DOUBLE)")
+		dbt.mustExec("INSERT INTO `test` VALUES (1, true, 127, 32767, 2147483647, 9223372036854775807, 1.25, 2.5)")
+
+		// Use binaryRows for intarpolateParams=false and textRows for intarpolateParams=true.
+		rows := dbt.mustQuery("SELECT b, i8, i16, i32, i64, f32, f64 FROM `test` WHERE id=?", 1)
+		if !rows.Next() {
+			dbt.Fatal("no data")
+		}
+		var b, i8, i16, i32, i64, f32, f64 any
+		err := rows.Scan(&b, &i8, &i16, &i32, &i64, &f32, &f64)
+		if err != nil {
+			dbt.Fatal(err)
+		}
+		if b.(int64) != 1 {
+			dbt.Errorf("b != 1")
+		}
+		if i8.(int64) != 127 {
+			dbt.Errorf("i8 != 127")
+		}
+		if i16.(int64) != 32767 {
+			dbt.Errorf("i16 != 32767")
+		}
+		if i32.(int64) != 2147483647 {
+			dbt.Errorf("i32 != 2147483647")
+		}
+		if i64.(int64) != 9223372036854775807 {
+			dbt.Errorf("i64 != 9223372036854775807")
+		}
+		if f32.(float32) != 1.25 {
+			dbt.Errorf("f32 != 1.25")
+		}
+		if f64.(float64) != 2.5 {
+			dbt.Errorf("f64 != 2.5")
+		}
+	})
+}
+
 func TestMultiQuery(t *testing.T) {
 	runTestsWithMultiStatement(t, dsn, func(dbt *DBTest) {
 		// Create Table
@@ -1808,13 +1839,13 @@ func TestConcurrent(t *testing.T) {
 	}
 
 	runTests(t, dsn, func(dbt *DBTest) {
-		var version string
-		if err := dbt.db.QueryRow("SELECT @@version").Scan(&version); err != nil {
-			dbt.Fatalf("%s", err.Error())
-		}
-		if strings.Contains(strings.ToLower(version), "mariadb") {
-			t.Skip(`TODO: "fix commands out of sync. Did you run multiple statements at once?" on MariaDB`)
-		}
+		// var version string
+		// if err := dbt.db.QueryRow("SELECT @@version").Scan(&version); err != nil {
+		// 	dbt.Fatal(err)
+		// }
+		// if strings.Contains(strings.ToLower(version), "mariadb") {
+		// 	t.Skip(`TODO: "fix commands out of sync. Did you run multiple statements at once?" on MariaDB`)
+		// }
 
 		var max int
 		err := dbt.db.QueryRow("SELECT @@max_connections").Scan(&max)
diff --git a/packets.go b/packets.go
index 1a7f2c376..66635c55b 100644
--- a/packets.go
+++ b/packets.go
@@ -17,6 +17,7 @@ import (
 	"fmt"
 	"io"
 	"math"
+	"strconv"
 	"time"
 )
 
@@ -834,7 +835,8 @@ func (rows *textRows) readRow(dest []driver.Value) error {
 
 	for i := range dest {
 		// Read bytes and convert to string
-		dest[i], isNull, n, err = readLengthEncodedString(data[pos:])
+		var buf []byte
+		buf, isNull, n, err = readLengthEncodedString(data[pos:])
 		pos += n
 
 		if err != nil {
@@ -846,19 +848,40 @@ func (rows *textRows) readRow(dest []driver.Value) error {
 			continue
 		}
 
-		if !mc.parseTime {
-			continue
-		}
-
-		// Parse time field
 		switch rows.rs.columns[i].fieldType {
 		case fieldTypeTimestamp,
 			fieldTypeDateTime,
 			fieldTypeDate,
 			fieldTypeNewDate:
-			if dest[i], err = parseDateTime(dest[i].([]byte), mc.cfg.Loc); err != nil {
-				return err
+			if mc.parseTime {
+				dest[i], err = parseDateTime(buf, mc.cfg.Loc)
+			} else {
+				dest[i] = buf
+			}
+
+		case fieldTypeTiny, fieldTypeShort, fieldTypeInt24, fieldTypeYear, fieldTypeLong:
+			dest[i], err = strconv.ParseInt(string(buf), 10, 32)
+
+		case fieldTypeLongLong:
+			if rows.rs.columns[i].flags&flagUnsigned != 0 {
+				dest[i], err = strconv.ParseUint(string(buf), 10, 64)
+			} else {
+				dest[i], err = strconv.ParseInt(string(buf), 10, 64)
 			}
+
+		case fieldTypeFloat:
+			var d float64
+			d, err = strconv.ParseFloat(string(buf), 32)
+			dest[i] = float32(d)
+
+		case fieldTypeDouble:
+			dest[i], err = strconv.ParseFloat(string(buf), 64)
+
+		default:
+			dest[i] = buf
+		}
+		if err != nil {
+			return err
 		}
 	}
 

From 0b18dac46f7f10d00411ab6fb10b8d6e4522c2d9 Mon Sep 17 00:00:00 2001
From: Daemonxiao <35677990+Daemonxiao@users.noreply.github.com>
Date: Thu, 13 Jul 2023 16:52:35 +0800
Subject: [PATCH 210/282] Add Daemonxiao to AUTHORS (#1459)

---
 AUTHORS | 1 +
 1 file changed, 1 insertion(+)

diff --git a/AUTHORS b/AUTHORS
index 7e4fac5a1..29e08b0ca 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -26,6 +26,7 @@ Carlos Nieto <jose.carlos at menteslibres.net>
 Chris Kirkland <chriskirkland at github.com>
 Chris Moos <chris at tech9computers.com>
 Craig Wilson <craiggwilson at gmail.com>
+Daemonxiao <735462752 at qq.com>
 Daniel Montoya <dsmontoyam at gmail.com>
 Daniel Nichter <nil at codenode.com>
 Daniël van Eeden <git at myname.nl>

From 2c81c69ebe815b611383d18002074e073bed745a Mon Sep 17 00:00:00 2001
From: i7a7467 <61368544+i7a7467@users.noreply.github.com>
Date: Thu, 3 Aug 2023 15:51:54 +0900
Subject: [PATCH 211/282] update docs link about load data local (#1468)

---
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 4eade6853..6ef19966c 100644
--- a/README.md
+++ b/README.md
@@ -151,7 +151,7 @@ Default:        false
 ```
 
 `allowAllFiles=true` disables the file allowlist for `LOAD DATA LOCAL INFILE` and allows *all* files.
-[*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)
+[*Might be insecure!*](https://dev.mysql.com/doc/refman/8.0/en/load-data.html#load-data-local)
 
 ##### `allowCleartextPasswords`
 
@@ -509,7 +509,7 @@ For this feature you need direct access to the package. Therefore you must chang
 import "github.com/go-sql-driver/mysql"
 ```
 
-Files must be explicitly allowed by registering them with `mysql.RegisterLocalFile(filepath)` (recommended) or the allowlist check must be deactivated by using the DSN parameter `allowAllFiles=true` ([*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)).
+Files must be explicitly allowed by registering them with `mysql.RegisterLocalFile(filepath)` (recommended) or the allowlist check must be deactivated by using the DSN parameter `allowAllFiles=true` ([*Might be insecure!*](https://dev.mysql.com/doc/refman/8.0/en/load-data.html#load-data-local)).
 
 To use a `io.Reader` a handler function must be registered with `mysql.RegisterReaderHandler(name, handler)` which returns a `io.Reader` or `io.ReadCloser`. The Reader is available with the filepath `Reader::<name>` then. Choose different names for different handlers and `DeregisterReaderHandler` when you don't need it anymore.
 

From e503d8d2c01d622d312e4b044fc2c19948d4663f Mon Sep 17 00:00:00 2001
From: Netzer7 <58796038+Netzer7@users.noreply.github.com>
Date: Mon, 7 Aug 2023 00:34:14 -0700
Subject: [PATCH 212/282] Update README.md (#1464)

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 6ef19966c..18fcc0276 100644
--- a/README.md
+++ b/README.md
@@ -200,7 +200,7 @@ Valid Values:   <name>
 Default:        none
 ```
 
-Sets the charset used for client-server interaction (`"SET NAMES <value>"`). If multiple charsets are set (separated by a comma), the following charset is used if setting the charset failes. This enables for example support for `utf8mb4` ([introduced in MySQL 5.5.3](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html)) with fallback to `utf8` for older servers (`charset=utf8mb4,utf8`).
+Sets the charset used for client-server interaction (`"SET NAMES <value>"`). If multiple charsets are set (separated by a comma), the following charset is used if setting the charset fails. This enables for example support for `utf8mb4` ([introduced in MySQL 5.5.3](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html)) with fallback to `utf8` for older servers (`charset=utf8mb4,utf8`).
 
 See also [Unicode Support](#unicode-support).
 

From 7cf548287682c36ebce3b7966f2693d58094bd5a Mon Sep 17 00:00:00 2001
From: ICHINOSE Shogo <shogo82148@gmail.com>
Date: Wed, 9 Aug 2023 20:35:39 +0900
Subject: [PATCH 213/282] add Go 1.21 and MySQL 8.1 to the build matrix (#1472)

* add Go 1.21 and MySQL 8.1 to the build matrix

* bump shogo82148/actions-setup-mysql v1.21.0
---
 .github/workflows/test.yml | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 3122c0e17..b25c9e389 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -31,12 +31,14 @@ jobs:
           import os
           go = [
               # Keep the most recent production release at the top
-              '1.20',
+              '1.21',
               # Older production releases
+              '1.20',
               '1.19',
               '1.18',
           ]
           mysql = [
+              '8.1',
               '8.0',
               '5.7',
               '5.6',
@@ -75,7 +77,7 @@ jobs:
       - uses: actions/setup-go@v4
         with:
           go-version: ${{ matrix.go }}
-      - uses: shogo82148/actions-setup-mysql@v1.16.0
+      - uses: shogo82148/actions-setup-mysql@v1.21.0
         with:
           mysql-version: ${{ matrix.mysql }}
           user: ${{ env.MYSQL_TEST_USER }}

From 43e9bef05581335f84d246aba6211af1b5133aae Mon Sep 17 00:00:00 2001
From: Pyry Kontio <pyry.kontio@drasa.eu>
Date: Sat, 2 Sep 2023 03:35:23 +0900
Subject: [PATCH 214/282] Improve DSN docstsrings (#1475)

---
 dsn.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/dsn.go b/dsn.go
index 380ca9570..f5b184e3f 100644
--- a/dsn.go
+++ b/dsn.go
@@ -36,8 +36,8 @@ var (
 type Config struct {
 	User                 string            // Username
 	Passwd               string            // Password (requires User)
-	Net                  string            // Network type
-	Addr                 string            // Network address (requires Net)
+	Net                  string            // Network (e.g. "tcp", "tcp6", "unix". default: "tcp")
+	Addr                 string            // Address (default: "127.0.0.1:3306" for "tcp" and "/tmp/mysql.sock" for "unix")
 	DBName               string            // Database name
 	Params               map[string]string // Connection parameters
 	ConnectionAttributes string            // Connection Attributes, comma-delimited string of user-defined "key:value" pairs

From 78e0387dba9f2894f3ee6004b98c49b9b11bf367 Mon Sep 17 00:00:00 2001
From: ShenFeng312 <49786112+ShenFeng312@users.noreply.github.com>
Date: Wed, 20 Sep 2023 11:55:24 +0800
Subject: [PATCH 215/282] packet: remove length check (#1481)

Fix #1478
---
 packets.go | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)

diff --git a/packets.go b/packets.go
index 66635c55b..4e27004aa 100644
--- a/packets.go
+++ b/packets.go
@@ -572,12 +572,9 @@ func (mc *okHandler) readResultSetHeaderPacket() (int, error) {
 		}
 
 		// column count
-		num, _, n := readLengthEncodedInteger(data)
-		if n-len(data) == 0 {
-			return int(num), nil
-		}
-
-		return 0, ErrMalformPkt
+		num, _, _ := readLengthEncodedInteger(data)
+		// ignore remaining data in the packet. see #1478.
+		return int(num), nil
 	}
 	return 0, err
 }

From 22e750b046938b5c13375da56a5f85ae9ce10e0b Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Thu, 28 Sep 2023 20:16:32 +0900
Subject: [PATCH 216/282] README: fix markup error (#1480)

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 18fcc0276..9257c1fd2 100644
--- a/README.md
+++ b/README.md
@@ -114,7 +114,7 @@ This has the same effect as an empty DSN string:
 
 ```
 
-`dbname` is escaped by [PathEscape()]()https://pkg.go.dev/net/url#PathEscape) since v1.8.0. If your database name is `dbname/withslash`, it becomes:
+`dbname` is escaped by [PathEscape()](https://pkg.go.dev/net/url#PathEscape) since v1.8.0. If your database name is `dbname/withslash`, it becomes:
 
 ```
 /dbname%2Fwithslash

From 19171b59bf90e6bf7a5bdf979e5e24a84b328b8a Mon Sep 17 00:00:00 2001
From: Oliver Bone <owbone@github.com>
Date: Sat, 30 Sep 2023 20:33:48 +0100
Subject: [PATCH 217/282] Close connection on ErrPktSync and ErrPktSyncMul
 (#1473)

An `ErrPktSync` or `ErrPktSyncMul` error always means that a packet
header has been read, but since the sequence ID was not correct then the
packet payload has not been read. This results in the connection being
left in a broken state, since any future operations will always result
in a "busy buffer" error. Keeping such connections alive leads to them
being repeatedly returned to the pool in this state, which can in turn
result in a large number of failures due to these "busy buffer" errors.

This commit fixes this problem by simply closing the connection before
returning either `ErrPktSync` or `ErrPktSyncMul`. This ensures that the
connection won't be returned to the pool, preventing it from causing any
further errors.
---
 AUTHORS         |  1 +
 packets.go      |  1 +
 packets_test.go | 52 ++++++++++++++++++++++++++-----------------------
 3 files changed, 30 insertions(+), 24 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 29e08b0ca..dec27daca 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -77,6 +77,7 @@ Maciej Zimnoch <maciej.zimnoch at codilime.com>
 Michael Woolnough <michael.woolnough at gmail.com>
 Nathanial Murphy <nathanial.murphy at gmail.com>
 Nicola Peduzzi <thenikso at gmail.com>
+Oliver Bone <owbone at github.com>
 Olivier Mengué <dolmen at cpan.org>
 oscarzhao <oscarzhaosl at gmail.com>
 Paul Bonser <misterpib at gmail.com>
diff --git a/packets.go b/packets.go
index 4e27004aa..0994d41a3 100644
--- a/packets.go
+++ b/packets.go
@@ -44,6 +44,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
 
 		// check packet sync [8 bit]
 		if data[3] != mc.sequence {
+			mc.Close()
 			if data[3] > mc.sequence {
 				return nil, ErrPktSyncMul
 			}
diff --git a/packets_test.go b/packets_test.go
index f429087e9..56c455188 100644
--- a/packets_test.go
+++ b/packets_test.go
@@ -133,30 +133,34 @@ func TestReadPacketSingleByte(t *testing.T) {
 }
 
 func TestReadPacketWrongSequenceID(t *testing.T) {
-	conn := new(mockConn)
-	mc := &mysqlConn{
-		buf: newBuffer(conn),
-	}
-
-	// too low sequence id
-	conn.data = []byte{0x01, 0x00, 0x00, 0x00, 0xff}
-	conn.maxReads = 1
-	mc.sequence = 1
-	_, err := mc.readPacket()
-	if err != ErrPktSync {
-		t.Errorf("expected ErrPktSync, got %v", err)
-	}
-
-	// reset
-	conn.reads = 0
-	mc.sequence = 0
-	mc.buf = newBuffer(conn)
-
-	// too high sequence id
-	conn.data = []byte{0x01, 0x00, 0x00, 0x42, 0xff}
-	_, err = mc.readPacket()
-	if err != ErrPktSyncMul {
-		t.Errorf("expected ErrPktSyncMul, got %v", err)
+	for _, testCase := range []struct {
+		ClientSequenceID byte
+		ServerSequenceID byte
+		ExpectedErr      error
+	}{
+		{
+			ClientSequenceID: 1,
+			ServerSequenceID: 0,
+			ExpectedErr:      ErrPktSync,
+		},
+		{
+			ClientSequenceID: 0,
+			ServerSequenceID: 0x42,
+			ExpectedErr:      ErrPktSyncMul,
+		},
+	} {
+		conn, mc := newRWMockConn(testCase.ClientSequenceID)
+
+		conn.data = []byte{0x01, 0x00, 0x00, testCase.ServerSequenceID, 0xff}
+		_, err := mc.readPacket()
+		if err != testCase.ExpectedErr {
+			t.Errorf("expected %v, got %v", testCase.ExpectedErr, err)
+		}
+
+		// connection should not be returned to the pool in this state
+		if mc.IsValid() {
+			t.Errorf("expected IsValid() to be false")
+		}
 	}
 }
 

From e5a2abc9cca895ca44570b171ff1f2f976d5921d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ville=20Skytt=C3=A4?= <ville.skytta@iki.fi>
Date: Wed, 4 Oct 2023 21:24:11 +0300
Subject: [PATCH 218/282] Spelling, grammar, and link fixes (#1485)

---
 CHANGELOG.md    |  6 +++---
 README.md       |  2 +-
 auth.go         |  4 ++--
 driver_test.go  | 10 +++++-----
 dsn_test.go     |  2 +-
 packets.go      |  6 +++---
 packets_test.go |  4 ++--
 7 files changed, 17 insertions(+), 17 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5166e4adb..213215c8d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -162,7 +162,7 @@ New Features:
 
  - Enable microsecond resolution on TIME, DATETIME and TIMESTAMP (#249)
  - Support for returning table alias on Columns() (#289, #359, #382)
- - Placeholder interpolation, can be actived with the DSN parameter `interpolateParams=true` (#309, #318, #490)
+ - Placeholder interpolation, can be activated with the DSN parameter `interpolateParams=true` (#309, #318, #490)
  - Support for uint64 parameters with high bit set (#332, #345)
  - Cleartext authentication plugin support (#327)
  - Exported ParseDSN function and the Config struct (#403, #419, #429)
@@ -206,7 +206,7 @@ Changes:
  - Also exported the MySQLWarning type
  - mysqlConn.Close returns the first error encountered instead of ignoring all errors
  - writePacket() automatically writes the packet size to the header
- - readPacket() uses an iterative approach instead of the recursive approach to merge splitted packets
+ - readPacket() uses an iterative approach instead of the recursive approach to merge split packets
 
 New Features:
 
@@ -254,7 +254,7 @@ Bugfixes:
 
   - Fixed MySQL 4.1 support: MySQL 4.1 sends packets with lengths which differ from the specification
   - Convert to DB timezone when inserting `time.Time`
-  - Splitted packets (more than 16MB) are now merged correctly
+  - Split packets (more than 16MB) are now merged correctly
   - Fixed false positive `io.EOF` errors when the data was fully read
   - Avoid panics on reuse of closed connections
   - Fixed empty string producing false nil values
diff --git a/README.md b/README.md
index 9257c1fd2..fff8969f3 100644
--- a/README.md
+++ b/README.md
@@ -127,7 +127,7 @@ Passwords can consist of any character. Escaping is **not** necessary.
 
 #### Protocol
 See [net.Dial](https://golang.org/pkg/net/#Dial) for more information which networks are available.
-In general you should use an Unix domain socket if available and TCP otherwise for best performance.
+In general you should use a Unix domain socket if available and TCP otherwise for best performance.
 
 #### Address
 For TCP and UDP networks, addresses have the form `host[:port]`.
diff --git a/auth.go b/auth.go
index d2ab0103d..bab282bd2 100644
--- a/auth.go
+++ b/auth.go
@@ -338,7 +338,7 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
 
 	switch plugin {
 
-	// https://insidemysql.com/preparing-your-community-connector-for-mysql-8-part-2-sha256/
+	// https://dev.mysql.com/blog-archive/preparing-your-community-connector-for-mysql-8-part-2-sha256/
 	case "caching_sha2_password":
 		switch len(authData) {
 		case 0:
@@ -376,7 +376,7 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
 						}
 
 						if data[0] != iAuthMoreData {
-							return fmt.Errorf("unexpect resp from server for caching_sha2_password perform full authentication")
+							return fmt.Errorf("unexpected resp from server for caching_sha2_password, perform full authentication")
 						}
 
 						// parse public key
diff --git a/driver_test.go b/driver_test.go
index 2748870b7..dd3d73141 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -1198,7 +1198,7 @@ func TestLongData(t *testing.T) {
 				dbt.Fatalf("LONGBLOB: length in: %d, length out: %d", len(inS), len(out))
 			}
 			if rows.Next() {
-				dbt.Error("LONGBLOB: unexpexted row")
+				dbt.Error("LONGBLOB: unexpected row")
 			}
 		} else {
 			dbt.Fatalf("LONGBLOB: no data")
@@ -1217,7 +1217,7 @@ func TestLongData(t *testing.T) {
 				dbt.Fatalf("LONGBLOB: length in: %d, length out: %d", len(in), len(out))
 			}
 			if rows.Next() {
-				dbt.Error("LONGBLOB: unexpexted row")
+				dbt.Error("LONGBLOB: unexpected row")
 			}
 		} else {
 			if err = rows.Err(); err != nil {
@@ -1293,7 +1293,7 @@ func TestLoadData(t *testing.T) {
 			dbt.Fatalf("unexpected row count: got %d, want 0", count)
 		}
 
-		// Then fille File with data and try to load it
+		// Then fill File with data and try to load it
 		file.WriteString("1\ta string\n2\ta string containing a \\t\n3\ta string containing a \\n\n4\ta string containing both \\t\\n\n")
 		file.Close()
 		dbt.mustExec(fmt.Sprintf("LOAD DATA LOCAL INFILE %q INTO TABLE test", file.Name()))
@@ -1899,7 +1899,7 @@ func TestConcurrent(t *testing.T) {
 			}(i)
 		}
 
-		// wait until all conections are open
+		// wait until all connections are open
 		wg.Wait()
 
 		if fatalError != "" {
@@ -1948,7 +1948,7 @@ func TestCustomDial(t *testing.T) {
 		t.Skipf("MySQL server not running on %s", netAddr)
 	}
 
-	// our custom dial function which justs wraps net.Dial here
+	// our custom dial function which just wraps net.Dial here
 	RegisterDialContext("mydial", func(ctx context.Context, addr string) (net.Conn, error) {
 		var d net.Dialer
 		return d.DialContext(ctx, prot, addr)
diff --git a/dsn_test.go b/dsn_test.go
index be50102de..8a6a0c10e 100644
--- a/dsn_test.go
+++ b/dsn_test.go
@@ -132,7 +132,7 @@ func TestDSNReformat(t *testing.T) {
 			dsn2 := cfg1.FormatDSN()
 			if dsn2 != dsn1 {
 				// Just log
-				t.Logf("%d. %q reformated as %q", i, dsn1, dsn2)
+				t.Logf("%d. %q reformatted as %q", i, dsn1, dsn2)
 			}
 
 			cfg2, err := ParseDSN(dsn2)
diff --git a/packets.go b/packets.go
index 0994d41a3..a1aaf20ee 100644
--- a/packets.go
+++ b/packets.go
@@ -240,7 +240,7 @@ func (mc *mysqlConn) readHandshakePacket() (data []byte, plugin string, err erro
 		// reserved (all [00]) [10 bytes]
 		pos += 1 + 2 + 2 + 1 + 10
 
-		// second part of the password cipher [mininum 13 bytes],
+		// second part of the password cipher [minimum 13 bytes],
 		// where len=MAX(13, length of auth-plugin-data - 8)
 		//
 		// The web documentation is ambiguous about the length. However,
@@ -538,7 +538,7 @@ func (mc *mysqlConn) readAuthResult() ([]byte, string, error) {
 	}
 }
 
-// Returns error if Packet is not an 'Result OK'-Packet
+// Returns error if Packet is not a 'Result OK'-Packet
 func (mc *okHandler) readResultOK() error {
 	data, err := mc.conn().readPacket()
 	if err != nil {
@@ -647,7 +647,7 @@ func (mc *mysqlConn) resultUnchanged() *okHandler {
 // Both return an instance of type *okHandler.
 type okHandler mysqlConn
 
-// Exposees the underlying type's methods.
+// Exposes the underlying type's methods.
 func (mc *okHandler) conn() *mysqlConn {
 	return (*mysqlConn)(mc)
 }
diff --git a/packets_test.go b/packets_test.go
index 56c455188..e86ec5848 100644
--- a/packets_test.go
+++ b/packets_test.go
@@ -188,7 +188,7 @@ func TestReadPacketSplit(t *testing.T) {
 	data[4] = 0x11
 	data[maxPacketSize+3] = 0x22
 
-	// 2nd packet has payload length 0 and squence id 1
+	// 2nd packet has payload length 0 and sequence id 1
 	// 00 00 00 01
 	data[pkt2ofs+3] = 0x01
 
@@ -220,7 +220,7 @@ func TestReadPacketSplit(t *testing.T) {
 	data[pkt2ofs+4] = 0x33
 	data[pkt2ofs+maxPacketSize+3] = 0x44
 
-	// 3rd packet has payload length 0 and squence id 2
+	// 3rd packet has payload length 0 and sequence id 2
 	// 00 00 00 02
 	data[pkt3ofs+3] = 0x02
 

From 37980127edfb00edd1ba2eb397a33fdea2828828 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ville=20Skytt=C3=A4?= <ville.skytta@iki.fi>
Date: Thu, 5 Oct 2023 11:44:35 +0300
Subject: [PATCH 219/282] use strings.Cut (#1486)

---
 connector.go | 9 ++++-----
 dsn.go       | 8 ++++----
 2 files changed, 8 insertions(+), 9 deletions(-)

diff --git a/connector.go b/connector.go
index 7e0b16734..ba3be71e7 100644
--- a/connector.go
+++ b/connector.go
@@ -38,13 +38,12 @@ func encodeConnectionAttributes(textAttributes string) string {
 
 	// user-defined connection attributes
 	for _, connAttr := range strings.Split(textAttributes, ",") {
-		attr := strings.SplitN(connAttr, ":", 2)
-		if len(attr) != 2 {
+		k, v, found := strings.Cut(connAttr, ":")
+		if !found {
 			continue
 		}
-		for _, v := range attr {
-			connAttrsBuf = appendLengthEncodedString(connAttrsBuf, v)
-		}
+		connAttrsBuf = appendLengthEncodedString(connAttrsBuf, k)
+		connAttrsBuf = appendLengthEncodedString(connAttrsBuf, v)
 	}
 
 	return string(connAttrsBuf)
diff --git a/dsn.go b/dsn.go
index f5b184e3f..50c7ec413 100644
--- a/dsn.go
+++ b/dsn.go
@@ -390,13 +390,13 @@ func ParseDSN(dsn string) (cfg *Config, err error) {
 // Values must be url.QueryEscape'ed
 func parseDSNParams(cfg *Config, params string) (err error) {
 	for _, v := range strings.Split(params, "&") {
-		param := strings.SplitN(v, "=", 2)
-		if len(param) != 2 {
+		key, value, found := strings.Cut(v, "=")
+		if !found {
 			continue
 		}
 
 		// cfg params
-		switch value := param[1]; param[0] {
+		switch key {
 		// Disable INFILE allowlist / enable all files
 		case "allowAllFiles":
 			var isBool bool
@@ -577,7 +577,7 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 				cfg.Params = make(map[string]string)
 			}
 
-			if cfg.Params[param[0]], err = url.QueryUnescape(value); err != nil {
+			if cfg.Params[key], err = url.QueryUnescape(value); err != nil {
 				return
 			}
 		}

From 5f74bcbcf0550e74cf0ac0170e5dd9f87683a355 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Mon, 9 Oct 2023 18:44:08 +0900
Subject: [PATCH 220/282] move stale connection check to ResetSession() (#1496)

When ResetSession was added, it was called when the connection is put into the pool.
Thet is why we had only set `mc.reset` flag on ResetSession().

In Go 1.15, this behavior was changed. (golang/go@971f8a2)

ResetSession is called when the connection is checked out from the pool.
So we can call checkConnLiveness() directly from ResetSession.
---
 connection.go | 27 +++++++++++++++++++++++++--
 packets.go    | 28 ----------------------------
 2 files changed, 25 insertions(+), 30 deletions(-)

diff --git a/connection.go b/connection.go
index 631a1dc24..660b2b0e0 100644
--- a/connection.go
+++ b/connection.go
@@ -34,7 +34,6 @@ type mysqlConn struct {
 	status           statusFlag
 	sequence         uint8
 	parseTime        bool
-	reset            bool // set when the Go SQL package calls ResetSession
 
 	// for context support (Go 1.8+)
 	watching bool
@@ -646,7 +645,31 @@ func (mc *mysqlConn) ResetSession(ctx context.Context) error {
 	if mc.closed.Load() {
 		return driver.ErrBadConn
 	}
-	mc.reset = true
+
+	// Perform a stale connection check. We only perform this check for
+	// the first query on a connection that has been checked out of the
+	// connection pool: a fresh connection from the pool is more likely
+	// to be stale, and it has not performed any previous writes that
+	// could cause data corruption, so it's safe to return ErrBadConn
+	// if the check fails.
+	if mc.cfg.CheckConnLiveness {
+		conn := mc.netConn
+		if mc.rawConn != nil {
+			conn = mc.rawConn
+		}
+		var err error
+		if mc.cfg.ReadTimeout != 0 {
+			err = conn.SetReadDeadline(time.Now().Add(mc.cfg.ReadTimeout))
+		}
+		if err == nil {
+			err = connCheck(conn)
+		}
+		if err != nil {
+			mc.cfg.Logger.Print("closing bad idle connection: ", err)
+			return driver.ErrBadConn
+		}
+	}
+
 	return nil
 }
 
diff --git a/packets.go b/packets.go
index a1aaf20ee..0127232ee 100644
--- a/packets.go
+++ b/packets.go
@@ -98,34 +98,6 @@ func (mc *mysqlConn) writePacket(data []byte) error {
 		return ErrPktTooLarge
 	}
 
-	// Perform a stale connection check. We only perform this check for
-	// the first query on a connection that has been checked out of the
-	// connection pool: a fresh connection from the pool is more likely
-	// to be stale, and it has not performed any previous writes that
-	// could cause data corruption, so it's safe to return ErrBadConn
-	// if the check fails.
-	if mc.reset {
-		mc.reset = false
-		conn := mc.netConn
-		if mc.rawConn != nil {
-			conn = mc.rawConn
-		}
-		var err error
-		if mc.cfg.CheckConnLiveness {
-			if mc.cfg.ReadTimeout != 0 {
-				err = conn.SetReadDeadline(time.Now().Add(mc.cfg.ReadTimeout))
-			}
-			if err == nil {
-				err = connCheck(conn)
-			}
-		}
-		if err != nil {
-			mc.cfg.Logger.Print("closing bad idle connection: ", err)
-			mc.Close()
-			return driver.ErrBadConn
-		}
-	}
-
 	for {
 		var size int
 		if pktLen >= maxPacketSize {

From 9c633df1f62eadfdc840840a0f229ea59cc15c33 Mon Sep 17 00:00:00 2001
From: ICHINOSE Shogo <shogo82148@gmail.com>
Date: Tue, 10 Oct 2023 17:39:46 +0900
Subject: [PATCH 221/282] fix race condition of TestConcurrent (#1490)

* fix race condition of TestConcurrent

* run tests with the '-race' option
---
 .github/workflows/test.yml | 2 +-
 driver_test.go             | 3 +--
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index b25c9e389..8e1cb9bc3 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -96,7 +96,7 @@ jobs:
 
       - name: test
         run: |
-          go test -v '-covermode=count' '-coverprofile=coverage.out'
+          go test -v '-race' '-covermode=atomic' '-coverprofile=coverage.out'
 
       - name: Send coverage
         uses: shogo82148/actions-goveralls@v1
diff --git a/driver_test.go b/driver_test.go
index dd3d73141..74f15c2d2 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -1872,7 +1872,6 @@ func TestConcurrent(t *testing.T) {
 				defer wg.Done()
 
 				tx, err := dbt.db.Begin()
-				atomic.AddInt32(&remaining, -1)
 
 				if err != nil {
 					if err.Error() != "Error 1040: Too many connections" {
@@ -1882,7 +1881,7 @@ func TestConcurrent(t *testing.T) {
 				}
 
 				// keep the connection busy until all connections are open
-				for remaining > 0 {
+				for atomic.AddInt32(&remaining, -1) > 0 {
 					if _, err = tx.Exec("DO 1"); err != nil {
 						fatalf("error on conn %d: %s", id, err.Error())
 						return

From 278a0b9e6b34ccc52aa213681836a79336714d34 Mon Sep 17 00:00:00 2001
From: ICHINOSE Shogo <shogo82148@gmail.com>
Date: Tue, 10 Oct 2023 17:48:58 +0900
Subject: [PATCH 222/282] mark fail, mustExec and mustQuery as test helpers
 (#1488)

---
 driver_test.go | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/driver_test.go b/driver_test.go
index 74f15c2d2..f46d38df6 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -165,6 +165,7 @@ func runTests(t *testing.T, dsn string, tests ...func(dbt *DBTest)) {
 }
 
 func (dbt *DBTest) fail(method, query string, err error) {
+	dbt.Helper()
 	if len(query) > 300 {
 		query = "[query too large to print]"
 	}
@@ -172,6 +173,7 @@ func (dbt *DBTest) fail(method, query string, err error) {
 }
 
 func (dbt *DBTest) mustExec(query string, args ...interface{}) (res sql.Result) {
+	dbt.Helper()
 	res, err := dbt.db.Exec(query, args...)
 	if err != nil {
 		dbt.fail("exec", query, err)
@@ -180,6 +182,7 @@ func (dbt *DBTest) mustExec(query string, args ...interface{}) (res sql.Result)
 }
 
 func (dbt *DBTest) mustQuery(query string, args ...interface{}) (rows *sql.Rows) {
+	dbt.Helper()
 	rows, err := dbt.db.Query(query, args...)
 	if err != nil {
 		dbt.fail("query", query, err)

From 1e6b8d7df47928193f2b1a04b5f7f06907187508 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Olivier=20Mengu=C3=A9?= <dolmen@cpan.org>
Date: Thu, 19 Oct 2023 07:33:23 +0200
Subject: [PATCH 223/282] Remove obsolete fuzz.go (#1498)

fuzz.go (added in #1097) uses gofuzz.
But #1444 added a better fuzzer that uses Go builtin fuzzing.

Closes #1445.
---
 fuzz.go | 25 -------------------------
 1 file changed, 25 deletions(-)
 delete mode 100644 fuzz.go

diff --git a/fuzz.go b/fuzz.go
deleted file mode 100644
index 3a4ec25a9..000000000
--- a/fuzz.go
+++ /dev/null
@@ -1,25 +0,0 @@
-// Go MySQL Driver - A MySQL-Driver for Go's database/sql package.
-//
-// Copyright 2020 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/.
-
-//go:build gofuzz
-// +build gofuzz
-
-package mysql
-
-import (
-	"database/sql"
-)
-
-func Fuzz(data []byte) int {
-	db, err := sql.Open("mysql", string(data))
-	if err != nil {
-		return 0
-	}
-	db.Close()
-	return 1
-}

From 62c29ce0b1b8f84567de97ca0d32cebd53f05aa9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Olivier=20Mengu=C3=A9?= <dolmen@cpan.org>
Date: Tue, 24 Oct 2023 10:05:53 +0200
Subject: [PATCH 224/282] Allow to change (or disable) the default driver name
 for registration (#1499)

A link variable now allows to change or disable the name of the driver
that is automatically registered with database/sql:

Change driver name:
    go build "-ldflags=-X github.com/go-sql-driver/mysql.driverName=custom"

Disable driver registration (set driverName to empty string):
    go build "-ldflags=-X github.com/go-sql-driver/mysql.driverName="

In the same way, a variable overridable at link time is also provided to
override the driver name used in the test suite. This allows to run our
test suite on another driver.

    go test "-ldflags=-X github.com/go-sql-driver/mysql.driverNameTest=custom"

driverName is propagated to driverNameTest unless driverNameTest is
explicitely defined.
---
 benchmark_test.go |  8 ++++----
 driver.go         |  8 +++++++-
 driver_test.go    | 28 +++++++++++++++++++---------
 3 files changed, 30 insertions(+), 14 deletions(-)

diff --git a/benchmark_test.go b/benchmark_test.go
index fc70df60d..a4ecc0a63 100644
--- a/benchmark_test.go
+++ b/benchmark_test.go
@@ -48,7 +48,7 @@ func (tb *TB) checkStmt(stmt *sql.Stmt, err error) *sql.Stmt {
 
 func initDB(b *testing.B, queries ...string) *sql.DB {
 	tb := (*TB)(b)
-	db := tb.checkDB(sql.Open("mysql", dsn))
+	db := tb.checkDB(sql.Open(driverNameTest, dsn))
 	for _, query := range queries {
 		if _, err := db.Exec(query); err != nil {
 			b.Fatalf("error on %q: %v", query, err)
@@ -105,7 +105,7 @@ func BenchmarkExec(b *testing.B) {
 	tb := (*TB)(b)
 	b.StopTimer()
 	b.ReportAllocs()
-	db := tb.checkDB(sql.Open("mysql", dsn))
+	db := tb.checkDB(sql.Open(driverNameTest, dsn))
 	db.SetMaxIdleConns(concurrencyLevel)
 	defer db.Close()
 
@@ -151,7 +151,7 @@ func BenchmarkRoundtripTxt(b *testing.B) {
 	sampleString := string(sample)
 	b.ReportAllocs()
 	tb := (*TB)(b)
-	db := tb.checkDB(sql.Open("mysql", dsn))
+	db := tb.checkDB(sql.Open(driverNameTest, dsn))
 	defer db.Close()
 	b.StartTimer()
 	var result string
@@ -184,7 +184,7 @@ func BenchmarkRoundtripBin(b *testing.B) {
 	sample, min, max := initRoundtripBenchmarks()
 	b.ReportAllocs()
 	tb := (*TB)(b)
-	db := tb.checkDB(sql.Open("mysql", dsn))
+	db := tb.checkDB(sql.Open(driverNameTest, dsn))
 	defer db.Close()
 	stmt := tb.checkStmt(db.Prepare("SELECT ?"))
 	defer stmt.Close()
diff --git a/driver.go b/driver.go
index 0ed8fa1c5..45528b920 100644
--- a/driver.go
+++ b/driver.go
@@ -90,8 +90,14 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
 	return c.Connect(context.Background())
 }
 
+// This variable can be replaced with -ldflags like below:
+// go build "-ldflags=-X github.com/go-sql-driver/mysql.driverName=custom"
+var driverName = "mysql"
+
 func init() {
-	sql.Register("mysql", &MySQLDriver{})
+	if driverName != "" {
+		sql.Register(driverName, &MySQLDriver{})
+	}
 }
 
 // NewConnector returns new driver.Connector.
diff --git a/driver_test.go b/driver_test.go
index f46d38df6..13e07e753 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -31,6 +31,16 @@ import (
 	"time"
 )
 
+// This variable can be replaced with -ldflags like below:
+// go test "-ldflags=-X github.com/go-sql-driver/mysql.driverNameTest=custom"
+var driverNameTest string
+
+func init() {
+	if driverNameTest == "" {
+		driverNameTest = driverName
+	}
+}
+
 // Ensure that all the driver interfaces are implemented
 var (
 	_ driver.Rows = &binaryRows{}
@@ -111,7 +121,7 @@ func runTestsWithMultiStatement(t *testing.T, dsn string, tests ...func(dbt *DBT
 	dsn += "&multiStatements=true"
 	var db *sql.DB
 	if _, err := ParseDSN(dsn); err != errInvalidDSNUnsafeCollation {
-		db, err = sql.Open("mysql", dsn)
+		db, err = sql.Open(driverNameTest, dsn)
 		if err != nil {
 			t.Fatalf("error connecting: %s", err.Error())
 		}
@@ -130,7 +140,7 @@ func runTests(t *testing.T, dsn string, tests ...func(dbt *DBTest)) {
 		t.Skipf("MySQL server not running on %s", netAddr)
 	}
 
-	db, err := sql.Open("mysql", dsn)
+	db, err := sql.Open(driverNameTest, dsn)
 	if err != nil {
 		t.Fatalf("error connecting: %s", err.Error())
 	}
@@ -141,7 +151,7 @@ func runTests(t *testing.T, dsn string, tests ...func(dbt *DBTest)) {
 	dsn2 := dsn + "&interpolateParams=true"
 	var db2 *sql.DB
 	if _, err := ParseDSN(dsn2); err != errInvalidDSNUnsafeCollation {
-		db2, err = sql.Open("mysql", dsn2)
+		db2, err = sql.Open(driverNameTest, dsn2)
 		if err != nil {
 			t.Fatalf("error connecting: %s", err.Error())
 		}
@@ -1917,7 +1927,7 @@ func testDialError(t *testing.T, dialErr error, expectErr error) {
 		return nil, dialErr
 	})
 
-	db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@mydial(%s)/%s?timeout=30s", user, pass, addr, dbname))
+	db, err := sql.Open(driverNameTest, fmt.Sprintf("%s:%s@mydial(%s)/%s?timeout=30s", user, pass, addr, dbname))
 	if err != nil {
 		t.Fatalf("error connecting: %s", err.Error())
 	}
@@ -1956,7 +1966,7 @@ func TestCustomDial(t *testing.T) {
 		return d.DialContext(ctx, prot, addr)
 	})
 
-	db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@mydial(%s)/%s?timeout=30s", user, pass, addr, dbname))
+	db, err := sql.Open(driverNameTest, fmt.Sprintf("%s:%s@mydial(%s)/%s?timeout=30s", user, pass, addr, dbname))
 	if err != nil {
 		t.Fatalf("error connecting: %s", err.Error())
 	}
@@ -2054,7 +2064,7 @@ func TestUnixSocketAuthFail(t *testing.T) {
 		}
 		t.Logf("socket: %s", socket)
 		badDSN := fmt.Sprintf("%s:%s@unix(%s)/%s?timeout=30s", user, badPass, socket, dbname)
-		db, err := sql.Open("mysql", badDSN)
+		db, err := sql.Open(driverNameTest, badDSN)
 		if err != nil {
 			t.Fatalf("error connecting: %s", err.Error())
 		}
@@ -2243,7 +2253,7 @@ func TestEmptyPassword(t *testing.T) {
 	}
 
 	dsn := fmt.Sprintf("%s:%s@%s/%s?timeout=30s", user, "", netAddr, dbname)
-	db, err := sql.Open("mysql", dsn)
+	db, err := sql.Open(driverNameTest, dsn)
 	if err == nil {
 		defer db.Close()
 		err = db.Ping()
@@ -3210,7 +3220,7 @@ func TestConnectorObeysDialTimeouts(t *testing.T) {
 		return d.DialContext(ctx, prot, addr)
 	})
 
-	db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@dialctxtest(%s)/%s?timeout=30s", user, pass, addr, dbname))
+	db, err := sql.Open(driverNameTest, fmt.Sprintf("%s:%s@dialctxtest(%s)/%s?timeout=30s", user, pass, addr, dbname))
 	if err != nil {
 		t.Fatalf("error connecting: %s", err.Error())
 	}
@@ -3375,7 +3385,7 @@ func TestConnectionAttributes(t *testing.T) {
 
 	var db *sql.DB
 	if _, err := ParseDSN(dsn); err != errInvalidDSNUnsafeCollation {
-		db, err = sql.Open("mysql", dsn)
+		db, err = sql.Open(driverNameTest, dsn)
 		if err != nil {
 			t.Fatalf("error connecting: %s", err.Error())
 		}

From c175348d98a9a245462ade75c6fde69424eb6fd4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Olivier=20Mengu=C3=A9?= <dolmen@cpan.org>
Date: Tue, 24 Oct 2023 10:08:26 +0200
Subject: [PATCH 225/282] testing: expose testing.TB in DBTest instead of full
 *testing.T (#1500)

Reduce the methods exposed by DBTest to the subset of testing.T exposed
in the testing.TB interface.
---
 driver_test.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/driver_test.go b/driver_test.go
index 13e07e753..f256011a7 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -92,7 +92,7 @@ func init() {
 }
 
 type DBTest struct {
-	*testing.T
+	testing.TB
 	db *sql.DB
 }
 

From 18b74e415dc148b486af13faa300fdefe26e484f Mon Sep 17 00:00:00 2001
From: Vaibhav Panvalkar <42548559+panvalkar1994@users.noreply.github.com>
Date: Tue, 7 Nov 2023 17:27:05 +0530
Subject: [PATCH 226/282]  symbol removed from installation command (#1510)

Co-authored-by: panvalkar1994 <vaibhav.panvalkar@elastic.run>
---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index fff8969f3..ac79890a7 100644
--- a/README.md
+++ b/README.md
@@ -48,7 +48,7 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
 ## Installation
 Simple install the package to your [$GOPATH](https://github.com/golang/go/wiki/GOPATH "GOPATH") with the [go tool](https://golang.org/cmd/go/ "go command") from shell:
 ```bash
-$ go get -u github.com/go-sql-driver/mysql
+go get -u github.com/go-sql-driver/mysql
 ```
 Make sure [Git is installed](https://git-scm.com/downloads) on your machine and in your system's `PATH`.
 

From b2e2ccbf16565d9706a2ffe77aafb21fb545a8d5 Mon Sep 17 00:00:00 2001
From: Xiang Zhang <angwerzx@126.com>
Date: Tue, 14 Nov 2023 19:17:17 +0800
Subject: [PATCH 227/282] QueryUnescape DSN ConnectionAttribute value (#1470)

---
 AUTHORS        |  2 ++
 driver_test.go | 18 +++++++++++++++---
 dsn.go         |  6 +++++-
 3 files changed, 22 insertions(+), 4 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index dec27daca..c84293100 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -109,6 +109,7 @@ Xiangyu Hu <xiangyu.hu at outlook.com>
 Xiaobing Jiang <s7v7nislands at gmail.com>
 Xiuming Chen <cc at cxm.cc>
 Xuehong Chan <chanxuehong at gmail.com>
+Zhang Xiang <angwerzx at 126.com>
 Zhenye Xie <xiezhenye at gmail.com>
 Zhixin Wen <john.wenzhixin at gmail.com>
 Ziheng Lyu <zihenglv at gmail.com>
@@ -127,6 +128,7 @@ InfoSum Ltd.
 Keybase Inc.
 Multiplay Ltd.
 Percona LLC
+PingCAP Inc.
 Pivotal Inc.
 Stripe Inc.
 Zendesk Inc.
diff --git a/driver_test.go b/driver_test.go
index f256011a7..8c02f6d1c 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -3379,9 +3379,10 @@ func TestConnectionAttributes(t *testing.T) {
 
 	attr1 := "attr1"
 	value1 := "value1"
-	attr2 := "foo"
-	value2 := "boo"
-	dsn += fmt.Sprintf("&connectionAttributes=%s:%s,%s:%s", attr1, value1, attr2, value2)
+	attr2 := "fo/o"
+	value2 := "bo/o"
+	dsn += "&connectionAttributes=" + url.QueryEscape(fmt.Sprintf("%s:%s,%s:%s", attr1, value1, attr2, value2))
+
 
 	var db *sql.DB
 	if _, err := ParseDSN(dsn); err != errInvalidDSNUnsafeCollation {
@@ -3407,6 +3408,17 @@ func TestConnectionAttributes(t *testing.T) {
 	}
 	rows.Close()
 
+	rows = dbt.mustQuery(queryString, attr1)
+	if rows.Next() {
+		rows.Scan(&attrValue)
+		if attrValue != value1 {
+			dbt.Errorf("expected %q, got %q", value1, attrValue)
+		}
+	} else {
+		dbt.Errorf("no data")
+	}
+	rows.Close()
+
 	rows = dbt.mustQuery(queryString, attr2)
 	if rows.Next() {
 		rows.Scan(&attrValue)
diff --git a/dsn.go b/dsn.go
index 50c7ec413..ef0608636 100644
--- a/dsn.go
+++ b/dsn.go
@@ -569,7 +569,11 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 
 		// Connection attributes
 		case "connectionAttributes":
-			cfg.ConnectionAttributes = value
+			connectionAttributes, err := url.QueryUnescape(value)
+			if err != nil {
+				return fmt.Errorf("invalid connectionAttributes value: %v", err)
+			}
+			cfg.ConnectionAttributes = connectionAttributes
 
 		default:
 			// lazy init

From a4c260b40eeb51bd823d8b04d0e0e8d072e56adf Mon Sep 17 00:00:00 2001
From: Aidan <97376271+keeplearning20221@users.noreply.github.com>
Date: Wed, 15 Nov 2023 18:40:52 +0800
Subject: [PATCH 228/282] fix hangup when error in multi resultsets (#1462)

Fix #1361

Co-authored-by: Inada Naoki <songofacandy@gmail.com>
---
 AUTHORS        |  1 +
 driver_test.go | 41 +++++++++++++++++++++++++++++++++++++++++
 rows.go        |  8 +++++++-
 3 files changed, 49 insertions(+), 1 deletion(-)

diff --git a/AUTHORS b/AUTHORS
index c84293100..c7e159603 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -13,6 +13,7 @@
 
 Aaron Hopkins <go-sql-driver at die.net>
 Achille Roussel <achille.roussel at gmail.com>
+Aidan <aidan.liu at pingcap.com>
 Alex Snast <alexsn at fb.com>
 Alexey Palazhchenko <alexey.palazhchenko at gmail.com>
 Andrew Reid <andrew.reid at tixtrack.com>
diff --git a/driver_test.go b/driver_test.go
index 8c02f6d1c..ab780f04c 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -3430,3 +3430,44 @@ func TestConnectionAttributes(t *testing.T) {
 	}
 	rows.Close()
 }
+
+func TestErrorInMultiResult(t *testing.T) {
+	// https://github.com/go-sql-driver/mysql/issues/1361
+	var db *sql.DB
+	if _, err := ParseDSN(dsn); err != errInvalidDSNUnsafeCollation {
+		db, err = sql.Open("mysql", dsn)
+		if err != nil {
+			t.Fatalf("error connecting: %s", err.Error())
+		}
+		defer db.Close()
+	}
+
+	dbt := &DBTest{t, db}
+	query := `
+CREATE PROCEDURE test_proc1()
+BEGIN
+	SELECT 1,2;
+	SELECT 3,4;
+	SIGNAL SQLSTATE '10000' SET MESSAGE_TEXT = "some error",  MYSQL_ERRNO = 10000;
+END;
+`
+	runCallCommand(dbt, query, "test_proc1")
+}
+
+func runCallCommand(dbt *DBTest, query, name string) {
+	dbt.mustExec(fmt.Sprintf("DROP PROCEDURE IF EXISTS %s", name))
+	dbt.mustExec(query)
+	defer dbt.mustExec("DROP PROCEDURE " + name)
+	rows, err := dbt.db.Query(fmt.Sprintf("CALL %s", name))
+	if err != nil {
+		return
+	}
+	defer rows.Close()
+
+	for rows.Next() {
+	}
+	for rows.NextResultSet() {
+		for rows.Next() {
+		}
+	}
+}
diff --git a/rows.go b/rows.go
index 63d0ed2d5..81fa6062c 100644
--- a/rows.go
+++ b/rows.go
@@ -163,7 +163,13 @@ func (rows *mysqlRows) nextResultSet() (int, error) {
 	rows.rs = resultSet{}
 	// rows.mc.affectedRows and rows.mc.insertIds accumulate on each call to
 	// nextResultSet.
-	return rows.mc.resultUnchanged().readResultSetHeaderPacket()
+	resLen, err := rows.mc.resultUnchanged().readResultSetHeaderPacket()
+	if err != nil {
+		// Clean up about multi-results flag
+		rows.rs.done = true
+		rows.mc.status = rows.mc.status & (^statusMoreResultsExists)
+	}
+	return resLen, err
 }
 
 func (rows *mysqlRows) nextNotEmptyResultSet() (int, error) {

From 98d72897bab37633105da6dce698ce074fd19995 Mon Sep 17 00:00:00 2001
From: Jason Ng <oblitorum@gmail.com>
Date: Thu, 23 Nov 2023 21:01:24 +0800
Subject: [PATCH 229/282] Add default connection attribute '_server_host'
 (#1506)

The `_server_host` connection attribute is supported in MariaDB (Connector/C)
https://mariadb.com/kb/en/mysql_optionsv/#connection-attribute-options
---
 AUTHORS           |  2 ++
 connector.go      | 21 +++++++-------
 connector_test.go |  7 ++---
 const.go          |  1 +
 driver.go         |  9 ++----
 driver_test.go    | 71 ++++++++++++++++++++++++-----------------------
 packets.go        | 16 +++++------
 packets_test.go   |  5 +---
 8 files changed, 64 insertions(+), 68 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index c7e159603..2caa7d706 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -50,6 +50,7 @@ INADA Naoki <songofacandy at gmail.com>
 Jacek Szwec <szwec.jacek at gmail.com>
 James Harr <james.harr at gmail.com>
 Janek Vedock <janekvedock at comcast.net>
+Jason Ng <oblitorum at gmail.com>
 Jean-Yves Pellé <jy at pelle.link>
 Jeff Hodges <jeff at somethingsimilar.com>
 Jeffrey Charles <jeffreycharles at gmail.com>
@@ -131,6 +132,7 @@ Multiplay Ltd.
 Percona LLC
 PingCAP Inc.
 Pivotal Inc.
+Shattered Silicon Ltd.
 Stripe Inc.
 Zendesk Inc.
 Dolthub Inc.
diff --git a/connector.go b/connector.go
index ba3be71e7..3cef7963f 100644
--- a/connector.go
+++ b/connector.go
@@ -11,7 +11,6 @@ package mysql
 import (
 	"context"
 	"database/sql/driver"
-	"fmt"
 	"net"
 	"os"
 	"strconv"
@@ -23,8 +22,8 @@ type connector struct {
 	encodedAttributes string  // Encoded connection attributes.
 }
 
-func encodeConnectionAttributes(textAttributes string) string {
-	connAttrsBuf := make([]byte, 0, 251)
+func encodeConnectionAttributes(cfg *Config) string {
+	connAttrsBuf := make([]byte, 0)
 
 	// default connection attributes
 	connAttrsBuf = appendLengthEncodedString(connAttrsBuf, connAttrClientName)
@@ -35,9 +34,14 @@ func encodeConnectionAttributes(textAttributes string) string {
 	connAttrsBuf = appendLengthEncodedString(connAttrsBuf, connAttrPlatformValue)
 	connAttrsBuf = appendLengthEncodedString(connAttrsBuf, connAttrPid)
 	connAttrsBuf = appendLengthEncodedString(connAttrsBuf, strconv.Itoa(os.Getpid()))
+	serverHost, _, _ := net.SplitHostPort(cfg.Addr)
+	if serverHost != "" {
+		connAttrsBuf = appendLengthEncodedString(connAttrsBuf, connAttrServerHost)
+		connAttrsBuf = appendLengthEncodedString(connAttrsBuf, serverHost)
+	}
 
 	// user-defined connection attributes
-	for _, connAttr := range strings.Split(textAttributes, ",") {
+	for _, connAttr := range strings.Split(cfg.ConnectionAttributes, ",") {
 		k, v, found := strings.Cut(connAttr, ":")
 		if !found {
 			continue
@@ -49,15 +53,12 @@ func encodeConnectionAttributes(textAttributes string) string {
 	return string(connAttrsBuf)
 }
 
-func newConnector(cfg *Config) (*connector, error) {
-	encodedAttributes := encodeConnectionAttributes(cfg.ConnectionAttributes)
-	if len(encodedAttributes) > 250 {
-		return nil, fmt.Errorf("connection attributes are longer than 250 bytes: %dbytes (%q)", len(encodedAttributes), cfg.ConnectionAttributes)
-	}
+func newConnector(cfg *Config) *connector {
+	encodedAttributes := encodeConnectionAttributes(cfg)
 	return &connector{
 		cfg:               cfg,
 		encodedAttributes: encodedAttributes,
-	}, nil
+	}
 }
 
 // Connect implements driver.Connector interface.
diff --git a/connector_test.go b/connector_test.go
index bedb44ce2..82d8c5989 100644
--- a/connector_test.go
+++ b/connector_test.go
@@ -8,16 +8,13 @@ import (
 )
 
 func TestConnectorReturnsTimeout(t *testing.T) {
-	connector, err := newConnector(&Config{
+	connector := newConnector(&Config{
 		Net:     "tcp",
 		Addr:    "1.1.1.1:1234",
 		Timeout: 10 * time.Millisecond,
 	})
-	if err != nil {
-		t.Fatal(err)
-	}
 
-	_, err = connector.Connect(context.Background())
+	_, err := connector.Connect(context.Background())
 	if err == nil {
 		t.Fatal("error expected")
 	}
diff --git a/const.go b/const.go
index 0f2621a6f..22526e031 100644
--- a/const.go
+++ b/const.go
@@ -26,6 +26,7 @@ const (
 	connAttrPlatform        = "_platform"
 	connAttrPlatformValue   = runtime.GOARCH
 	connAttrPid             = "_pid"
+	connAttrServerHost      = "_server_host"
 )
 
 // MySQL constants documentation:
diff --git a/driver.go b/driver.go
index 45528b920..105316b81 100644
--- a/driver.go
+++ b/driver.go
@@ -83,10 +83,7 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
 	if err != nil {
 		return nil, err
 	}
-	c, err := newConnector(cfg)
-	if err != nil {
-		return nil, err
-	}
+	c := newConnector(cfg)
 	return c.Connect(context.Background())
 }
 
@@ -108,7 +105,7 @@ func NewConnector(cfg *Config) (driver.Connector, error) {
 	if err := cfg.normalize(); err != nil {
 		return nil, err
 	}
-	return newConnector(cfg)
+	return newConnector(cfg), nil
 }
 
 // OpenConnector implements driver.DriverContext.
@@ -117,5 +114,5 @@ func (d MySQLDriver) OpenConnector(dsn string) (driver.Connector, error) {
 	if err != nil {
 		return nil, err
 	}
-	return newConnector(cfg)
+	return newConnector(cfg), nil
 }
diff --git a/driver_test.go b/driver_test.go
index ab780f04c..efbff1792 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -24,6 +24,7 @@ import (
 	"os"
 	"reflect"
 	"runtime"
+	"strconv"
 	"strings"
 	"sync"
 	"sync/atomic"
@@ -3377,12 +3378,30 @@ func TestConnectionAttributes(t *testing.T) {
 		t.Skipf("MySQL server not running on %s", netAddr)
 	}
 
-	attr1 := "attr1"
-	value1 := "value1"
-	attr2 := "fo/o"
-	value2 := "bo/o"
-	dsn += "&connectionAttributes=" + url.QueryEscape(fmt.Sprintf("%s:%s,%s:%s", attr1, value1, attr2, value2))
+	defaultAttrs := []string{
+		connAttrClientName,
+		connAttrOS,
+		connAttrPlatform,
+		connAttrPid,
+		connAttrServerHost,
+	}
+	host, _, _ := net.SplitHostPort(addr)
+	defaultAttrValues := []string{
+		connAttrClientNameValue,
+		connAttrOSValue,
+		connAttrPlatformValue,
+		strconv.Itoa(os.Getpid()),
+		host,
+	}
+
+	customAttrs := []string{"attr1", "fo/o"}
+	customAttrValues := []string{"value1", "bo/o"}
 
+	customAttrStrs := make([]string, len(customAttrs))
+	for i := range customAttrs {
+		customAttrStrs[i] = fmt.Sprintf("%s:%s", customAttrs[i], customAttrValues[i])
+	}
+	dsn += "&connectionAttributes=" + url.QueryEscape(strings.Join(customAttrStrs, ","))
 
 	var db *sql.DB
 	if _, err := ParseDSN(dsn); err != errInvalidDSNUnsafeCollation {
@@ -3395,40 +3414,24 @@ func TestConnectionAttributes(t *testing.T) {
 
 	dbt := &DBTest{t, db}
 
-	var attrValue string
-	queryString := "SELECT ATTR_VALUE FROM performance_schema.session_account_connect_attrs WHERE PROCESSLIST_ID = CONNECTION_ID() and ATTR_NAME = ?"
-	rows := dbt.mustQuery(queryString, connAttrClientName)
-	if rows.Next() {
-		rows.Scan(&attrValue)
-		if attrValue != connAttrClientNameValue {
-			dbt.Errorf("expected %q, got %q", connAttrClientNameValue, attrValue)
-		}
-	} else {
-		dbt.Errorf("no data")
-	}
-	rows.Close()
+	queryString := "SELECT ATTR_NAME, ATTR_VALUE FROM performance_schema.session_account_connect_attrs WHERE PROCESSLIST_ID = CONNECTION_ID()"
+	rows := dbt.mustQuery(queryString)
+	defer rows.Close()
 
-	rows = dbt.mustQuery(queryString, attr1)
-	if rows.Next() {
-		rows.Scan(&attrValue)
-		if attrValue != value1 {
-			dbt.Errorf("expected %q, got %q", value1, attrValue)
-		}
-	} else {
-		dbt.Errorf("no data")
+	rowsMap := make(map[string]string)
+	for rows.Next() {
+		var attrName, attrValue string
+		rows.Scan(&attrName, &attrValue)
+		rowsMap[attrName] = attrValue
 	}
-	rows.Close()
 
-	rows = dbt.mustQuery(queryString, attr2)
-	if rows.Next() {
-		rows.Scan(&attrValue)
-		if attrValue != value2 {
-			dbt.Errorf("expected %q, got %q", value2, attrValue)
+	connAttrs := append(append([]string{}, defaultAttrs...), customAttrs...)
+	expectedAttrValues := append(append([]string{}, defaultAttrValues...), customAttrValues...)
+	for i := range connAttrs {
+		if gotValue := rowsMap[connAttrs[i]]; gotValue != expectedAttrValues[i] {
+			dbt.Errorf("expected %q, got %q", expectedAttrValues[i], gotValue)
 		}
-	} else {
-		dbt.Errorf("no data")
 	}
-	rows.Close()
 }
 
 func TestErrorInMultiResult(t *testing.T) {
diff --git a/packets.go b/packets.go
index 0127232ee..49e6bb058 100644
--- a/packets.go
+++ b/packets.go
@@ -292,15 +292,14 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 		pktLen += n + 1
 	}
 
-	// 1 byte to store length of all key-values
-	// NOTE: Actually, this is length encoded integer.
-	// But we support only len(connAttrBuf) < 251 for now because takeSmallBuffer
-	// doesn't support buffer size more than 4096 bytes.
-	// TODO(methane): Rewrite buffer management.
-	pktLen += 1 + len(mc.connector.encodedAttributes)
+	// encode length of the connection attributes
+	var connAttrsLEIBuf [9]byte
+	connAttrsLen := len(mc.connector.encodedAttributes)
+	connAttrsLEI := appendLengthEncodedInteger(connAttrsLEIBuf[:0], uint64(connAttrsLen))
+	pktLen += len(connAttrsLEI) + len(mc.connector.encodedAttributes)
 
 	// Calculate packet length and get buffer with that size
-	data, err := mc.buf.takeSmallBuffer(pktLen + 4)
+	data, err := mc.buf.takeBuffer(pktLen + 4)
 	if err != nil {
 		// cannot take the buffer. Something must be wrong with the connection
 		mc.cfg.Logger.Print(err)
@@ -380,8 +379,7 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 	pos++
 
 	// Connection Attributes
-	data[pos] = byte(len(mc.connector.encodedAttributes))
-	pos++
+	pos += copy(data[pos:], connAttrsLEI)
 	pos += copy(data[pos:], []byte(mc.connector.encodedAttributes))
 
 	// Send Auth packet
diff --git a/packets_test.go b/packets_test.go
index e86ec5848..fa4683eab 100644
--- a/packets_test.go
+++ b/packets_test.go
@@ -96,10 +96,7 @@ var _ net.Conn = new(mockConn)
 
 func newRWMockConn(sequence uint8) (*mockConn, *mysqlConn) {
 	conn := new(mockConn)
-	connector, err := newConnector(NewConfig())
-	if err != nil {
-		panic(err)
-	}
+	connector := newConnector(NewConfig())
 	mc := &mysqlConn{
 		buf:              newBuffer(conn),
 		cfg:              connector.cfg,

From d9f43839450e9361c16685ea24f0bce0da1935b7 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Tue, 12 Dec 2023 14:21:53 +0900
Subject: [PATCH 230/282] fix fragile test (#1522)

---
 driver_test.go | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/driver_test.go b/driver_test.go
index efbff1792..87892a09a 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -128,6 +128,8 @@ func runTestsWithMultiStatement(t *testing.T, dsn string, tests ...func(dbt *DBT
 		}
 		defer db.Close()
 	}
+	// Previous test may be skipped without dropping the test table
+	db.Exec("DROP TABLE IF EXISTS test")
 
 	dbt := &DBTest{t, db}
 	for _, test := range tests {
@@ -147,6 +149,7 @@ func runTests(t *testing.T, dsn string, tests ...func(dbt *DBTest)) {
 	}
 	defer db.Close()
 
+	// Previous test may be skipped without dropping the test table
 	db.Exec("DROP TABLE IF EXISTS test")
 
 	dsn2 := dsn + "&interpolateParams=true"

From fc589cbaba22032382488393c72b9b3b5366917c Mon Sep 17 00:00:00 2001
From: Gusted <williamzijl7@hotmail.com>
Date: Tue, 12 Dec 2023 10:26:35 +0100
Subject: [PATCH 231/282] Add client_ed25519 authentication (#1518)

Implements the necessary client code for [ed25519 authentication](https://mariadb.com/kb/en/authentication-plugin-ed25519/).

This patch uses filippo.io/edwards25519 to implement the crypto bits.
The standard library `crypto/ed25519` cannot be used as MariaDB chose
a scheme that is simply not compatible with what the standard library provides.
---
 AUTHORS        |  1 +
 auth.go        | 47 ++++++++++++++++++++++++++++++++++++++++++++++
 auth_test.go   | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++
 driver_test.go | 10 +++++-----
 go.mod         |  2 ++
 go.sum         |  2 ++
 6 files changed, 108 insertions(+), 5 deletions(-)
 create mode 100644 go.sum

diff --git a/AUTHORS b/AUTHORS
index 2caa7d706..954e7ac7a 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -39,6 +39,7 @@ Evan Elias <evan at skeema.net>
 Evan Shaw <evan at vendhq.com>
 Frederick Mayle <frederickmayle at gmail.com>
 Gustavo Kristic <gkristic at gmail.com>
+Gusted <postmaster at gusted.xyz>
 Hajime Nakagami <nakagami at gmail.com>
 Hanno Braun <mail at hannobraun.com>
 Henri Yandell <flamefew at gmail.com>
diff --git a/auth.go b/auth.go
index bab282bd2..658259b24 100644
--- a/auth.go
+++ b/auth.go
@@ -13,10 +13,13 @@ import (
 	"crypto/rsa"
 	"crypto/sha1"
 	"crypto/sha256"
+	"crypto/sha512"
 	"crypto/x509"
 	"encoding/pem"
 	"fmt"
 	"sync"
+
+	"filippo.io/edwards25519"
 )
 
 // server pub keys registry
@@ -225,6 +228,44 @@ func encryptPassword(password string, seed []byte, pub *rsa.PublicKey) ([]byte,
 	return rsa.EncryptOAEP(sha1, rand.Reader, pub, plain, nil)
 }
 
+// authEd25519 does ed25519 authentication used by MariaDB.
+func authEd25519(scramble []byte, password string) ([]byte, error) {
+	// Derived from https://github.com/MariaDB/server/blob/d8e6bb00888b1f82c031938f4c8ac5d97f6874c3/plugin/auth_ed25519/ref10/sign.c
+	// Code style is from https://cs.opensource.google/go/go/+/refs/tags/go1.21.5:src/crypto/ed25519/ed25519.go;l=207
+	h := sha512.Sum512([]byte(password))
+
+	s, err := edwards25519.NewScalar().SetBytesWithClamping(h[:32])
+	if err != nil {
+		return nil, err
+	}
+	A := (&edwards25519.Point{}).ScalarBaseMult(s)
+
+	mh := sha512.New()
+	mh.Write(h[32:])
+	mh.Write(scramble)
+	messageDigest := mh.Sum(nil)
+	r, err := edwards25519.NewScalar().SetUniformBytes(messageDigest)
+	if err != nil {
+		return nil, err
+	}
+
+	R := (&edwards25519.Point{}).ScalarBaseMult(r)
+
+	kh := sha512.New()
+	kh.Write(R.Bytes())
+	kh.Write(A.Bytes())
+	kh.Write(scramble)
+	hramDigest := kh.Sum(nil)
+	k, err := edwards25519.NewScalar().SetUniformBytes(hramDigest)
+	if err != nil {
+		return nil, err
+	}
+
+	S := k.MultiplyAdd(k, s, r)
+
+	return append(R.Bytes(), S.Bytes()...), nil
+}
+
 func (mc *mysqlConn) sendEncryptedPassword(seed []byte, pub *rsa.PublicKey) error {
 	enc, err := encryptPassword(mc.cfg.Passwd, seed, pub)
 	if err != nil {
@@ -290,6 +331,12 @@ func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, error) {
 		enc, err := encryptPassword(mc.cfg.Passwd, authData, pubKey)
 		return enc, err
 
+	case "client_ed25519":
+		if len(authData) != 32 {
+			return nil, ErrMalformPkt
+		}
+		return authEd25519(authData, mc.cfg.Passwd)
+
 	default:
 		mc.cfg.Logger.Print("unknown auth plugin:", plugin)
 		return nil, ErrUnknownPlugin
diff --git a/auth_test.go b/auth_test.go
index 3ce0ea6e0..8caed1fff 100644
--- a/auth_test.go
+++ b/auth_test.go
@@ -1328,3 +1328,54 @@ func TestAuthSwitchSHA256PasswordSecure(t *testing.T) {
 		t.Errorf("got unexpected data: %v", conn.written)
 	}
 }
+
+// Derived from https://github.com/MariaDB/server/blob/6b2287fff23fbdc362499501c562f01d0d2db52e/plugin/auth_ed25519/ed25519-t.c
+func TestEd25519Auth(t *testing.T) {
+	conn, mc := newRWMockConn(1)
+	mc.cfg.User = "root"
+	mc.cfg.Passwd = "foobar"
+
+	authData := []byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
+	plugin := "client_ed25519"
+
+	// Send Client Authentication Packet
+	authResp, err := mc.auth(authData, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = mc.writeHandshakeResponsePacket(authResp, plugin)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// check written auth response
+	authRespStart := 4 + 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1
+	authRespEnd := authRespStart + 1 + len(authResp)
+	writtenAuthRespLen := conn.written[authRespStart]
+	writtenAuthResp := conn.written[authRespStart+1 : authRespEnd]
+	expectedAuthResp := []byte{
+		232, 61, 201, 63, 67, 63, 51, 53, 86, 73, 238, 35, 170, 117, 146,
+		214, 26, 17, 35, 9, 8, 132, 245, 141, 48, 99, 66, 58, 36, 228, 48,
+		84, 115, 254, 187, 168, 88, 162, 249, 57, 35, 85, 79, 238, 167, 106,
+		68, 117, 56, 135, 171, 47, 20, 14, 133, 79, 15, 229, 124, 160, 176,
+		100, 138, 14,
+	}
+	if writtenAuthRespLen != 64 {
+		t.Fatalf("expected 64 bytes from client, got %d", writtenAuthRespLen)
+	}
+	if !bytes.Equal(writtenAuthResp, expectedAuthResp) {
+		t.Fatalf("auth response did not match expected value:\n%v\n%v", writtenAuthResp, expectedAuthResp)
+	}
+	conn.written = nil
+
+	// auth response
+	conn.data = []byte{
+		7, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, // OK
+	}
+	conn.maxReads = 1
+
+	// Handle response to auth packet
+	if err := mc.handleAuthResult(authData, plugin); err != nil {
+		t.Errorf("got error: %v", err)
+	}
+}
diff --git a/driver_test.go b/driver_test.go
index 87892a09a..97fd5a17a 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -165,14 +165,14 @@ func runTests(t *testing.T, dsn string, tests ...func(dbt *DBTest)) {
 	for _, test := range tests {
 		t.Run("default", func(t *testing.T) {
 			dbt := &DBTest{t, db}
+			defer dbt.db.Exec("DROP TABLE IF EXISTS test")
 			test(dbt)
-			dbt.db.Exec("DROP TABLE IF EXISTS test")
 		})
 		if db2 != nil {
 			t.Run("interpolateParams", func(t *testing.T) {
 				dbt2 := &DBTest{t, db2}
+				defer dbt2.db.Exec("DROP TABLE IF EXISTS test")
 				test(dbt2)
-				dbt2.db.Exec("DROP TABLE IF EXISTS test")
 			})
 		}
 	}
@@ -3181,14 +3181,14 @@ func TestRawBytesAreNotModified(t *testing.T) {
 
 				rows, err := dbt.db.QueryContext(ctx, `SELECT id, value FROM test`)
 				if err != nil {
-					t.Fatal(err)
+					dbt.Fatal(err)
 				}
 
 				var b int
 				var raw sql.RawBytes
 				for rows.Next() {
 					if err := rows.Scan(&b, &raw); err != nil {
-						t.Fatal(err)
+						dbt.Fatal(err)
 					}
 
 					before := string(raw)
@@ -3198,7 +3198,7 @@ func TestRawBytesAreNotModified(t *testing.T) {
 					after := string(raw)
 
 					if before != after {
-						t.Fatalf("the backing storage for sql.RawBytes has been modified (i=%v)", i)
+						dbt.Fatalf("the backing storage for sql.RawBytes has been modified (i=%v)", i)
 					}
 				}
 				rows.Close()
diff --git a/go.mod b/go.mod
index 77bbb8dbf..4629714c0 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,5 @@
 module github.com/go-sql-driver/mysql
 
 go 1.18
+
+require filippo.io/edwards25519 v1.1.0
diff --git a/go.sum b/go.sum
new file mode 100644
index 000000000..359ca94b4
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,2 @@
+filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
+filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=

From 2cdf62442f2edb873d1270897d994fc83b78f118 Mon Sep 17 00:00:00 2001
From: ICHINOSE Shogo <shogo82148@gmail.com>
Date: Wed, 13 Dec 2023 15:21:30 +0900
Subject: [PATCH 232/282] Fix sql.RawBytes corruption issue (#1523)

---
 driver_test.go | 27 ++++++++++++++-------------
 1 file changed, 14 insertions(+), 13 deletions(-)

diff --git a/driver_test.go b/driver_test.go
index 97fd5a17a..d7359085d 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -3183,25 +3183,26 @@ func TestRawBytesAreNotModified(t *testing.T) {
 				if err != nil {
 					dbt.Fatal(err)
 				}
+				defer rows.Close()
 
 				var b int
 				var raw sql.RawBytes
-				for rows.Next() {
-					if err := rows.Scan(&b, &raw); err != nil {
-						dbt.Fatal(err)
-					}
+				if !rows.Next() {
+					dbt.Fatal("expected at least one row")
+				}
+				if err := rows.Scan(&b, &raw); err != nil {
+					dbt.Fatal(err)
+				}
 
-					before := string(raw)
-					// Ensure cancelling the query does not corrupt the contents of `raw`
-					cancel()
-					time.Sleep(time.Microsecond * 100)
-					after := string(raw)
+				before := string(raw)
+				// Ensure cancelling the query does not corrupt the contents of `raw`
+				cancel()
+				time.Sleep(time.Microsecond * 100)
+				after := string(raw)
 
-					if before != after {
-						dbt.Fatalf("the backing storage for sql.RawBytes has been modified (i=%v)", i)
-					}
+				if before != after {
+					dbt.Fatalf("the backing storage for sql.RawBytes has been modified (i=%v)", i)
 				}
-				rows.Close()
 			}()
 		}
 	})

From d4517c5d905ccd3cc1e750f592edfa88d774d908 Mon Sep 17 00:00:00 2001
From: jennifersp <44716627+jennifersp@users.noreply.github.com>
Date: Wed, 13 Dec 2023 00:50:21 -0800
Subject: [PATCH 233/282] Support ENUM and SET type in DatabaseTypeName()
 (#1520)

---
 AUTHORS        | 5 +++--
 driver_test.go | 2 ++
 fields.go      | 5 +++++
 3 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 954e7ac7a..0ada02d86 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -21,6 +21,7 @@ Animesh Ray <mail.rayanimesh at gmail.com>
 Arne Hormann <arnehormann at gmail.com>
 Ariel Mashraki <ariel at mashraki.co.il>
 Asta Xie <xiemengjun at gmail.com>
+Brian Hendriks <brian at dolthub.com>
 Bulat Gaifullin <gaifullinbf at gmail.com>
 Caine Jette <jette at alum.mit.edu>
 Carlos Nieto <jose.carlos at menteslibres.net>
@@ -55,6 +56,7 @@ Jason Ng <oblitorum at gmail.com>
 Jean-Yves Pellé <jy at pelle.link>
 Jeff Hodges <jeff at somethingsimilar.com>
 Jeffrey Charles <jeffreycharles at gmail.com>
+Jennifer Purevsuren <jennifer at dolthub.com>
 Jerome Meyer <jxmeyer at gmail.com>
 Jiajia Zhong <zhong2plus at gmail.com>
 Jian Zhen <zhenjl at gmail.com>
@@ -116,13 +118,13 @@ Zhang Xiang <angwerzx at 126.com>
 Zhenye Xie <xiezhenye at gmail.com>
 Zhixin Wen <john.wenzhixin at gmail.com>
 Ziheng Lyu <zihenglv at gmail.com>
-Brian Hendriks <brian at dolthub.com>
 
 # Organizations
 
 Barracuda Networks, Inc.
 Counting Ltd.
 DigitalOcean Inc.
+Dolthub Inc.
 dyves labs AG
 Facebook Inc.
 GitHub Inc.
@@ -136,4 +138,3 @@ Pivotal Inc.
 Shattered Silicon Ltd.
 Stripe Inc.
 Zendesk Inc.
-Dolthub Inc.
diff --git a/driver_test.go b/driver_test.go
index d7359085d..8ec1be412 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -3007,6 +3007,8 @@ func TestRowsColumnTypes(t *testing.T) {
 		{"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}},
 		{"date", "DATE", "DATE", scanTypeNullTime, true, 0, 0, [3]string{"'2006-01-02'", "NULL", "'2006-03-04'"}, [3]interface{}{nd1, ndNULL, nd2}},
 		{"year", "YEAR NOT NULL", "YEAR", scanTypeUint16, false, 0, 0, [3]string{"2006", "2000", "1994"}, [3]interface{}{uint16(2006), uint16(2000), uint16(1994)}},
+		{"enum", "ENUM('', 'v1', 'v2')", "ENUM", scanTypeNullString, true, 0, 0, [3]string{"''", "'v1'", "'v2'"}, [3]interface{}{ns(""), ns("v1"), ns("v2")}},
+		{"set", "set('', 'v1', 'v2')", "SET", scanTypeNullString, true, 0, 0, [3]string{"''", "'v1'", "'v1,v2'"}, [3]interface{}{ns(""), ns("v1"), ns("v1,v2")}},
 	}
 
 	schema := ""
diff --git a/fields.go b/fields.go
index 30f31cbfb..2a397b245 100644
--- a/fields.go
+++ b/fields.go
@@ -77,6 +77,11 @@ func (mf *mysqlField) typeDatabaseName() string {
 		}
 		return "SMALLINT"
 	case fieldTypeString:
+		if mf.flags&flagEnum != 0 {
+			return "ENUM"
+		} else if mf.flags&flagSet != 0 {
+			return "SET"
+		}
 		if mf.charSet == binaryCollationID {
 			return "BINARY"
 		}

From 0004702b931d3429afb3e16df444ed80be24d1f4 Mon Sep 17 00:00:00 2001
From: ICHINOSE Shogo <shogo82148@gmail.com>
Date: Wed, 13 Dec 2023 20:25:41 +0900
Subject: [PATCH 234/282] Parallelize test (#1525)

* Refactor test cleanup in driver_test.go

* parallelize TestEmptyQuery and TestCRUD

* parallelize TestNumbersToAny

* parallelize TestInt

* parallelize TestFloat32

* parallelize TestFloat64

* parallelize TestFloat64Placeholder

* parallelize TestString

* parallelize TestRawBytes

* parallelize TestRawMessage

* parallelize TestValuer

* parallelize TestValuerWithValidation

* parallelize TestTimestampMicros

* parallelize TestNULL

* parallelize TestUint64

* parallelize TestLongData

* parallelize TestContextCancelExec

* parallelize TestPingContext

* parallelize TestContextCancelQuery

* parallelize TestContextCancelQueryRow

* Revert "parallelize TestLongData"

This reverts commit a360be7a110bb6372bed8cf7bc467e3c2dae3c66.

* parallelize TestContextCancelPrepare

* parallelize TestContextCancelStmtExec

* parallelize TestContextCancelStmtQuery

* parallelize TestContextCancelBegin

* parallelize TestContextBeginIsolationLevel

* parallelize TestContextBeginReadOnly

* parallelize TestValuerWithValueReceiverGivenNilValue

* parallelize TestRawBytesAreNotModified

* parallelize TestFoundRows

* parallelize TestRowsClose

* parallelize TestCloseStmtBeforeRows

* parallelize TestStmtMultiRows

* Revert "parallelize TestRawBytesAreNotModified"

This reverts commit 91622f05d44481dd9867eeaaf382da239afe3925.

* parallelize TestStaleConnectionChecks

* parallelize TestFailingCharset

* parallelize TestColumnsWithAlias

* parallelize TestRawBytesResultExceedsBuffer

* parallelize TestUnixSocketAuthFail

* parallelize TestSkipResults

* Add parallel flag to go test command

* Revert "parallelize TestUnixSocketAuthFail"

This reverts commit b3df7bd130a21294a45c3733f1d2541b15582111.
---
 .github/workflows/test.yml |   2 +-
 conncheck_test.go          |   2 +-
 driver_test.go             | 332 ++++++++++++++++++++++---------------
 3 files changed, 198 insertions(+), 138 deletions(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 8e1cb9bc3..aae421196 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -96,7 +96,7 @@ jobs:
 
       - name: test
         run: |
-          go test -v '-race' '-covermode=atomic' '-coverprofile=coverage.out'
+          go test -v '-race' '-covermode=atomic' '-coverprofile=coverage.out' -parallel 10
 
       - name: Send coverage
         uses: shogo82148/actions-goveralls@v1
diff --git a/conncheck_test.go b/conncheck_test.go
index f7e025680..6b60cb7d6 100644
--- a/conncheck_test.go
+++ b/conncheck_test.go
@@ -17,7 +17,7 @@ import (
 )
 
 func TestStaleConnectionChecks(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
+	runTestsParallel(t, dsn, func(dbt *DBTest, _ string) {
 		dbt.mustExec("SET @@SESSION.wait_timeout = 2")
 
 		if err := dbt.db.Ping(); err != nil {
diff --git a/driver_test.go b/driver_test.go
index 8ec1be412..6bdb78c78 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -11,6 +11,7 @@ package mysql
 import (
 	"bytes"
 	"context"
+	"crypto/rand"
 	"crypto/tls"
 	"database/sql"
 	"database/sql/driver"
@@ -149,8 +150,9 @@ func runTests(t *testing.T, dsn string, tests ...func(dbt *DBTest)) {
 	}
 	defer db.Close()
 
-	// Previous test may be skipped without dropping the test table
-	db.Exec("DROP TABLE IF EXISTS test")
+	cleanup := func() {
+		db.Exec("DROP TABLE IF EXISTS test")
+	}
 
 	dsn2 := dsn + "&interpolateParams=true"
 	var db2 *sql.DB
@@ -163,21 +165,80 @@ func runTests(t *testing.T, dsn string, tests ...func(dbt *DBTest)) {
 	}
 
 	for _, test := range tests {
+		test := test
 		t.Run("default", func(t *testing.T) {
 			dbt := &DBTest{t, db}
-			defer dbt.db.Exec("DROP TABLE IF EXISTS test")
+			t.Cleanup(cleanup)
 			test(dbt)
 		})
 		if db2 != nil {
 			t.Run("interpolateParams", func(t *testing.T) {
 				dbt2 := &DBTest{t, db2}
-				defer dbt2.db.Exec("DROP TABLE IF EXISTS test")
+				t.Cleanup(cleanup)
 				test(dbt2)
 			})
 		}
 	}
 }
 
+// runTestsParallel runs the tests in parallel with a separate database connection for each test.
+func runTestsParallel(t *testing.T, dsn string, tests ...func(dbt *DBTest, tableName string)) {
+	if !available {
+		t.Skipf("MySQL server not running on %s", netAddr)
+	}
+
+	newTableName := func(t *testing.T) string {
+		t.Helper()
+		var buf [8]byte
+		if _, err := rand.Read(buf[:]); err != nil {
+			t.Fatal(err)
+		}
+		return fmt.Sprintf("test_%x", buf[:])
+	}
+
+	t.Parallel()
+	for _, test := range tests {
+		test := test
+
+		t.Run("default", func(t *testing.T) {
+			t.Parallel()
+
+			tableName := newTableName(t)
+			db, err := sql.Open("mysql", dsn)
+			if err != nil {
+				t.Fatalf("error connecting: %s", err.Error())
+			}
+			t.Cleanup(func() {
+				db.Exec("DROP TABLE IF EXISTS " + tableName)
+				db.Close()
+			})
+
+			dbt := &DBTest{t, db}
+			test(dbt, tableName)
+		})
+
+		dsn2 := dsn + "&interpolateParams=true"
+		if _, err := ParseDSN(dsn2); err == errInvalidDSNUnsafeCollation {
+			t.Run("interpolateParams", func(t *testing.T) {
+				t.Parallel()
+
+				tableName := newTableName(t)
+				db, err := sql.Open("mysql", dsn2)
+				if err != nil {
+					t.Fatalf("error connecting: %s", err.Error())
+				}
+				t.Cleanup(func() {
+					db.Exec("DROP TABLE IF EXISTS " + tableName)
+					db.Close()
+				})
+
+				dbt := &DBTest{t, db}
+				test(dbt, tableName)
+			})
+		}
+	}
+}
+
 func (dbt *DBTest) fail(method, query string, err error) {
 	dbt.Helper()
 	if len(query) > 300 {
@@ -216,7 +277,7 @@ func maybeSkip(t *testing.T, err error, skipErrno uint16) {
 }
 
 func TestEmptyQuery(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
+	runTestsParallel(t, dsn, func(dbt *DBTest, _ string) {
 		// just a comment, no query
 		rows := dbt.mustQuery("--")
 		defer rows.Close()
@@ -228,20 +289,20 @@ func TestEmptyQuery(t *testing.T) {
 }
 
 func TestCRUD(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
+	runTestsParallel(t, dsn, func(dbt *DBTest, tbl string) {
 		// Create Table
-		dbt.mustExec("CREATE TABLE test (value BOOL)")
+		dbt.mustExec("CREATE TABLE " + tbl + " (value BOOL)")
 
 		// Test for unexpected data
 		var out bool
-		rows := dbt.mustQuery("SELECT * FROM test")
+		rows := dbt.mustQuery("SELECT * FROM " + tbl)
 		if rows.Next() {
 			dbt.Error("unexpected data in empty table")
 		}
 		rows.Close()
 
 		// Create Data
-		res := dbt.mustExec("INSERT INTO test VALUES (1)")
+		res := dbt.mustExec("INSERT INTO " + tbl + " VALUES (1)")
 		count, err := res.RowsAffected()
 		if err != nil {
 			dbt.Fatalf("res.RowsAffected() returned error: %s", err.Error())
@@ -259,7 +320,7 @@ func TestCRUD(t *testing.T) {
 		}
 
 		// Read
-		rows = dbt.mustQuery("SELECT value FROM test")
+		rows = dbt.mustQuery("SELECT value FROM " + tbl)
 		if rows.Next() {
 			rows.Scan(&out)
 			if true != out {
@@ -275,7 +336,7 @@ func TestCRUD(t *testing.T) {
 		rows.Close()
 
 		// Update
-		res = dbt.mustExec("UPDATE test SET value = ? WHERE value = ?", false, true)
+		res = dbt.mustExec("UPDATE "+tbl+" SET value = ? WHERE value = ?", false, true)
 		count, err = res.RowsAffected()
 		if err != nil {
 			dbt.Fatalf("res.RowsAffected() returned error: %s", err.Error())
@@ -285,7 +346,7 @@ func TestCRUD(t *testing.T) {
 		}
 
 		// Check Update
-		rows = dbt.mustQuery("SELECT value FROM test")
+		rows = dbt.mustQuery("SELECT value FROM " + tbl)
 		if rows.Next() {
 			rows.Scan(&out)
 			if false != out {
@@ -301,7 +362,7 @@ func TestCRUD(t *testing.T) {
 		rows.Close()
 
 		// Delete
-		res = dbt.mustExec("DELETE FROM test WHERE value = ?", false)
+		res = dbt.mustExec("DELETE FROM "+tbl+" WHERE value = ?", false)
 		count, err = res.RowsAffected()
 		if err != nil {
 			dbt.Fatalf("res.RowsAffected() returned error: %s", err.Error())
@@ -311,7 +372,7 @@ func TestCRUD(t *testing.T) {
 		}
 
 		// Check for unexpected rows
-		res = dbt.mustExec("DELETE FROM test")
+		res = dbt.mustExec("DELETE FROM " + tbl)
 		count, err = res.RowsAffected()
 		if err != nil {
 			dbt.Fatalf("res.RowsAffected() returned error: %s", err.Error())
@@ -325,13 +386,13 @@ func TestCRUD(t *testing.T) {
 // TestNumbers test that selecting numeric columns.
 // Both of textRows and binaryRows should return same type and value.
 func TestNumbersToAny(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		dbt.mustExec("CREATE TABLE `test` (id INT PRIMARY KEY, b BOOL, i8 TINYINT, " +
+	runTestsParallel(t, dsn, func(dbt *DBTest, tbl string) {
+		dbt.mustExec("CREATE TABLE " + tbl + " (id INT PRIMARY KEY, b BOOL, i8 TINYINT, " +
 			"i16 SMALLINT, i32 INT, i64 BIGINT, f32 FLOAT, f64 DOUBLE)")
-		dbt.mustExec("INSERT INTO `test` VALUES (1, true, 127, 32767, 2147483647, 9223372036854775807, 1.25, 2.5)")
+		dbt.mustExec("INSERT INTO " + tbl + " VALUES (1, true, 127, 32767, 2147483647, 9223372036854775807, 1.25, 2.5)")
 
 		// Use binaryRows for intarpolateParams=false and textRows for intarpolateParams=true.
-		rows := dbt.mustQuery("SELECT b, i8, i16, i32, i64, f32, f64 FROM `test` WHERE id=?", 1)
+		rows := dbt.mustQuery("SELECT b, i8, i16, i32, i64, f32, f64 FROM "+tbl+" WHERE id=?", 1)
 		if !rows.Next() {
 			dbt.Fatal("no data")
 		}
@@ -410,7 +471,7 @@ func TestMultiQuery(t *testing.T) {
 }
 
 func TestInt(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
+	runTestsParallel(t, dsn, func(dbt *DBTest, tbl string) {
 		types := [5]string{"TINYINT", "SMALLINT", "MEDIUMINT", "INT", "BIGINT"}
 		in := int64(42)
 		var out int64
@@ -418,11 +479,11 @@ func TestInt(t *testing.T) {
 
 		// SIGNED
 		for _, v := range types {
-			dbt.mustExec("CREATE TABLE test (value " + v + ")")
+			dbt.mustExec("CREATE TABLE " + tbl + " (value " + v + ")")
 
-			dbt.mustExec("INSERT INTO test VALUES (?)", in)
+			dbt.mustExec("INSERT INTO "+tbl+" VALUES (?)", in)
 
-			rows = dbt.mustQuery("SELECT value FROM test")
+			rows = dbt.mustQuery("SELECT value FROM " + tbl)
 			if rows.Next() {
 				rows.Scan(&out)
 				if in != out {
@@ -433,16 +494,16 @@ func TestInt(t *testing.T) {
 			}
 			rows.Close()
 
-			dbt.mustExec("DROP TABLE IF EXISTS test")
+			dbt.mustExec("DROP TABLE IF EXISTS " + tbl)
 		}
 
 		// UNSIGNED ZEROFILL
 		for _, v := range types {
-			dbt.mustExec("CREATE TABLE test (value " + v + " ZEROFILL)")
+			dbt.mustExec("CREATE TABLE " + tbl + " (value " + v + " ZEROFILL)")
 
-			dbt.mustExec("INSERT INTO test VALUES (?)", in)
+			dbt.mustExec("INSERT INTO "+tbl+" VALUES (?)", in)
 
-			rows = dbt.mustQuery("SELECT value FROM test")
+			rows = dbt.mustQuery("SELECT value FROM " + tbl)
 			if rows.Next() {
 				rows.Scan(&out)
 				if in != out {
@@ -453,21 +514,21 @@ func TestInt(t *testing.T) {
 			}
 			rows.Close()
 
-			dbt.mustExec("DROP TABLE IF EXISTS test")
+			dbt.mustExec("DROP TABLE IF EXISTS " + tbl)
 		}
 	})
 }
 
 func TestFloat32(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
+	runTestsParallel(t, dsn, func(dbt *DBTest, tbl string) {
 		types := [2]string{"FLOAT", "DOUBLE"}
 		in := float32(42.23)
 		var out float32
 		var rows *sql.Rows
 		for _, v := range types {
-			dbt.mustExec("CREATE TABLE test (value " + v + ")")
-			dbt.mustExec("INSERT INTO test VALUES (?)", in)
-			rows = dbt.mustQuery("SELECT value FROM test")
+			dbt.mustExec("CREATE TABLE " + tbl + " (value " + v + ")")
+			dbt.mustExec("INSERT INTO "+tbl+" VALUES (?)", in)
+			rows = dbt.mustQuery("SELECT value FROM " + tbl)
 			if rows.Next() {
 				rows.Scan(&out)
 				if in != out {
@@ -477,21 +538,21 @@ func TestFloat32(t *testing.T) {
 				dbt.Errorf("%s: no data", v)
 			}
 			rows.Close()
-			dbt.mustExec("DROP TABLE IF EXISTS test")
+			dbt.mustExec("DROP TABLE IF EXISTS " + tbl)
 		}
 	})
 }
 
 func TestFloat64(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
+	runTestsParallel(t, dsn, func(dbt *DBTest, tbl string) {
 		types := [2]string{"FLOAT", "DOUBLE"}
 		var expected float64 = 42.23
 		var out float64
 		var rows *sql.Rows
 		for _, v := range types {
-			dbt.mustExec("CREATE TABLE test (value " + v + ")")
-			dbt.mustExec("INSERT INTO test VALUES (42.23)")
-			rows = dbt.mustQuery("SELECT value FROM test")
+			dbt.mustExec("CREATE TABLE " + tbl + " (value " + v + ")")
+			dbt.mustExec("INSERT INTO " + tbl + " VALUES (42.23)")
+			rows = dbt.mustQuery("SELECT value FROM " + tbl)
 			if rows.Next() {
 				rows.Scan(&out)
 				if expected != out {
@@ -501,21 +562,21 @@ func TestFloat64(t *testing.T) {
 				dbt.Errorf("%s: no data", v)
 			}
 			rows.Close()
-			dbt.mustExec("DROP TABLE IF EXISTS test")
+			dbt.mustExec("DROP TABLE IF EXISTS " + tbl)
 		}
 	})
 }
 
 func TestFloat64Placeholder(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
+	runTestsParallel(t, dsn, func(dbt *DBTest, tbl string) {
 		types := [2]string{"FLOAT", "DOUBLE"}
 		var expected float64 = 42.23
 		var out float64
 		var rows *sql.Rows
 		for _, v := range types {
-			dbt.mustExec("CREATE TABLE test (id int, value " + v + ")")
-			dbt.mustExec("INSERT INTO test VALUES (1, 42.23)")
-			rows = dbt.mustQuery("SELECT value FROM test WHERE id = ?", 1)
+			dbt.mustExec("CREATE TABLE " + tbl + " (id int, value " + v + ")")
+			dbt.mustExec("INSERT INTO " + tbl + " VALUES (1, 42.23)")
+			rows = dbt.mustQuery("SELECT value FROM "+tbl+" WHERE id = ?", 1)
 			if rows.Next() {
 				rows.Scan(&out)
 				if expected != out {
@@ -525,24 +586,24 @@ func TestFloat64Placeholder(t *testing.T) {
 				dbt.Errorf("%s: no data", v)
 			}
 			rows.Close()
-			dbt.mustExec("DROP TABLE IF EXISTS test")
+			dbt.mustExec("DROP TABLE IF EXISTS " + tbl)
 		}
 	})
 }
 
 func TestString(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
+	runTestsParallel(t, dsn, func(dbt *DBTest, tbl string) {
 		types := [6]string{"CHAR(255)", "VARCHAR(255)", "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT"}
 		in := "κόσμε üöäßñóùéàâÿœ'îë Árvíztűrő いろはにほへとちりぬるを イロハニホヘト דג סקרן чащах  น่าฟังเอย"
 		var out string
 		var rows *sql.Rows
 
 		for _, v := range types {
-			dbt.mustExec("CREATE TABLE test (value " + v + ") CHARACTER SET utf8")
+			dbt.mustExec("CREATE TABLE " + tbl + " (value " + v + ") CHARACTER SET utf8")
 
-			dbt.mustExec("INSERT INTO test VALUES (?)", in)
+			dbt.mustExec("INSERT INTO "+tbl+" VALUES (?)", in)
 
-			rows = dbt.mustQuery("SELECT value FROM test")
+			rows = dbt.mustQuery("SELECT value FROM " + tbl)
 			if rows.Next() {
 				rows.Scan(&out)
 				if in != out {
@@ -553,11 +614,11 @@ func TestString(t *testing.T) {
 			}
 			rows.Close()
 
-			dbt.mustExec("DROP TABLE IF EXISTS test")
+			dbt.mustExec("DROP TABLE IF EXISTS " + tbl)
 		}
 
 		// BLOB
-		dbt.mustExec("CREATE TABLE test (id int, value BLOB) CHARACTER SET utf8")
+		dbt.mustExec("CREATE TABLE " + tbl + " (id int, value BLOB) CHARACTER SET utf8")
 
 		id := 2
 		in = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, " +
@@ -568,9 +629,9 @@ func TestString(t *testing.T) {
 			"sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, " +
 			"sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. " +
 			"Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."
-		dbt.mustExec("INSERT INTO test VALUES (?, ?)", id, in)
+		dbt.mustExec("INSERT INTO "+tbl+" VALUES (?, ?)", id, in)
 
-		err := dbt.db.QueryRow("SELECT value FROM test WHERE id = ?", id).Scan(&out)
+		err := dbt.db.QueryRow("SELECT value FROM "+tbl+" WHERE id = ?", id).Scan(&out)
 		if err != nil {
 			dbt.Fatalf("Error on BLOB-Query: %s", err.Error())
 		} else if out != in {
@@ -580,7 +641,7 @@ func TestString(t *testing.T) {
 }
 
 func TestRawBytes(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
+	runTestsParallel(t, dsn, func(dbt *DBTest, _ string) {
 		v1 := []byte("aaa")
 		v2 := []byte("bbb")
 		rows := dbt.mustQuery("SELECT ?, ?", v1, v2)
@@ -609,7 +670,7 @@ func TestRawBytes(t *testing.T) {
 }
 
 func TestRawMessage(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
+	runTestsParallel(t, dsn, func(dbt *DBTest, _ string) {
 		v1 := json.RawMessage("{}")
 		v2 := json.RawMessage("[]")
 		rows := dbt.mustQuery("SELECT ?, ?", v1, v2)
@@ -640,14 +701,14 @@ func (tv testValuer) Value() (driver.Value, error) {
 }
 
 func TestValuer(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
+	runTestsParallel(t, dsn, func(dbt *DBTest, tbl string) {
 		in := testValuer{"a_value"}
 		var out string
 		var rows *sql.Rows
 
-		dbt.mustExec("CREATE TABLE test (value VARCHAR(255)) CHARACTER SET utf8")
-		dbt.mustExec("INSERT INTO test VALUES (?)", in)
-		rows = dbt.mustQuery("SELECT value FROM test")
+		dbt.mustExec("CREATE TABLE " + tbl + " (value VARCHAR(255)) CHARACTER SET utf8")
+		dbt.mustExec("INSERT INTO "+tbl+" VALUES (?)", in)
+		rows = dbt.mustQuery("SELECT value FROM " + tbl)
 		if rows.Next() {
 			rows.Scan(&out)
 			if in.value != out {
@@ -657,8 +718,6 @@ func TestValuer(t *testing.T) {
 			dbt.Errorf("Valuer: no data")
 		}
 		rows.Close()
-
-		dbt.mustExec("DROP TABLE IF EXISTS test")
 	})
 }
 
@@ -675,15 +734,15 @@ func (tv testValuerWithValidation) Value() (driver.Value, error) {
 }
 
 func TestValuerWithValidation(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
+	runTestsParallel(t, dsn, func(dbt *DBTest, tbl string) {
 		in := testValuerWithValidation{"a_value"}
 		var out string
 		var rows *sql.Rows
 
-		dbt.mustExec("CREATE TABLE testValuer (value VARCHAR(255)) CHARACTER SET utf8")
-		dbt.mustExec("INSERT INTO testValuer VALUES (?)", in)
+		dbt.mustExec("CREATE TABLE " + tbl + " (value VARCHAR(255)) CHARACTER SET utf8")
+		dbt.mustExec("INSERT INTO "+tbl+" VALUES (?)", in)
 
-		rows = dbt.mustQuery("SELECT value FROM testValuer")
+		rows = dbt.mustQuery("SELECT value FROM " + tbl)
 		defer rows.Close()
 
 		if rows.Next() {
@@ -695,19 +754,17 @@ func TestValuerWithValidation(t *testing.T) {
 			dbt.Errorf("Valuer: no data")
 		}
 
-		if _, err := dbt.db.Exec("INSERT INTO testValuer VALUES (?)", testValuerWithValidation{""}); err == nil {
+		if _, err := dbt.db.Exec("INSERT INTO "+tbl+" VALUES (?)", testValuerWithValidation{""}); err == nil {
 			dbt.Errorf("Failed to check valuer error")
 		}
 
-		if _, err := dbt.db.Exec("INSERT INTO testValuer VALUES (?)", nil); err != nil {
+		if _, err := dbt.db.Exec("INSERT INTO "+tbl+" VALUES (?)", nil); err != nil {
 			dbt.Errorf("Failed to check nil")
 		}
 
-		if _, err := dbt.db.Exec("INSERT INTO testValuer VALUES (?)", map[string]bool{}); err == nil {
+		if _, err := dbt.db.Exec("INSERT INTO "+tbl+" VALUES (?)", map[string]bool{}); err == nil {
 			dbt.Errorf("Failed to check not valuer")
 		}
-
-		dbt.mustExec("DROP TABLE IF EXISTS testValuer")
 	})
 }
 
@@ -941,7 +998,7 @@ func TestTimestampMicros(t *testing.T) {
 	f0 := format[:19]
 	f1 := format[:21]
 	f6 := format[:26]
-	runTests(t, dsn, func(dbt *DBTest) {
+	runTestsParallel(t, dsn, func(dbt *DBTest, tbl string) {
 		// check if microseconds are supported.
 		// Do not use timestamp(x) for that check - before 5.5.6, x would mean display width
 		// and not precision.
@@ -956,7 +1013,7 @@ func TestTimestampMicros(t *testing.T) {
 			return
 		}
 		_, err := dbt.db.Exec(`
-			CREATE TABLE test (
+			CREATE TABLE ` + tbl + ` (
 				value0 TIMESTAMP NOT NULL DEFAULT '` + f0 + `',
 				value1 TIMESTAMP(1) NOT NULL DEFAULT '` + f1 + `',
 				value6 TIMESTAMP(6) NOT NULL DEFAULT '` + f6 + `'
@@ -965,10 +1022,10 @@ func TestTimestampMicros(t *testing.T) {
 		if err != nil {
 			dbt.Error(err)
 		}
-		defer dbt.mustExec("DROP TABLE IF EXISTS test")
-		dbt.mustExec("INSERT INTO test SET value0=?, value1=?, value6=?", f0, f1, f6)
+		defer dbt.mustExec("DROP TABLE IF EXISTS " + tbl)
+		dbt.mustExec("INSERT INTO "+tbl+" SET value0=?, value1=?, value6=?", f0, f1, f6)
 		var res0, res1, res6 string
-		rows := dbt.mustQuery("SELECT * FROM test")
+		rows := dbt.mustQuery("SELECT * FROM " + tbl)
 		defer rows.Close()
 		if !rows.Next() {
 			dbt.Errorf("test contained no selectable values")
@@ -990,7 +1047,7 @@ func TestTimestampMicros(t *testing.T) {
 }
 
 func TestNULL(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
+	runTestsParallel(t, dsn, func(dbt *DBTest, tbl string) {
 		nullStmt, err := dbt.db.Prepare("SELECT NULL")
 		if err != nil {
 			dbt.Fatal(err)
@@ -1122,12 +1179,12 @@ func TestNULL(t *testing.T) {
 		}
 
 		// Insert NULL
-		dbt.mustExec("CREATE TABLE test (dummmy1 int, value int, dummy2 int)")
+		dbt.mustExec("CREATE TABLE " + tbl + " (dummmy1 int, value int, dummy2 int)")
 
-		dbt.mustExec("INSERT INTO test VALUES (?, ?, ?)", 1, nil, 2)
+		dbt.mustExec("INSERT INTO "+tbl+" VALUES (?, ?, ?)", 1, nil, 2)
 
 		var out interface{}
-		rows := dbt.mustQuery("SELECT * FROM test")
+		rows := dbt.mustQuery("SELECT * FROM " + tbl)
 		defer rows.Close()
 		if rows.Next() {
 			rows.Scan(&out)
@@ -1151,7 +1208,7 @@ func TestUint64(t *testing.T) {
 		shigh = int64(uhigh)
 		stop  = ^shigh
 	)
-	runTests(t, dsn, func(dbt *DBTest) {
+	runTestsParallel(t, dsn, func(dbt *DBTest, _ string) {
 		stmt, err := dbt.db.Prepare(`SELECT ?, ?, ? ,?, ?, ?, ?, ?`)
 		if err != nil {
 			dbt.Fatal(err)
@@ -1347,12 +1404,12 @@ func TestLoadData(t *testing.T) {
 	})
 }
 
-func TestFoundRows(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		dbt.mustExec("CREATE TABLE test (id INT NOT NULL ,data INT NOT NULL)")
-		dbt.mustExec("INSERT INTO test (id, data) VALUES (0, 0),(0, 0),(1, 0),(1, 0),(1, 1)")
+func TestFoundRows1(t *testing.T) {
+	runTestsParallel(t, dsn, func(dbt *DBTest, tbl string) {
+		dbt.mustExec("CREATE TABLE " + tbl + " (id INT NOT NULL ,data INT NOT NULL)")
+		dbt.mustExec("INSERT INTO " + tbl + " (id, data) VALUES (0, 0),(0, 0),(1, 0),(1, 0),(1, 1)")
 
-		res := dbt.mustExec("UPDATE test SET data = 1 WHERE id = 0")
+		res := dbt.mustExec("UPDATE " + tbl + " SET data = 1 WHERE id = 0")
 		count, err := res.RowsAffected()
 		if err != nil {
 			dbt.Fatalf("res.RowsAffected() returned error: %s", err.Error())
@@ -1360,7 +1417,7 @@ func TestFoundRows(t *testing.T) {
 		if count != 2 {
 			dbt.Fatalf("Expected 2 affected rows, got %d", count)
 		}
-		res = dbt.mustExec("UPDATE test SET data = 1 WHERE id = 1")
+		res = dbt.mustExec("UPDATE " + tbl + " SET data = 1 WHERE id = 1")
 		count, err = res.RowsAffected()
 		if err != nil {
 			dbt.Fatalf("res.RowsAffected() returned error: %s", err.Error())
@@ -1369,11 +1426,14 @@ func TestFoundRows(t *testing.T) {
 			dbt.Fatalf("Expected 2 affected rows, got %d", count)
 		}
 	})
-	runTests(t, dsn+"&clientFoundRows=true", func(dbt *DBTest) {
-		dbt.mustExec("CREATE TABLE test (id INT NOT NULL ,data INT NOT NULL)")
-		dbt.mustExec("INSERT INTO test (id, data) VALUES (0, 0),(0, 0),(1, 0),(1, 0),(1, 1)")
+}
+
+func TestFoundRows2(t *testing.T) {
+	runTestsParallel(t, dsn+"&clientFoundRows=true", func(dbt *DBTest, tbl string) {
+		dbt.mustExec("CREATE TABLE " + tbl + " (id INT NOT NULL ,data INT NOT NULL)")
+		dbt.mustExec("INSERT INTO " + tbl + " (id, data) VALUES (0, 0),(0, 0),(1, 0),(1, 0),(1, 1)")
 
-		res := dbt.mustExec("UPDATE test SET data = 1 WHERE id = 0")
+		res := dbt.mustExec("UPDATE " + tbl + " SET data = 1 WHERE id = 0")
 		count, err := res.RowsAffected()
 		if err != nil {
 			dbt.Fatalf("res.RowsAffected() returned error: %s", err.Error())
@@ -1381,7 +1441,7 @@ func TestFoundRows(t *testing.T) {
 		if count != 2 {
 			dbt.Fatalf("Expected 2 matched rows, got %d", count)
 		}
-		res = dbt.mustExec("UPDATE test SET data = 1 WHERE id = 1")
+		res = dbt.mustExec("UPDATE " + tbl + " SET data = 1 WHERE id = 1")
 		count, err = res.RowsAffected()
 		if err != nil {
 			dbt.Fatalf("res.RowsAffected() returned error: %s", err.Error())
@@ -1507,7 +1567,7 @@ func TestCharset(t *testing.T) {
 }
 
 func TestFailingCharset(t *testing.T) {
-	runTests(t, dsn+"&charset=none", func(dbt *DBTest) {
+	runTestsParallel(t, dsn+"&charset=none", func(dbt *DBTest, _ string) {
 		// run query to really establish connection...
 		_, err := dbt.db.Exec("SELECT 1")
 		if err == nil {
@@ -1556,7 +1616,7 @@ func TestCollation(t *testing.T) {
 }
 
 func TestColumnsWithAlias(t *testing.T) {
-	runTests(t, dsn+"&columnsWithAlias=true", func(dbt *DBTest) {
+	runTestsParallel(t, dsn+"&columnsWithAlias=true", func(dbt *DBTest, _ string) {
 		rows := dbt.mustQuery("SELECT 1 AS A")
 		defer rows.Close()
 		cols, _ := rows.Columns()
@@ -1580,7 +1640,7 @@ func TestColumnsWithAlias(t *testing.T) {
 }
 
 func TestRawBytesResultExceedsBuffer(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
+	runTestsParallel(t, dsn, func(dbt *DBTest, _ string) {
 		// defaultBufSize from buffer.go
 		expected := strings.Repeat("abc", defaultBufSize)
 
@@ -1639,7 +1699,7 @@ func TestTimezoneConversion(t *testing.T) {
 // Special cases
 
 func TestRowsClose(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
+	runTestsParallel(t, dsn, func(dbt *DBTest, _ string) {
 		rows, err := dbt.db.Query("SELECT 1")
 		if err != nil {
 			dbt.Fatal(err)
@@ -1664,7 +1724,7 @@ func TestRowsClose(t *testing.T) {
 // dangling statements
 // http://code.google.com/p/go/issues/detail?id=3865
 func TestCloseStmtBeforeRows(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
+	runTestsParallel(t, dsn, func(dbt *DBTest, _ string) {
 		stmt, err := dbt.db.Prepare("SELECT 1")
 		if err != nil {
 			dbt.Fatal(err)
@@ -1705,7 +1765,7 @@ func TestCloseStmtBeforeRows(t *testing.T) {
 // It is valid to have multiple Rows for the same Stmt
 // http://code.google.com/p/go/issues/detail?id=3734
 func TestStmtMultiRows(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
+	runTestsParallel(t, dsn, func(dbt *DBTest, _ string) {
 		stmt, err := dbt.db.Prepare("SELECT 1 UNION SELECT 0")
 		if err != nil {
 			dbt.Fatal(err)
@@ -2507,7 +2567,7 @@ func TestExecMultipleResults(t *testing.T) {
 // tests if rows are set in a proper state if some results were ignored before
 // calling rows.NextResultSet.
 func TestSkipResults(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
+	runTestsParallel(t, dsn, func(dbt *DBTest, _ string) {
 		rows := dbt.mustQuery("SELECT 1, 2")
 		defer rows.Close()
 
@@ -2562,7 +2622,7 @@ func TestQueryMultipleResults(t *testing.T) {
 }
 
 func TestPingContext(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
+	runTestsParallel(t, dsn, func(dbt *DBTest, _ string) {
 		ctx, cancel := context.WithCancel(context.Background())
 		cancel()
 		if err := dbt.db.PingContext(ctx); err != context.Canceled {
@@ -2572,8 +2632,8 @@ func TestPingContext(t *testing.T) {
 }
 
 func TestContextCancelExec(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		dbt.mustExec("CREATE TABLE test (v INTEGER)")
+	runTestsParallel(t, dsn, func(dbt *DBTest, tbl string) {
+		dbt.mustExec("CREATE TABLE " + tbl + " (v INTEGER)")
 		ctx, cancel := context.WithCancel(context.Background())
 
 		// Delay execution for just a bit until db.ExecContext has begun.
@@ -2581,7 +2641,7 @@ func TestContextCancelExec(t *testing.T) {
 
 		// This query will be canceled.
 		startTime := time.Now()
-		if _, err := dbt.db.ExecContext(ctx, "INSERT INTO test VALUES (SLEEP(1))"); err != context.Canceled {
+		if _, err := dbt.db.ExecContext(ctx, "INSERT INTO "+tbl+" VALUES (SLEEP(1))"); err != context.Canceled {
 			dbt.Errorf("expected context.Canceled, got %v", err)
 		}
 		if d := time.Since(startTime); d > 500*time.Millisecond {
@@ -2593,7 +2653,7 @@ func TestContextCancelExec(t *testing.T) {
 
 		// Check how many times the query is executed.
 		var v int
-		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM test").Scan(&v); err != nil {
+		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM " + tbl).Scan(&v); err != nil {
 			dbt.Fatalf("%s", err.Error())
 		}
 		if v != 1 { // TODO: need to kill the query, and v should be 0.
@@ -2601,14 +2661,14 @@ func TestContextCancelExec(t *testing.T) {
 		}
 
 		// Context is already canceled, so error should come before execution.
-		if _, err := dbt.db.ExecContext(ctx, "INSERT INTO test VALUES (1)"); err == nil {
+		if _, err := dbt.db.ExecContext(ctx, "INSERT INTO "+tbl+" VALUES (1)"); err == nil {
 			dbt.Error("expected error")
 		} else if err.Error() != "context canceled" {
 			dbt.Fatalf("unexpected error: %s", err)
 		}
 
 		// The second insert query will fail, so the table has no changes.
-		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM test").Scan(&v); err != nil {
+		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM " + tbl).Scan(&v); err != nil {
 			dbt.Fatalf("%s", err.Error())
 		}
 		if v != 1 {
@@ -2618,8 +2678,8 @@ func TestContextCancelExec(t *testing.T) {
 }
 
 func TestContextCancelQuery(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		dbt.mustExec("CREATE TABLE test (v INTEGER)")
+	runTestsParallel(t, dsn, func(dbt *DBTest, tbl string) {
+		dbt.mustExec("CREATE TABLE " + tbl + " (v INTEGER)")
 		ctx, cancel := context.WithCancel(context.Background())
 
 		// Delay execution for just a bit until db.ExecContext has begun.
@@ -2627,7 +2687,7 @@ func TestContextCancelQuery(t *testing.T) {
 
 		// This query will be canceled.
 		startTime := time.Now()
-		if _, err := dbt.db.QueryContext(ctx, "INSERT INTO test VALUES (SLEEP(1))"); err != context.Canceled {
+		if _, err := dbt.db.QueryContext(ctx, "INSERT INTO "+tbl+" VALUES (SLEEP(1))"); err != context.Canceled {
 			dbt.Errorf("expected context.Canceled, got %v", err)
 		}
 		if d := time.Since(startTime); d > 500*time.Millisecond {
@@ -2639,7 +2699,7 @@ func TestContextCancelQuery(t *testing.T) {
 
 		// Check how many times the query is executed.
 		var v int
-		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM test").Scan(&v); err != nil {
+		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM " + tbl).Scan(&v); err != nil {
 			dbt.Fatalf("%s", err.Error())
 		}
 		if v != 1 { // TODO: need to kill the query, and v should be 0.
@@ -2647,12 +2707,12 @@ func TestContextCancelQuery(t *testing.T) {
 		}
 
 		// Context is already canceled, so error should come before execution.
-		if _, err := dbt.db.QueryContext(ctx, "INSERT INTO test VALUES (1)"); err != context.Canceled {
+		if _, err := dbt.db.QueryContext(ctx, "INSERT INTO "+tbl+" VALUES (1)"); err != context.Canceled {
 			dbt.Errorf("expected context.Canceled, got %v", err)
 		}
 
 		// The second insert query will fail, so the table has no changes.
-		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM test").Scan(&v); err != nil {
+		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM " + tbl).Scan(&v); err != nil {
 			dbt.Fatalf("%s", err.Error())
 		}
 		if v != 1 {
@@ -2662,12 +2722,12 @@ func TestContextCancelQuery(t *testing.T) {
 }
 
 func TestContextCancelQueryRow(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		dbt.mustExec("CREATE TABLE test (v INTEGER)")
-		dbt.mustExec("INSERT INTO test VALUES (1), (2), (3)")
+	runTestsParallel(t, dsn, func(dbt *DBTest, tbl string) {
+		dbt.mustExec("CREATE TABLE " + tbl + " (v INTEGER)")
+		dbt.mustExec("INSERT INTO " + tbl + " VALUES (1), (2), (3)")
 		ctx, cancel := context.WithCancel(context.Background())
 
-		rows, err := dbt.db.QueryContext(ctx, "SELECT v FROM test")
+		rows, err := dbt.db.QueryContext(ctx, "SELECT v FROM "+tbl)
 		if err != nil {
 			dbt.Fatalf("%s", err.Error())
 		}
@@ -2695,7 +2755,7 @@ func TestContextCancelQueryRow(t *testing.T) {
 }
 
 func TestContextCancelPrepare(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
+	runTestsParallel(t, dsn, func(dbt *DBTest, _ string) {
 		ctx, cancel := context.WithCancel(context.Background())
 		cancel()
 		if _, err := dbt.db.PrepareContext(ctx, "SELECT 1"); err != context.Canceled {
@@ -2705,10 +2765,10 @@ func TestContextCancelPrepare(t *testing.T) {
 }
 
 func TestContextCancelStmtExec(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		dbt.mustExec("CREATE TABLE test (v INTEGER)")
+	runTestsParallel(t, dsn, func(dbt *DBTest, tbl string) {
+		dbt.mustExec("CREATE TABLE " + tbl + " (v INTEGER)")
 		ctx, cancel := context.WithCancel(context.Background())
-		stmt, err := dbt.db.PrepareContext(ctx, "INSERT INTO test VALUES (SLEEP(1))")
+		stmt, err := dbt.db.PrepareContext(ctx, "INSERT INTO "+tbl+" VALUES (SLEEP(1))")
 		if err != nil {
 			dbt.Fatalf("unexpected error: %v", err)
 		}
@@ -2730,7 +2790,7 @@ func TestContextCancelStmtExec(t *testing.T) {
 
 		// Check how many times the query is executed.
 		var v int
-		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM test").Scan(&v); err != nil {
+		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM " + tbl).Scan(&v); err != nil {
 			dbt.Fatalf("%s", err.Error())
 		}
 		if v != 1 { // TODO: need to kill the query, and v should be 0.
@@ -2740,10 +2800,10 @@ func TestContextCancelStmtExec(t *testing.T) {
 }
 
 func TestContextCancelStmtQuery(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		dbt.mustExec("CREATE TABLE test (v INTEGER)")
+	runTestsParallel(t, dsn, func(dbt *DBTest, tbl string) {
+		dbt.mustExec("CREATE TABLE " + tbl + " (v INTEGER)")
 		ctx, cancel := context.WithCancel(context.Background())
-		stmt, err := dbt.db.PrepareContext(ctx, "INSERT INTO test VALUES (SLEEP(1))")
+		stmt, err := dbt.db.PrepareContext(ctx, "INSERT INTO "+tbl+" VALUES (SLEEP(1))")
 		if err != nil {
 			dbt.Fatalf("unexpected error: %v", err)
 		}
@@ -2765,7 +2825,7 @@ func TestContextCancelStmtQuery(t *testing.T) {
 
 		// Check how many times the query is executed.
 		var v int
-		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM test").Scan(&v); err != nil {
+		if err := dbt.db.QueryRow("SELECT COUNT(*) FROM " + tbl).Scan(&v); err != nil {
 			dbt.Fatalf("%s", err.Error())
 		}
 		if v != 1 { // TODO: need to kill the query, and v should be 0.
@@ -2779,8 +2839,8 @@ func TestContextCancelBegin(t *testing.T) {
 		t.Skip(`FIXME: it sometime fails with "expected driver.ErrBadConn, got sql: connection is already closed" on windows and macOS`)
 	}
 
-	runTests(t, dsn, func(dbt *DBTest) {
-		dbt.mustExec("CREATE TABLE test (v INTEGER)")
+	runTestsParallel(t, dsn, func(dbt *DBTest, tbl string) {
+		dbt.mustExec("CREATE TABLE " + tbl + " (v INTEGER)")
 		ctx, cancel := context.WithCancel(context.Background())
 		conn, err := dbt.db.Conn(ctx)
 		if err != nil {
@@ -2797,7 +2857,7 @@ func TestContextCancelBegin(t *testing.T) {
 
 		// This query will be canceled.
 		startTime := time.Now()
-		if _, err := tx.ExecContext(ctx, "INSERT INTO test VALUES (SLEEP(1))"); err != context.Canceled {
+		if _, err := tx.ExecContext(ctx, "INSERT INTO "+tbl+" VALUES (SLEEP(1))"); err != context.Canceled {
 			dbt.Errorf("expected context.Canceled, got %v", err)
 		}
 		if d := time.Since(startTime); d > 500*time.Millisecond {
@@ -2835,8 +2895,8 @@ func TestContextCancelBegin(t *testing.T) {
 }
 
 func TestContextBeginIsolationLevel(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		dbt.mustExec("CREATE TABLE test (v INTEGER)")
+	runTestsParallel(t, dsn, func(dbt *DBTest, tbl string) {
+		dbt.mustExec("CREATE TABLE " + tbl + " (v INTEGER)")
 		ctx, cancel := context.WithCancel(context.Background())
 		defer cancel()
 
@@ -2854,13 +2914,13 @@ func TestContextBeginIsolationLevel(t *testing.T) {
 			dbt.Fatal(err)
 		}
 
-		_, err = tx1.ExecContext(ctx, "INSERT INTO test VALUES (1)")
+		_, err = tx1.ExecContext(ctx, "INSERT INTO "+tbl+" VALUES (1)")
 		if err != nil {
 			dbt.Fatal(err)
 		}
 
 		var v int
-		row := tx2.QueryRowContext(ctx, "SELECT COUNT(*) FROM test")
+		row := tx2.QueryRowContext(ctx, "SELECT COUNT(*) FROM "+tbl)
 		if err := row.Scan(&v); err != nil {
 			dbt.Fatal(err)
 		}
@@ -2874,7 +2934,7 @@ func TestContextBeginIsolationLevel(t *testing.T) {
 			dbt.Fatal(err)
 		}
 
-		row = tx2.QueryRowContext(ctx, "SELECT COUNT(*) FROM test")
+		row = tx2.QueryRowContext(ctx, "SELECT COUNT(*) FROM "+tbl)
 		if err := row.Scan(&v); err != nil {
 			dbt.Fatal(err)
 		}
@@ -2887,8 +2947,8 @@ func TestContextBeginIsolationLevel(t *testing.T) {
 }
 
 func TestContextBeginReadOnly(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		dbt.mustExec("CREATE TABLE test (v INTEGER)")
+	runTestsParallel(t, dsn, func(dbt *DBTest, tbl string) {
+		dbt.mustExec("CREATE TABLE " + tbl + " (v INTEGER)")
 		ctx, cancel := context.WithCancel(context.Background())
 		defer cancel()
 
@@ -2903,14 +2963,14 @@ func TestContextBeginReadOnly(t *testing.T) {
 		}
 
 		// INSERT queries fail in a READ ONLY transaction.
-		_, err = tx.ExecContext(ctx, "INSERT INTO test VALUES (1)")
+		_, err = tx.ExecContext(ctx, "INSERT INTO "+tbl+" VALUES (1)")
 		if _, ok := err.(*MySQLError); !ok {
 			dbt.Errorf("expected MySQLError, got %v", err)
 		}
 
 		// SELECT queries can be executed.
 		var v int
-		row := tx.QueryRowContext(ctx, "SELECT COUNT(*) FROM test")
+		row := tx.QueryRowContext(ctx, "SELECT COUNT(*) FROM "+tbl)
 		if err := row.Scan(&v); err != nil {
 			dbt.Fatal(err)
 		}
@@ -3147,9 +3207,9 @@ func TestRowsColumnTypes(t *testing.T) {
 }
 
 func TestValuerWithValueReceiverGivenNilValue(t *testing.T) {
-	runTests(t, dsn, func(dbt *DBTest) {
-		dbt.mustExec("CREATE TABLE test (value VARCHAR(255))")
-		dbt.db.Exec("INSERT INTO test VALUES (?)", (*testValuer)(nil))
+	runTestsParallel(t, dsn, func(dbt *DBTest, tbl string) {
+		dbt.mustExec("CREATE TABLE " + tbl + " (value VARCHAR(255))")
+		dbt.db.Exec("INSERT INTO "+tbl+" VALUES (?)", (*testValuer)(nil))
 		// This test will panic on the INSERT if ConvertValue() does not check for typed nil before calling Value()
 	})
 }

From c48c0e7da17e8fc06133e431ce7c10e7a3e94f06 Mon Sep 17 00:00:00 2001
From: shi yuhang <52435083+shiyuhang0@users.noreply.github.com>
Date: Fri, 5 Jan 2024 16:47:16 +0800
Subject: [PATCH 235/282] Fix unsigned int overflow (#1530)

---
 driver_test.go | 15 +++++++++------
 packets.go     |  2 +-
 2 files changed, 10 insertions(+), 7 deletions(-)

diff --git a/driver_test.go b/driver_test.go
index 6bdb78c78..5934caab6 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -388,16 +388,16 @@ func TestCRUD(t *testing.T) {
 func TestNumbersToAny(t *testing.T) {
 	runTestsParallel(t, dsn, func(dbt *DBTest, tbl string) {
 		dbt.mustExec("CREATE TABLE " + tbl + " (id INT PRIMARY KEY, b BOOL, i8 TINYINT, " +
-			"i16 SMALLINT, i32 INT, i64 BIGINT, f32 FLOAT, f64 DOUBLE)")
-		dbt.mustExec("INSERT INTO " + tbl + " VALUES (1, true, 127, 32767, 2147483647, 9223372036854775807, 1.25, 2.5)")
+			"i16 SMALLINT, i32 INT, i64 BIGINT, f32 FLOAT, f64 DOUBLE, iu32 INT UNSIGNED)")
+		dbt.mustExec("INSERT INTO " + tbl + " VALUES (1, true, 127, 32767, 2147483647, 9223372036854775807, 1.25, 2.5, 4294967295)")
 
-		// Use binaryRows for intarpolateParams=false and textRows for intarpolateParams=true.
-		rows := dbt.mustQuery("SELECT b, i8, i16, i32, i64, f32, f64 FROM "+tbl+" WHERE id=?", 1)
+		// Use binaryRows for interpolateParams=false and textRows for interpolateParams=true.
+		rows := dbt.mustQuery("SELECT b, i8, i16, i32, i64, f32, f64, iu32 FROM "+tbl+" WHERE id=?", 1)
 		if !rows.Next() {
 			dbt.Fatal("no data")
 		}
-		var b, i8, i16, i32, i64, f32, f64 any
-		err := rows.Scan(&b, &i8, &i16, &i32, &i64, &f32, &f64)
+		var b, i8, i16, i32, i64, f32, f64, iu32 any
+		err := rows.Scan(&b, &i8, &i16, &i32, &i64, &f32, &f64, &iu32)
 		if err != nil {
 			dbt.Fatal(err)
 		}
@@ -422,6 +422,9 @@ func TestNumbersToAny(t *testing.T) {
 		if f64.(float64) != 2.5 {
 			dbt.Errorf("f64 != 2.5")
 		}
+		if iu32.(int64) != 4294967295 {
+			dbt.Errorf("iu32 != 4294967295")
+		}
 	})
 }
 
diff --git a/packets.go b/packets.go
index 49e6bb058..94b46b10f 100644
--- a/packets.go
+++ b/packets.go
@@ -828,7 +828,7 @@ func (rows *textRows) readRow(dest []driver.Value) error {
 			}
 
 		case fieldTypeTiny, fieldTypeShort, fieldTypeInt24, fieldTypeYear, fieldTypeLong:
-			dest[i], err = strconv.ParseInt(string(buf), 10, 32)
+			dest[i], err = strconv.ParseInt(string(buf), 10, 64)
 
 		case fieldTypeLongLong:
 			if rows.rs.columns[i].flags&flagUnsigned != 0 {

From 743e263bab87912dfb61789f36c21d9685887c76 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Paulius=20Lo=C5=BEys?=
 <42966213+PauliusLozys@users.noreply.github.com>
Date: Wed, 31 Jan 2024 11:34:24 +0200
Subject: [PATCH 236/282] Introduce `timeTruncate` parameter for `time.Time`
 arguments (#1541)

Co-authored-by: Inada Naoki <songofacandy@gmail.com>
---
 AUTHORS       |  1 +
 README.md     |  9 ++++++
 connection.go |  2 +-
 dsn.go        | 12 +++++++
 dsn_test.go   |  3 ++
 packets.go    |  2 +-
 utils.go      |  6 +++-
 utils_test.go | 89 ++++++++++++++++++++++++++++++++++++++-------------
 8 files changed, 98 insertions(+), 26 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 0ada02d86..63ee516e5 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -86,6 +86,7 @@ Oliver Bone <owbone at github.com>
 Olivier Mengué <dolmen at cpan.org>
 oscarzhao <oscarzhaosl at gmail.com>
 Paul Bonser <misterpib at gmail.com>
+Paulius Lozys <pauliuslozys at gmail.com>
 Peter Schultz <peter.schultz at classmarkets.com>
 Phil Porada <philporada at gmail.com>
 Rebecca Chin <rchin at pivotal.io>
diff --git a/README.md b/README.md
index ac79890a7..018e1dd7c 100644
--- a/README.md
+++ b/README.md
@@ -285,6 +285,15 @@ Note that this sets the location for time.Time values but does not change MySQL'
 
 Please keep in mind, that param values must be [url.QueryEscape](https://golang.org/pkg/net/url/#QueryEscape)'ed. Alternatively you can manually replace the `/` with `%2F`. For example `US/Pacific` would be `loc=US%2FPacific`.
 
+##### `timeTruncate`
+
+```
+Type:           duration
+Default:        0
+```
+
+[Truncate time values](https://pkg.go.dev/time#Duration.Truncate) to the specified duration. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*.
+
 ##### `maxAllowedPacket`
 ```
 Type:          decimal number
diff --git a/connection.go b/connection.go
index 660b2b0e0..99eb8a808 100644
--- a/connection.go
+++ b/connection.go
@@ -251,7 +251,7 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
 				buf = append(buf, "'0000-00-00'"...)
 			} else {
 				buf = append(buf, '\'')
-				buf, err = appendDateTime(buf, v.In(mc.cfg.Loc))
+				buf, err = appendDateTime(buf, v.In(mc.cfg.Loc), mc.cfg.TimeTruncate)
 				if err != nil {
 					return "", err
 				}
diff --git a/dsn.go b/dsn.go
index ef0608636..ce5d85ff0 100644
--- a/dsn.go
+++ b/dsn.go
@@ -48,6 +48,7 @@ type Config struct {
 	pubKey               *rsa.PublicKey    // Server public key
 	TLSConfig            string            // TLS configuration name
 	TLS                  *tls.Config       // TLS configuration, its priority is higher than TLSConfig
+	TimeTruncate         time.Duration     // Truncate time.Time values to the specified duration
 	Timeout              time.Duration     // Dial timeout
 	ReadTimeout          time.Duration     // I/O read timeout
 	WriteTimeout         time.Duration     // I/O write timeout
@@ -262,6 +263,10 @@ func (cfg *Config) FormatDSN() string {
 		writeDSNParam(&buf, &hasParam, "parseTime", "true")
 	}
 
+	if cfg.TimeTruncate > 0 {
+		writeDSNParam(&buf, &hasParam, "timeTruncate", cfg.TimeTruncate.String())
+	}
+
 	if cfg.ReadTimeout > 0 {
 		writeDSNParam(&buf, &hasParam, "readTimeout", cfg.ReadTimeout.String())
 	}
@@ -502,6 +507,13 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 				return errors.New("invalid bool value: " + value)
 			}
 
+		// time.Time truncation
+		case "timeTruncate":
+			cfg.TimeTruncate, err = time.ParseDuration(value)
+			if err != nil {
+				return
+			}
+
 		// I/O read Timeout
 		case "readTimeout":
 			cfg.ReadTimeout, err = time.ParseDuration(value)
diff --git a/dsn_test.go b/dsn_test.go
index 8a6a0c10e..75cbda700 100644
--- a/dsn_test.go
+++ b/dsn_test.go
@@ -74,6 +74,9 @@ var testDSNs = []struct {
 }, {
 	"tcp(de:ad:be:ef::ca:fe)/dbname",
 	&Config{Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:3306", DBName: "dbname", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
+}, {
+	"user:password@/dbname?loc=UTC&timeout=30s&parseTime=true&timeTruncate=1h",
+	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Loc: time.UTC, Timeout: 30 * time.Second, ParseTime: true, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, TimeTruncate: time.Hour},
 },
 }
 
diff --git a/packets.go b/packets.go
index 94b46b10f..e5a6e4727 100644
--- a/packets.go
+++ b/packets.go
@@ -1172,7 +1172,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 				if v.IsZero() {
 					b = append(b, "0000-00-00"...)
 				} else {
-					b, err = appendDateTime(b, v.In(mc.cfg.Loc))
+					b, err = appendDateTime(b, v.In(mc.cfg.Loc), mc.cfg.TimeTruncate)
 					if err != nil {
 						return err
 					}
diff --git a/utils.go b/utils.go
index a24197b93..cda24fe74 100644
--- a/utils.go
+++ b/utils.go
@@ -265,7 +265,11 @@ func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Va
 	return nil, fmt.Errorf("invalid DATETIME packet length %d", num)
 }
 
-func appendDateTime(buf []byte, t time.Time) ([]byte, error) {
+func appendDateTime(buf []byte, t time.Time, timeTruncate time.Duration) ([]byte, error) {
+	if timeTruncate > 0 {
+		t = t.Truncate(timeTruncate)
+	}
+
 	year, month, day := t.Date()
 	hour, min, sec := t.Clock()
 	nsec := t.Nanosecond()
diff --git a/utils_test.go b/utils_test.go
index 4e5fc3cb7..80aebddff 100644
--- a/utils_test.go
+++ b/utils_test.go
@@ -237,8 +237,10 @@ func TestIsolationLevelMapping(t *testing.T) {
 
 func TestAppendDateTime(t *testing.T) {
 	tests := []struct {
-		t   time.Time
-		str string
+		t            time.Time
+		str          string
+		timeTruncate time.Duration
+		expectedErr  bool
 	}{
 		{
 			t:   time.Date(1234, 5, 6, 0, 0, 0, 0, time.UTC),
@@ -276,34 +278,75 @@ func TestAppendDateTime(t *testing.T) {
 			t:   time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC),
 			str: "0001-01-01",
 		},
+		// Truncated time
+		{
+			t:            time.Date(1234, 5, 6, 0, 0, 0, 0, time.UTC),
+			str:          "1234-05-06",
+			timeTruncate: time.Second,
+		},
+		{
+			t:            time.Date(4567, 12, 31, 12, 0, 0, 0, time.UTC),
+			str:          "4567-12-31 12:00:00",
+			timeTruncate: time.Minute,
+		},
+		{
+			t:            time.Date(2020, 5, 30, 12, 34, 0, 0, time.UTC),
+			str:          "2020-05-30 12:34:00",
+			timeTruncate: 0,
+		},
+		{
+			t:            time.Date(2020, 5, 30, 12, 34, 56, 0, time.UTC),
+			str:          "2020-05-30 12:34:56",
+			timeTruncate: time.Second,
+		},
+		{
+			t:            time.Date(2020, 5, 30, 22, 33, 44, 123000000, time.UTC),
+			str:          "2020-05-30 22:33:44",
+			timeTruncate: time.Second,
+		},
+		{
+			t:            time.Date(2020, 5, 30, 22, 33, 44, 123456000, time.UTC),
+			str:          "2020-05-30 22:33:44.123",
+			timeTruncate: time.Millisecond,
+		},
+		{
+			t:            time.Date(2020, 5, 30, 22, 33, 44, 123456789, time.UTC),
+			str:          "2020-05-30 22:33:44",
+			timeTruncate: time.Second,
+		},
+		{
+			t:            time.Date(9999, 12, 31, 23, 59, 59, 999999999, time.UTC),
+			str:          "9999-12-31 23:59:59.999999999",
+			timeTruncate: 0,
+		},
+		{
+			t:            time.Date(1, 1, 1, 1, 1, 1, 1, time.UTC),
+			str:          "0001-01-01",
+			timeTruncate: 365 * 24 * time.Hour,
+		},
+		// year out of range
+		{
+			t:           time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC),
+			expectedErr: true,
+		},
+		{
+			t:           time.Date(10000, 1, 1, 0, 0, 0, 0, time.UTC),
+			expectedErr: true,
+		},
 	}
 	for _, v := range tests {
 		buf := make([]byte, 0, 32)
-		buf, _ = appendDateTime(buf, v.t)
+		buf, err := appendDateTime(buf, v.t, v.timeTruncate)
+		if err != nil {
+			if !v.expectedErr {
+				t.Errorf("appendDateTime(%v) returned an errror: %v", v.t, err)
+			}
+			continue
+		}
 		if str := string(buf); str != v.str {
 			t.Errorf("appendDateTime(%v), have: %s, want: %s", v.t, str, v.str)
 		}
 	}
-
-	// year out of range
-	{
-		v := time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC)
-		buf := make([]byte, 0, 32)
-		_, err := appendDateTime(buf, v)
-		if err == nil {
-			t.Error("want an error")
-			return
-		}
-	}
-	{
-		v := time.Date(10000, 1, 1, 0, 0, 0, 0, time.UTC)
-		buf := make([]byte, 0, 32)
-		_, err := appendDateTime(buf, v)
-		if err == nil {
-			t.Error("want an error")
-			return
-		}
-	}
 }
 
 func TestParseDateTime(t *testing.T) {

From f019727e4706bf9c4f60579382f6e72b94bd0305 Mon Sep 17 00:00:00 2001
From: crazycs <crazycs520@gmail.com>
Date: Mon, 5 Feb 2024 16:57:21 +0800
Subject: [PATCH 237/282] add TiDB support in README.md (#1333)

Signed-off-by: crazycs520 <crazycs520@gmail.com>
Co-authored-by: Inada Naoki <songofacandy@gmail.com>
---
 README.md | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 018e1dd7c..9d0d806ef 100644
--- a/README.md
+++ b/README.md
@@ -40,8 +40,16 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
   * Optional placeholder interpolation
 
 ## Requirements
-  * Go 1.18 or higher. We aim to support the 3 latest versions of Go.
-  * MySQL (5.6+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
+
+* Go 1.19 or higher. We aim to support the 3 latest versions of Go.
+* MySQL (5.7+) and MariaDB (10.3+) are supported.
+* [TiDB](https://github.com/pingcap/tidb) is supported by PingCAP.
+  * Do not ask questions about TiDB in our issue tracker or forum.
+  * [Document](https://docs.pingcap.com/tidb/v6.1/dev-guide-sample-application-golang)
+  * [Forum](https://ask.pingcap.com/)
+* go-mysql would work with Percona Server, Google CloudSQL or Sphinx (2.2.3+).
+  * Maintainers won't support them. Do not expect issues are investigated and resolved by maintainers.
+  * Investigate issues yourself and please send a pull request to fix it.
 
 ---------------------------------------
 

From 097fe6e3ad83bbd7c84debe810aec4c4a533bcaa Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Mon, 5 Feb 2024 20:29:00 +0900
Subject: [PATCH 238/282] Update workflows (#1547)

---
 .github/workflows/codeql.yml |  8 ++++----
 .github/workflows/test.yml   | 10 +++++-----
 2 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index d9d29a8b7..83a3d6ee8 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -24,18 +24,18 @@ jobs:
 
     steps:
       - name: Checkout
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
 
       - name: Initialize CodeQL
-        uses: github/codeql-action/init@v2
+        uses: github/codeql-action/init@v3
         with:
           languages: ${{ matrix.language }}
           queries: +security-and-quality
 
       - name: Autobuild
-        uses: github/codeql-action/autobuild@v2
+        uses: github/codeql-action/autobuild@v3
 
       - name: Perform CodeQL Analysis
-        uses: github/codeql-action/analyze@v2
+        uses: github/codeql-action/analyze@v3
         with:
           category: "/language:${{ matrix.language }}"
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index aae421196..f5a115802 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -14,10 +14,10 @@ jobs:
   lint:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
       - uses: dominikh/staticcheck-action@v1.3.0
         with:
-          version: "2023.1.3"
+          version: "2023.1.6"
 
   list:
     runs-on: ubuntu-latest
@@ -73,11 +73,11 @@ jobs:
       fail-fast: false
       matrix: ${{ fromJSON(needs.list.outputs.matrix) }}
     steps:
-      - uses: actions/checkout@v3
-      - uses: actions/setup-go@v4
+      - uses: actions/checkout@v4
+      - uses: actions/setup-go@v5
         with:
           go-version: ${{ matrix.go }}
-      - uses: shogo82148/actions-setup-mysql@v1.21.0
+      - uses: shogo82148/actions-setup-mysql@v1
         with:
           mysql-version: ${{ matrix.mysql }}
           user: ${{ env.MYSQL_TEST_USER }}

From 6964272ffd13a41ad66383cd2ea738fded75ad06 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Thu, 7 Mar 2024 00:32:18 +0900
Subject: [PATCH 239/282] Make TimeTruncate functional option (#1552)

---
 connection.go |  2 +-
 dsn.go        | 47 ++++++++++++++++++++++++++++++++++++++++-------
 dsn_test.go   |  2 +-
 packets.go    |  2 +-
 result.go     |  5 ++---
 5 files changed, 45 insertions(+), 13 deletions(-)

diff --git a/connection.go b/connection.go
index 99eb8a808..c170114fe 100644
--- a/connection.go
+++ b/connection.go
@@ -251,7 +251,7 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
 				buf = append(buf, "'0000-00-00'"...)
 			} else {
 				buf = append(buf, '\'')
-				buf, err = appendDateTime(buf, v.In(mc.cfg.Loc), mc.cfg.TimeTruncate)
+				buf, err = appendDateTime(buf, v.In(mc.cfg.Loc), mc.cfg.timeTruncate)
 				if err != nil {
 					return "", err
 				}
diff --git a/dsn.go b/dsn.go
index ce5d85ff0..d0fbf3bd9 100644
--- a/dsn.go
+++ b/dsn.go
@@ -34,6 +34,8 @@ var (
 // If a new Config is created instead of being parsed from a DSN string,
 // the NewConfig function should be used, which sets default values.
 type Config struct {
+	// non boolean fields
+
 	User                 string            // Username
 	Passwd               string            // Password (requires User)
 	Net                  string            // Network (e.g. "tcp", "tcp6", "unix". default: "tcp")
@@ -45,15 +47,15 @@ type Config struct {
 	Loc                  *time.Location    // Location for time.Time values
 	MaxAllowedPacket     int               // Max packet size allowed
 	ServerPubKey         string            // Server public key name
-	pubKey               *rsa.PublicKey    // Server public key
 	TLSConfig            string            // TLS configuration name
 	TLS                  *tls.Config       // TLS configuration, its priority is higher than TLSConfig
-	TimeTruncate         time.Duration     // Truncate time.Time values to the specified duration
 	Timeout              time.Duration     // Dial timeout
 	ReadTimeout          time.Duration     // I/O read timeout
 	WriteTimeout         time.Duration     // I/O write timeout
 	Logger               Logger            // Logger
 
+	// boolean fields
+
 	AllowAllFiles            bool // Allow all files to be used with LOAD DATA LOCAL INFILE
 	AllowCleartextPasswords  bool // Allows the cleartext client side plugin
 	AllowFallbackToPlaintext bool // Allows fallback to unencrypted connection if server does not support TLS
@@ -66,17 +68,48 @@ type Config struct {
 	MultiStatements          bool // Allow multiple statements in one query
 	ParseTime                bool // Parse time values to time.Time
 	RejectReadOnly           bool // Reject read-only connections
+
+	// unexported fields. new options should be come here
+
+	pubKey       *rsa.PublicKey // Server public key
+	timeTruncate time.Duration  // Truncate time.Time values to the specified duration
 }
 
+// Functional Options Pattern
+// https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis
+type Option func(*Config) error
+
 // NewConfig creates a new Config and sets default values.
 func NewConfig() *Config {
-	return &Config{
+	cfg := &Config{
 		Loc:                  time.UTC,
 		MaxAllowedPacket:     defaultMaxAllowedPacket,
 		Logger:               defaultLogger,
 		AllowNativePasswords: true,
 		CheckConnLiveness:    true,
 	}
+
+	return cfg
+}
+
+// Apply applies the given options to the Config object.
+func (c *Config) Apply(opts ...Option) error {
+	for _, opt := range opts {
+		err := opt(c)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// TimeTruncate sets the time duration to truncate time.Time values in
+// query parameters.
+func TimeTruncate(d time.Duration) Option {
+	return func(cfg *Config) error {
+		cfg.timeTruncate = d
+		return nil
+	}
 }
 
 func (cfg *Config) Clone() *Config {
@@ -263,8 +296,8 @@ func (cfg *Config) FormatDSN() string {
 		writeDSNParam(&buf, &hasParam, "parseTime", "true")
 	}
 
-	if cfg.TimeTruncate > 0 {
-		writeDSNParam(&buf, &hasParam, "timeTruncate", cfg.TimeTruncate.String())
+	if cfg.timeTruncate > 0 {
+		writeDSNParam(&buf, &hasParam, "timeTruncate", cfg.timeTruncate.String())
 	}
 
 	if cfg.ReadTimeout > 0 {
@@ -509,9 +542,9 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 
 		// time.Time truncation
 		case "timeTruncate":
-			cfg.TimeTruncate, err = time.ParseDuration(value)
+			cfg.timeTruncate, err = time.ParseDuration(value)
 			if err != nil {
-				return
+				return fmt.Errorf("invalid timeTruncate value: %v, error: %w", value, err)
 			}
 
 		// I/O read Timeout
diff --git a/dsn_test.go b/dsn_test.go
index 75cbda700..dd8cd935c 100644
--- a/dsn_test.go
+++ b/dsn_test.go
@@ -76,7 +76,7 @@ var testDSNs = []struct {
 	&Config{Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:3306", DBName: "dbname", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"user:password@/dbname?loc=UTC&timeout=30s&parseTime=true&timeTruncate=1h",
-	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Loc: time.UTC, Timeout: 30 * time.Second, ParseTime: true, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, TimeTruncate: time.Hour},
+	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Loc: time.UTC, Timeout: 30 * time.Second, ParseTime: true, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, timeTruncate: time.Hour},
 },
 }
 
diff --git a/packets.go b/packets.go
index e5a6e4727..3d6e5308c 100644
--- a/packets.go
+++ b/packets.go
@@ -1172,7 +1172,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 				if v.IsZero() {
 					b = append(b, "0000-00-00"...)
 				} else {
-					b, err = appendDateTime(b, v.In(mc.cfg.Loc), mc.cfg.TimeTruncate)
+					b, err = appendDateTime(b, v.In(mc.cfg.Loc), mc.cfg.timeTruncate)
 					if err != nil {
 						return err
 					}
diff --git a/result.go b/result.go
index 36a432e81..d51631468 100644
--- a/result.go
+++ b/result.go
@@ -15,9 +15,8 @@ import "database/sql/driver"
 // This is accessible by executing statements using sql.Conn.Raw() and
 // downcasting the returned result:
 //
-//    res, err := rawConn.Exec(...)
-//    res.(mysql.Result).AllRowsAffected()
-//
+//	res, err := rawConn.Exec(...)
+//	res.(mysql.Result).AllRowsAffected()
 type Result interface {
 	driver.Result
 	// AllRowsAffected returns a slice containing the affected rows for each

From 33b7747a9144946e50399904d3f27ecc0f96c2b6 Mon Sep 17 00:00:00 2001
From: "Alessandro (Ale) Segala" <43508+ItalyPaleAle@users.noreply.github.com>
Date: Sat, 9 Mar 2024 07:57:08 +0100
Subject: [PATCH 240/282] Add BeforeConnect callback to configuration object
 (#1469)

This can be used to alter the connection options for each connection, right before it's established

Co-authored-by: Inada Naoki <songofacandy@gmail.com>
---
 AUTHORS        |  1 +
 connector.go   | 12 +++++++++++-
 driver_test.go | 34 ++++++++++++++++++++++++++++++++++
 dsn.go         | 14 ++++++++++++--
 4 files changed, 58 insertions(+), 3 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 63ee516e5..4021b96cc 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -132,6 +132,7 @@ GitHub Inc.
 Google Inc.
 InfoSum Ltd.
 Keybase Inc.
+Microsoft Corp.
 Multiplay Ltd.
 Percona LLC
 PingCAP Inc.
diff --git a/connector.go b/connector.go
index 3cef7963f..a0ee62839 100644
--- a/connector.go
+++ b/connector.go
@@ -66,12 +66,22 @@ func newConnector(cfg *Config) *connector {
 func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
 	var err error
 
+	// Invoke beforeConnect if present, with a copy of the configuration
+	cfg := c.cfg
+	if c.cfg.beforeConnect != nil {
+		cfg = c.cfg.Clone()
+		err = c.cfg.beforeConnect(ctx, cfg)
+		if err != nil {
+			return nil, err
+		}
+	}
+
 	// New mysqlConn
 	mc := &mysqlConn{
 		maxAllowedPacket: maxPacketSize,
 		maxWriteSize:     maxPacketSize - 1,
 		closech:          make(chan struct{}),
-		cfg:              c.cfg,
+		cfg:              cfg,
 		connector:        c,
 	}
 	mc.parseTime = mc.cfg.ParseTime
diff --git a/driver_test.go b/driver_test.go
index 5934caab6..001957244 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -2044,6 +2044,40 @@ func TestCustomDial(t *testing.T) {
 	}
 }
 
+func TestBeforeConnect(t *testing.T) {
+	if !available {
+		t.Skipf("MySQL server not running on %s", netAddr)
+	}
+
+	// dbname is set in the BeforeConnect handle
+	cfg, err := ParseDSN(fmt.Sprintf("%s:%s@%s/%s?timeout=30s", user, pass, netAddr, "_"))
+	if err != nil {
+		t.Fatalf("error parsing DSN: %v", err)
+	}
+
+	cfg.Apply(BeforeConnect(func(ctx context.Context, c *Config) error {
+		c.DBName = dbname
+		return nil
+	}))
+
+	connector, err := NewConnector(cfg)
+	if err != nil {
+		t.Fatalf("error creating connector: %v", err)
+	}
+
+	db := sql.OpenDB(connector)
+	defer db.Close()
+
+	var connectedDb string
+	err = db.QueryRow("SELECT DATABASE();").Scan(&connectedDb)
+	if err != nil {
+		t.Fatalf("error executing query: %v", err)
+	}
+	if connectedDb != dbname {
+		t.Fatalf("expected to connect to DB %s, but connected to %s instead", dbname, connectedDb)
+	}
+}
+
 func TestSQLInjection(t *testing.T) {
 	createTest := func(arg string) func(dbt *DBTest) {
 		return func(dbt *DBTest) {
diff --git a/dsn.go b/dsn.go
index d0fbf3bd9..65f5a0242 100644
--- a/dsn.go
+++ b/dsn.go
@@ -10,6 +10,7 @@ package mysql
 
 import (
 	"bytes"
+	"context"
 	"crypto/rsa"
 	"crypto/tls"
 	"errors"
@@ -71,8 +72,9 @@ type Config struct {
 
 	// unexported fields. new options should be come here
 
-	pubKey       *rsa.PublicKey // Server public key
-	timeTruncate time.Duration  // Truncate time.Time values to the specified duration
+	beforeConnect func(context.Context, *Config) error // Invoked before a connection is established
+	pubKey        *rsa.PublicKey                       // Server public key
+	timeTruncate  time.Duration                        // Truncate time.Time values to the specified duration
 }
 
 // Functional Options Pattern
@@ -112,6 +114,14 @@ func TimeTruncate(d time.Duration) Option {
 	}
 }
 
+// BeforeConnect sets the function to be invoked before a connection is established.
+func BeforeConnect(fn func(context.Context, *Config) error) Option {
+	return func(cfg *Config) error {
+		cfg.beforeConnect = fn
+		return nil
+	}
+}
+
 func (cfg *Config) Clone() *Config {
 	cp := *cfg
 	if cp.TLS != nil {

From 3147497dd6a98708e5ee4da04f2a686b4d7979a7 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Mon, 11 Mar 2024 13:44:06 +0900
Subject: [PATCH 241/282] ci: update Go and MySQL versions (#1557)

---
 .github/workflows/test.yml | 18 ++++++++----------
 1 file changed, 8 insertions(+), 10 deletions(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index f5a115802..c5b2aa313 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -31,22 +31,20 @@ jobs:
           import os
           go = [
               # Keep the most recent production release at the top
-              '1.21',
+              '1.22',
               # Older production releases
+              '1.21',
               '1.20',
-              '1.19',
-              '1.18',
           ]
           mysql = [
-              '8.1',
               '8.0',
+              '8.3',
               '5.7',
-              '5.6',
-              'mariadb-10.11',
-              'mariadb-10.6',
+              'mariadb-11.3',
+              'mariadb-11.1',
+              'mariadb-10.11',  # LTS
+              'mariadb-10.6',   # LTS
               'mariadb-10.5',
-              'mariadb-10.4',
-              'mariadb-10.3',
           ]
 
           includes = []
@@ -64,7 +62,7 @@ jobs:
           }
           output = json.dumps(matrix, separators=(',', ':'))
           with open(os.environ["GITHUB_OUTPUT"], 'a', encoding="utf-8") as f:
-              f.write('matrix={0}\n'.format(output))
+              print(f"matrix={output}", file=f)
         shell: python
   test:
     needs: list

From 8a327a3575a42f7222f6e51263326d5a0eaecab0 Mon Sep 17 00:00:00 2001
From: ICHINOSE Shogo <shogo82148@gmail.com>
Date: Mon, 11 Mar 2024 23:54:40 +0900
Subject: [PATCH 242/282] Drop support of go1.19 (#1558)

* drop support of Go 1.19

* replace atomicBool by atomic.Bool

* Update Go and MariaDB versions in README.md
---
 README.md            |  4 +--
 atomic_bool.go       | 19 ------------
 atomic_bool_go118.go | 47 -----------------------------
 atomic_bool_test.go  | 71 --------------------------------------------
 connection.go        |  3 +-
 go.mod               |  2 +-
 6 files changed, 5 insertions(+), 141 deletions(-)
 delete mode 100644 atomic_bool.go
 delete mode 100644 atomic_bool_go118.go
 delete mode 100644 atomic_bool_test.go

diff --git a/README.md b/README.md
index 9d0d806ef..c3204ef11 100644
--- a/README.md
+++ b/README.md
@@ -41,8 +41,8 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
 
 ## Requirements
 
-* Go 1.19 or higher. We aim to support the 3 latest versions of Go.
-* MySQL (5.7+) and MariaDB (10.3+) are supported.
+* Go 1.20 or higher. We aim to support the 3 latest versions of Go.
+* MySQL (5.7+) and MariaDB (10.5+) are supported.
 * [TiDB](https://github.com/pingcap/tidb) is supported by PingCAP.
   * Do not ask questions about TiDB in our issue tracker or forum.
   * [Document](https://docs.pingcap.com/tidb/v6.1/dev-guide-sample-application-golang)
diff --git a/atomic_bool.go b/atomic_bool.go
deleted file mode 100644
index 1b7e19f3e..000000000
--- a/atomic_bool.go
+++ /dev/null
@@ -1,19 +0,0 @@
-// Go MySQL Driver - A MySQL-Driver for Go's database/sql package.
-//
-// Copyright 2022 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/.
-//go:build go1.19
-// +build go1.19
-
-package mysql
-
-import "sync/atomic"
-
-/******************************************************************************
-*                               Sync utils                                    *
-******************************************************************************/
-
-type atomicBool = atomic.Bool
diff --git a/atomic_bool_go118.go b/atomic_bool_go118.go
deleted file mode 100644
index 2e9a7f0b6..000000000
--- a/atomic_bool_go118.go
+++ /dev/null
@@ -1,47 +0,0 @@
-// Go MySQL Driver - A MySQL-Driver for Go's database/sql package.
-//
-// Copyright 2022 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/.
-//go:build !go1.19
-// +build !go1.19
-
-package mysql
-
-import "sync/atomic"
-
-/******************************************************************************
-*                               Sync utils                                    *
-******************************************************************************/
-
-// atomicBool is an implementation of atomic.Bool for older version of Go.
-// it is a wrapper around uint32 for usage as a boolean value with
-// atomic access.
-type atomicBool struct {
-	_     noCopy
-	value uint32
-}
-
-// Load returns whether the current boolean value is true
-func (ab *atomicBool) Load() bool {
-	return atomic.LoadUint32(&ab.value) > 0
-}
-
-// Store sets the value of the bool regardless of the previous value
-func (ab *atomicBool) Store(value bool) {
-	if value {
-		atomic.StoreUint32(&ab.value, 1)
-	} else {
-		atomic.StoreUint32(&ab.value, 0)
-	}
-}
-
-// Swap sets the value of the bool and returns the old value.
-func (ab *atomicBool) Swap(value bool) bool {
-	if value {
-		return atomic.SwapUint32(&ab.value, 1) > 0
-	}
-	return atomic.SwapUint32(&ab.value, 0) > 0
-}
diff --git a/atomic_bool_test.go b/atomic_bool_test.go
deleted file mode 100644
index a3b4ea0e8..000000000
--- a/atomic_bool_test.go
+++ /dev/null
@@ -1,71 +0,0 @@
-// Go MySQL Driver - A MySQL-Driver for Go's database/sql package.
-//
-// Copyright 2022 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/.
-//go:build !go1.19
-// +build !go1.19
-
-package mysql
-
-import (
-	"testing"
-)
-
-func TestAtomicBool(t *testing.T) {
-	var ab atomicBool
-	if ab.Load() {
-		t.Fatal("Expected value to be false")
-	}
-
-	ab.Store(true)
-	if ab.value != 1 {
-		t.Fatal("Set(true) did not set value to 1")
-	}
-	if !ab.Load() {
-		t.Fatal("Expected value to be true")
-	}
-
-	ab.Store(true)
-	if !ab.Load() {
-		t.Fatal("Expected value to be true")
-	}
-
-	ab.Store(false)
-	if ab.value != 0 {
-		t.Fatal("Set(false) did not set value to 0")
-	}
-	if ab.Load() {
-		t.Fatal("Expected value to be false")
-	}
-
-	ab.Store(false)
-	if ab.Load() {
-		t.Fatal("Expected value to be false")
-	}
-	if ab.Swap(false) {
-		t.Fatal("Expected the old value to be false")
-	}
-	if ab.Swap(true) {
-		t.Fatal("Expected the old value to be false")
-	}
-	if !ab.Load() {
-		t.Fatal("Expected value to be true")
-	}
-
-	ab.Store(true)
-	if !ab.Load() {
-		t.Fatal("Expected value to be true")
-	}
-	if !ab.Swap(true) {
-		t.Fatal("Expected the old value to be true")
-	}
-	if !ab.Swap(false) {
-		t.Fatal("Expected the old value to be true")
-	}
-	if ab.Load() {
-		t.Fatal("Expected value to be false")
-	}
-}
diff --git a/connection.go b/connection.go
index c170114fe..55e42eb18 100644
--- a/connection.go
+++ b/connection.go
@@ -17,6 +17,7 @@ import (
 	"net"
 	"strconv"
 	"strings"
+	"sync/atomic"
 	"time"
 )
 
@@ -41,7 +42,7 @@ type mysqlConn struct {
 	closech  chan struct{}
 	finished chan<- struct{}
 	canceled atomicError // set non-nil if conn is canceled
-	closed   atomicBool  // set when conn is closed, before closech is closed
+	closed   atomic.Bool // set when conn is closed, before closech is closed
 }
 
 // Handles parameters set in DSN after the connection is established
diff --git a/go.mod b/go.mod
index 4629714c0..2eed53ebb 100644
--- a/go.mod
+++ b/go.mod
@@ -1,5 +1,5 @@
 module github.com/go-sql-driver/mysql
 
-go 1.18
+go 1.20
 
 require filippo.io/edwards25519 v1.1.0

From 35847bed632a869c89234080ebee1e7b78d140e6 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Sat, 16 Mar 2024 23:23:22 +0900
Subject: [PATCH 243/282] replace interface{} with any (#1560)

---
 README.md         |   2 +-
 driver_test.go    | 116 +++++++++++++++++++++++-----------------------
 errors.go         |   4 +-
 fields.go         |   2 +-
 nulltime.go       |   2 +-
 nulltime_test.go  |   2 +-
 statement.go      |   2 +-
 statement_test.go |   4 +-
 8 files changed, 67 insertions(+), 67 deletions(-)

diff --git a/README.md b/README.md
index c3204ef11..6c6abf9c4 100644
--- a/README.md
+++ b/README.md
@@ -326,7 +326,7 @@ It's possible to access the last inserted ID and number of affected rows for mul
 
 ```go
 conn, _ := db.Conn(ctx)
-conn.Raw(func(conn interface{}) error {
+conn.Raw(func(conn any) error {
   ex := conn.(driver.Execer)
   res, err := ex.Exec(`
   UPDATE point SET x = 1 WHERE y = 2;
diff --git a/driver_test.go b/driver_test.go
index 001957244..6b52650c2 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -247,7 +247,7 @@ func (dbt *DBTest) fail(method, query string, err error) {
 	dbt.Fatalf("error on %s %s: %s", method, query, err.Error())
 }
 
-func (dbt *DBTest) mustExec(query string, args ...interface{}) (res sql.Result) {
+func (dbt *DBTest) mustExec(query string, args ...any) (res sql.Result) {
 	dbt.Helper()
 	res, err := dbt.db.Exec(query, args...)
 	if err != nil {
@@ -256,7 +256,7 @@ func (dbt *DBTest) mustExec(query string, args ...interface{}) (res sql.Result)
 	return res
 }
 
-func (dbt *DBTest) mustQuery(query string, args ...interface{}) (rows *sql.Rows) {
+func (dbt *DBTest) mustQuery(query string, args ...any) (rows *sql.Rows) {
 	dbt.Helper()
 	rows, err := dbt.db.Query(query, args...)
 	if err != nil {
@@ -844,7 +844,7 @@ func (t timeTest) run(dbt *DBTest, dbtype, tlayout string, mode timeMode) {
 		dbt.Errorf("%s [%s]: %s", dbtype, mode, err)
 		return
 	}
-	var dst interface{}
+	var dst any
 	err = rows.Scan(&dst)
 	if err != nil {
 		dbt.Errorf("%s [%s]: %s", dbtype, mode, err)
@@ -875,7 +875,7 @@ func (t timeTest) run(dbt *DBTest, dbtype, tlayout string, mode timeMode) {
 			t.s, val.Format(tlayout),
 		)
 	default:
-		fmt.Printf("%#v\n", []interface{}{dbtype, tlayout, mode, t.s, t.t})
+		fmt.Printf("%#v\n", []any{dbtype, tlayout, mode, t.s, t.t})
 		dbt.Errorf("%s [%s]: unhandled type %T (is '%v')",
 			dbtype, mode,
 			val, val,
@@ -1186,7 +1186,7 @@ func TestNULL(t *testing.T) {
 
 		dbt.mustExec("INSERT INTO "+tbl+" VALUES (?, ?, ?)", 1, nil, 2)
 
-		var out interface{}
+		var out any
 		rows := dbt.mustQuery("SELECT * FROM " + tbl)
 		defer rows.Close()
 		if rows.Next() {
@@ -1894,7 +1894,7 @@ func TestPreparedManyCols(t *testing.T) {
 
 		// create more parameters than fit into the buffer
 		// which will take nil-values
-		params := make([]interface{}, numParams)
+		params := make([]any, numParams)
 		rows, err := stmt.Query(params...)
 		if err != nil {
 			dbt.Fatal(err)
@@ -1941,7 +1941,7 @@ func TestConcurrent(t *testing.T) {
 
 		var fatalError string
 		var once sync.Once
-		fatalf := func(s string, vals ...interface{}) {
+		fatalf := func(s string, vals ...any) {
 			once.Do(func() {
 				fatalError = fmt.Sprintf(s, vals...)
 			})
@@ -2314,7 +2314,7 @@ func TestPing(t *testing.T) {
 		}
 
 		// Check that affectedRows and insertIds are cleared after each call.
-		conn.Raw(func(conn interface{}) error {
+		conn.Raw(func(conn any) error {
 			c := conn.(*mysqlConn)
 
 			// Issue a query that sets affectedRows and insertIds.
@@ -2577,7 +2577,7 @@ func TestExecMultipleResults(t *testing.T) {
 		if err != nil {
 			t.Fatalf("failed to connect: %v", err)
 		}
-		conn.Raw(func(conn interface{}) error {
+		conn.Raw(func(conn any) error {
 			//lint:ignore SA1019 this is a test
 			ex := conn.(driver.Execer)
 			res, err := ex.Exec(`
@@ -2635,7 +2635,7 @@ func TestQueryMultipleResults(t *testing.T) {
 		if err != nil {
 			t.Fatalf("failed to connect: %v", err)
 		}
-		conn.Raw(func(conn interface{}) error {
+		conn.Raw(func(conn any) error {
 			//lint:ignore SA1019 this is a test
 			qr := conn.(driver.Queryer)
 			c := conn.(*mysqlConn)
@@ -3058,54 +3058,54 @@ func TestRowsColumnTypes(t *testing.T) {
 		precision        int64 // 0 if not ok
 		scale            int64
 		valuesIn         [3]string
-		valuesOut        [3]interface{}
+		valuesOut        [3]any
 	}{
-		{"bit8null", "BIT(8)", "BIT", scanTypeBytes, true, 0, 0, [3]string{"0x0", "NULL", "0x42"}, [3]interface{}{bx0, bNULL, bx42}},
-		{"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)}},
-		{"mediumintnull", "MEDIUMINT", "MEDIUMINT", scanTypeNullInt, true, 0, 0, [3]string{"0", "42", "NULL"}, [3]interface{}{ni0, ni42, niNULL}},
-		{"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", "UNSIGNED TINYINT", scanTypeUint8, false, 0, 0, [3]string{"0", "255", "42"}, [3]interface{}{uint8(0), uint8(255), uint8(42)}},
-		{"smalluint", "SMALLINT UNSIGNED NOT NULL", "UNSIGNED SMALLINT", scanTypeUint16, false, 0, 0, [3]string{"0", "65535", "42"}, [3]interface{}{uint16(0), uint16(65535), uint16(42)}},
-		{"biguint", "BIGINT UNSIGNED NOT NULL", "UNSIGNED BIGINT", scanTypeUint64, false, 0, 0, [3]string{"0", "65535", "42"}, [3]interface{}{uint64(0), uint64(65535), uint64(42)}},
-		{"mediumuint", "MEDIUMINT UNSIGNED NOT NULL", "UNSIGNED MEDIUMINT", scanTypeUint32, false, 0, 0, [3]string{"0", "16777215", "42"}, [3]interface{}{uint32(0), uint32(16777215), uint32(42)}},
-		{"uint13", "INT(13) UNSIGNED NOT NULL", "UNSIGNED 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", scanTypeString, false, 10, 6, [3]string{"0", "13.37", "1234.123456"}, [3]interface{}{"0.000000", "13.370000", "1234.123456"}},
-		{"decimal1null", "DECIMAL(10,6)", "DECIMAL", scanTypeNullString, true, 10, 6, [3]string{"0", "NULL", "1234.123456"}, [3]interface{}{ns("0.000000"), nsNULL, ns("1234.123456")}},
-		{"decimal2", "DECIMAL(8,4) NOT NULL", "DECIMAL", scanTypeString, false, 8, 4, [3]string{"0", "13.37", "1234.123456"}, [3]interface{}{"0.0000", "13.3700", "1234.1235"}},
-		{"decimal2null", "DECIMAL(8,4)", "DECIMAL", scanTypeNullString, true, 8, 4, [3]string{"0", "NULL", "1234.123456"}, [3]interface{}{ns("0.0000"), nsNULL, ns("1234.1235")}},
-		{"decimal3", "DECIMAL(5,0) NOT NULL", "DECIMAL", scanTypeString, false, 5, 0, [3]string{"0", "13.37", "-12345.123456"}, [3]interface{}{"0", "13", "-12345"}},
-		{"decimal3null", "DECIMAL(5,0)", "DECIMAL", scanTypeNullString, true, 5, 0, [3]string{"0", "NULL", "-12345.123456"}, [3]interface{}{ns0, nsNULL, ns("-12345")}},
-		{"char25null", "CHAR(25)", "CHAR", scanTypeNullString, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{ns0, nsNULL, nsTest}},
-		{"varchar42", "VARCHAR(42) NOT NULL", "VARCHAR", scanTypeString, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{"0", "Test", "42"}},
-		{"binary4null", "BINARY(4)", "BINARY", scanTypeBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{b0pad4, bNULL, bTest}},
-		{"varbinary42", "VARBINARY(42) NOT NULL", "VARBINARY", scanTypeBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{b0, bTest, b42}},
-		{"tinyblobnull", "TINYBLOB", "BLOB", scanTypeBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{b0, bNULL, bTest}},
-		{"tinytextnull", "TINYTEXT", "TEXT", scanTypeNullString, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{ns0, nsNULL, nsTest}},
-		{"blobnull", "BLOB", "BLOB", scanTypeBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{b0, bNULL, bTest}},
-		{"textnull", "TEXT", "TEXT", scanTypeNullString, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]interface{}{ns0, nsNULL, nsTest}},
-		{"mediumblob", "MEDIUMBLOB NOT NULL", "BLOB", scanTypeBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{b0, bTest, b42}},
-		{"mediumtext", "MEDIUMTEXT NOT NULL", "TEXT", scanTypeString, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{"0", "Test", "42"}},
-		{"longblob", "LONGBLOB NOT NULL", "BLOB", scanTypeBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{b0, bTest, b42}},
-		{"longtext", "LONGTEXT NOT NULL", "TEXT", scanTypeString, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]interface{}{"0", "Test", "42"}},
-		{"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}},
-		{"date", "DATE", "DATE", scanTypeNullTime, true, 0, 0, [3]string{"'2006-01-02'", "NULL", "'2006-03-04'"}, [3]interface{}{nd1, ndNULL, nd2}},
-		{"year", "YEAR NOT NULL", "YEAR", scanTypeUint16, false, 0, 0, [3]string{"2006", "2000", "1994"}, [3]interface{}{uint16(2006), uint16(2000), uint16(1994)}},
-		{"enum", "ENUM('', 'v1', 'v2')", "ENUM", scanTypeNullString, true, 0, 0, [3]string{"''", "'v1'", "'v2'"}, [3]interface{}{ns(""), ns("v1"), ns("v2")}},
-		{"set", "set('', 'v1', 'v2')", "SET", scanTypeNullString, true, 0, 0, [3]string{"''", "'v1'", "'v1,v2'"}, [3]interface{}{ns(""), ns("v1"), ns("v1,v2")}},
+		{"bit8null", "BIT(8)", "BIT", scanTypeBytes, true, 0, 0, [3]string{"0x0", "NULL", "0x42"}, [3]any{bx0, bNULL, bx42}},
+		{"boolnull", "BOOL", "TINYINT", scanTypeNullInt, true, 0, 0, [3]string{"NULL", "true", "0"}, [3]any{niNULL, ni1, ni0}},
+		{"bool", "BOOL NOT NULL", "TINYINT", scanTypeInt8, false, 0, 0, [3]string{"1", "0", "FALSE"}, [3]any{int8(1), int8(0), int8(0)}},
+		{"intnull", "INTEGER", "INT", scanTypeNullInt, true, 0, 0, [3]string{"0", "NULL", "42"}, [3]any{ni0, niNULL, ni42}},
+		{"smallint", "SMALLINT NOT NULL", "SMALLINT", scanTypeInt16, false, 0, 0, [3]string{"0", "-32768", "32767"}, [3]any{int16(0), int16(-32768), int16(32767)}},
+		{"smallintnull", "SMALLINT", "SMALLINT", scanTypeNullInt, true, 0, 0, [3]string{"0", "NULL", "42"}, [3]any{ni0, niNULL, ni42}},
+		{"int3null", "INT(3)", "INT", scanTypeNullInt, true, 0, 0, [3]string{"0", "NULL", "42"}, [3]any{ni0, niNULL, ni42}},
+		{"int7", "INT(7) NOT NULL", "INT", scanTypeInt32, false, 0, 0, [3]string{"0", "-1337", "42"}, [3]any{int32(0), int32(-1337), int32(42)}},
+		{"mediumintnull", "MEDIUMINT", "MEDIUMINT", scanTypeNullInt, true, 0, 0, [3]string{"0", "42", "NULL"}, [3]any{ni0, ni42, niNULL}},
+		{"bigint", "BIGINT NOT NULL", "BIGINT", scanTypeInt64, false, 0, 0, [3]string{"0", "65535", "-42"}, [3]any{int64(0), int64(65535), int64(-42)}},
+		{"bigintnull", "BIGINT", "BIGINT", scanTypeNullInt, true, 0, 0, [3]string{"NULL", "1", "42"}, [3]any{niNULL, ni1, ni42}},
+		{"tinyuint", "TINYINT UNSIGNED NOT NULL", "UNSIGNED TINYINT", scanTypeUint8, false, 0, 0, [3]string{"0", "255", "42"}, [3]any{uint8(0), uint8(255), uint8(42)}},
+		{"smalluint", "SMALLINT UNSIGNED NOT NULL", "UNSIGNED SMALLINT", scanTypeUint16, false, 0, 0, [3]string{"0", "65535", "42"}, [3]any{uint16(0), uint16(65535), uint16(42)}},
+		{"biguint", "BIGINT UNSIGNED NOT NULL", "UNSIGNED BIGINT", scanTypeUint64, false, 0, 0, [3]string{"0", "65535", "42"}, [3]any{uint64(0), uint64(65535), uint64(42)}},
+		{"mediumuint", "MEDIUMINT UNSIGNED NOT NULL", "UNSIGNED MEDIUMINT", scanTypeUint32, false, 0, 0, [3]string{"0", "16777215", "42"}, [3]any{uint32(0), uint32(16777215), uint32(42)}},
+		{"uint13", "INT(13) UNSIGNED NOT NULL", "UNSIGNED INT", scanTypeUint32, false, 0, 0, [3]string{"0", "1337", "42"}, [3]any{uint32(0), uint32(1337), uint32(42)}},
+		{"float", "FLOAT NOT NULL", "FLOAT", scanTypeFloat32, false, math.MaxInt64, math.MaxInt64, [3]string{"0", "42", "13.37"}, [3]any{float32(0), float32(42), float32(13.37)}},
+		{"floatnull", "FLOAT", "FLOAT", scanTypeNullFloat, true, math.MaxInt64, math.MaxInt64, [3]string{"0", "NULL", "13.37"}, [3]any{nf0, nfNULL, nf1337}},
+		{"float74null", "FLOAT(7,4)", "FLOAT", scanTypeNullFloat, true, math.MaxInt64, 4, [3]string{"0", "NULL", "13.37"}, [3]any{nf0, nfNULL, nf1337}},
+		{"double", "DOUBLE NOT NULL", "DOUBLE", scanTypeFloat64, false, math.MaxInt64, math.MaxInt64, [3]string{"0", "42", "13.37"}, [3]any{float64(0), float64(42), float64(13.37)}},
+		{"doublenull", "DOUBLE", "DOUBLE", scanTypeNullFloat, true, math.MaxInt64, math.MaxInt64, [3]string{"0", "NULL", "13.37"}, [3]any{nf0, nfNULL, nf1337}},
+		{"decimal1", "DECIMAL(10,6) NOT NULL", "DECIMAL", scanTypeString, false, 10, 6, [3]string{"0", "13.37", "1234.123456"}, [3]any{"0.000000", "13.370000", "1234.123456"}},
+		{"decimal1null", "DECIMAL(10,6)", "DECIMAL", scanTypeNullString, true, 10, 6, [3]string{"0", "NULL", "1234.123456"}, [3]any{ns("0.000000"), nsNULL, ns("1234.123456")}},
+		{"decimal2", "DECIMAL(8,4) NOT NULL", "DECIMAL", scanTypeString, false, 8, 4, [3]string{"0", "13.37", "1234.123456"}, [3]any{"0.0000", "13.3700", "1234.1235"}},
+		{"decimal2null", "DECIMAL(8,4)", "DECIMAL", scanTypeNullString, true, 8, 4, [3]string{"0", "NULL", "1234.123456"}, [3]any{ns("0.0000"), nsNULL, ns("1234.1235")}},
+		{"decimal3", "DECIMAL(5,0) NOT NULL", "DECIMAL", scanTypeString, false, 5, 0, [3]string{"0", "13.37", "-12345.123456"}, [3]any{"0", "13", "-12345"}},
+		{"decimal3null", "DECIMAL(5,0)", "DECIMAL", scanTypeNullString, true, 5, 0, [3]string{"0", "NULL", "-12345.123456"}, [3]any{ns0, nsNULL, ns("-12345")}},
+		{"char25null", "CHAR(25)", "CHAR", scanTypeNullString, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]any{ns0, nsNULL, nsTest}},
+		{"varchar42", "VARCHAR(42) NOT NULL", "VARCHAR", scanTypeString, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]any{"0", "Test", "42"}},
+		{"binary4null", "BINARY(4)", "BINARY", scanTypeBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]any{b0pad4, bNULL, bTest}},
+		{"varbinary42", "VARBINARY(42) NOT NULL", "VARBINARY", scanTypeBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]any{b0, bTest, b42}},
+		{"tinyblobnull", "TINYBLOB", "BLOB", scanTypeBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]any{b0, bNULL, bTest}},
+		{"tinytextnull", "TINYTEXT", "TEXT", scanTypeNullString, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]any{ns0, nsNULL, nsTest}},
+		{"blobnull", "BLOB", "BLOB", scanTypeBytes, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]any{b0, bNULL, bTest}},
+		{"textnull", "TEXT", "TEXT", scanTypeNullString, true, 0, 0, [3]string{"0", "NULL", "'Test'"}, [3]any{ns0, nsNULL, nsTest}},
+		{"mediumblob", "MEDIUMBLOB NOT NULL", "BLOB", scanTypeBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]any{b0, bTest, b42}},
+		{"mediumtext", "MEDIUMTEXT NOT NULL", "TEXT", scanTypeString, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]any{"0", "Test", "42"}},
+		{"longblob", "LONGBLOB NOT NULL", "BLOB", scanTypeBytes, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]any{b0, bTest, b42}},
+		{"longtext", "LONGTEXT NOT NULL", "TEXT", scanTypeString, false, 0, 0, [3]string{"0", "'Test'", "42"}, [3]any{"0", "Test", "42"}},
+		{"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]any{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]any{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]any{nt0, nt1, nt6}},
+		{"date", "DATE", "DATE", scanTypeNullTime, true, 0, 0, [3]string{"'2006-01-02'", "NULL", "'2006-03-04'"}, [3]any{nd1, ndNULL, nd2}},
+		{"year", "YEAR NOT NULL", "YEAR", scanTypeUint16, false, 0, 0, [3]string{"2006", "2000", "1994"}, [3]any{uint16(2006), uint16(2000), uint16(1994)}},
+		{"enum", "ENUM('', 'v1', 'v2')", "ENUM", scanTypeNullString, true, 0, 0, [3]string{"''", "'v1'", "'v2'"}, [3]any{ns(""), ns("v1"), ns("v2")}},
+		{"set", "set('', 'v1', 'v2')", "SET", scanTypeNullString, true, 0, 0, [3]string{"''", "'v1'", "'v1,v2'"}, [3]any{ns(""), ns("v1"), ns("v1,v2")}},
 	}
 
 	schema := ""
@@ -3215,7 +3215,7 @@ func TestRowsColumnTypes(t *testing.T) {
 		if t.Failed() {
 			return
 		}
-		values := make([]interface{}, len(tt))
+		values := make([]any, len(tt))
 		for i := range values {
 			values[i] = reflect.New(types[i]).Interface()
 		}
diff --git a/errors.go b/errors.go
index a9a3060c9..a7ef88909 100644
--- a/errors.go
+++ b/errors.go
@@ -41,14 +41,14 @@ var defaultLogger = Logger(log.New(os.Stderr, "[mysql] ", log.Ldate|log.Ltime|lo
 
 // Logger is used to log critical error messages.
 type Logger interface {
-	Print(v ...interface{})
+	Print(v ...any)
 }
 
 // NopLogger is a nop implementation of the Logger interface.
 type NopLogger struct{}
 
 // Print implements Logger interface.
-func (nl *NopLogger) Print(_ ...interface{}) {}
+func (nl *NopLogger) Print(_ ...any) {}
 
 // SetLogger is used to set the default logger for critical errors.
 // The initial logger is os.Stderr.
diff --git a/fields.go b/fields.go
index 2a397b245..286084247 100644
--- a/fields.go
+++ b/fields.go
@@ -134,7 +134,7 @@ var (
 	scanTypeString     = reflect.TypeOf("")
 	scanTypeNullString = reflect.TypeOf(sql.NullString{})
 	scanTypeBytes      = reflect.TypeOf([]byte{})
-	scanTypeUnknown    = reflect.TypeOf(new(interface{}))
+	scanTypeUnknown    = reflect.TypeOf(new(any))
 )
 
 type mysqlField struct {
diff --git a/nulltime.go b/nulltime.go
index 7d381d5c2..316a48aae 100644
--- a/nulltime.go
+++ b/nulltime.go
@@ -38,7 +38,7 @@ type NullTime sql.NullTime
 // Scan implements the Scanner interface.
 // The value type must be time.Time or string / []byte (formatted time-string),
 // otherwise Scan fails.
-func (nt *NullTime) Scan(value interface{}) (err error) {
+func (nt *NullTime) Scan(value any) (err error) {
 	if value == nil {
 		nt.Time, nt.Valid = time.Time{}, false
 		return
diff --git a/nulltime_test.go b/nulltime_test.go
index a14ec0607..4f1d9029e 100644
--- a/nulltime_test.go
+++ b/nulltime_test.go
@@ -23,7 +23,7 @@ var (
 
 func TestScanNullTime(t *testing.T) {
 	var scanTests = []struct {
-		in    interface{}
+		in    any
 		error bool
 		valid bool
 		time  time.Time
diff --git a/statement.go b/statement.go
index 31e7799c4..d8b921b8e 100644
--- a/statement.go
+++ b/statement.go
@@ -141,7 +141,7 @@ type converter struct{}
 // implementation does not.  This function should be kept in sync with
 // database/sql/driver defaultConverter.ConvertValue() except for that
 // deliberate difference.
-func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
+func (c converter) ConvertValue(v any) (driver.Value, error) {
 	if driver.IsValue(v) {
 		return v, nil
 	}
diff --git a/statement_test.go b/statement_test.go
index 2563ece55..15f9d7c33 100644
--- a/statement_test.go
+++ b/statement_test.go
@@ -77,7 +77,7 @@ func TestConvertPointer(t *testing.T) {
 }
 
 func TestConvertSignedIntegers(t *testing.T) {
-	values := []interface{}{
+	values := []any{
 		int8(-42),
 		int16(-42),
 		int32(-42),
@@ -106,7 +106,7 @@ func (u myUint64) Value() (driver.Value, error) {
 }
 
 func TestConvertUnsignedIntegers(t *testing.T) {
-	values := []interface{}{
+	values := []any{
 		uint8(42),
 		uint16(42),
 		uint32(42),

From 1a6477358cbbc917d5370c53d3e35a13b45aed19 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Sat, 16 Mar 2024 23:24:21 +0900
Subject: [PATCH 244/282] add wrapper method to call mc.cfg.Logger (#1563)

---
 auth.go       |  2 +-
 connection.go | 23 ++++++++++++++---------
 packets.go    | 24 ++++++++++++------------
 statement.go  |  4 ++--
 4 files changed, 29 insertions(+), 24 deletions(-)

diff --git a/auth.go b/auth.go
index 658259b24..74e1bd03e 100644
--- a/auth.go
+++ b/auth.go
@@ -338,7 +338,7 @@ func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, error) {
 		return authEd25519(authData, mc.cfg.Passwd)
 
 	default:
-		mc.cfg.Logger.Print("unknown auth plugin:", plugin)
+		mc.log("unknown auth plugin:", plugin)
 		return nil, ErrUnknownPlugin
 	}
 }
diff --git a/connection.go b/connection.go
index 55e42eb18..5061b69ca 100644
--- a/connection.go
+++ b/connection.go
@@ -45,6 +45,11 @@ type mysqlConn struct {
 	closed   atomic.Bool // set when conn is closed, before closech is closed
 }
 
+// Helper function to call per-connection logger.
+func (mc *mysqlConn) log(v ...any) {
+	mc.cfg.Logger.Print(v...)
+}
+
 // Handles parameters set in DSN after the connection is established
 func (mc *mysqlConn) handleParams() (err error) {
 	var cmdSet strings.Builder
@@ -110,7 +115,7 @@ func (mc *mysqlConn) Begin() (driver.Tx, error) {
 
 func (mc *mysqlConn) begin(readOnly bool) (driver.Tx, error) {
 	if mc.closed.Load() {
-		mc.cfg.Logger.Print(ErrInvalidConn)
+		mc.log(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
 	var q string
@@ -152,7 +157,7 @@ func (mc *mysqlConn) cleanup() {
 		return
 	}
 	if err := mc.netConn.Close(); err != nil {
-		mc.cfg.Logger.Print(err)
+		mc.log(err)
 	}
 	mc.clearResult()
 }
@@ -169,14 +174,14 @@ func (mc *mysqlConn) error() error {
 
 func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
 	if mc.closed.Load() {
-		mc.cfg.Logger.Print(ErrInvalidConn)
+		mc.log(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
 	// Send command
 	err := mc.writeCommandPacketStr(comStmtPrepare, query)
 	if err != nil {
 		// STMT_PREPARE is safe to retry.  So we can return ErrBadConn here.
-		mc.cfg.Logger.Print(err)
+		mc.log(err)
 		return nil, driver.ErrBadConn
 	}
 
@@ -210,7 +215,7 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
 	buf, err := mc.buf.takeCompleteBuffer()
 	if err != nil {
 		// can not take the buffer. Something must be wrong with the connection
-		mc.cfg.Logger.Print(err)
+		mc.log(err)
 		return "", ErrInvalidConn
 	}
 	buf = buf[:0]
@@ -302,7 +307,7 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
 
 func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
 	if mc.closed.Load() {
-		mc.cfg.Logger.Print(ErrInvalidConn)
+		mc.log(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
 	if len(args) != 0 {
@@ -362,7 +367,7 @@ func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error)
 	handleOk := mc.clearResult()
 
 	if mc.closed.Load() {
-		mc.cfg.Logger.Print(ErrInvalidConn)
+		mc.log(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
 	if len(args) != 0 {
@@ -457,7 +462,7 @@ func (mc *mysqlConn) finish() {
 // Ping implements driver.Pinger interface
 func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
 	if mc.closed.Load() {
-		mc.cfg.Logger.Print(ErrInvalidConn)
+		mc.log(ErrInvalidConn)
 		return driver.ErrBadConn
 	}
 
@@ -666,7 +671,7 @@ func (mc *mysqlConn) ResetSession(ctx context.Context) error {
 			err = connCheck(conn)
 		}
 		if err != nil {
-			mc.cfg.Logger.Print("closing bad idle connection: ", err)
+			mc.log("closing bad idle connection: ", err)
 			return driver.ErrBadConn
 		}
 	}
diff --git a/packets.go b/packets.go
index 3d6e5308c..d727f00fe 100644
--- a/packets.go
+++ b/packets.go
@@ -34,7 +34,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
 			if cerr := mc.canceled.Value(); cerr != nil {
 				return nil, cerr
 			}
-			mc.cfg.Logger.Print(err)
+			mc.log(err)
 			mc.Close()
 			return nil, ErrInvalidConn
 		}
@@ -57,7 +57,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
 		if pktLen == 0 {
 			// there was no previous packet
 			if prevData == nil {
-				mc.cfg.Logger.Print(ErrMalformPkt)
+				mc.log(ErrMalformPkt)
 				mc.Close()
 				return nil, ErrInvalidConn
 			}
@@ -71,7 +71,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
 			if cerr := mc.canceled.Value(); cerr != nil {
 				return nil, cerr
 			}
-			mc.cfg.Logger.Print(err)
+			mc.log(err)
 			mc.Close()
 			return nil, ErrInvalidConn
 		}
@@ -134,7 +134,7 @@ func (mc *mysqlConn) writePacket(data []byte) error {
 		// Handle error
 		if err == nil { // n != len(data)
 			mc.cleanup()
-			mc.cfg.Logger.Print(ErrMalformPkt)
+			mc.log(ErrMalformPkt)
 		} else {
 			if cerr := mc.canceled.Value(); cerr != nil {
 				return cerr
@@ -144,7 +144,7 @@ func (mc *mysqlConn) writePacket(data []byte) error {
 				return errBadConnNoWrite
 			}
 			mc.cleanup()
-			mc.cfg.Logger.Print(err)
+			mc.log(err)
 		}
 		return ErrInvalidConn
 	}
@@ -302,7 +302,7 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 	data, err := mc.buf.takeBuffer(pktLen + 4)
 	if err != nil {
 		// cannot take the buffer. Something must be wrong with the connection
-		mc.cfg.Logger.Print(err)
+		mc.log(err)
 		return errBadConnNoWrite
 	}
 
@@ -392,7 +392,7 @@ func (mc *mysqlConn) writeAuthSwitchPacket(authData []byte) error {
 	data, err := mc.buf.takeSmallBuffer(pktLen)
 	if err != nil {
 		// cannot take the buffer. Something must be wrong with the connection
-		mc.cfg.Logger.Print(err)
+		mc.log(err)
 		return errBadConnNoWrite
 	}
 
@@ -412,7 +412,7 @@ func (mc *mysqlConn) writeCommandPacket(command byte) error {
 	data, err := mc.buf.takeSmallBuffer(4 + 1)
 	if err != nil {
 		// cannot take the buffer. Something must be wrong with the connection
-		mc.cfg.Logger.Print(err)
+		mc.log(err)
 		return errBadConnNoWrite
 	}
 
@@ -431,7 +431,7 @@ func (mc *mysqlConn) writeCommandPacketStr(command byte, arg string) error {
 	data, err := mc.buf.takeBuffer(pktLen + 4)
 	if err != nil {
 		// cannot take the buffer. Something must be wrong with the connection
-		mc.cfg.Logger.Print(err)
+		mc.log(err)
 		return errBadConnNoWrite
 	}
 
@@ -452,7 +452,7 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
 	data, err := mc.buf.takeSmallBuffer(4 + 1 + 4)
 	if err != nil {
 		// cannot take the buffer. Something must be wrong with the connection
-		mc.cfg.Logger.Print(err)
+		mc.log(err)
 		return errBadConnNoWrite
 	}
 
@@ -994,7 +994,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 	}
 	if err != nil {
 		// cannot take the buffer. Something must be wrong with the connection
-		mc.cfg.Logger.Print(err)
+		mc.log(err)
 		return errBadConnNoWrite
 	}
 
@@ -1193,7 +1193,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 		if valuesCap != cap(paramValues) {
 			data = append(data[:pos], paramValues...)
 			if err = mc.buf.store(data); err != nil {
-				mc.cfg.Logger.Print(err)
+				mc.log(err)
 				return errBadConnNoWrite
 			}
 		}
diff --git a/statement.go b/statement.go
index d8b921b8e..0436f2240 100644
--- a/statement.go
+++ b/statement.go
@@ -51,7 +51,7 @@ func (stmt *mysqlStmt) CheckNamedValue(nv *driver.NamedValue) (err error) {
 
 func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
 	if stmt.mc.closed.Load() {
-		stmt.mc.cfg.Logger.Print(ErrInvalidConn)
+		stmt.mc.log(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
 	// Send command
@@ -95,7 +95,7 @@ func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
 
 func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) {
 	if stmt.mc.closed.Load() {
-		stmt.mc.cfg.Logger.Print(ErrInvalidConn)
+		stmt.mc.log(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
 	// Send command

From d86c4527bae98ccd4e5060f72887520ce30eda5e Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Sun, 17 Mar 2024 13:30:21 +0900
Subject: [PATCH 245/282] fix race condition when context is canceled (#1562)

Fix #1559.
---
 connection.go | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/connection.go b/connection.go
index 5061b69ca..f3656f0e6 100644
--- a/connection.go
+++ b/connection.go
@@ -138,7 +138,7 @@ func (mc *mysqlConn) Close() (err error) {
 	}
 
 	mc.cleanup()
-
+	mc.clearResult()
 	return
 }
 
@@ -153,13 +153,16 @@ func (mc *mysqlConn) cleanup() {
 
 	// Makes cleanup idempotent
 	close(mc.closech)
-	if mc.netConn == nil {
+	nc := mc.netConn
+	if nc == nil {
 		return
 	}
-	if err := mc.netConn.Close(); err != nil {
+	if err := nc.Close(); err != nil {
 		mc.log(err)
 	}
-	mc.clearResult()
+	// This function can be called from multiple goroutines.
+	// So we can not mc.clearResult() here.
+	// Caller should do it if they are in safe goroutine.
 }
 
 func (mc *mysqlConn) error() error {

From d7ddb8b9e324830b1ede89c5fea090c824497c51 Mon Sep 17 00:00:00 2001
From: ICHINOSE Shogo <shogo82148@gmail.com>
Date: Sat, 23 Mar 2024 00:57:24 +0900
Subject: [PATCH 246/282] Fix issue 1567 (#1570)

### Description

closes https://github.com/go-sql-driver/mysql/issues/1567

When TLS is enabled, `mc.netConn` is rewritten after the TLS handshak as
detailed here:


https://github.com/go-sql-driver/mysql/blob/d86c4527bae98ccd4e5060f72887520ce30eda5e/packets.go#L355

Therefore, `mc.netConn` should not be accessed within the watcher
goroutine.
Instead, `mc.rawConn` should be initialized prior to invoking
`mc.startWatcher`, and `mc.rawConn` should be used in lieu of
`mc.netConn`.

### Checklist
- [x] Code compiles correctly
- [x] Created tests which fail without the change (if possible)
- [x] All tests passing
- [x] Extended the README / documentation, if necessary
- [x] Added myself / the copyright holder to the AUTHORS file


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Refactor**
	- Improved variable naming for better code readability and maintenance.
	- Enhanced network connection handling logic.
- **New Features**
	- Updated TCP connection handling to better support TCP Keepalives.
- **Tests**
- Added a new test to address and verify the fix for a specific issue
related to TLS, connection pooling, and round trip time estimation.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---
 connection.go  |  6 +++---
 connector.go   |  2 +-
 driver_test.go | 33 +++++++++++++++++++++++++++++++++
 packets.go     |  1 -
 4 files changed, 37 insertions(+), 5 deletions(-)

diff --git a/connection.go b/connection.go
index f3656f0e6..7b8abeb00 100644
--- a/connection.go
+++ b/connection.go
@@ -153,11 +153,11 @@ func (mc *mysqlConn) cleanup() {
 
 	// Makes cleanup idempotent
 	close(mc.closech)
-	nc := mc.netConn
-	if nc == nil {
+	conn := mc.rawConn
+	if conn == nil {
 		return
 	}
-	if err := nc.Close(); err != nil {
+	if err := conn.Close(); err != nil {
 		mc.log(err)
 	}
 	// This function can be called from multiple goroutines.
diff --git a/connector.go b/connector.go
index a0ee62839..b67077596 100644
--- a/connector.go
+++ b/connector.go
@@ -102,10 +102,10 @@ func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
 		nd := net.Dialer{Timeout: mc.cfg.Timeout}
 		mc.netConn, err = nd.DialContext(ctx, mc.cfg.Net, mc.cfg.Addr)
 	}
-
 	if err != nil {
 		return nil, err
 	}
+	mc.rawConn = mc.netConn
 
 	// Enable TCP Keepalives on TCP connections
 	if tc, ok := mc.netConn.(*net.TCPConn); ok {
diff --git a/driver_test.go b/driver_test.go
index 6b52650c2..4fd196d4b 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -20,6 +20,7 @@ import (
 	"io"
 	"log"
 	"math"
+	mrand "math/rand"
 	"net"
 	"net/url"
 	"os"
@@ -3577,3 +3578,35 @@ func runCallCommand(dbt *DBTest, query, name string) {
 		}
 	}
 }
+
+func TestIssue1567(t *testing.T) {
+	// enable TLS.
+	runTests(t, dsn+"&tls=skip-verify", func(dbt *DBTest) {
+		// disable connection pooling.
+		// data race happens when new connection is created.
+		dbt.db.SetMaxIdleConns(0)
+
+		// estimate round trip time.
+		start := time.Now()
+		if err := dbt.db.PingContext(context.Background()); err != nil {
+			t.Fatal(err)
+		}
+		rtt := time.Since(start)
+		if rtt <= 0 {
+			// In some environments, rtt may become 0, so set it to at least 1ms.
+			rtt = time.Millisecond
+		}
+
+		count := 1000
+		if testing.Short() {
+			count = 10
+		}
+
+		for i := 0; i < count; i++ {
+			timeout := time.Duration(mrand.Int63n(int64(rtt)))
+			ctx, cancel := context.WithTimeout(context.Background(), timeout)
+			dbt.db.PingContext(ctx)
+			cancel()
+		}
+	})
+}
diff --git a/packets.go b/packets.go
index d727f00fe..90a34728b 100644
--- a/packets.go
+++ b/packets.go
@@ -351,7 +351,6 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 		if err := tlsConn.Handshake(); err != nil {
 			return err
 		}
-		mc.rawConn = mc.netConn
 		mc.netConn = tlsConn
 		mc.buf.nc = tlsConn
 	}

From 8d421d9c69403dbea52832f311b6d49cff004dbd Mon Sep 17 00:00:00 2001
From: ICHINOSE Shogo <shogo82148@gmail.com>
Date: Tue, 30 Apr 2024 11:54:28 +0900
Subject: [PATCH 247/282] update changelog for releasing v1.8.1 (#1576) (#1577)

cherry pick of
https://github.com/shogo82148/mysql/commit/476df92ad2293daaba19414bd1495c1b2b6c0bad

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Bug Fixes**
	- Addressed race conditions when the context is canceled.
- **New Features**
	- Enhanced database connection with charset and collation settings.
	- Improved path escaping in database names.
	- Dropped support for Go versions 1.13-17.
	- Implemented parsing numbers over text protocol.
	- Introduced new configuration options for advanced usage.
- **Enhancements**
	- Made logger configurable per connection.
- Fixed handling of `mediumint unsigned` in
`ColumnType.DatabaseTypeName`.
	- Added connection attributes for more detailed connection information.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---
 CHANGELOG.md | 42 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 42 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 213215c8d..0c9bd9b10 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,45 @@
+## Version 1.8.1 (2024-03-26)
+
+Bugfixes:
+
+- fix race condition when context is canceled in [#1562](https://github.com/go-sql-driver/mysql/pull/1562) and [#1570](https://github.com/go-sql-driver/mysql/pull/1570)
+
+## Version 1.8.0 (2024-03-09)
+
+Major Changes:
+
+- Use `SET NAMES charset COLLATE collation`. by @methane in [#1437](https://github.com/go-sql-driver/mysql/pull/1437)
+  - Older go-mysql-driver used `collation_id` in the handshake packet. But it caused collation mismatch in some situation.
+  - If you don't specify charset nor collation, go-mysql-driver sends `SET NAMES utf8mb4` for new connection. This uses server's default collation for utf8mb4.
+  - If you specify charset, go-mysql-driver sends `SET NAMES <charset>`. This uses the server's default collation for `<charset>`.
+  - If you specify collation and/or charset, go-mysql-driver sends `SET NAMES charset COLLATE collation`.
+- PathEscape dbname in DSN. by @methane in [#1432](https://github.com/go-sql-driver/mysql/pull/1432)
+  - This is backward incompatible in rare case. Check your DSN.
+- Drop Go 1.13-17 support by @methane in [#1420](https://github.com/go-sql-driver/mysql/pull/1420)
+  - Use Go 1.18+
+- Parse numbers on text protocol too by @methane in [#1452](https://github.com/go-sql-driver/mysql/pull/1452)
+  - When text protocol is used, go-mysql-driver passed bare `[]byte` to database/sql for avoid unnecessary allocation and conversion.
+  - If user specified `*any` to `Scan()`, database/sql passed the `[]byte` into the target variable.
+  - This confused users because most user doesn't know when text/binary protocol used.
+  - go-mysql-driver 1.8 converts integer/float values into int64/double even in text protocol. This doesn't increase allocation compared to `[]byte` and conversion cost is negatable.
+- New options start using the Functional Option Pattern to avoid increasing technical debt in the Config object. Future version may introduce Functional Option for existing options, but not for now.
+  - Make TimeTruncate functional option by @methane in [1552](https://github.com/go-sql-driver/mysql/pull/1552)
+  - Add BeforeConnect callback to configuration object by @ItalyPaleAle in [#1469](https://github.com/go-sql-driver/mysql/pull/1469)
+
+
+Other changes:
+
+- Adding DeregisterDialContext to prevent memory leaks with dialers we don't need anymore by @jypelle in https://github.com/go-sql-driver/mysql/pull/1422
+- Make logger configurable per connection by @frozenbonito in https://github.com/go-sql-driver/mysql/pull/1408
+- Fix ColumnType.DatabaseTypeName for mediumint unsigned by @evanelias in https://github.com/go-sql-driver/mysql/pull/1428
+- Add connection attributes by @Daemonxiao in https://github.com/go-sql-driver/mysql/pull/1389
+- Stop `ColumnTypeScanType()` from returning `sql.RawBytes` by @methane in https://github.com/go-sql-driver/mysql/pull/1424
+- Exec() now provides access to status of multiple statements. by @mherr-google in https://github.com/go-sql-driver/mysql/pull/1309
+- Allow to change (or disable) the default driver name for registration by @dolmen in https://github.com/go-sql-driver/mysql/pull/1499
+- Add default connection attribute '_server_host' by @oblitorum in https://github.com/go-sql-driver/mysql/pull/1506
+- QueryUnescape DSN ConnectionAttribute value by @zhangyangyu in https://github.com/go-sql-driver/mysql/pull/1470
+- Add client_ed25519 authentication by @Gusted in https://github.com/go-sql-driver/mysql/pull/1518
+
 ## Version 1.7.1 (2023-04-25)
 
 Changes:

From 7939f5923ddca00fbfcaba7ab72eca484d5f9060 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Tue, 30 Apr 2024 22:26:36 +0900
Subject: [PATCH 248/282] update URL for protocol docs (#1580)

---
 packets.go | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/packets.go b/packets.go
index 90a34728b..cf3412ff6 100644
--- a/packets.go
+++ b/packets.go
@@ -21,8 +21,9 @@ import (
 	"time"
 )
 
-// Packets documentation:
-// http://dev.mysql.com/doc/internals/en/client-server-protocol.html
+// MySQL client/server protocol documentations.
+// https://dev.mysql.com/doc/dev/mysql-server/latest/PAGE_PROTOCOL.html
+// https://mariadb.com/kb/en/clientserver-protocol/
 
 // Read packet to buffer 'data'
 func (mc *mysqlConn) readPacket() ([]byte, error) {

From af8d7931954ec21a96df9610a99c09c2887f2ee7 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Tue, 30 Apr 2024 22:27:06 +0900
Subject: [PATCH 249/282] unify short name for mysqlConn in connection_test
 (#1581)

---
 connection_test.go | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/connection_test.go b/connection_test.go
index 98c985ae1..c59cb6176 100644
--- a/connection_test.go
+++ b/connection_test.go
@@ -117,8 +117,8 @@ func TestInterpolateParamsUint64(t *testing.T) {
 
 func TestCheckNamedValue(t *testing.T) {
 	value := driver.NamedValue{Value: ^uint64(0)}
-	x := &mysqlConn{}
-	err := x.CheckNamedValue(&value)
+	mc := &mysqlConn{}
+	err := mc.CheckNamedValue(&value)
 
 	if err != nil {
 		t.Fatal("uint64 high-bit not convertible", err)
@@ -159,13 +159,13 @@ func TestCleanCancel(t *testing.T) {
 
 func TestPingMarkBadConnection(t *testing.T) {
 	nc := badConnection{err: errors.New("boom")}
-	ms := &mysqlConn{
+	mc := &mysqlConn{
 		netConn:          nc,
 		buf:              newBuffer(nc),
 		maxAllowedPacket: defaultMaxAllowedPacket,
 	}
 
-	err := ms.Ping(context.Background())
+	err := mc.Ping(context.Background())
 
 	if err != driver.ErrBadConn {
 		t.Errorf("expected driver.ErrBadConn, got  %#v", err)
@@ -174,7 +174,7 @@ func TestPingMarkBadConnection(t *testing.T) {
 
 func TestPingErrInvalidConn(t *testing.T) {
 	nc := badConnection{err: errors.New("failed to write"), n: 10}
-	ms := &mysqlConn{
+	mc := &mysqlConn{
 		netConn:          nc,
 		buf:              newBuffer(nc),
 		maxAllowedPacket: defaultMaxAllowedPacket,
@@ -182,7 +182,7 @@ func TestPingErrInvalidConn(t *testing.T) {
 		cfg:              NewConfig(),
 	}
 
-	err := ms.Ping(context.Background())
+	err := mc.Ping(context.Background())
 
 	if err != ErrInvalidConn {
 		t.Errorf("expected ErrInvalidConn, got  %#v", err)

From 2f7015e5c48d361a7dd188c01ae95379c7b9f6f9 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Tue, 4 Jun 2024 19:12:35 +0900
Subject: [PATCH 250/282] log: add "filename:line" prefix by ourself (#1589)

go-sql-driver/mysql#1563 broke the filename:lineno prefix in the log
message by introducing a helper function.
This commit adds the "filename:line" prefix in the helper function
instead of log.Lshortfile option to show correct filename:lineno.
---
 connection.go | 12 ++++++++++++
 errors.go     |  2 +-
 2 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/connection.go b/connection.go
index 7b8abeb00..a3dc09d2c 100644
--- a/connection.go
+++ b/connection.go
@@ -13,8 +13,10 @@ import (
 	"database/sql"
 	"database/sql/driver"
 	"encoding/json"
+	"fmt"
 	"io"
 	"net"
+	"runtime"
 	"strconv"
 	"strings"
 	"sync/atomic"
@@ -47,6 +49,16 @@ type mysqlConn struct {
 
 // Helper function to call per-connection logger.
 func (mc *mysqlConn) log(v ...any) {
+	_, filename, lineno, ok := runtime.Caller(1)
+	if ok {
+		pos := strings.LastIndexByte(filename, '/')
+		if pos != -1 {
+			filename = filename[pos+1:]
+		}
+		prefix := fmt.Sprintf("%s:%d ", filename, lineno)
+		v = append([]any{prefix}, v...)
+	}
+
 	mc.cfg.Logger.Print(v...)
 }
 
diff --git a/errors.go b/errors.go
index a7ef88909..238e480f3 100644
--- a/errors.go
+++ b/errors.go
@@ -37,7 +37,7 @@ var (
 	errBadConnNoWrite = errors.New("bad connection")
 )
 
-var defaultLogger = Logger(log.New(os.Stderr, "[mysql] ", log.Ldate|log.Ltime|log.Lshortfile))
+var defaultLogger = Logger(log.New(os.Stderr, "[mysql] ", log.Ldate|log.Ltime))
 
 // Logger is used to log critical error messages.
 type Logger interface {

From 05325d8c2d8a3f5469086f2fd15552cc7960926c Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Tue, 11 Jun 2024 22:34:45 +0900
Subject: [PATCH 251/282] fix some write error handling (#1595)

interpolateParams() returned ErrInvalidConn without closing the connection.
Since database/sql doesn't understand ErrInvalidConn, there is a risk
that database/sql reuse this connection and ErrInvalidConn is returned
repeatedly.
---
 connection.go | 6 ++++--
 packets.go    | 2 ++
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/connection.go b/connection.go
index a3dc09d2c..bf102cdf9 100644
--- a/connection.go
+++ b/connection.go
@@ -230,8 +230,10 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
 	buf, err := mc.buf.takeCompleteBuffer()
 	if err != nil {
 		// can not take the buffer. Something must be wrong with the connection
-		mc.log(err)
-		return "", ErrInvalidConn
+		mc.cleanup()
+		// interpolateParams would be called before sending any query.
+		// So its safe to retry.
+		return "", driver.ErrBadConn
 	}
 	buf = buf[:0]
 	argPos := 0
diff --git a/packets.go b/packets.go
index cf3412ff6..033ef201e 100644
--- a/packets.go
+++ b/packets.go
@@ -117,6 +117,8 @@ func (mc *mysqlConn) writePacket(data []byte) error {
 		// Write packet
 		if mc.writeTimeout > 0 {
 			if err := mc.netConn.SetWriteDeadline(time.Now().Add(mc.writeTimeout)); err != nil {
+				mc.cleanup()
+				mc.log(err)
 				return err
 			}
 		}

From 9b8d28eff68e1b0dec9d45e9868796e7f7a9af49 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Tue, 11 Jun 2024 22:49:22 +0900
Subject: [PATCH 252/282] fix missing skip test when no DB is available (#1594)

Fix `go test` fails when no DB is set up.
---
 driver_test.go | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/driver_test.go b/driver_test.go
index 4fd196d4b..24d73c34f 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -3539,6 +3539,9 @@ func TestConnectionAttributes(t *testing.T) {
 }
 
 func TestErrorInMultiResult(t *testing.T) {
+	if !available {
+		t.Skipf("MySQL server not running on %s", netAddr)
+	}
 	// https://github.com/go-sql-driver/mysql/issues/1361
 	var db *sql.DB
 	if _, err := ParseDSN(dsn); err != errInvalidDSNUnsafeCollation {

From 87443b94dfd43b6cab62182a30c0e7d9759bc18d Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Fri, 14 Jun 2024 13:51:24 +0900
Subject: [PATCH 253/282] small code cleanup (#1598)

* Go programmers familier with `if err != nil {}` than `if err == nil {}`.
* Update some URLs about MySQL client/server protocol.
---
 connection.go | 43 +++++++++++++++++++++++--------------------
 packets.go    | 33 +++++++++++++++++----------------
 2 files changed, 40 insertions(+), 36 deletions(-)

diff --git a/connection.go b/connection.go
index bf102cdf9..462e7d134 100644
--- a/connection.go
+++ b/connection.go
@@ -400,31 +400,34 @@ func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error)
 	}
 	// Send command
 	err := mc.writeCommandPacketStr(comQuery, query)
-	if err == nil {
-		// Read Result
-		var resLen int
-		resLen, err = handleOk.readResultSetHeaderPacket()
-		if err == nil {
-			rows := new(textRows)
-			rows.mc = mc
+	if err != nil {
+		return nil, mc.markBadConn(err)
+	}
 
-			if resLen == 0 {
-				rows.rs.done = true
+	// Read Result
+	var resLen int
+	resLen, err = handleOk.readResultSetHeaderPacket()
+	if err != nil {
+		return nil, mc.markBadConn(err)
+	}
 
-				switch err := rows.NextResultSet(); err {
-				case nil, io.EOF:
-					return rows, nil
-				default:
-					return nil, err
-				}
-			}
+	rows := new(textRows)
+	rows.mc = mc
 
-			// Columns
-			rows.rs.columns, err = mc.readColumns(resLen)
-			return rows, err
+	if resLen == 0 {
+		rows.rs.done = true
+
+		switch err := rows.NextResultSet(); err {
+		case nil, io.EOF:
+			return rows, nil
+		default:
+			return nil, err
 		}
 	}
-	return nil, mc.markBadConn(err)
+
+	// Columns
+	rows.rs.columns, err = mc.readColumns(resLen)
+	return rows, err
 }
 
 // Gets the value of the given MySQL System Variable
diff --git a/packets.go b/packets.go
index 033ef201e..b90b14c5c 100644
--- a/packets.go
+++ b/packets.go
@@ -524,32 +524,33 @@ func (mc *okHandler) readResultOK() error {
 }
 
 // Result Set Header Packet
-// http://dev.mysql.com/doc/internals/en/com-query-response.html#packet-ProtocolText::Resultset
+// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_query_response.html
 func (mc *okHandler) readResultSetHeaderPacket() (int, error) {
 	// handleOkPacket replaces both values; other cases leave the values unchanged.
 	mc.result.affectedRows = append(mc.result.affectedRows, 0)
 	mc.result.insertIds = append(mc.result.insertIds, 0)
 
 	data, err := mc.conn().readPacket()
-	if err == nil {
-		switch data[0] {
-
-		case iOK:
-			return 0, mc.handleOkPacket(data)
+	if err != nil {
+		return 0, err
+	}
 
-		case iERR:
-			return 0, mc.conn().handleErrorPacket(data)
+	switch data[0] {
+	case iOK:
+		return 0, mc.handleOkPacket(data)
 
-		case iLocalInFile:
-			return 0, mc.handleInFileRequest(string(data[1:]))
-		}
+	case iERR:
+		return 0, mc.conn().handleErrorPacket(data)
 
-		// column count
-		num, _, _ := readLengthEncodedInteger(data)
-		// ignore remaining data in the packet. see #1478.
-		return int(num), nil
+	case iLocalInFile:
+		return 0, mc.handleInFileRequest(string(data[1:]))
 	}
-	return 0, err
+
+	// column count
+	// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_query_response_text_resultset.html
+	num, _, _ := readLengthEncodedInteger(data)
+	// ignore remaining data in the packet. see #1478.
+	return int(num), nil
 }
 
 // Error Packet

From 2f69712cd480487ecb7e513b2fe1e0e7fe138767 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Sun, 16 Jun 2024 10:18:42 +0900
Subject: [PATCH 254/282] fix unnecesssary allocation in infile.go (#1600)

---
 infile.go | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/infile.go b/infile.go
index 0c8af9f11..cf892beae 100644
--- a/infile.go
+++ b/infile.go
@@ -95,7 +95,6 @@ const defaultPacketSize = 16 * 1024 // 16KB is small enough for disk readahead a
 
 func (mc *okHandler) handleInFileRequest(name string) (err error) {
 	var rdr io.Reader
-	var data []byte
 	packetSize := defaultPacketSize
 	if mc.maxWriteSize < packetSize {
 		packetSize = mc.maxWriteSize
@@ -147,9 +146,11 @@ func (mc *okHandler) handleInFileRequest(name string) (err error) {
 	}
 
 	// send content packets
+	var data []byte
+
 	// if packetSize == 0, the Reader contains no data
 	if err == nil && packetSize > 0 {
-		data := make([]byte, 4+packetSize)
+		data = make([]byte, 4+packetSize)
 		var n int
 		for err == nil {
 			n, err = rdr.Read(data[4:])

From 52c1917d99904701db2b0e4f14baffa948009cd7 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Sun, 16 Jun 2024 10:20:06 +0900
Subject: [PATCH 255/282] remove unnecessary logs (#1599)

Logging ErrInvalidConn when the connection already closed doesn't
provide any help to users.

Additonally, database/sql now uses Validator() to check connection
liveness before calling query methods.
So stop using `mc.log(ErrInvalidConn)` idiom.

This PR includes some cleanup and documentation relating to
`mc.markBadConn()`.
---
 connection.go | 21 +++++++++------------
 errors.go     |  2 +-
 statement.go  |  2 --
 3 files changed, 10 insertions(+), 15 deletions(-)

diff --git a/connection.go b/connection.go
index 462e7d134..2b19c9272 100644
--- a/connection.go
+++ b/connection.go
@@ -111,14 +111,13 @@ func (mc *mysqlConn) handleParams() (err error) {
 	return
 }
 
+// markBadConn replaces errBadConnNoWrite with driver.ErrBadConn.
+// This function is used to return driver.ErrBadConn only when safe to retry.
 func (mc *mysqlConn) markBadConn(err error) error {
-	if mc == nil {
-		return err
-	}
-	if err != errBadConnNoWrite {
-		return err
+	if err == errBadConnNoWrite {
+		return driver.ErrBadConn
 	}
-	return driver.ErrBadConn
+	return err
 }
 
 func (mc *mysqlConn) Begin() (driver.Tx, error) {
@@ -127,7 +126,6 @@ func (mc *mysqlConn) Begin() (driver.Tx, error) {
 
 func (mc *mysqlConn) begin(readOnly bool) (driver.Tx, error) {
 	if mc.closed.Load() {
-		mc.log(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
 	var q string
@@ -189,7 +187,6 @@ func (mc *mysqlConn) error() error {
 
 func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
 	if mc.closed.Load() {
-		mc.log(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
 	// Send command
@@ -324,7 +321,6 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
 
 func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
 	if mc.closed.Load() {
-		mc.log(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
 	if len(args) != 0 {
@@ -384,7 +380,6 @@ func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error)
 	handleOk := mc.clearResult()
 
 	if mc.closed.Load() {
-		mc.log(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
 	if len(args) != 0 {
@@ -408,7 +403,7 @@ func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error)
 	var resLen int
 	resLen, err = handleOk.readResultSetHeaderPacket()
 	if err != nil {
-		return nil, mc.markBadConn(err)
+		return nil, err
 	}
 
 	rows := new(textRows)
@@ -482,7 +477,6 @@ func (mc *mysqlConn) finish() {
 // Ping implements driver.Pinger interface
 func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
 	if mc.closed.Load() {
-		mc.log(ErrInvalidConn)
 		return driver.ErrBadConn
 	}
 
@@ -704,3 +698,6 @@ func (mc *mysqlConn) ResetSession(ctx context.Context) error {
 func (mc *mysqlConn) IsValid() bool {
 	return !mc.closed.Load()
 }
+
+var _ driver.SessionResetter = &mysqlConn{}
+var _ driver.Validator = &mysqlConn{}
diff --git a/errors.go b/errors.go
index 238e480f3..584617b11 100644
--- a/errors.go
+++ b/errors.go
@@ -32,7 +32,7 @@ var (
 
 	// errBadConnNoWrite is used for connection errors where nothing was sent to the database yet.
 	// If this happens first in a function starting a database interaction, it should be replaced by driver.ErrBadConn
-	// to trigger a resend.
+	// to trigger a resend. Use mc.markBadConn(err) to do this.
 	// See https://github.com/go-sql-driver/mysql/pull/302
 	errBadConnNoWrite = errors.New("bad connection")
 )
diff --git a/statement.go b/statement.go
index 0436f2240..35b02bbeb 100644
--- a/statement.go
+++ b/statement.go
@@ -51,7 +51,6 @@ func (stmt *mysqlStmt) CheckNamedValue(nv *driver.NamedValue) (err error) {
 
 func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
 	if stmt.mc.closed.Load() {
-		stmt.mc.log(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
 	// Send command
@@ -95,7 +94,6 @@ func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
 
 func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) {
 	if stmt.mc.closed.Load() {
-		stmt.mc.log(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
 	// Send command

From 3484db1f68a7b493faffc08c1897360fdd7a67f9 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Sat, 29 Jun 2024 08:36:17 +0900
Subject: [PATCH 256/282] improve error handling in writePacket (#1601)

* handle error before success case.
* return io.ErrShortWrite if not all bytes were written but err is nil.
* return err instead of ErrInvalidConn.
---
 connection_test.go |  6 ++++--
 packets.go         | 34 +++++++++++++++++-----------------
 2 files changed, 21 insertions(+), 19 deletions(-)

diff --git a/connection_test.go b/connection_test.go
index c59cb6176..6f8d2a6d7 100644
--- a/connection_test.go
+++ b/connection_test.go
@@ -163,6 +163,8 @@ func TestPingMarkBadConnection(t *testing.T) {
 		netConn:          nc,
 		buf:              newBuffer(nc),
 		maxAllowedPacket: defaultMaxAllowedPacket,
+		closech:          make(chan struct{}),
+		cfg:              NewConfig(),
 	}
 
 	err := mc.Ping(context.Background())
@@ -184,8 +186,8 @@ func TestPingErrInvalidConn(t *testing.T) {
 
 	err := mc.Ping(context.Background())
 
-	if err != ErrInvalidConn {
-		t.Errorf("expected ErrInvalidConn, got  %#v", err)
+	if err != nc.err {
+		t.Errorf("expected %#v, got  %#v", nc.err, err)
 	}
 }
 
diff --git a/packets.go b/packets.go
index b90b14c5c..df850fd41 100644
--- a/packets.go
+++ b/packets.go
@@ -124,32 +124,32 @@ func (mc *mysqlConn) writePacket(data []byte) error {
 		}
 
 		n, err := mc.netConn.Write(data[:4+size])
-		if err == nil && n == 4+size {
-			mc.sequence++
-			if size != maxPacketSize {
-				return nil
-			}
-			pktLen -= size
-			data = data[size:]
-			continue
-		}
-
-		// Handle error
-		if err == nil { // n != len(data)
-			mc.cleanup()
-			mc.log(ErrMalformPkt)
-		} else {
+		if err != nil {
 			if cerr := mc.canceled.Value(); cerr != nil {
 				return cerr
 			}
+			mc.cleanup()
 			if n == 0 && pktLen == len(data)-4 {
 				// only for the first loop iteration when nothing was written yet
+				mc.log(err)
 				return errBadConnNoWrite
+			} else {
+				return err
 			}
+		}
+		if n != 4+size {
+			// io.Writer(b) must return a non-nil error if it cannot write len(b) bytes.
+			// The io.ErrShortWrite error is used to indicate that this rule has not been followed.
 			mc.cleanup()
-			mc.log(err)
+			return io.ErrShortWrite
+		}
+
+		mc.sequence++
+		if size != maxPacketSize {
+			return nil
 		}
-		return ErrInvalidConn
+		pktLen -= size
+		data = data[size:]
 	}
 }
 

From 9c20169374dba4e362a065b8d7183864ee076212 Mon Sep 17 00:00:00 2001
From: Dirkjan Bussink <d.bussink@gmail.com>
Date: Fri, 19 Jul 2024 06:02:06 +0200
Subject: [PATCH 257/282] Add support for new VECTOR type (#1609)

MySQL 9.0.0 added support for the VECTOR type. This adds basic support
so it can be handled at the protocol level.

See also
https://dev.mysql.com/doc/dev/mysql-server/latest/field__types_8h.html
---
 AUTHORS    | 1 +
 const.go   | 5 ++++-
 fields.go  | 4 +++-
 packets.go | 3 ++-
 4 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 4021b96cc..bab66a3b2 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -33,6 +33,7 @@ Daniel Montoya <dsmontoyam at gmail.com>
 Daniel Nichter <nil at codenode.com>
 Daniël van Eeden <git at myname.nl>
 Dave Protasowski <dprotaso at gmail.com>
+Dirkjan Bussink <d.bussink at gmail.com>
 DisposaBoy <disposaboy at dby.me>
 Egor Smolyakov <egorsmkv at gmail.com>
 Erwan Martin <hello at erwan.io>
diff --git a/const.go b/const.go
index 22526e031..0cee9b2ee 100644
--- a/const.go
+++ b/const.go
@@ -125,7 +125,10 @@ const (
 	fieldTypeBit
 )
 const (
-	fieldTypeJSON fieldType = iota + 0xf5
+	fieldTypeVector fieldType = iota + 0xf2
+	fieldTypeInvalid
+	fieldTypeBool
+	fieldTypeJSON
 	fieldTypeNewDecimal
 	fieldTypeEnum
 	fieldTypeSet
diff --git a/fields.go b/fields.go
index 286084247..be5cd809a 100644
--- a/fields.go
+++ b/fields.go
@@ -112,6 +112,8 @@ func (mf *mysqlField) typeDatabaseName() string {
 		return "VARCHAR"
 	case fieldTypeYear:
 		return "YEAR"
+	case fieldTypeVector:
+		return "VECTOR"
 	default:
 		return ""
 	}
@@ -198,7 +200,7 @@ func (mf *mysqlField) scanType() reflect.Type {
 		return scanTypeNullFloat
 
 	case fieldTypeBit, fieldTypeTinyBLOB, fieldTypeMediumBLOB, fieldTypeLongBLOB,
-		fieldTypeBLOB, fieldTypeVarString, fieldTypeString, fieldTypeGeometry:
+		fieldTypeBLOB, fieldTypeVarString, fieldTypeString, fieldTypeGeometry, fieldTypeVector:
 		if mf.charSet == binaryCollationID {
 			return scanTypeBytes
 		}
diff --git a/packets.go b/packets.go
index df850fd41..ccdd532b3 100644
--- a/packets.go
+++ b/packets.go
@@ -1329,7 +1329,8 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
 		case fieldTypeDecimal, fieldTypeNewDecimal, fieldTypeVarChar,
 			fieldTypeBit, fieldTypeEnum, fieldTypeSet, fieldTypeTinyBLOB,
 			fieldTypeMediumBLOB, fieldTypeLongBLOB, fieldTypeBLOB,
-			fieldTypeVarString, fieldTypeString, fieldTypeGeometry, fieldTypeJSON:
+			fieldTypeVarString, fieldTypeString, fieldTypeGeometry, fieldTypeJSON,
+			fieldTypeVector:
 			var isNull bool
 			var n int
 			dest[i], isNull, n, err = readLengthEncodedString(data[pos:])

From f6a18cf1ac3e6bc282f72874a3742469a99e5762 Mon Sep 17 00:00:00 2001
From: ICHINOSE Shogo <shogo82148@gmail.com>
Date: Fri, 19 Jul 2024 13:20:36 +0900
Subject: [PATCH 258/282] MySQL 9.0 and MariaDB 11.4 are released (#1610)

---
 .github/workflows/test.yml | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index c5b2aa313..df37eab59 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -37,14 +37,16 @@ jobs:
               '1.20',
           ]
           mysql = [
+              '9.0',
+              '8.4', # LTS
               '8.0',
-              '8.3',
               '5.7',
-              'mariadb-11.3',
+              'mariadb-11.4',   # LTS
+              'mariadb-11.2',
               'mariadb-11.1',
               'mariadb-10.11',  # LTS
               'mariadb-10.6',   # LTS
-              'mariadb-10.5',
+              'mariadb-10.5',   # LTS
           ]
 
           includes = []

From 44553d64bcde78a5b58cb133a5cc708281c333e0 Mon Sep 17 00:00:00 2001
From: Chris Kirkland <chriskirkland@github.com>
Date: Tue, 23 Jul 2024 21:45:26 -0500
Subject: [PATCH 259/282] doc: clarify connection close behavior of context
 (#1606)

Updates the README to make it clear that `go-sql-driver/mysql` closes
the current connection if the `context.Context` provided to
`ExecContext`, `SelectContext`, etc. is cancelled or times out prior to
the query returning.
---
 README.md | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/README.md b/README.md
index 6c6abf9c4..c83f4f74f 100644
--- a/README.md
+++ b/README.md
@@ -519,6 +519,9 @@ This driver supports the [`ColumnType` interface](https://golang.org/pkg/databas
 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.
 
+> [!IMPORTANT]
+> The `QueryContext`, `ExecContext`, etc. variants provided by `database/sql` will cause the connection to be closed if the provided context is cancelled or timed out before the result is received by the driver.
+
 
 ### `LOAD DATA LOCAL INFILE` support
 For this feature you need direct access to the package. Therefore you must change the import path (no `_`):

From c7276ee51ed3f9eeb720ab003e24f80303a7ce08 Mon Sep 17 00:00:00 2001
From: Nao Yokotsuka <32049413+yokonao@users.noreply.github.com>
Date: Sun, 4 Aug 2024 16:52:29 +0900
Subject: [PATCH 260/282] Check mysqlConnector.canceled.Value when failed to
 TLS handshake (#1615)

### Description
Check if the context is canceled when failed to TLS handshake.

fix: #1614

### Checklist
- [x] Code compiles correctly
- [x] Created tests which fail without the change (if possible)
- [x] All tests passing
- [x] Extended the README / documentation, if necessary
- [x] Added myself / the copyright holder to the AUTHORS file


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Added Nao Yokotsuka to the contributors list for improved project
documentation.

- **Bug Fixes**
- Enhanced error handling in the TLS handshake process to better manage
cancellation requests, improving connection responsiveness.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---
 AUTHORS    | 1 +
 packets.go | 3 +++
 2 files changed, 4 insertions(+)

diff --git a/AUTHORS b/AUTHORS
index bab66a3b2..287176fb4 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -81,6 +81,7 @@ Lunny Xiao <xiaolunwen at gmail.com>
 Luke Scott <luke at webconnex.com>
 Maciej Zimnoch <maciej.zimnoch at codilime.com>
 Michael Woolnough <michael.woolnough at gmail.com>
+Nao Yokotsuka <yokotukanao at gmail.com>
 Nathanial Murphy <nathanial.murphy at gmail.com>
 Nicola Peduzzi <thenikso at gmail.com>
 Oliver Bone <owbone at github.com>
diff --git a/packets.go b/packets.go
index ccdd532b3..5ca6491a8 100644
--- a/packets.go
+++ b/packets.go
@@ -352,6 +352,9 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 		// Switch to TLS
 		tlsConn := tls.Client(mc.netConn, mc.cfg.TLS)
 		if err := tlsConn.Handshake(); err != nil {
+			if cerr := mc.canceled.Value(); cerr != nil {
+				return cerr
+			}
 			return err
 		}
 		mc.netConn = tlsConn

From 2f1527670cb7207fd213f92c7120f9387fe256cf Mon Sep 17 00:00:00 2001
From: pengbanban <pengbanban@aliyun.com>
Date: Mon, 5 Aug 2024 14:31:35 +0900
Subject: [PATCH 261/282] chore: fix comment (#1620)

---
 utils_test.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/utils_test.go b/utils_test.go
index 80aebddff..42a88393c 100644
--- a/utils_test.go
+++ b/utils_test.go
@@ -339,7 +339,7 @@ func TestAppendDateTime(t *testing.T) {
 		buf, err := appendDateTime(buf, v.t, v.timeTruncate)
 		if err != nil {
 			if !v.expectedErr {
-				t.Errorf("appendDateTime(%v) returned an errror: %v", v.t, err)
+				t.Errorf("appendDateTime(%v) returned an error: %v", v.t, err)
 			}
 			continue
 		}

From 00dc21a6243c02c1a84fc82d08a821c08fde4053 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Fri, 30 Aug 2024 14:38:05 +0900
Subject: [PATCH 262/282] allow unknown collation name (#1604)

Fix #1603
---
 collations.go |  2 +-
 connection.go | 43 +++++++++----------------------------------
 connector.go  | 19 +++++++++++++++++++
 dsn.go        | 11 ++++++++++-
 dsn_test.go   |  6 +++---
 packets.go    | 20 +++++++++-----------
 6 files changed, 51 insertions(+), 50 deletions(-)

diff --git a/collations.go b/collations.go
index 1cdf97b67..29b1aa43f 100644
--- a/collations.go
+++ b/collations.go
@@ -8,7 +8,7 @@
 
 package mysql
 
-const defaultCollation = "utf8mb4_general_ci"
+const defaultCollationID = 45 // utf8mb4_general_ci
 const binaryCollationID = 63
 
 // A list of available collations mapped to the internal ID.
diff --git a/connection.go b/connection.go
index 2b19c9272..ef6fc9e40 100644
--- a/connection.go
+++ b/connection.go
@@ -67,45 +67,20 @@ func (mc *mysqlConn) handleParams() (err error) {
 	var cmdSet strings.Builder
 
 	for param, val := range mc.cfg.Params {
-		switch param {
-		// Charset: character_set_connection, character_set_client, character_set_results
-		case "charset":
-			charsets := strings.Split(val, ",")
-			for _, cs := range charsets {
-				// ignore errors here - a charset may not exist
-				if mc.cfg.Collation != "" {
-					err = mc.exec("SET NAMES " + cs + " COLLATE " + mc.cfg.Collation)
-				} else {
-					err = mc.exec("SET NAMES " + cs)
-				}
-				if err == nil {
-					break
-				}
-			}
-			if err != nil {
-				return
-			}
-
-		// Other system vars accumulated in a single SET command
-		default:
-			if cmdSet.Len() == 0 {
-				// Heuristic: 29 chars for each other key=value to reduce reallocations
-				cmdSet.Grow(4 + len(param) + 3 + len(val) + 30*(len(mc.cfg.Params)-1))
-				cmdSet.WriteString("SET ")
-			} else {
-				cmdSet.WriteString(", ")
-			}
-			cmdSet.WriteString(param)
-			cmdSet.WriteString(" = ")
-			cmdSet.WriteString(val)
+		if cmdSet.Len() == 0 {
+			// Heuristic: 29 chars for each other key=value to reduce reallocations
+			cmdSet.Grow(4 + len(param) + 3 + len(val) + 30*(len(mc.cfg.Params)-1))
+			cmdSet.WriteString("SET ")
+		} else {
+			cmdSet.WriteString(", ")
 		}
+		cmdSet.WriteString(param)
+		cmdSet.WriteString(" = ")
+		cmdSet.WriteString(val)
 	}
 
 	if cmdSet.Len() > 0 {
 		err = mc.exec(cmdSet.String())
-		if err != nil {
-			return
-		}
 	}
 
 	return
diff --git a/connector.go b/connector.go
index b67077596..62012dba3 100644
--- a/connector.go
+++ b/connector.go
@@ -180,6 +180,25 @@ func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
 		mc.maxWriteSize = mc.maxAllowedPacket
 	}
 
+	// Charset: character_set_connection, character_set_client, character_set_results
+	if len(mc.cfg.charsets) > 0 {
+		for _, cs := range mc.cfg.charsets {
+			// ignore errors here - a charset may not exist
+			if mc.cfg.Collation != "" {
+				err = mc.exec("SET NAMES " + cs + " COLLATE " + mc.cfg.Collation)
+			} else {
+				err = mc.exec("SET NAMES " + cs)
+			}
+			if err == nil {
+				break
+			}
+		}
+		if err != nil {
+			mc.Close()
+			return nil, err
+		}
+	}
+
 	// Handle DSN Params
 	err = mc.handleParams()
 	if err != nil {
diff --git a/dsn.go b/dsn.go
index 65f5a0242..3c7a6e215 100644
--- a/dsn.go
+++ b/dsn.go
@@ -44,7 +44,8 @@ type Config struct {
 	DBName               string            // Database name
 	Params               map[string]string // Connection parameters
 	ConnectionAttributes string            // Connection Attributes, comma-delimited string of user-defined "key:value" pairs
-	Collation            string            // Connection collation
+	charsets             []string          // Connection charset. When set, this will be set in SET NAMES <charset> query
+	Collation            string            // Connection collation. When set, this will be set in SET NAMES <charset> COLLATE <collation> query
 	Loc                  *time.Location    // Location for time.Time values
 	MaxAllowedPacket     int               // Max packet size allowed
 	ServerPubKey         string            // Server public key name
@@ -282,6 +283,10 @@ func (cfg *Config) FormatDSN() string {
 		writeDSNParam(&buf, &hasParam, "clientFoundRows", "true")
 	}
 
+	if charsets := cfg.charsets; len(charsets) > 0 {
+		writeDSNParam(&buf, &hasParam, "charset", strings.Join(charsets, ","))
+	}
+
 	if col := cfg.Collation; col != "" {
 		writeDSNParam(&buf, &hasParam, "collation", col)
 	}
@@ -501,6 +506,10 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 				return errors.New("invalid bool value: " + value)
 			}
 
+		// charset
+		case "charset":
+			cfg.charsets = strings.Split(value, ",")
+
 		// Collation
 		case "collation":
 			cfg.Collation = value
diff --git a/dsn_test.go b/dsn_test.go
index dd8cd935c..863d14824 100644
--- a/dsn_test.go
+++ b/dsn_test.go
@@ -31,13 +31,13 @@ var testDSNs = []struct {
 	&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, ColumnsWithAlias: true, MultiStatements: true},
 }, {
 	"user@unix(/path/to/socket)/dbname?charset=utf8",
-	&Config{User: "user", Net: "unix", Addr: "/path/to/socket", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
+	&Config{User: "user", Net: "unix", Addr: "/path/to/socket", DBName: "dbname", charsets: []string{"utf8"}, Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true},
 }, {
 	"user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true",
-	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, TLSConfig: "true"},
+	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", charsets: []string{"utf8"}, Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, TLSConfig: "true"},
 }, {
 	"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify",
-	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8mb4,utf8"}, Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, TLSConfig: "skip-verify"},
+	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", charsets: []string{"utf8mb4", "utf8"}, Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, TLSConfig: "skip-verify"},
 }, {
 	"user:password@/dbname?loc=UTC&timeout=30s&readTimeout=1s&writeTimeout=1s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci&maxAllowedPacket=16777216&tls=false&allowCleartextPasswords=true&parseTime=true&rejectReadOnly=true",
 	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_unicode_ci", Loc: time.UTC, TLSConfig: "false", AllowCleartextPasswords: true, AllowNativePasswords: true, Timeout: 30 * time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, Logger: defaultLogger, AllowAllFiles: true, AllowOldPasswords: true, CheckConnLiveness: true, ClientFoundRows: true, MaxAllowedPacket: 16777216, ParseTime: true, RejectReadOnly: true},
diff --git a/packets.go b/packets.go
index 5ca6491a8..014a1deee 100644
--- a/packets.go
+++ b/packets.go
@@ -322,17 +322,15 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 	data[11] = 0x00
 
 	// Collation ID [1 byte]
-	cname := mc.cfg.Collation
-	if cname == "" {
-		cname = defaultCollation
-	}
-	var found bool
-	data[12], found = collations[cname]
-	if !found {
-		// Note possibility for false negatives:
-		// could be triggered  although the collation is valid if the
-		// collations map does not contain entries the server supports.
-		return fmt.Errorf("unknown collation: %q", cname)
+	data[12] = defaultCollationID
+	if cname := mc.cfg.Collation; cname != "" {
+		colID, ok := collations[cname]
+		if ok {
+			data[12] = colID
+		} else if len(mc.cfg.charsets) > 0 {
+			// When cfg.charset is set, the collation is set by `SET NAMES <charset> COLLATE <collation>`.
+			return fmt.Errorf("unknown collation: %q", cname)
+		}
 	}
 
 	// Filler [23 bytes] (all 0x00)

From 91ad4fb77b05cf5b4a413d2b4b67aa7dee6e9f60 Mon Sep 17 00:00:00 2001
From: Aaron Jheng <wentworth@outlook.com>
Date: Sun, 10 Nov 2024 12:10:43 +0800
Subject: [PATCH 263/282] Specify a custom dial function per config (#1527)

Specify a custom dial function per config instead of using
RegisterDialContext.
---
 connector.go | 31 ++++++++++++++++++-------------
 dsn.go       |  2 ++
 2 files changed, 20 insertions(+), 13 deletions(-)

diff --git a/connector.go b/connector.go
index 62012dba3..769b3adc9 100644
--- a/connector.go
+++ b/connector.go
@@ -87,20 +87,25 @@ func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
 	mc.parseTime = mc.cfg.ParseTime
 
 	// Connect to Server
-	dialsLock.RLock()
-	dial, ok := dials[mc.cfg.Net]
-	dialsLock.RUnlock()
-	if ok {
-		dctx := ctx
-		if mc.cfg.Timeout > 0 {
-			var cancel context.CancelFunc
-			dctx, cancel = context.WithTimeout(ctx, c.cfg.Timeout)
-			defer cancel()
-		}
-		mc.netConn, err = dial(dctx, mc.cfg.Addr)
+	dctx := ctx
+	if mc.cfg.Timeout > 0 {
+		var cancel context.CancelFunc
+		dctx, cancel = context.WithTimeout(ctx, c.cfg.Timeout)
+		defer cancel()
+	}
+
+	if c.cfg.DialFunc != nil {
+		mc.netConn, err = c.cfg.DialFunc(dctx, mc.cfg.Net, mc.cfg.Addr)
 	} else {
-		nd := net.Dialer{Timeout: mc.cfg.Timeout}
-		mc.netConn, err = nd.DialContext(ctx, mc.cfg.Net, mc.cfg.Addr)
+		dialsLock.RLock()
+		dial, ok := dials[mc.cfg.Net]
+		dialsLock.RUnlock()
+		if ok {
+			mc.netConn, err = dial(dctx, mc.cfg.Addr)
+		} else {
+			nd := net.Dialer{}
+			mc.netConn, err = nd.DialContext(dctx, mc.cfg.Net, mc.cfg.Addr)
+		}
 	}
 	if err != nil {
 		return nil, err
diff --git a/dsn.go b/dsn.go
index 3c7a6e215..f391a8fc9 100644
--- a/dsn.go
+++ b/dsn.go
@@ -55,6 +55,8 @@ type Config struct {
 	ReadTimeout          time.Duration     // I/O read timeout
 	WriteTimeout         time.Duration     // I/O write timeout
 	Logger               Logger            // Logger
+	// DialFunc specifies the dial function for creating connections
+	DialFunc func(ctx context.Context, network, addr string) (net.Conn, error)
 
 	// boolean fields
 

From fc64d3f08fb84395f911a6a23a266db92ac8a7e1 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Mon, 11 Nov 2024 11:14:04 +0900
Subject: [PATCH 264/282] ci: update Go and staticcheck versions (#1639)

- Add Go 1.23 support
- Remove Go 1.20 support
- Update staticcheck action
---
 .github/workflows/test.yml | 8 +++-----
 README.md                  | 2 +-
 go.mod                     | 2 +-
 3 files changed, 5 insertions(+), 7 deletions(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index df37eab59..b1c1f2b34 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -15,9 +15,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v4
-      - uses: dominikh/staticcheck-action@v1.3.0
-        with:
-          version: "2023.1.6"
+      - uses: dominikh/staticcheck-action@v1.3.1
 
   list:
     runs-on: ubuntu-latest
@@ -31,10 +29,10 @@ jobs:
           import os
           go = [
               # Keep the most recent production release at the top
-              '1.22',
+              '1.23',
               # Older production releases
+              '1.22',
               '1.21',
-              '1.20',
           ]
           mysql = [
               '9.0',
diff --git a/README.md b/README.md
index c83f4f74f..e9d9222ba 100644
--- a/README.md
+++ b/README.md
@@ -41,7 +41,7 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
 
 ## Requirements
 
-* Go 1.20 or higher. We aim to support the 3 latest versions of Go.
+* Go 1.21 or higher. We aim to support the 3 latest versions of Go.
 * MySQL (5.7+) and MariaDB (10.5+) are supported.
 * [TiDB](https://github.com/pingcap/tidb) is supported by PingCAP.
   * Do not ask questions about TiDB in our issue tracker or forum.
diff --git a/go.mod b/go.mod
index 2eed53ebb..33c4dd5b1 100644
--- a/go.mod
+++ b/go.mod
@@ -1,5 +1,5 @@
 module github.com/go-sql-driver/mysql
 
-go 1.20
+go 1.21
 
 require filippo.io/edwards25519 v1.1.0

From f62f523d2458d82587f03e9357396a9c8a93fcba Mon Sep 17 00:00:00 2001
From: KratkyZobak <kratky@zobak.cz>
Date: Mon, 11 Nov 2024 03:14:49 +0100
Subject: [PATCH 265/282] Fix auth errors when username/password are too long
 (#1482) (#1625)

---
 AUTHORS    | 1 +
 packets.go | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/AUTHORS b/AUTHORS
index 287176fb4..a98608504 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -51,6 +51,7 @@ ICHINOSE Shogo <shogo82148 at gmail.com>
 Ilia Cimpoes <ichimpoesh at gmail.com>
 INADA Naoki <songofacandy at gmail.com>
 Jacek Szwec <szwec.jacek at gmail.com>
+Jakub Adamus <kratky at zobak.cz>
 James Harr <james.harr at gmail.com>
 Janek Vedock <janekvedock at comcast.net>
 Jason Ng <oblitorum at gmail.com>
diff --git a/packets.go b/packets.go
index 014a1deee..eb4e0cefe 100644
--- a/packets.go
+++ b/packets.go
@@ -392,7 +392,7 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
 func (mc *mysqlConn) writeAuthSwitchPacket(authData []byte) error {
 	pktLen := 4 + len(authData)
-	data, err := mc.buf.takeSmallBuffer(pktLen)
+	data, err := mc.buf.takeBuffer(pktLen)
 	if err != nil {
 		// cannot take the buffer. Something must be wrong with the connection
 		mc.log(err)

From 41a5fa29f2f73060c426547f762dc49b62e1f2a5 Mon Sep 17 00:00:00 2001
From: raffertyyu <raffertyyu@tencent.com>
Date: Tue, 19 Nov 2024 12:09:49 +0800
Subject: [PATCH 266/282] Check if MySQL supports CLIENT_CONNECT_ATTRS before
 sending client attributes. (#1640)

---
 packets.go | 26 ++++++++++++++++++--------
 1 file changed, 18 insertions(+), 8 deletions(-)

diff --git a/packets.go b/packets.go
index eb4e0cefe..a2e7ef95c 100644
--- a/packets.go
+++ b/packets.go
@@ -210,10 +210,13 @@ func (mc *mysqlConn) readHandshakePacket() (data []byte, plugin string, err erro
 	if len(data) > pos {
 		// character set [1 byte]
 		// status flags [2 bytes]
+		pos += 3
 		// capability flags (upper 2 bytes) [2 bytes]
+		mc.flags |= clientFlag(binary.LittleEndian.Uint16(data[pos:pos+2])) << 16
+		pos += 2
 		// length of auth-plugin-data [1 byte]
 		// reserved (all [00]) [10 bytes]
-		pos += 1 + 2 + 2 + 1 + 10
+		pos += 11
 
 		// second part of the password cipher [minimum 13 bytes],
 		// where len=MAX(13, length of auth-plugin-data - 8)
@@ -261,9 +264,11 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 		clientLocalFiles |
 		clientPluginAuth |
 		clientMultiResults |
-		clientConnectAttrs |
+		mc.flags&clientConnectAttrs |
 		mc.flags&clientLongFlag
 
+	sendConnectAttrs := mc.flags&clientConnectAttrs != 0
+
 	if mc.cfg.ClientFoundRows {
 		clientFlags |= clientFoundRows
 	}
@@ -296,10 +301,13 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 	}
 
 	// encode length of the connection attributes
-	var connAttrsLEIBuf [9]byte
-	connAttrsLen := len(mc.connector.encodedAttributes)
-	connAttrsLEI := appendLengthEncodedInteger(connAttrsLEIBuf[:0], uint64(connAttrsLen))
-	pktLen += len(connAttrsLEI) + len(mc.connector.encodedAttributes)
+	var connAttrsLEI []byte
+	if sendConnectAttrs {
+		var connAttrsLEIBuf [9]byte
+		connAttrsLen := len(mc.connector.encodedAttributes)
+		connAttrsLEI = appendLengthEncodedInteger(connAttrsLEIBuf[:0], uint64(connAttrsLen))
+		pktLen += len(connAttrsLEI) + len(mc.connector.encodedAttributes)
+	}
 
 	// Calculate packet length and get buffer with that size
 	data, err := mc.buf.takeBuffer(pktLen + 4)
@@ -382,8 +390,10 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 	pos++
 
 	// Connection Attributes
-	pos += copy(data[pos:], connAttrsLEI)
-	pos += copy(data[pos:], []byte(mc.connector.encodedAttributes))
+	if sendConnectAttrs {
+		pos += copy(data[pos:], connAttrsLEI)
+		pos += copy(data[pos:], []byte(mc.connector.encodedAttributes))
+	}
 
 	// Send Auth packet
 	return mc.writePacket(data[:pos])

From 9c8d6a5ddc5b4c2a658e77cb4d03583327901ca5 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Fri, 22 Nov 2024 00:48:25 +0900
Subject: [PATCH 267/282] Reduce "busy buffer" logs (#1641)

Reduce the use of `errBadConnNoWrite` to improve maintainability.

ResetSession() and IsValid() checks if the buffer is busy. This reduces
the risk of busy buffer error during connection in use. In principle,
the risk of this is zero. So I removed errBadConnNoWrite when checking
the busy buffer.

After this change, only `writePacke()` returns errBadConnNoWrite.

Additionally, I do not send COM_QUIT when readPacket() encounter read error.
It caused "busy buffer" error too and hide real errors.
---
 buffer.go     |  5 +++++
 connection.go | 10 +++++++---
 packets.go    | 44 ++++++++++++++------------------------------
 3 files changed, 26 insertions(+), 33 deletions(-)

diff --git a/buffer.go b/buffer.go
index 0774c5c8c..d3d009ccf 100644
--- a/buffer.go
+++ b/buffer.go
@@ -43,6 +43,11 @@ func newBuffer(nc net.Conn) buffer {
 	}
 }
 
+// busy returns true if the buffer contains some read data.
+func (b *buffer) busy() bool {
+	return b.length > 0
+}
+
 // flip replaces the active buffer with the background buffer
 // this is a delayed flip that simply increases the buffer counter;
 // the actual flip will be performed the next time we call `buffer.fill`
diff --git a/connection.go b/connection.go
index ef6fc9e40..c220a8360 100644
--- a/connection.go
+++ b/connection.go
@@ -121,10 +121,14 @@ func (mc *mysqlConn) Close() (err error) {
 	if !mc.closed.Load() {
 		err = mc.writeCommandPacket(comQuit)
 	}
+	mc.close()
+	return
+}
 
+// close closes the network connection and clear results without sending COM_QUIT.
+func (mc *mysqlConn) close() {
 	mc.cleanup()
 	mc.clearResult()
-	return
 }
 
 // Closes the network connection and unsets internal variables. Do not call this
@@ -637,7 +641,7 @@ func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) {
 // ResetSession implements driver.SessionResetter.
 // (From Go 1.10)
 func (mc *mysqlConn) ResetSession(ctx context.Context) error {
-	if mc.closed.Load() {
+	if mc.closed.Load() || mc.buf.busy() {
 		return driver.ErrBadConn
 	}
 
@@ -671,7 +675,7 @@ func (mc *mysqlConn) ResetSession(ctx context.Context) error {
 // IsValid implements driver.Validator interface
 // (From Go 1.15)
 func (mc *mysqlConn) IsValid() bool {
-	return !mc.closed.Load()
+	return !mc.closed.Load() && !mc.buf.busy()
 }
 
 var _ driver.SessionResetter = &mysqlConn{}
diff --git a/packets.go b/packets.go
index a2e7ef95c..4695fb81a 100644
--- a/packets.go
+++ b/packets.go
@@ -32,11 +32,11 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
 		// read packet header
 		data, err := mc.buf.readNext(4)
 		if err != nil {
+			mc.close()
 			if cerr := mc.canceled.Value(); cerr != nil {
 				return nil, cerr
 			}
 			mc.log(err)
-			mc.Close()
 			return nil, ErrInvalidConn
 		}
 
@@ -45,7 +45,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
 
 		// check packet sync [8 bit]
 		if data[3] != mc.sequence {
-			mc.Close()
+			mc.close()
 			if data[3] > mc.sequence {
 				return nil, ErrPktSyncMul
 			}
@@ -59,7 +59,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
 			// there was no previous packet
 			if prevData == nil {
 				mc.log(ErrMalformPkt)
-				mc.Close()
+				mc.close()
 				return nil, ErrInvalidConn
 			}
 
@@ -69,11 +69,11 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
 		// read packet body [pktLen bytes]
 		data, err = mc.buf.readNext(pktLen)
 		if err != nil {
+			mc.close()
 			if cerr := mc.canceled.Value(); cerr != nil {
 				return nil, cerr
 			}
 			mc.log(err)
-			mc.Close()
 			return nil, ErrInvalidConn
 		}
 
@@ -125,10 +125,10 @@ func (mc *mysqlConn) writePacket(data []byte) error {
 
 		n, err := mc.netConn.Write(data[:4+size])
 		if err != nil {
+			mc.cleanup()
 			if cerr := mc.canceled.Value(); cerr != nil {
 				return cerr
 			}
-			mc.cleanup()
 			if n == 0 && pktLen == len(data)-4 {
 				// only for the first loop iteration when nothing was written yet
 				mc.log(err)
@@ -162,11 +162,6 @@ func (mc *mysqlConn) writePacket(data []byte) error {
 func (mc *mysqlConn) readHandshakePacket() (data []byte, plugin string, err error) {
 	data, err = mc.readPacket()
 	if err != nil {
-		// for init we can rewrite this to ErrBadConn for sql.Driver to retry, since
-		// in connection initialization we don't risk retrying non-idempotent actions.
-		if err == ErrInvalidConn {
-			return nil, "", driver.ErrBadConn
-		}
 		return
 	}
 
@@ -312,9 +307,8 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 	// Calculate packet length and get buffer with that size
 	data, err := mc.buf.takeBuffer(pktLen + 4)
 	if err != nil {
-		// cannot take the buffer. Something must be wrong with the connection
-		mc.log(err)
-		return errBadConnNoWrite
+		mc.cleanup()
+		return err
 	}
 
 	// ClientFlags [32 bit]
@@ -404,9 +398,8 @@ func (mc *mysqlConn) writeAuthSwitchPacket(authData []byte) error {
 	pktLen := 4 + len(authData)
 	data, err := mc.buf.takeBuffer(pktLen)
 	if err != nil {
-		// cannot take the buffer. Something must be wrong with the connection
-		mc.log(err)
-		return errBadConnNoWrite
+		mc.cleanup()
+		return err
 	}
 
 	// Add the auth data [EOF]
@@ -424,9 +417,7 @@ func (mc *mysqlConn) writeCommandPacket(command byte) error {
 
 	data, err := mc.buf.takeSmallBuffer(4 + 1)
 	if err != nil {
-		// cannot take the buffer. Something must be wrong with the connection
-		mc.log(err)
-		return errBadConnNoWrite
+		return err
 	}
 
 	// Add command byte
@@ -443,9 +434,7 @@ func (mc *mysqlConn) writeCommandPacketStr(command byte, arg string) error {
 	pktLen := 1 + len(arg)
 	data, err := mc.buf.takeBuffer(pktLen + 4)
 	if err != nil {
-		// cannot take the buffer. Something must be wrong with the connection
-		mc.log(err)
-		return errBadConnNoWrite
+		return err
 	}
 
 	// Add command byte
@@ -464,9 +453,7 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
 
 	data, err := mc.buf.takeSmallBuffer(4 + 1 + 4)
 	if err != nil {
-		// cannot take the buffer. Something must be wrong with the connection
-		mc.log(err)
-		return errBadConnNoWrite
+		return err
 	}
 
 	// Add command byte
@@ -1007,9 +994,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 		// In this case the len(data) == cap(data) which is used to optimise the flow below.
 	}
 	if err != nil {
-		// cannot take the buffer. Something must be wrong with the connection
-		mc.log(err)
-		return errBadConnNoWrite
+		return err
 	}
 
 	// command [1 byte]
@@ -1207,8 +1192,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 		if valuesCap != cap(paramValues) {
 			data = append(data[:pos], paramValues...)
 			if err = mc.buf.store(data); err != nil {
-				mc.log(err)
-				return errBadConnNoWrite
+				return err
 			}
 		}
 

From 2df7a26b03e5f9a55bc31544bc9240ac5705e235 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Wed, 27 Nov 2024 12:41:28 +0900
Subject: [PATCH 268/282] stmt.Close() returns nil when double close (#1642)

ErrBadConn needs special care to ensure it is safe to retry.
To improve maintenance, I don't want to use the error where I don't have to.

Additionally, update the old comment about Go's bug that had been fixed long time ago.
---
 statement.go | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/statement.go b/statement.go
index 35b02bbeb..35df85457 100644
--- a/statement.go
+++ b/statement.go
@@ -24,11 +24,12 @@ type mysqlStmt struct {
 
 func (stmt *mysqlStmt) Close() error {
 	if stmt.mc == nil || stmt.mc.closed.Load() {
-		// driver.Stmt.Close can be called more than once, thus this function
-		// has to be idempotent.
-		// See also Issue #450 and golang/go#16019.
-		//errLog.Print(ErrInvalidConn)
-		return driver.ErrBadConn
+		// driver.Stmt.Close could be called more than once, thus this function
+		// had to be idempotent. See also Issue #450 and golang/go#16019.
+		// This bug has been fixed in Go 1.8.
+		// https://github.com/golang/go/commit/90b8a0ca2d0b565c7c7199ffcf77b15ea6b6db3a
+		// But we keep this function idempotent because it is safer.
+		return nil
 	}
 
 	err := stmt.mc.writeCommandPacketUint32(comStmtClose, stmt.id)

From 575e1b288d624fb14bf56532689f3ec1c1989149 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Sun, 1 Dec 2024 10:08:42 +0900
Subject: [PATCH 269/282] stop double-buffering (#1643)

Since we dropped Go 1.20 support, we do not need double buffering.

This pull request stop double buffering and simplify buffer
implementation a lot.

Fix #1435
---
 buffer.go  | 118 ++++++++++++++++++++---------------------------------
 packets.go |   4 +-
 rows.go    |   7 ----
 3 files changed, 45 insertions(+), 84 deletions(-)

diff --git a/buffer.go b/buffer.go
index d3d009ccf..dd82c9313 100644
--- a/buffer.go
+++ b/buffer.go
@@ -22,47 +22,30 @@ const maxCachedBufSize = 256 * 1024
 // In other words, we can't write and read simultaneously on the same connection.
 // The buffer is similar to bufio.Reader / Writer but zero-copy-ish
 // Also highly optimized for this particular use case.
-// This buffer is backed by two byte slices in a double-buffering scheme
 type buffer struct {
-	buf     []byte // buf is a byte buffer who's length and capacity are equal.
-	nc      net.Conn
-	idx     int
-	length  int
-	timeout time.Duration
-	dbuf    [2][]byte // dbuf is an array with the two byte slices that back this buffer
-	flipcnt uint      // flipccnt is the current buffer counter for double-buffering
+	buf       []byte // read buffer.
+	cachedBuf []byte // buffer that will be reused. len(cachedBuf) <= maxCachedBufSize.
+	nc        net.Conn
+	timeout   time.Duration
 }
 
 // newBuffer allocates and returns a new buffer.
 func newBuffer(nc net.Conn) buffer {
-	fg := make([]byte, defaultBufSize)
 	return buffer{
-		buf:  fg,
-		nc:   nc,
-		dbuf: [2][]byte{fg, nil},
+		cachedBuf: make([]byte, defaultBufSize),
+		nc:        nc,
 	}
 }
 
-// busy returns true if the buffer contains some read data.
+// busy returns true if the read buffer is not empty.
 func (b *buffer) busy() bool {
-	return b.length > 0
+	return len(b.buf) > 0
 }
 
-// flip replaces the active buffer with the background buffer
-// this is a delayed flip that simply increases the buffer counter;
-// the actual flip will be performed the next time we call `buffer.fill`
-func (b *buffer) flip() {
-	b.flipcnt += 1
-}
-
-// fill reads into the buffer until at least _need_ bytes are in it
+// fill reads into the read buffer until at least _need_ bytes are in it.
 func (b *buffer) fill(need int) error {
-	n := b.length
-	// fill data into its double-buffering target: if we've called
-	// flip on this buffer, we'll be copying to the background buffer,
-	// and then filling it with network data; otherwise we'll just move
-	// the contents of the current buffer to the front before filling it
-	dest := b.dbuf[b.flipcnt&1]
+	// we'll move the contents of the current buffer to dest before filling it.
+	dest := b.cachedBuf
 
 	// grow buffer if necessary to fit the whole packet.
 	if need > len(dest) {
@@ -72,18 +55,13 @@ func (b *buffer) fill(need int) error {
 		// if the allocated buffer is not too large, move it to backing storage
 		// to prevent extra allocations on applications that perform large reads
 		if len(dest) <= maxCachedBufSize {
-			b.dbuf[b.flipcnt&1] = dest
+			b.cachedBuf = dest
 		}
 	}
 
-	// if we're filling the fg buffer, move the existing data to the start of it.
-	// if we're filling the bg buffer, copy over the data
-	if n > 0 {
-		copy(dest[:n], b.buf[b.idx:])
-	}
-
-	b.buf = dest
-	b.idx = 0
+	// move the existing data to the start of the buffer.
+	n := len(b.buf)
+	copy(dest[:n], b.buf)
 
 	for {
 		if b.timeout > 0 {
@@ -92,44 +70,39 @@ func (b *buffer) fill(need int) error {
 			}
 		}
 
-		nn, err := b.nc.Read(b.buf[n:])
+		nn, err := b.nc.Read(dest[n:])
 		n += nn
 
-		switch err {
-		case nil:
-			if n < need {
-				continue
-			}
-			b.length = n
-			return nil
+		if err == nil && n < need {
+			continue
+		}
 
-		case io.EOF:
-			if n >= need {
-				b.length = n
-				return nil
-			}
-			return io.ErrUnexpectedEOF
+		b.buf = dest[:n]
 
-		default:
-			return err
+		if err == io.EOF {
+			if n < need {
+				err = io.ErrUnexpectedEOF
+			} else {
+				err = nil
+			}
 		}
+		return err
 	}
 }
 
 // returns next N bytes from buffer.
 // The returned slice is only guaranteed to be valid until the next read
 func (b *buffer) readNext(need int) ([]byte, error) {
-	if b.length < need {
+	if len(b.buf) < need {
 		// refill
 		if err := b.fill(need); err != nil {
 			return nil, err
 		}
 	}
 
-	offset := b.idx
-	b.idx += need
-	b.length -= need
-	return b.buf[offset:b.idx], nil
+	data := b.buf[:need]
+	b.buf = b.buf[need:]
+	return data, nil
 }
 
 // takeBuffer returns a buffer with the requested size.
@@ -137,18 +110,18 @@ func (b *buffer) readNext(need int) ([]byte, error) {
 // Otherwise a bigger buffer is made.
 // Only one buffer (total) can be used at a time.
 func (b *buffer) takeBuffer(length int) ([]byte, error) {
-	if b.length > 0 {
+	if b.busy() {
 		return nil, ErrBusyBuffer
 	}
 
 	// test (cheap) general case first
-	if length <= cap(b.buf) {
-		return b.buf[:length], nil
+	if length <= len(b.cachedBuf) {
+		return b.cachedBuf[:length], nil
 	}
 
-	if length < maxPacketSize {
-		b.buf = make([]byte, length)
-		return b.buf, nil
+	if length < maxCachedBufSize {
+		b.cachedBuf = make([]byte, length)
+		return b.cachedBuf, nil
 	}
 
 	// buffer is larger than we want to store.
@@ -159,10 +132,10 @@ func (b *buffer) takeBuffer(length int) ([]byte, error) {
 // known to be smaller than defaultBufSize.
 // Only one buffer (total) can be used at a time.
 func (b *buffer) takeSmallBuffer(length int) ([]byte, error) {
-	if b.length > 0 {
+	if b.busy() {
 		return nil, ErrBusyBuffer
 	}
-	return b.buf[:length], nil
+	return b.cachedBuf[:length], nil
 }
 
 // takeCompleteBuffer returns the complete existing buffer.
@@ -170,18 +143,15 @@ func (b *buffer) takeSmallBuffer(length int) ([]byte, error) {
 // cap and len of the returned buffer will be equal.
 // Only one buffer (total) can be used at a time.
 func (b *buffer) takeCompleteBuffer() ([]byte, error) {
-	if b.length > 0 {
+	if b.busy() {
 		return nil, ErrBusyBuffer
 	}
-	return b.buf, nil
+	return b.cachedBuf, nil
 }
 
 // store stores buf, an updated buffer, if its suitable to do so.
-func (b *buffer) store(buf []byte) error {
-	if b.length > 0 {
-		return ErrBusyBuffer
-	} else if cap(buf) <= maxPacketSize && cap(buf) > cap(b.buf) {
-		b.buf = buf[:cap(buf)]
+func (b *buffer) store(buf []byte) {
+	if cap(buf) <= maxCachedBufSize && cap(buf) > cap(b.cachedBuf) {
+		b.cachedBuf = buf[:cap(buf)]
 	}
-	return nil
 }
diff --git a/packets.go b/packets.go
index 4695fb81a..736e4418c 100644
--- a/packets.go
+++ b/packets.go
@@ -1191,9 +1191,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 		// In that case we must build the data packet with the new values buffer
 		if valuesCap != cap(paramValues) {
 			data = append(data[:pos], paramValues...)
-			if err = mc.buf.store(data); err != nil {
-				return err
-			}
+			mc.buf.store(data) // allow this buffer to be reused
 		}
 
 		pos += len(paramValues)
diff --git a/rows.go b/rows.go
index 81fa6062c..df98417b8 100644
--- a/rows.go
+++ b/rows.go
@@ -111,13 +111,6 @@ func (rows *mysqlRows) Close() (err error) {
 		return err
 	}
 
-	// flip the buffer for this connection if we need to drain it.
-	// note that for a successful query (i.e. one where rows.next()
-	// has been called until it returns false), `rows.mc` will be nil
-	// by the time the user calls `(*Rows).Close`, so we won't reach this
-	// see: https://github.com/golang/go/commit/651ddbdb5056ded455f47f9c494c67b389622a47
-	mc.buf.flip()
-
 	// Remove unread packets from stream
 	if !rows.rs.done {
 		err = mc.readUntilEOF()

From c9f41c074062d5ab9aeb5e44adeac3a7d85fbc4e Mon Sep 17 00:00:00 2001
From: Minh Quang <minhquang4334@gmail.com>
Date: Sun, 15 Dec 2024 10:37:13 +0700
Subject: [PATCH 270/282] fix typo in comment (#1647)

Fix #1646
---
 AUTHORS       | 1 +
 connection.go | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/AUTHORS b/AUTHORS
index a98608504..361c6b647 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -92,6 +92,7 @@ Paul Bonser <misterpib at gmail.com>
 Paulius Lozys <pauliuslozys at gmail.com>
 Peter Schultz <peter.schultz at classmarkets.com>
 Phil Porada <philporada at gmail.com>
+Minh Quang <minhquang4334 at gmail.com>
 Rebecca Chin <rchin at pivotal.io>
 Reed Allman <rdallman10 at gmail.com>
 Richard Wilkes <wilkes at me.com>
diff --git a/connection.go b/connection.go
index c220a8360..acc627086 100644
--- a/connection.go
+++ b/connection.go
@@ -435,7 +435,7 @@ func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
 	return nil, err
 }
 
-// finish is called when the query has canceled.
+// cancel is called when the query has canceled.
 func (mc *mysqlConn) cancel(err error) {
 	mc.canceled.Set(err)
 	mc.cleanup()

From 3348e573da4c1d7186ae7d6eabd4d7333bd486a0 Mon Sep 17 00:00:00 2001
From: Joe Mann <contact@joemann.co.uk>
Date: Thu, 19 Dec 2024 04:14:14 +0100
Subject: [PATCH 271/282] Implement zlib compression (#1487)

Implemented the SQL compression protocol. This new feature is enabled by:

* Adding `compress=true` in DSN.
* `cfg.Apply(Compress(True))`

Co-authored-by: Brigitte Lamarche <brigitte.lamarche@mongodb.com>
Co-authored-by: Julien Schmidt <julienschmidt@users.noreply.github.com>
Co-authored-by: Jeffrey Charles <jeffreycharles@gmail.com>
Co-authored-by: Jeff Hodges <jeff@somethingsimilar.com>
Co-authored-by: Daniel Montoya <dsmontoyam@gmail.com>
Co-authored-by: Justin Li <jli.justinli@gmail.com>
Co-authored-by: Dave Stubbs <dave@randomjunk.co.uk>
Co-authored-by: Linh Tran Tuan <linxGnu@users.noreply.github.com>
Co-authored-by: Robert R. Russell <robert@rrbrussell.com>
Co-authored-by: INADA Naoki <methane@users.noreply.github.com>
Co-authored-by: Kieron Woodhouse <kieron.woodhouse@yahoo.co.uk>
Co-authored-by: Alexey Palazhchenko <alexey.palazhchenko@gmail.com>
Co-authored-by: Reed Allman <rdallman10@gmail.com>
Co-authored-by: Joe Mann <joe.mann@showpad.com>
---
 .github/workflows/test.yml |   2 +-
 AUTHORS                    |   2 +
 README.md                  |  11 ++
 benchmark_test.go          |  28 +++--
 buffer.go                  |  26 ++---
 compress.go                | 214 +++++++++++++++++++++++++++++++++++++
 compress_test.go           | 119 +++++++++++++++++++++
 connection.go              |  43 +++++++-
 connection_test.go         |  14 +--
 connector.go               |  10 +-
 const.go                   |   2 +
 driver_test.go             |  45 ++++++--
 dsn.go                     |  24 ++++-
 infile.go                  |   1 +
 packets.go                 | 113 ++++++++++++--------
 packets_test.go            |  24 +++--
 utils.go                   |  12 +++
 17 files changed, 581 insertions(+), 109 deletions(-)
 create mode 100644 compress.go
 create mode 100644 compress_test.go

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index b1c1f2b34..2e07fea91 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -83,7 +83,7 @@ jobs:
           my-cnf: |
             innodb_log_file_size=256MB
             innodb_buffer_pool_size=512MB
-            max_allowed_packet=16MB
+            max_allowed_packet=48MB
             ; TestConcurrent fails if max_connections is too large
             max_connections=50
             local_infile=1
diff --git a/AUTHORS b/AUTHORS
index 361c6b647..cbcc90f51 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -21,6 +21,7 @@ Animesh Ray <mail.rayanimesh at gmail.com>
 Arne Hormann <arnehormann at gmail.com>
 Ariel Mashraki <ariel at mashraki.co.il>
 Asta Xie <xiemengjun at gmail.com>
+B Lamarche <blam413 at gmail.com>
 Brian Hendriks <brian at dolthub.com>
 Bulat Gaifullin <gaifullinbf at gmail.com>
 Caine Jette <jette at alum.mit.edu>
@@ -62,6 +63,7 @@ Jennifer Purevsuren <jennifer at dolthub.com>
 Jerome Meyer <jxmeyer at gmail.com>
 Jiajia Zhong <zhong2plus at gmail.com>
 Jian Zhen <zhenjl at gmail.com>
+Joe Mann <contact at joemann.co.uk>
 Joshua Prunier <joshua.prunier at gmail.com>
 Julien Lefevre <julien.lefevr at gmail.com>
 Julien Schmidt <go-sql-driver at julienschmidt.com>
diff --git a/README.md b/README.md
index e9d9222ba..da4593ccf 100644
--- a/README.md
+++ b/README.md
@@ -38,6 +38,7 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
   * Secure `LOAD DATA LOCAL INFILE` support with file allowlisting and `io.Reader` support
   * Optional `time.Time` parsing
   * Optional placeholder interpolation
+  * Supports zlib compression.
 
 ## Requirements
 
@@ -267,6 +268,16 @@ SELECT u.id FROM users as u
 
 will return `u.id` instead of just `id` if `columnsWithAlias=true`.
 
+##### `compress`
+
+```
+Type:           bool
+Valid Values:   true, false
+Default:        false
+```
+
+Toggles zlib compression. false by default.
+
 ##### `interpolateParams`
 
 ```
diff --git a/benchmark_test.go b/benchmark_test.go
index a4ecc0a63..5c9a046b5 100644
--- a/benchmark_test.go
+++ b/benchmark_test.go
@@ -46,9 +46,13 @@ func (tb *TB) checkStmt(stmt *sql.Stmt, err error) *sql.Stmt {
 	return stmt
 }
 
-func initDB(b *testing.B, queries ...string) *sql.DB {
+func initDB(b *testing.B, useCompression bool, queries ...string) *sql.DB {
 	tb := (*TB)(b)
-	db := tb.checkDB(sql.Open(driverNameTest, dsn))
+	comprStr := ""
+	if useCompression {
+		comprStr = "&compress=1"
+	}
+	db := tb.checkDB(sql.Open(driverNameTest, dsn+comprStr))
 	for _, query := range queries {
 		if _, err := db.Exec(query); err != nil {
 			b.Fatalf("error on %q: %v", query, err)
@@ -60,10 +64,18 @@ func initDB(b *testing.B, queries ...string) *sql.DB {
 const concurrencyLevel = 10
 
 func BenchmarkQuery(b *testing.B) {
+	benchmarkQueryHelper(b, false)
+}
+
+func BenchmarkQueryCompression(b *testing.B) {
+	benchmarkQueryHelper(b, true)
+}
+
+func benchmarkQueryHelper(b *testing.B, compr bool) {
 	tb := (*TB)(b)
 	b.StopTimer()
 	b.ReportAllocs()
-	db := initDB(b,
+	db := initDB(b, compr,
 		"DROP TABLE IF EXISTS foo",
 		"CREATE TABLE foo (id INT PRIMARY KEY, val CHAR(50))",
 		`INSERT INTO foo VALUES (1, "one")`,
@@ -222,7 +234,7 @@ func BenchmarkInterpolation(b *testing.B) {
 		},
 		maxAllowedPacket: maxPacketSize,
 		maxWriteSize:     maxPacketSize - 1,
-		buf:              newBuffer(nil),
+		buf:              newBuffer(),
 	}
 
 	args := []driver.Value{
@@ -269,7 +281,7 @@ func benchmarkQueryContext(b *testing.B, db *sql.DB, p int) {
 }
 
 func BenchmarkQueryContext(b *testing.B) {
-	db := initDB(b,
+	db := initDB(b, false,
 		"DROP TABLE IF EXISTS foo",
 		"CREATE TABLE foo (id INT PRIMARY KEY, val CHAR(50))",
 		`INSERT INTO foo VALUES (1, "one")`,
@@ -305,7 +317,7 @@ func benchmarkExecContext(b *testing.B, db *sql.DB, p int) {
 }
 
 func BenchmarkExecContext(b *testing.B) {
-	db := initDB(b,
+	db := initDB(b, false,
 		"DROP TABLE IF EXISTS foo",
 		"CREATE TABLE foo (id INT PRIMARY KEY, val CHAR(50))",
 		`INSERT INTO foo VALUES (1, "one")`,
@@ -323,7 +335,7 @@ func BenchmarkExecContext(b *testing.B) {
 // "size=" means size of each blobs.
 func BenchmarkQueryRawBytes(b *testing.B) {
 	var sizes []int = []int{100, 1000, 2000, 4000, 8000, 12000, 16000, 32000, 64000, 256000}
-	db := initDB(b,
+	db := initDB(b, false,
 		"DROP TABLE IF EXISTS bench_rawbytes",
 		"CREATE TABLE bench_rawbytes (id INT PRIMARY KEY, val LONGBLOB)",
 	)
@@ -376,7 +388,7 @@ func BenchmarkQueryRawBytes(b *testing.B) {
 // BenchmarkReceiveMassiveRows measures performance of receiving large number of rows.
 func BenchmarkReceiveMassiveRows(b *testing.B) {
 	// Setup -- prepare 10000 rows.
-	db := initDB(b,
+	db := initDB(b, false,
 		"DROP TABLE IF EXISTS foo",
 		"CREATE TABLE foo (id INT PRIMARY KEY, val TEXT)")
 	defer db.Close()
diff --git a/buffer.go b/buffer.go
index dd82c9313..a65324315 100644
--- a/buffer.go
+++ b/buffer.go
@@ -10,13 +10,16 @@ package mysql
 
 import (
 	"io"
-	"net"
-	"time"
 )
 
 const defaultBufSize = 4096
 const maxCachedBufSize = 256 * 1024
 
+// readerFunc is a function that compatible with io.Reader.
+// We use this function type instead of io.Reader because we want to
+// just pass mc.readWithTimeout.
+type readerFunc func([]byte) (int, error)
+
 // A buffer which is used for both reading and writing.
 // This is possible since communication on each connection is synchronous.
 // In other words, we can't write and read simultaneously on the same connection.
@@ -25,15 +28,12 @@ const maxCachedBufSize = 256 * 1024
 type buffer struct {
 	buf       []byte // read buffer.
 	cachedBuf []byte // buffer that will be reused. len(cachedBuf) <= maxCachedBufSize.
-	nc        net.Conn
-	timeout   time.Duration
 }
 
 // newBuffer allocates and returns a new buffer.
-func newBuffer(nc net.Conn) buffer {
+func newBuffer() buffer {
 	return buffer{
 		cachedBuf: make([]byte, defaultBufSize),
-		nc:        nc,
 	}
 }
 
@@ -43,7 +43,7 @@ func (b *buffer) busy() bool {
 }
 
 // fill reads into the read buffer until at least _need_ bytes are in it.
-func (b *buffer) fill(need int) error {
+func (b *buffer) fill(need int, r readerFunc) error {
 	// we'll move the contents of the current buffer to dest before filling it.
 	dest := b.cachedBuf
 
@@ -64,13 +64,7 @@ func (b *buffer) fill(need int) error {
 	copy(dest[:n], b.buf)
 
 	for {
-		if b.timeout > 0 {
-			if err := b.nc.SetReadDeadline(time.Now().Add(b.timeout)); err != nil {
-				return err
-			}
-		}
-
-		nn, err := b.nc.Read(dest[n:])
+		nn, err := r(dest[n:])
 		n += nn
 
 		if err == nil && n < need {
@@ -92,10 +86,10 @@ func (b *buffer) fill(need int) error {
 
 // returns next N bytes from buffer.
 // The returned slice is only guaranteed to be valid until the next read
-func (b *buffer) readNext(need int) ([]byte, error) {
+func (b *buffer) readNext(need int, r readerFunc) ([]byte, error) {
 	if len(b.buf) < need {
 		// refill
-		if err := b.fill(need); err != nil {
+		if err := b.fill(need, r); err != nil {
 			return nil, err
 		}
 	}
diff --git a/compress.go b/compress.go
new file mode 100644
index 000000000..fa42772ac
--- /dev/null
+++ b/compress.go
@@ -0,0 +1,214 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2024 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 (
+	"bytes"
+	"compress/zlib"
+	"fmt"
+	"io"
+	"sync"
+)
+
+var (
+	zrPool *sync.Pool // Do not use directly. Use zDecompress() instead.
+	zwPool *sync.Pool // Do not use directly. Use zCompress() instead.
+)
+
+func init() {
+	zrPool = &sync.Pool{
+		New: func() any { return nil },
+	}
+	zwPool = &sync.Pool{
+		New: func() any {
+			zw, err := zlib.NewWriterLevel(new(bytes.Buffer), 2)
+			if err != nil {
+				panic(err) // compress/zlib return non-nil error only if level is invalid
+			}
+			return zw
+		},
+	}
+}
+
+func zDecompress(src []byte, dst *bytes.Buffer) (int, error) {
+	br := bytes.NewReader(src)
+	var zr io.ReadCloser
+	var err error
+
+	if a := zrPool.Get(); a == nil {
+		if zr, err = zlib.NewReader(br); err != nil {
+			return 0, err
+		}
+	} else {
+		zr = a.(io.ReadCloser)
+		if err := zr.(zlib.Resetter).Reset(br, nil); err != nil {
+			return 0, err
+		}
+	}
+
+	n, _ := dst.ReadFrom(zr) // ignore err because zr.Close() will return it again.
+	err = zr.Close()         // zr.Close() may return chuecksum error.
+	zrPool.Put(zr)
+	return int(n), err
+}
+
+func zCompress(src []byte, dst io.Writer) error {
+	zw := zwPool.Get().(*zlib.Writer)
+	zw.Reset(dst)
+	if _, err := zw.Write(src); err != nil {
+		return err
+	}
+	err := zw.Close()
+	zwPool.Put(zw)
+	return err
+}
+
+type compIO struct {
+	mc   *mysqlConn
+	buff bytes.Buffer
+}
+
+func newCompIO(mc *mysqlConn) *compIO {
+	return &compIO{
+		mc: mc,
+	}
+}
+
+func (c *compIO) reset() {
+	c.buff.Reset()
+}
+
+func (c *compIO) readNext(need int, r readerFunc) ([]byte, error) {
+	for c.buff.Len() < need {
+		if err := c.readCompressedPacket(r); err != nil {
+			return nil, err
+		}
+	}
+	data := c.buff.Next(need)
+	return data[:need:need], nil // prevent caller writes into c.buff
+}
+
+func (c *compIO) readCompressedPacket(r readerFunc) error {
+	header, err := c.mc.buf.readNext(7, r) // size of compressed header
+	if err != nil {
+		return err
+	}
+	_ = header[6] // bounds check hint to compiler; guaranteed by readNext
+
+	// compressed header structure
+	comprLength := getUint24(header[0:3])
+	compressionSequence := uint8(header[3])
+	uncompressedLength := getUint24(header[4:7])
+	if debug {
+		fmt.Printf("uncompress cmplen=%v uncomplen=%v pkt_cmp_seq=%v expected_cmp_seq=%v\n",
+			comprLength, uncompressedLength, compressionSequence, c.mc.sequence)
+	}
+	// Do not return ErrPktSync here.
+	// Server may return error packet (e.g. 1153 Got a packet bigger than 'max_allowed_packet' bytes)
+	// before receiving all packets from client. In this case, seqnr is younger than expected.
+	// NOTE: Both of mariadbclient and mysqlclient do not check seqnr. Only server checks it.
+	if debug && compressionSequence != c.mc.sequence {
+		fmt.Printf("WARN: unexpected cmpress seq nr: expected %v, got %v",
+			c.mc.sequence, compressionSequence)
+	}
+	c.mc.sequence = compressionSequence + 1
+	c.mc.compressSequence = c.mc.sequence
+
+	comprData, err := c.mc.buf.readNext(comprLength, r)
+	if err != nil {
+		return err
+	}
+
+	// if payload is uncompressed, its length will be specified as zero, and its
+	// true length is contained in comprLength
+	if uncompressedLength == 0 {
+		c.buff.Write(comprData)
+		return nil
+	}
+
+	// use existing capacity in bytesBuf if possible
+	c.buff.Grow(uncompressedLength)
+	nread, err := zDecompress(comprData, &c.buff)
+	if err != nil {
+		return err
+	}
+	if nread != uncompressedLength {
+		return fmt.Errorf("invalid compressed packet: uncompressed length in header is %d, actual %d",
+			uncompressedLength, nread)
+	}
+	return nil
+}
+
+const minCompressLength = 150
+const maxPayloadLen = maxPacketSize - 4
+
+// writePackets sends one or some packets with compression.
+// Use this instead of mc.netConn.Write() when mc.compress is true.
+func (c *compIO) writePackets(packets []byte) (int, error) {
+	totalBytes := len(packets)
+	blankHeader := make([]byte, 7)
+	buf := &c.buff
+
+	for len(packets) > 0 {
+		payloadLen := min(maxPayloadLen, len(packets))
+		payload := packets[:payloadLen]
+		uncompressedLen := payloadLen
+
+		buf.Reset()
+		buf.Write(blankHeader) // Buffer.Write() never returns error
+
+		// If payload is less than minCompressLength, don't compress.
+		if uncompressedLen < minCompressLength {
+			buf.Write(payload)
+			uncompressedLen = 0
+		} else {
+			err := zCompress(payload, buf)
+			if debug && err != nil {
+				fmt.Printf("zCompress error: %v", err)
+			}
+			// do not compress if compressed data is larger than uncompressed data
+			// I intentionally miss 7 byte header in the buf; zCompress must compress more than 7 bytes.
+			if err != nil || buf.Len() >= uncompressedLen {
+				buf.Reset()
+				buf.Write(blankHeader)
+				buf.Write(payload)
+				uncompressedLen = 0
+			}
+		}
+
+		if n, err := c.writeCompressedPacket(buf.Bytes(), uncompressedLen); err != nil {
+			// To allow returning ErrBadConn when sending really 0 bytes, we sum
+			// up compressed bytes that is returned by underlying Write().
+			return totalBytes - len(packets) + n, err
+		}
+		packets = packets[payloadLen:]
+	}
+
+	return totalBytes, nil
+}
+
+// writeCompressedPacket writes a compressed packet with header.
+// data should start with 7 size space for header followed by payload.
+func (c *compIO) writeCompressedPacket(data []byte, uncompressedLen int) (int, error) {
+	mc := c.mc
+	comprLength := len(data) - 7
+	if debug {
+		fmt.Printf(
+			"writeCompressedPacket: comprLength=%v, uncompressedLen=%v, seq=%v",
+			comprLength, uncompressedLen, mc.compressSequence)
+	}
+
+	// compression header
+	putUint24(data[0:3], comprLength)
+	data[3] = mc.compressSequence
+	putUint24(data[4:7], uncompressedLen)
+
+	mc.compressSequence++
+	return mc.writeWithTimeout(data)
+}
diff --git a/compress_test.go b/compress_test.go
new file mode 100644
index 000000000..030deaefa
--- /dev/null
+++ b/compress_test.go
@@ -0,0 +1,119 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2024 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 (
+	"bytes"
+	"crypto/rand"
+	"io"
+	"testing"
+)
+
+func makeRandByteSlice(size int) []byte {
+	randBytes := make([]byte, size)
+	rand.Read(randBytes)
+	return randBytes
+}
+
+// compressHelper compresses uncompressedPacket and checks state variables
+func compressHelper(t *testing.T, mc *mysqlConn, uncompressedPacket []byte) []byte {
+	conn := new(mockConn)
+	mc.netConn = conn
+
+	err := mc.writePacket(append(make([]byte, 4), uncompressedPacket...))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	return conn.written
+}
+
+// uncompressHelper uncompresses compressedPacket and checks state variables
+func uncompressHelper(t *testing.T, mc *mysqlConn, compressedPacket []byte) []byte {
+	// mocking out buf variable
+	conn := new(mockConn)
+	conn.data = compressedPacket
+	mc.netConn = conn
+
+	uncompressedPacket, err := mc.readPacket()
+	if err != nil {
+		if err != io.EOF {
+			t.Fatalf("non-nil/non-EOF error when reading contents: %s", err.Error())
+		}
+	}
+	return uncompressedPacket
+}
+
+// roundtripHelper compresses then uncompresses uncompressedPacket and checks state variables
+func roundtripHelper(t *testing.T, cSend *mysqlConn, cReceive *mysqlConn, uncompressedPacket []byte) []byte {
+	compressed := compressHelper(t, cSend, uncompressedPacket)
+	return uncompressHelper(t, cReceive, compressed)
+}
+
+// TestRoundtrip tests two connections, where one is reading and the other is writing
+func TestRoundtrip(t *testing.T) {
+	tests := []struct {
+		uncompressed []byte
+		desc         string
+	}{
+		{uncompressed: []byte("a"),
+			desc: "a"},
+		{uncompressed: []byte("hello world"),
+			desc: "hello world"},
+		{uncompressed: make([]byte, 100),
+			desc: "100 bytes"},
+		{uncompressed: make([]byte, 32768),
+			desc: "32768 bytes"},
+		{uncompressed: make([]byte, 330000),
+			desc: "33000 bytes"},
+		{uncompressed: makeRandByteSlice(10),
+			desc: "10 rand bytes",
+		},
+		{uncompressed: makeRandByteSlice(100),
+			desc: "100 rand bytes",
+		},
+		{uncompressed: makeRandByteSlice(32768),
+			desc: "32768 rand bytes",
+		},
+		{uncompressed: bytes.Repeat(makeRandByteSlice(100), 10000),
+			desc: "100 rand * 10000 repeat bytes",
+		},
+	}
+
+	_, cSend := newRWMockConn(0)
+	cSend.compress = true
+	cSend.compIO = newCompIO(cSend)
+	_, cReceive := newRWMockConn(0)
+	cReceive.compress = true
+	cReceive.compIO = newCompIO(cReceive)
+
+	for _, test := range tests {
+		t.Run(test.desc, func(t *testing.T) {
+			cSend.resetSequence()
+			cReceive.resetSequence()
+
+			uncompressed := roundtripHelper(t, cSend, cReceive, test.uncompressed)
+			if len(uncompressed) != len(test.uncompressed) {
+				t.Errorf("uncompressed size is unexpected. expected %d but got %d",
+					len(test.uncompressed), len(uncompressed))
+			}
+			if !bytes.Equal(uncompressed, test.uncompressed) {
+				t.Errorf("roundtrip failed")
+			}
+			if cSend.sequence != cReceive.sequence {
+				t.Errorf("inconsistent sequence number: send=%v recv=%v",
+					cSend.sequence, cReceive.sequence)
+			}
+			if cSend.compressSequence != cReceive.compressSequence {
+				t.Errorf("inconsistent compress sequence number: send=%v recv=%v",
+					cSend.compressSequence, cReceive.compressSequence)
+			}
+		})
+	}
+}
diff --git a/connection.go b/connection.go
index acc627086..3e455a3ff 100644
--- a/connection.go
+++ b/connection.go
@@ -28,15 +28,17 @@ type mysqlConn struct {
 	netConn          net.Conn
 	rawConn          net.Conn    // underlying connection when netConn is TLS connection.
 	result           mysqlResult // managed by clearResult() and handleOkPacket().
+	compIO           *compIO
 	cfg              *Config
 	connector        *connector
 	maxAllowedPacket int
 	maxWriteSize     int
-	writeTimeout     time.Duration
 	flags            clientFlag
 	status           statusFlag
 	sequence         uint8
+	compressSequence uint8
 	parseTime        bool
+	compress         bool
 
 	// for context support (Go 1.8+)
 	watching bool
@@ -62,6 +64,43 @@ func (mc *mysqlConn) log(v ...any) {
 	mc.cfg.Logger.Print(v...)
 }
 
+func (mc *mysqlConn) readWithTimeout(b []byte) (int, error) {
+	to := mc.cfg.ReadTimeout
+	if to > 0 {
+		if err := mc.netConn.SetReadDeadline(time.Now().Add(to)); err != nil {
+			return 0, err
+		}
+	}
+	return mc.netConn.Read(b)
+}
+
+func (mc *mysqlConn) writeWithTimeout(b []byte) (int, error) {
+	to := mc.cfg.WriteTimeout
+	if to > 0 {
+		if err := mc.netConn.SetWriteDeadline(time.Now().Add(to)); err != nil {
+			return 0, err
+		}
+	}
+	return mc.netConn.Write(b)
+}
+
+func (mc *mysqlConn) resetSequence() {
+	mc.sequence = 0
+	mc.compressSequence = 0
+}
+
+// syncSequence must be called when finished writing some packet and before start reading.
+func (mc *mysqlConn) syncSequence() {
+	// Syncs compressionSequence to sequence.
+	// This is not documented but done in `net_flush()` in MySQL and MariaDB.
+	// https://github.com/mariadb-corporation/mariadb-connector-c/blob/8228164f850b12353da24df1b93a1e53cc5e85e9/libmariadb/ma_net.c#L170-L171
+	// https://github.com/mysql/mysql-server/blob/824e2b4064053f7daf17d7f3f84b7a3ed92e5fb4/sql-common/net_serv.cc#L293
+	if mc.compress {
+		mc.sequence = mc.compressSequence
+		mc.compIO.reset()
+	}
+}
+
 // Handles parameters set in DSN after the connection is established
 func (mc *mysqlConn) handleParams() (err error) {
 	var cmdSet strings.Builder
@@ -147,7 +186,7 @@ func (mc *mysqlConn) cleanup() {
 		return
 	}
 	if err := conn.Close(); err != nil {
-		mc.log(err)
+		mc.log("closing connection:", err)
 	}
 	// This function can be called from multiple goroutines.
 	// So we can not mc.clearResult() here.
diff --git a/connection_test.go b/connection_test.go
index 6f8d2a6d7..f7740898e 100644
--- a/connection_test.go
+++ b/connection_test.go
@@ -19,7 +19,7 @@ import (
 
 func TestInterpolateParams(t *testing.T) {
 	mc := &mysqlConn{
-		buf:              newBuffer(nil),
+		buf:              newBuffer(),
 		maxAllowedPacket: maxPacketSize,
 		cfg: &Config{
 			InterpolateParams: true,
@@ -39,7 +39,7 @@ func TestInterpolateParams(t *testing.T) {
 
 func TestInterpolateParamsJSONRawMessage(t *testing.T) {
 	mc := &mysqlConn{
-		buf:              newBuffer(nil),
+		buf:              newBuffer(),
 		maxAllowedPacket: maxPacketSize,
 		cfg: &Config{
 			InterpolateParams: true,
@@ -66,7 +66,7 @@ func TestInterpolateParamsJSONRawMessage(t *testing.T) {
 
 func TestInterpolateParamsTooManyPlaceholders(t *testing.T) {
 	mc := &mysqlConn{
-		buf:              newBuffer(nil),
+		buf:              newBuffer(),
 		maxAllowedPacket: maxPacketSize,
 		cfg: &Config{
 			InterpolateParams: true,
@@ -83,7 +83,7 @@ func TestInterpolateParamsTooManyPlaceholders(t *testing.T) {
 // https://github.com/go-sql-driver/mysql/pull/490
 func TestInterpolateParamsPlaceholderInString(t *testing.T) {
 	mc := &mysqlConn{
-		buf:              newBuffer(nil),
+		buf:              newBuffer(),
 		maxAllowedPacket: maxPacketSize,
 		cfg: &Config{
 			InterpolateParams: true,
@@ -99,7 +99,7 @@ func TestInterpolateParamsPlaceholderInString(t *testing.T) {
 
 func TestInterpolateParamsUint64(t *testing.T) {
 	mc := &mysqlConn{
-		buf:              newBuffer(nil),
+		buf:              newBuffer(),
 		maxAllowedPacket: maxPacketSize,
 		cfg: &Config{
 			InterpolateParams: true,
@@ -161,7 +161,7 @@ func TestPingMarkBadConnection(t *testing.T) {
 	nc := badConnection{err: errors.New("boom")}
 	mc := &mysqlConn{
 		netConn:          nc,
-		buf:              newBuffer(nc),
+		buf:              newBuffer(),
 		maxAllowedPacket: defaultMaxAllowedPacket,
 		closech:          make(chan struct{}),
 		cfg:              NewConfig(),
@@ -178,7 +178,7 @@ func TestPingErrInvalidConn(t *testing.T) {
 	nc := badConnection{err: errors.New("failed to write"), n: 10}
 	mc := &mysqlConn{
 		netConn:          nc,
-		buf:              newBuffer(nc),
+		buf:              newBuffer(),
 		maxAllowedPacket: defaultMaxAllowedPacket,
 		closech:          make(chan struct{}),
 		cfg:              NewConfig(),
diff --git a/connector.go b/connector.go
index 769b3adc9..a4f3655ef 100644
--- a/connector.go
+++ b/connector.go
@@ -127,11 +127,7 @@ func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
 	}
 	defer mc.finish()
 
-	mc.buf = newBuffer(mc.netConn)
-
-	// Set I/O timeouts
-	mc.buf.timeout = mc.cfg.ReadTimeout
-	mc.writeTimeout = mc.cfg.WriteTimeout
+	mc.buf = newBuffer()
 
 	// Reading Handshake Initialization Packet
 	authData, plugin, err := mc.readHandshakePacket()
@@ -170,6 +166,10 @@ func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
 		return nil, err
 	}
 
+	if mc.cfg.compress && mc.flags&clientCompress == clientCompress {
+		mc.compress = true
+		mc.compIO = newCompIO(mc)
+	}
 	if mc.cfg.MaxAllowedPacket > 0 {
 		mc.maxAllowedPacket = mc.cfg.MaxAllowedPacket
 	} else {
diff --git a/const.go b/const.go
index 0cee9b2ee..4aadcd642 100644
--- a/const.go
+++ b/const.go
@@ -11,6 +11,8 @@ package mysql
 import "runtime"
 
 const (
+	debug = false // for debugging. Set true only in development.
+
 	defaultAuthPlugin       = "mysql_native_password"
 	defaultMaxAllowedPacket = 64 << 20 // 64 MiB. See https://github.com/go-sql-driver/mysql/issues/1355
 	minProtocolVersion      = 10
diff --git a/driver_test.go b/driver_test.go
index 24d73c34f..58b3cb38d 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -147,12 +147,11 @@ func runTests(t *testing.T, dsn string, tests ...func(dbt *DBTest)) {
 
 	db, err := sql.Open(driverNameTest, dsn)
 	if err != nil {
-		t.Fatalf("error connecting: %s", err.Error())
+		t.Fatalf("connecting %q: %s", dsn, err)
 	}
 	defer db.Close()
-
-	cleanup := func() {
-		db.Exec("DROP TABLE IF EXISTS test")
+	if err = db.Ping(); err != nil {
+		t.Fatalf("connecting %q: %s", dsn, err)
 	}
 
 	dsn2 := dsn + "&interpolateParams=true"
@@ -160,25 +159,46 @@ func runTests(t *testing.T, dsn string, tests ...func(dbt *DBTest)) {
 	if _, err := ParseDSN(dsn2); err != errInvalidDSNUnsafeCollation {
 		db2, err = sql.Open(driverNameTest, dsn2)
 		if err != nil {
-			t.Fatalf("error connecting: %s", err.Error())
+			t.Fatalf("connecting %q: %s", dsn2, err)
 		}
 		defer db2.Close()
 	}
 
+	dsn3 := dsn + "&compress=true"
+	var db3 *sql.DB
+	db3, err = sql.Open(driverNameTest, dsn3)
+	if err != nil {
+		t.Fatalf("connecting %q: %s", dsn3, err)
+	}
+	defer db3.Close()
+
+	cleanupSql := "DROP TABLE IF EXISTS test"
+
 	for _, test := range tests {
 		test := test
 		t.Run("default", func(t *testing.T) {
 			dbt := &DBTest{t, db}
-			t.Cleanup(cleanup)
+			t.Cleanup(func() {
+				db.Exec(cleanupSql)
+			})
 			test(dbt)
 		})
 		if db2 != nil {
 			t.Run("interpolateParams", func(t *testing.T) {
 				dbt2 := &DBTest{t, db2}
-				t.Cleanup(cleanup)
+				t.Cleanup(func() {
+					db2.Exec(cleanupSql)
+				})
 				test(dbt2)
 			})
 		}
+		t.Run("compress", func(t *testing.T) {
+			dbt3 := &DBTest{t, db3}
+			t.Cleanup(func() {
+				db3.Exec(cleanupSql)
+			})
+			test(dbt3)
+		})
 	}
 }
 
@@ -958,12 +978,16 @@ func TestDateTime(t *testing.T) {
 			var err error
 			rows, err = dbt.db.Query(`SELECT cast("00:00:00.1" as TIME(1)) = "00:00:00.1"`)
 			if err == nil {
-				rows.Scan(&microsecsSupported)
+				if rows.Next() {
+					rows.Scan(&microsecsSupported)
+				}
 				rows.Close()
 			}
 			rows, err = dbt.db.Query(`SELECT cast("0000-00-00" as DATE) = "0000-00-00"`)
 			if err == nil {
-				rows.Scan(&zeroDateSupported)
+				if rows.Next() {
+					rows.Scan(&zeroDateSupported)
+				}
 				rows.Close()
 			}
 			for _, setups := range testcases {
@@ -1265,8 +1289,7 @@ func TestLongData(t *testing.T) {
 		var rows *sql.Rows
 
 		// Long text data
-		const nonDataQueryLen = 28 // length query w/o value
-		inS := in[:maxAllowedPacketSize-nonDataQueryLen]
+		inS := in[:maxAllowedPacketSize-100]
 		dbt.mustExec("INSERT INTO test VALUES('" + inS + "')")
 		rows = dbt.mustQuery("SELECT value FROM test")
 		defer rows.Close()
diff --git a/dsn.go b/dsn.go
index f391a8fc9..9b560b735 100644
--- a/dsn.go
+++ b/dsn.go
@@ -73,7 +73,10 @@ type Config struct {
 	ParseTime                bool // Parse time values to time.Time
 	RejectReadOnly           bool // Reject read-only connections
 
-	// unexported fields. new options should be come here
+	// unexported fields. new options should be come here.
+	// boolean first. alphabetical order.
+
+	compress bool // Enable zlib compression
 
 	beforeConnect func(context.Context, *Config) error // Invoked before a connection is established
 	pubKey        *rsa.PublicKey                       // Server public key
@@ -93,7 +96,6 @@ func NewConfig() *Config {
 		AllowNativePasswords: true,
 		CheckConnLiveness:    true,
 	}
-
 	return cfg
 }
 
@@ -125,6 +127,14 @@ func BeforeConnect(fn func(context.Context, *Config) error) Option {
 	}
 }
 
+// EnableCompress sets the compression mode.
+func EnableCompression(yes bool) Option {
+	return func(cfg *Config) error {
+		cfg.compress = yes
+		return nil
+	}
+}
+
 func (cfg *Config) Clone() *Config {
 	cp := *cfg
 	if cp.TLS != nil {
@@ -297,6 +307,10 @@ func (cfg *Config) FormatDSN() string {
 		writeDSNParam(&buf, &hasParam, "columnsWithAlias", "true")
 	}
 
+	if cfg.compress {
+		writeDSNParam(&buf, &hasParam, "compress", "true")
+	}
+
 	if cfg.InterpolateParams {
 		writeDSNParam(&buf, &hasParam, "interpolateParams", "true")
 	}
@@ -525,7 +539,11 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 
 		// Compression
 		case "compress":
-			return errors.New("compression not implemented yet")
+			var isBool bool
+			cfg.compress, isBool = readBool(value)
+			if !isBool {
+				return errors.New("invalid bool value: " + value)
+			}
 
 		// Enable client side placeholder substitution
 		case "interpolateParams":
diff --git a/infile.go b/infile.go
index cf892beae..555ef71ad 100644
--- a/infile.go
+++ b/infile.go
@@ -172,6 +172,7 @@ func (mc *okHandler) handleInFileRequest(name string) (err error) {
 	if ioErr := mc.conn().writePacket(data[:4]); ioErr != nil {
 		return ioErr
 	}
+	mc.conn().syncSequence()
 
 	// read OK packet
 	if err == nil {
diff --git a/packets.go b/packets.go
index 736e4418c..e4d2820ed 100644
--- a/packets.go
+++ b/packets.go
@@ -28,9 +28,16 @@ import (
 // Read packet to buffer 'data'
 func (mc *mysqlConn) readPacket() ([]byte, error) {
 	var prevData []byte
+	invalidSequence := false
+
+	readNext := mc.buf.readNext
+	if mc.compress {
+		readNext = mc.compIO.readNext
+	}
+
 	for {
 		// read packet header
-		data, err := mc.buf.readNext(4)
+		data, err := readNext(4, mc.readWithTimeout)
 		if err != nil {
 			mc.close()
 			if cerr := mc.canceled.Value(); cerr != nil {
@@ -41,17 +48,29 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
 		}
 
 		// packet length [24 bit]
-		pktLen := int(uint32(data[0]) | uint32(data[1])<<8 | uint32(data[2])<<16)
-
-		// check packet sync [8 bit]
-		if data[3] != mc.sequence {
-			mc.close()
-			if data[3] > mc.sequence {
-				return nil, ErrPktSyncMul
+		pktLen := getUint24(data[:3])
+		seq := data[3]
+
+		if mc.compress {
+			// MySQL and MariaDB doesn't check packet nr in compressed packet.
+			if debug && seq != mc.compressSequence {
+				fmt.Printf("[debug] mismatched compression sequence nr: expected: %v, got %v",
+					mc.compressSequence, seq)
+			}
+			mc.compressSequence = seq + 1
+		} else {
+			// check packet sync [8 bit]
+			if seq != mc.sequence {
+				mc.log(fmt.Sprintf("[warn] unexpected seq nr: expected %v, got %v", mc.sequence, seq))
+				// For large packets, we stop reading as soon as sync error.
+				if len(prevData) > 0 {
+					mc.close()
+					return nil, ErrPktSyncMul
+				}
+				invalidSequence = true
 			}
-			return nil, ErrPktSync
+			mc.sequence++
 		}
-		mc.sequence++
 
 		// packets with length 0 terminate a previous packet which is a
 		// multiple of (2^24)-1 bytes long
@@ -62,12 +81,11 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
 				mc.close()
 				return nil, ErrInvalidConn
 			}
-
 			return prevData, nil
 		}
 
 		// read packet body [pktLen bytes]
-		data, err = mc.buf.readNext(pktLen)
+		data, err = readNext(pktLen, mc.readWithTimeout)
 		if err != nil {
 			mc.close()
 			if cerr := mc.canceled.Value(); cerr != nil {
@@ -80,11 +98,18 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
 		// return data if this was the last packet
 		if pktLen < maxPacketSize {
 			// zero allocations for non-split packets
-			if prevData == nil {
-				return data, nil
+			if prevData != nil {
+				data = append(prevData, data...)
 			}
-
-			return append(prevData, data...), nil
+			if invalidSequence {
+				mc.close()
+				// return sync error only for regular packet.
+				// error packets may have wrong sequence number.
+				if data[0] != iERR {
+					return nil, ErrPktSync
+				}
+			}
+			return data, nil
 		}
 
 		prevData = append(prevData, data...)
@@ -94,36 +119,26 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
 // Write packet buffer 'data'
 func (mc *mysqlConn) writePacket(data []byte) error {
 	pktLen := len(data) - 4
-
 	if pktLen > mc.maxAllowedPacket {
 		return ErrPktTooLarge
 	}
 
+	writeFunc := mc.writeWithTimeout
+	if mc.compress {
+		writeFunc = mc.compIO.writePackets
+	}
+
 	for {
-		var size int
-		if pktLen >= maxPacketSize {
-			data[0] = 0xff
-			data[1] = 0xff
-			data[2] = 0xff
-			size = maxPacketSize
-		} else {
-			data[0] = byte(pktLen)
-			data[1] = byte(pktLen >> 8)
-			data[2] = byte(pktLen >> 16)
-			size = pktLen
-		}
+		size := min(maxPacketSize, pktLen)
+		putUint24(data[:3], size)
 		data[3] = mc.sequence
 
 		// Write packet
-		if mc.writeTimeout > 0 {
-			if err := mc.netConn.SetWriteDeadline(time.Now().Add(mc.writeTimeout)); err != nil {
-				mc.cleanup()
-				mc.log(err)
-				return err
-			}
+		if debug {
+			fmt.Printf("writePacket: size=%v seq=%v", size, mc.sequence)
 		}
 
-		n, err := mc.netConn.Write(data[:4+size])
+		n, err := writeFunc(data[:4+size])
 		if err != nil {
 			mc.cleanup()
 			if cerr := mc.canceled.Value(); cerr != nil {
@@ -267,7 +282,9 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 	if mc.cfg.ClientFoundRows {
 		clientFlags |= clientFoundRows
 	}
-
+	if mc.cfg.compress && mc.flags&clientCompress == clientCompress {
+		clientFlags |= clientCompress
+	}
 	// To enable TLS / SSL
 	if mc.cfg.TLS != nil {
 		clientFlags |= clientSSL
@@ -358,7 +375,6 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 			return err
 		}
 		mc.netConn = tlsConn
-		mc.buf.nc = tlsConn
 	}
 
 	// User [null terminated string]
@@ -413,7 +429,7 @@ func (mc *mysqlConn) writeAuthSwitchPacket(authData []byte) error {
 
 func (mc *mysqlConn) writeCommandPacket(command byte) error {
 	// Reset Packet Sequence
-	mc.sequence = 0
+	mc.resetSequence()
 
 	data, err := mc.buf.takeSmallBuffer(4 + 1)
 	if err != nil {
@@ -429,7 +445,7 @@ func (mc *mysqlConn) writeCommandPacket(command byte) error {
 
 func (mc *mysqlConn) writeCommandPacketStr(command byte, arg string) error {
 	// Reset Packet Sequence
-	mc.sequence = 0
+	mc.resetSequence()
 
 	pktLen := 1 + len(arg)
 	data, err := mc.buf.takeBuffer(pktLen + 4)
@@ -444,12 +460,14 @@ func (mc *mysqlConn) writeCommandPacketStr(command byte, arg string) error {
 	copy(data[5:], arg)
 
 	// Send CMD packet
-	return mc.writePacket(data)
+	err = mc.writePacket(data)
+	mc.syncSequence()
+	return err
 }
 
 func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
 	// Reset Packet Sequence
-	mc.sequence = 0
+	mc.resetSequence()
 
 	data, err := mc.buf.takeSmallBuffer(4 + 1 + 4)
 	if err != nil {
@@ -932,7 +950,7 @@ func (stmt *mysqlStmt) writeCommandLongData(paramID int, arg []byte) error {
 			pktLen = dataOffset + argLen
 		}
 
-		stmt.mc.sequence = 0
+		stmt.mc.resetSequence()
 		// Add command byte [1 byte]
 		data[4] = comStmtSendLongData
 
@@ -953,11 +971,10 @@ func (stmt *mysqlStmt) writeCommandLongData(paramID int, arg []byte) error {
 			continue
 		}
 		return err
-
 	}
 
 	// Reset Packet Sequence
-	stmt.mc.sequence = 0
+	stmt.mc.resetSequence()
 	return nil
 }
 
@@ -982,7 +999,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 	}
 
 	// Reset packet-sequence
-	mc.sequence = 0
+	mc.resetSequence()
 
 	var data []byte
 	var err error
@@ -1198,7 +1215,9 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 		data = data[:pos]
 	}
 
-	return mc.writePacket(data)
+	err = mc.writePacket(data)
+	mc.syncSequence()
+	return err
 }
 
 // For each remaining resultset in the stream, discards its rows and updates
diff --git a/packets_test.go b/packets_test.go
index fa4683eab..694b0564c 100644
--- a/packets_test.go
+++ b/packets_test.go
@@ -98,7 +98,7 @@ func newRWMockConn(sequence uint8) (*mockConn, *mysqlConn) {
 	conn := new(mockConn)
 	connector := newConnector(NewConfig())
 	mc := &mysqlConn{
-		buf:              newBuffer(conn),
+		buf:              newBuffer(),
 		cfg:              connector.cfg,
 		connector:        connector,
 		netConn:          conn,
@@ -112,7 +112,9 @@ func newRWMockConn(sequence uint8) (*mockConn, *mysqlConn) {
 func TestReadPacketSingleByte(t *testing.T) {
 	conn := new(mockConn)
 	mc := &mysqlConn{
-		buf: newBuffer(conn),
+		netConn: conn,
+		buf:     newBuffer(),
+		cfg:     NewConfig(),
 	}
 
 	conn.data = []byte{0x01, 0x00, 0x00, 0x00, 0xff}
@@ -143,12 +145,12 @@ func TestReadPacketWrongSequenceID(t *testing.T) {
 		{
 			ClientSequenceID: 0,
 			ServerSequenceID: 0x42,
-			ExpectedErr:      ErrPktSyncMul,
+			ExpectedErr:      ErrPktSync,
 		},
 	} {
 		conn, mc := newRWMockConn(testCase.ClientSequenceID)
 
-		conn.data = []byte{0x01, 0x00, 0x00, testCase.ServerSequenceID, 0xff}
+		conn.data = []byte{0x01, 0x00, 0x00, testCase.ServerSequenceID, 0x22}
 		_, err := mc.readPacket()
 		if err != testCase.ExpectedErr {
 			t.Errorf("expected %v, got %v", testCase.ExpectedErr, err)
@@ -164,7 +166,9 @@ func TestReadPacketWrongSequenceID(t *testing.T) {
 func TestReadPacketSplit(t *testing.T) {
 	conn := new(mockConn)
 	mc := &mysqlConn{
-		buf: newBuffer(conn),
+		netConn: conn,
+		buf:     newBuffer(),
+		cfg:     NewConfig(),
 	}
 
 	data := make([]byte, maxPacketSize*2+4*3)
@@ -269,7 +273,8 @@ func TestReadPacketSplit(t *testing.T) {
 func TestReadPacketFail(t *testing.T) {
 	conn := new(mockConn)
 	mc := &mysqlConn{
-		buf:     newBuffer(conn),
+		netConn: conn,
+		buf:     newBuffer(),
 		closech: make(chan struct{}),
 		cfg:     NewConfig(),
 	}
@@ -285,7 +290,7 @@ func TestReadPacketFail(t *testing.T) {
 	// reset
 	conn.reads = 0
 	mc.sequence = 0
-	mc.buf = newBuffer(conn)
+	mc.buf = newBuffer()
 
 	// fail to read header
 	conn.closed = true
@@ -298,7 +303,7 @@ func TestReadPacketFail(t *testing.T) {
 	conn.closed = false
 	conn.reads = 0
 	mc.sequence = 0
-	mc.buf = newBuffer(conn)
+	mc.buf = newBuffer()
 
 	// fail to read body
 	conn.maxReads = 1
@@ -313,7 +318,8 @@ func TestReadPacketFail(t *testing.T) {
 func TestRegression801(t *testing.T) {
 	conn := new(mockConn)
 	mc := &mysqlConn{
-		buf:      newBuffer(conn),
+		netConn:  conn,
+		buf:      newBuffer(),
 		cfg:      new(Config),
 		sequence: 42,
 		closech:  make(chan struct{}),
diff --git a/utils.go b/utils.go
index cda24fe74..d902f3b60 100644
--- a/utils.go
+++ b/utils.go
@@ -490,6 +490,18 @@ func formatBinaryTime(src []byte, length uint8) (driver.Value, error) {
 *                       Convert from and to bytes                             *
 ******************************************************************************/
 
+// 24bit integer: used for packet headers.
+
+func putUint24(data []byte, n int) {
+	data[2] = byte(n >> 16)
+	data[1] = byte(n >> 8)
+	data[0] = byte(n)
+}
+
+func getUint24(data []byte) int {
+	return int(data[2])<<16 | int(data[1])<<8 | int(data[0])
+}
+
 func uint64ToBytes(n uint64) []byte {
 	return []byte{
 		byte(n),

From b335ed33d6a10803949fb71bbd7e0974c5be38b2 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Fri, 20 Dec 2024 14:13:32 +0900
Subject: [PATCH 272/282] use binary.LittleEndian (#1651)

Recent Go does inlinine functions well.
Using `LittleEndian.Put*` would better for readability and minimize bound check.

Additionally, Go 1.19 introduced `LittleEndian.Append*`. It reduce more code.
---
 packets.go | 72 ++++++++----------------------------------------------
 utils.go   | 29 ++++++----------------
 2 files changed, 17 insertions(+), 84 deletions(-)

diff --git a/packets.go b/packets.go
index e4d2820ed..f3860c5f8 100644
--- a/packets.go
+++ b/packets.go
@@ -329,16 +329,10 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
 	}
 
 	// ClientFlags [32 bit]
-	data[4] = byte(clientFlags)
-	data[5] = byte(clientFlags >> 8)
-	data[6] = byte(clientFlags >> 16)
-	data[7] = byte(clientFlags >> 24)
+	binary.LittleEndian.PutUint32(data[4:], uint32(clientFlags))
 
 	// MaxPacketSize [32 bit] (none)
-	data[8] = 0x00
-	data[9] = 0x00
-	data[10] = 0x00
-	data[11] = 0x00
+	binary.LittleEndian.PutUint32(data[8:], 0)
 
 	// Collation ID [1 byte]
 	data[12] = defaultCollationID
@@ -478,10 +472,7 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
 	data[4] = command
 
 	// Add arg [32 bit]
-	data[5] = byte(arg)
-	data[6] = byte(arg >> 8)
-	data[7] = byte(arg >> 16)
-	data[8] = byte(arg >> 24)
+	binary.LittleEndian.PutUint32(data[5:], arg)
 
 	// Send CMD packet
 	return mc.writePacket(data)
@@ -955,14 +946,10 @@ func (stmt *mysqlStmt) writeCommandLongData(paramID int, arg []byte) error {
 		data[4] = comStmtSendLongData
 
 		// Add stmtID [32 bit]
-		data[5] = byte(stmt.id)
-		data[6] = byte(stmt.id >> 8)
-		data[7] = byte(stmt.id >> 16)
-		data[8] = byte(stmt.id >> 24)
+		binary.LittleEndian.PutUint32(data[5:], stmt.id)
 
 		// Add paramID [16 bit]
-		data[9] = byte(paramID)
-		data[10] = byte(paramID >> 8)
+		binary.LittleEndian.PutUint16(data[9:], uint16(paramID))
 
 		// Send CMD packet
 		err := stmt.mc.writePacket(data[:4+pktLen])
@@ -1018,19 +1005,13 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 	data[4] = comStmtExecute
 
 	// statement_id [4 bytes]
-	data[5] = byte(stmt.id)
-	data[6] = byte(stmt.id >> 8)
-	data[7] = byte(stmt.id >> 16)
-	data[8] = byte(stmt.id >> 24)
+	binary.LittleEndian.PutUint32(data[5:], stmt.id)
 
 	// flags (0: CURSOR_TYPE_NO_CURSOR) [1 byte]
 	data[9] = 0x00
 
 	// iteration_count (uint32(1)) [4 bytes]
-	data[10] = 0x01
-	data[11] = 0x00
-	data[12] = 0x00
-	data[13] = 0x00
+	binary.LittleEndian.PutUint32(data[10:], 1)
 
 	if len(args) > 0 {
 		pos := minPktLen
@@ -1084,50 +1065,17 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 			case int64:
 				paramTypes[i+i] = byte(fieldTypeLongLong)
 				paramTypes[i+i+1] = 0x00
-
-				if cap(paramValues)-len(paramValues)-8 >= 0 {
-					paramValues = paramValues[:len(paramValues)+8]
-					binary.LittleEndian.PutUint64(
-						paramValues[len(paramValues)-8:],
-						uint64(v),
-					)
-				} else {
-					paramValues = append(paramValues,
-						uint64ToBytes(uint64(v))...,
-					)
-				}
+				paramValues = binary.LittleEndian.AppendUint64(paramValues, uint64(v))
 
 			case uint64:
 				paramTypes[i+i] = byte(fieldTypeLongLong)
 				paramTypes[i+i+1] = 0x80 // type is unsigned
-
-				if cap(paramValues)-len(paramValues)-8 >= 0 {
-					paramValues = paramValues[:len(paramValues)+8]
-					binary.LittleEndian.PutUint64(
-						paramValues[len(paramValues)-8:],
-						uint64(v),
-					)
-				} else {
-					paramValues = append(paramValues,
-						uint64ToBytes(uint64(v))...,
-					)
-				}
+				paramValues = binary.LittleEndian.AppendUint64(paramValues, uint64(v))
 
 			case float64:
 				paramTypes[i+i] = byte(fieldTypeDouble)
 				paramTypes[i+i+1] = 0x00
-
-				if cap(paramValues)-len(paramValues)-8 >= 0 {
-					paramValues = paramValues[:len(paramValues)+8]
-					binary.LittleEndian.PutUint64(
-						paramValues[len(paramValues)-8:],
-						math.Float64bits(v),
-					)
-				} else {
-					paramValues = append(paramValues,
-						uint64ToBytes(math.Float64bits(v))...,
-					)
-				}
+				paramValues = binary.LittleEndian.AppendUint64(paramValues, math.Float64bits(v))
 
 			case bool:
 				paramTypes[i+i] = byte(fieldTypeTiny)
diff --git a/utils.go b/utils.go
index d902f3b60..44f43ef7b 100644
--- a/utils.go
+++ b/utils.go
@@ -502,19 +502,6 @@ func getUint24(data []byte) int {
 	return int(data[2])<<16 | int(data[1])<<8 | int(data[0])
 }
 
-func uint64ToBytes(n uint64) []byte {
-	return []byte{
-		byte(n),
-		byte(n >> 8),
-		byte(n >> 16),
-		byte(n >> 24),
-		byte(n >> 32),
-		byte(n >> 40),
-		byte(n >> 48),
-		byte(n >> 56),
-	}
-}
-
 func uint64ToString(n uint64) []byte {
 	var a [20]byte
 	i := 20
@@ -598,18 +585,15 @@ func readLengthEncodedInteger(b []byte) (uint64, bool, int) {
 
 	// 252: value of following 2
 	case 0xfc:
-		return uint64(b[1]) | uint64(b[2])<<8, false, 3
+		return uint64(binary.LittleEndian.Uint16(b[1:])), false, 3
 
 	// 253: value of following 3
 	case 0xfd:
-		return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16, false, 4
+		return uint64(getUint24(b[1:])), false, 4
 
 	// 254: value of following 8
 	case 0xfe:
-		return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16 |
-				uint64(b[4])<<24 | uint64(b[5])<<32 | uint64(b[6])<<40 |
-				uint64(b[7])<<48 | uint64(b[8])<<56,
-			false, 9
+		return uint64(binary.LittleEndian.Uint64(b[1:])), false, 9
 	}
 
 	// 0-250: value of first byte
@@ -623,13 +607,14 @@ func appendLengthEncodedInteger(b []byte, n uint64) []byte {
 		return append(b, byte(n))
 
 	case n <= 0xffff:
-		return append(b, 0xfc, byte(n), byte(n>>8))
+		b = append(b, 0xfc)
+		return binary.LittleEndian.AppendUint16(b, uint16(n))
 
 	case n <= 0xffffff:
 		return append(b, 0xfd, byte(n), byte(n>>8), byte(n>>16))
 	}
-	return append(b, 0xfe, byte(n), byte(n>>8), byte(n>>16), byte(n>>24),
-		byte(n>>32), byte(n>>40), byte(n>>48), byte(n>>56))
+	b = append(b, 0xfe)
+	return binary.LittleEndian.AppendUint64(b, n)
 }
 
 func appendLengthEncodedString(b []byte, s string) []byte {

From 7403860363ca112af503b4612568c3096fecb466 Mon Sep 17 00:00:00 2001
From: Artur Melanchyk <arturmelanchyk@users.noreply.github.com>
Date: Tue, 24 Dec 2024 05:10:11 +0100
Subject: [PATCH 273/282] Make fileRegister a set (#1653)

---
 AUTHORS   |  1 +
 infile.go | 10 +++++-----
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index cbcc90f51..a38395797 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -20,6 +20,7 @@ Andrew Reid <andrew.reid at tixtrack.com>
 Animesh Ray <mail.rayanimesh at gmail.com>
 Arne Hormann <arnehormann at gmail.com>
 Ariel Mashraki <ariel at mashraki.co.il>
+Artur Melanchyk <artur.melanchyk@gmail.com>
 Asta Xie <xiemengjun at gmail.com>
 B Lamarche <blam413 at gmail.com>
 Brian Hendriks <brian at dolthub.com>
diff --git a/infile.go b/infile.go
index 555ef71ad..453ae091e 100644
--- a/infile.go
+++ b/infile.go
@@ -17,7 +17,7 @@ import (
 )
 
 var (
-	fileRegister       map[string]bool
+	fileRegister       map[string]struct{}
 	fileRegisterLock   sync.RWMutex
 	readerRegister     map[string]func() io.Reader
 	readerRegisterLock sync.RWMutex
@@ -37,10 +37,10 @@ func RegisterLocalFile(filePath string) {
 	fileRegisterLock.Lock()
 	// lazy map init
 	if fileRegister == nil {
-		fileRegister = make(map[string]bool)
+		fileRegister = make(map[string]struct{})
 	}
 
-	fileRegister[strings.Trim(filePath, `"`)] = true
+	fileRegister[strings.Trim(filePath, `"`)] = struct{}{}
 	fileRegisterLock.Unlock()
 }
 
@@ -123,9 +123,9 @@ func (mc *okHandler) handleInFileRequest(name string) (err error) {
 	} else { // File
 		name = strings.Trim(name, `"`)
 		fileRegisterLock.RLock()
-		fr := fileRegister[name]
+		_, exists := fileRegister[name]
 		fileRegisterLock.RUnlock()
-		if mc.cfg.AllowAllFiles || fr {
+		if mc.cfg.AllowAllFiles || exists {
 			var file *os.File
 			var fi os.FileInfo
 

From 255d1ad98f1d3be99661d2a8c0a7a91418acbc8d Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Wed, 22 Jan 2025 14:59:24 +0900
Subject: [PATCH 274/282] better max_allowed_packet parsing (#1661)

Remove `stringToInt()` and use `strconv.Atoi` instead.
---
 connector.go |  8 +++++++-
 utils.go     | 10 ----------
 2 files changed, 7 insertions(+), 11 deletions(-)

diff --git a/connector.go b/connector.go
index a4f3655ef..bc1d46afc 100644
--- a/connector.go
+++ b/connector.go
@@ -11,6 +11,7 @@ package mysql
 import (
 	"context"
 	"database/sql/driver"
+	"fmt"
 	"net"
 	"os"
 	"strconv"
@@ -179,7 +180,12 @@ func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
 			mc.Close()
 			return nil, err
 		}
-		mc.maxAllowedPacket = stringToInt(maxap) - 1
+		n, err := strconv.Atoi(string(maxap))
+		if err != nil {
+			mc.Close()
+			return nil, fmt.Errorf("invalid max_allowed_packet value (%q): %w", maxap, err)
+		}
+		mc.maxAllowedPacket = n - 1
 	}
 	if mc.maxAllowedPacket < maxPacketSize {
 		mc.maxWriteSize = mc.maxAllowedPacket
diff --git a/utils.go b/utils.go
index 44f43ef7b..8716c26c5 100644
--- a/utils.go
+++ b/utils.go
@@ -524,16 +524,6 @@ func uint64ToString(n uint64) []byte {
 	return a[i:]
 }
 
-// treats string value as unsigned integer representation
-func stringToInt(b []byte) int {
-	val := 0
-	for i := range b {
-		val *= 10
-		val += int(b[i] - 0x30)
-	}
-	return val
-}
-
 // returns the string read as a bytes slice, whether the value is NULL,
 // the number of bytes read and an error, in case the string is longer than
 // the input slice

From 85c6311943c82f1300077b2d0e94687106ab61e7 Mon Sep 17 00:00:00 2001
From: Kolbe Kegel <kolbek@squareup.com>
Date: Thu, 23 Jan 2025 01:32:31 -0800
Subject: [PATCH 275/282] Add error 1290/ER_READ_ONLY_MODE to rejectReadOnly
 handling (#1660)

---
 packets.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/packets.go b/packets.go
index f3860c5f8..9951bdf80 100644
--- a/packets.go
+++ b/packets.go
@@ -574,7 +574,8 @@ func (mc *mysqlConn) handleErrorPacket(data []byte) error {
 
 	// 1792: ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION
 	// 1290: ER_OPTION_PREVENTS_STATEMENT (returned by Aurora during failover)
-	if (errno == 1792 || errno == 1290) && mc.cfg.RejectReadOnly {
+	// 1836: ER_READ_ONLY_MODE
+	if (errno == 1792 || errno == 1290 || errno == 1836) && mc.cfg.RejectReadOnly {
 		// Oops; we are connected to a read-only connection, and won't be able
 		// to issue any write statements. Since RejectReadOnly is configured,
 		// we throw away this connection hoping this one would have write

From 341a5a5246835b2ac4b8d36bb12a9dfad70663f4 Mon Sep 17 00:00:00 2001
From: Bes Dollma <143414965+bdollma-te@users.noreply.github.com>
Date: Wed, 29 Jan 2025 07:59:01 +0200
Subject: [PATCH 276/282] Fix auth_switch_request packet handling

auth_data contains last NUL.

Fix #1666

Signed-off-by: Bes Dollma (bdollma) <bdollma@cisco.com>
---
 AUTHORS      |  2 ++
 auth_test.go | 24 ++++++++++++------------
 packets.go   |  3 +++
 3 files changed, 17 insertions(+), 12 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index a38395797..123b5dc50 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -23,6 +23,7 @@ Ariel Mashraki <ariel at mashraki.co.il>
 Artur Melanchyk <artur.melanchyk@gmail.com>
 Asta Xie <xiemengjun at gmail.com>
 B Lamarche <blam413 at gmail.com>
+Bes Dollma <bdollma@thousandeyes.com>
 Brian Hendriks <brian at dolthub.com>
 Bulat Gaifullin <gaifullinbf at gmail.com>
 Caine Jette <jette at alum.mit.edu>
@@ -146,4 +147,5 @@ PingCAP Inc.
 Pivotal Inc.
 Shattered Silicon Ltd.
 Stripe Inc.
+ThousandEyes
 Zendesk Inc.
diff --git a/auth_test.go b/auth_test.go
index 8caed1fff..46e1e3b4e 100644
--- a/auth_test.go
+++ b/auth_test.go
@@ -734,9 +734,9 @@ func TestAuthSwitchCachingSHA256PasswordCached(t *testing.T) {
 
 	expectedReply := []byte{
 		// 1. Packet: Hash
-		32, 0, 0, 3, 129, 93, 132, 95, 114, 48, 79, 215, 128, 62, 193, 118, 128,
-		54, 75, 208, 159, 252, 227, 215, 129, 15, 242, 97, 19, 159, 31, 20, 58,
-		153, 9, 130,
+		32, 0, 0, 3, 219, 72, 64, 97, 56, 197, 167, 203, 64, 236, 168, 80, 223,
+		56, 103, 217, 196, 176, 124, 60, 253, 41, 195, 10, 205, 190, 177, 206, 63,
+		118, 211, 69,
 	}
 	if !bytes.Equal(conn.written, expectedReply) {
 		t.Errorf("got unexpected data: %v", conn.written)
@@ -803,9 +803,9 @@ func TestAuthSwitchCachingSHA256PasswordFullRSA(t *testing.T) {
 
 	expectedReplyPrefix := []byte{
 		// 1. Packet: Hash
-		32, 0, 0, 3, 129, 93, 132, 95, 114, 48, 79, 215, 128, 62, 193, 118, 128,
-		54, 75, 208, 159, 252, 227, 215, 129, 15, 242, 97, 19, 159, 31, 20, 58,
-		153, 9, 130,
+		32, 0, 0, 3, 219, 72, 64, 97, 56, 197, 167, 203, 64, 236, 168, 80, 223,
+		56, 103, 217, 196, 176, 124, 60, 253, 41, 195, 10, 205, 190, 177, 206, 63,
+		118, 211, 69,
 
 		// 2. Packet: Pub Key Request
 		1, 0, 0, 5, 2,
@@ -848,9 +848,9 @@ func TestAuthSwitchCachingSHA256PasswordFullRSAWithKey(t *testing.T) {
 
 	expectedReplyPrefix := []byte{
 		// 1. Packet: Hash
-		32, 0, 0, 3, 129, 93, 132, 95, 114, 48, 79, 215, 128, 62, 193, 118, 128,
-		54, 75, 208, 159, 252, 227, 215, 129, 15, 242, 97, 19, 159, 31, 20, 58,
-		153, 9, 130,
+		32, 0, 0, 3, 219, 72, 64, 97, 56, 197, 167, 203, 64, 236, 168, 80, 223,
+		56, 103, 217, 196, 176, 124, 60, 253, 41, 195, 10, 205, 190, 177, 206, 63,
+		118, 211, 69,
 
 		// 2. Packet: Encrypted Password
 		0, 1, 0, 5, // [changing bytes]
@@ -891,9 +891,9 @@ func TestAuthSwitchCachingSHA256PasswordFullSecure(t *testing.T) {
 
 	expectedReply := []byte{
 		// 1. Packet: Hash
-		32, 0, 0, 3, 129, 93, 132, 95, 114, 48, 79, 215, 128, 62, 193, 118, 128,
-		54, 75, 208, 159, 252, 227, 215, 129, 15, 242, 97, 19, 159, 31, 20, 58,
-		153, 9, 130,
+		32, 0, 0, 3, 219, 72, 64, 97, 56, 197, 167, 203, 64, 236, 168, 80, 223,
+		56, 103, 217, 196, 176, 124, 60, 253, 41, 195, 10, 205, 190, 177, 206, 63,
+		118, 211, 69,
 
 		// 2. Packet: Cleartext password
 		7, 0, 0, 5, 115, 101, 99, 114, 101, 116, 0,
diff --git a/packets.go b/packets.go
index 9951bdf80..4b8362160 100644
--- a/packets.go
+++ b/packets.go
@@ -510,6 +510,9 @@ func (mc *mysqlConn) readAuthResult() ([]byte, string, error) {
 		}
 		plugin := string(data[1:pluginEndIndex])
 		authData := data[pluginEndIndex+1:]
+		if len(authData) > 0 && authData[len(authData)-1] == 0 {
+			authData = authData[:len(authData)-1]
+		}
 		return authData, plugin, nil
 
 	default: // Error otherwise

From 5d1bb8a9cf03422554dd52abf5eba89b8ca11307 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Tue, 18 Feb 2025 12:05:50 +0900
Subject: [PATCH 277/282] fix flaky test. (#1663)

TestIssue1567 fails by max_connections error. This makes our CI unhappy.

https://github.com/go-sql-driver/mysql/actions/runs/12904961433/job/35984402310
---
 driver_test.go | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/driver_test.go b/driver_test.go
index 58b3cb38d..00e828657 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -3608,6 +3608,12 @@ func runCallCommand(dbt *DBTest, query, name string) {
 func TestIssue1567(t *testing.T) {
 	// enable TLS.
 	runTests(t, dsn+"&tls=skip-verify", func(dbt *DBTest) {
+		var max int
+		err := dbt.db.QueryRow("SELECT @@max_connections").Scan(&max)
+		if err != nil {
+			dbt.Fatalf("%s", err.Error())
+		}
+
 		// disable connection pooling.
 		// data race happens when new connection is created.
 		dbt.db.SetMaxIdleConns(0)
@@ -3627,6 +3633,9 @@ func TestIssue1567(t *testing.T) {
 		if testing.Short() {
 			count = 10
 		}
+		if count > max {
+			count = max
+		}
 
 		for i := 0; i < count; i++ {
 			timeout := time.Duration(mrand.Int63n(int64(rtt)))

From 58941dd8a7888cf3d593d7bb182120e42168eac9 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Tue, 18 Feb 2025 12:37:58 +0900
Subject: [PATCH 278/282] release v1.9.0 (#1662)

---
 CHANGELOG.md | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0c9bd9b10..d8c3aac1e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,28 @@
+# Changelog
+
+## v1.9.0 (2025-02-18)
+
+### Major Changes
+
+- Implement zlib compression. (#1487)
+- Supported Go version is updated to Go 1.21+. (#1639)
+- Add support for VECTOR type introduced in MySQL 9.0. (#1609)
+- Config object can have custom dial function. (#1527)
+
+### Bugfixes
+
+- Fix auth errors when username/password are too long. (#1625)
+- Check if MySQL supports CLIENT_CONNECT_ATTRS before sending client attributes. (#1640)
+- Fix auth switch request handling. (#1666)
+
+### Other changes
+
+- Add "filename:line" prefix to log in go-mysql. Custom loggers now show it. (#1589)
+- Improve error handling. It reduces the "busy buffer" errors. (#1595, #1601, #1641)
+- Use `strconv.Atoi` to parse max_allowed_packet. (#1661)
+- `rejectReadOnly` option now handles ER_READ_ONLY_MODE (1290) error too. (#1660)
+
+
 ## Version 1.8.1 (2024-03-26)
 
 Bugfixes:

From c87981610c07572d94be59d39550be1e3b1b5bb3 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Mon, 10 Mar 2025 11:33:49 +0900
Subject: [PATCH 279/282] add Charset() option (#1679)

Fix #1664.
---
 dsn.go | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/dsn.go b/dsn.go
index 9b560b735..9bafab441 100644
--- a/dsn.go
+++ b/dsn.go
@@ -44,7 +44,6 @@ type Config struct {
 	DBName               string            // Database name
 	Params               map[string]string // Connection parameters
 	ConnectionAttributes string            // Connection Attributes, comma-delimited string of user-defined "key:value" pairs
-	charsets             []string          // Connection charset. When set, this will be set in SET NAMES <charset> query
 	Collation            string            // Connection collation. When set, this will be set in SET NAMES <charset> COLLATE <collation> query
 	Loc                  *time.Location    // Location for time.Time values
 	MaxAllowedPacket     int               // Max packet size allowed
@@ -81,6 +80,7 @@ type Config struct {
 	beforeConnect func(context.Context, *Config) error // Invoked before a connection is established
 	pubKey        *rsa.PublicKey                       // Server public key
 	timeTruncate  time.Duration                        // Truncate time.Time values to the specified duration
+	charsets      []string                             // Connection charset. When set, this will be set in SET NAMES <charset> query
 }
 
 // Functional Options Pattern
@@ -135,6 +135,21 @@ func EnableCompression(yes bool) Option {
 	}
 }
 
+// Charset sets the connection charset and collation.
+//
+// charset is the connection charset.
+// collation is the connection collation. It can be null or empty string.
+//
+// When collation is not specified, `SET NAMES <charset>` command is sent when the connection is established.
+// When collation is specified, `SET NAMES <charset> COLLATE <collation>` command is sent when the connection is established.
+func Charset(charset, collation string) Option {
+	return func(cfg *Config) error {
+		cfg.charsets = []string{charset}
+		cfg.Collation = collation
+		return nil
+	}
+}
+
 func (cfg *Config) Clone() *Config {
 	cp := *cfg
 	if cp.TLS != nil {

From 88ff88b5915d34bde2b2c59991c586abb8ea9eca Mon Sep 17 00:00:00 2001
From: Bogdan Constantinescu <bog.con.bc@gmail.com>
Date: Mon, 10 Mar 2025 04:48:22 +0200
Subject: [PATCH 280/282] Fix FormatDSN missing ConnectionAttributes (#1619)

Fix #1618
---
 AUTHORS     | 1 +
 dsn.go      | 4 ++++
 dsn_test.go | 6 +++++-
 3 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/AUTHORS b/AUTHORS
index 123b5dc50..510b869b7 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -24,6 +24,7 @@ Artur Melanchyk <artur.melanchyk@gmail.com>
 Asta Xie <xiemengjun at gmail.com>
 B Lamarche <blam413 at gmail.com>
 Bes Dollma <bdollma@thousandeyes.com>
+Bogdan Constantinescu <bog.con.bc at gmail.com>
 Brian Hendriks <brian at dolthub.com>
 Bulat Gaifullin <gaifullinbf at gmail.com>
 Caine Jette <jette at alum.mit.edu>
diff --git a/dsn.go b/dsn.go
index 9bafab441..ecf62567a 100644
--- a/dsn.go
+++ b/dsn.go
@@ -322,6 +322,10 @@ func (cfg *Config) FormatDSN() string {
 		writeDSNParam(&buf, &hasParam, "columnsWithAlias", "true")
 	}
 
+	if cfg.ConnectionAttributes != "" {
+		writeDSNParam(&buf, &hasParam, "connectionAttributes", url.QueryEscape(cfg.ConnectionAttributes))
+	}
+
 	if cfg.compress {
 		writeDSNParam(&buf, &hasParam, "compress", "true")
 	}
diff --git a/dsn_test.go b/dsn_test.go
index 863d14824..436f77992 100644
--- a/dsn_test.go
+++ b/dsn_test.go
@@ -77,6 +77,9 @@ var testDSNs = []struct {
 }, {
 	"user:password@/dbname?loc=UTC&timeout=30s&parseTime=true&timeTruncate=1h",
 	&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Loc: time.UTC, Timeout: 30 * time.Second, ParseTime: true, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, timeTruncate: time.Hour},
+}, {
+	"foo:bar@tcp(192.168.1.50:3307)/baz?timeout=10s&connectionAttributes=program_name:MySQLGoDriver%2FTest,program_version:1.2.3",
+	&Config{User: "foo", Passwd: "bar", Net: "tcp", Addr: "192.168.1.50:3307", DBName: "baz", Loc: time.UTC, Timeout: 10 * time.Second, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, AllowNativePasswords: true, CheckConnLiveness: true, ConnectionAttributes: "program_name:MySQLGoDriver/Test,program_version:1.2.3"},
 },
 }
 
@@ -109,7 +112,8 @@ func TestDSNParserInvalid(t *testing.T) {
 		"User:pass@tcp(1.2.3.4:3306)",           // no trailing slash
 		"net()/",                                // unknown default addr
 		"user:pass@tcp(127.0.0.1:3306)/db/name", // invalid dbname
-		"user:password@/dbname?allowFallbackToPlaintext=PREFERRED", // wrong bool flag
+		"user:password@/dbname?allowFallbackToPlaintext=PREFERRED",          // wrong bool flag
+		"user:password@/dbname?connectionAttributes=attr1:/unescaped/value", // unescaped
 		//"/dbname?arg=/some/unescaped/path",
 	}
 

From b84ac5af9c77b13e4c6417e484cbed087cd672f3 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Fri, 14 Mar 2025 22:05:00 +0900
Subject: [PATCH 281/282] go.mod: fix go version format (#1682)

As of Go 1.21, toolchain versions must use the 1.N.P syntax.
https://go.dev/doc/toolchain#version
---
 go.mod | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/go.mod b/go.mod
index 33c4dd5b1..187aff179 100644
--- a/go.mod
+++ b/go.mod
@@ -1,5 +1,5 @@
 module github.com/go-sql-driver/mysql
 
-go 1.21
+go 1.21.0
 
 require filippo.io/edwards25519 v1.1.0

From 1fbafa8082dab81e2c2e8caeb55d569dfeafcf94 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
Date: Fri, 21 Mar 2025 11:04:43 +0900
Subject: [PATCH 282/282] release v1.9.1 (#1683)

---
 CHANGELOG.md | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index d8c3aac1e..a1b23c66b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,16 @@
 # Changelog
 
+## v1.9.1 (2025-03-21)
+
+### Major Changes
+
+* Add Charset() option. (#1679)
+
+### Bugfixes
+
+* go.mod: fix go version format (#1682)
+* Fix FormatDSN missing ConnectionAttributes (#1619)
+
 ## v1.9.0 (2025-02-18)
 
 ### Major Changes