Merge pull request #823 from github/update-go-sql-driver-2020-02

Update go-sql-driver to latest
This commit is contained in:
Tim Vaillancourt 2020-08-19 21:07:58 +02:00 committed by GitHub
commit 835e86e682
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 601 additions and 476 deletions

View File

@ -1,10 +1,10 @@
sudo: false sudo: false
language: go language: go
go: go:
- 1.9.x
- 1.10.x - 1.10.x
- 1.11.x - 1.11.x
- 1.12.x - 1.12.x
- 1.13.x
- master - master
before_install: before_install:
@ -105,6 +105,7 @@ matrix:
homebrew: homebrew:
packages: packages:
- mysql - mysql
update: true
go: 1.12.x go: 1.12.x
before_install: before_install:
- go get golang.org/x/tools/cmd/cover - go get golang.org/x/tools/cmd/cover

View File

@ -13,6 +13,7 @@
Aaron Hopkins <go-sql-driver at die.net> Aaron Hopkins <go-sql-driver at die.net>
Achille Roussel <achille.roussel at gmail.com> Achille Roussel <achille.roussel at gmail.com>
Alex Snast <alexsn at fb.com>
Alexey Palazhchenko <alexey.palazhchenko at gmail.com> Alexey Palazhchenko <alexey.palazhchenko at gmail.com>
Andrew Reid <andrew.reid at tixtrack.com> Andrew Reid <andrew.reid at tixtrack.com>
Arne Hormann <arnehormann at gmail.com> Arne Hormann <arnehormann at gmail.com>
@ -44,6 +45,7 @@ James Harr <james.harr at gmail.com>
Jeff Hodges <jeff at somethingsimilar.com> Jeff Hodges <jeff at somethingsimilar.com>
Jeffrey Charles <jeffreycharles at gmail.com> Jeffrey Charles <jeffreycharles at gmail.com>
Jerome Meyer <jxmeyer at gmail.com> Jerome Meyer <jxmeyer at gmail.com>
Jiajia Zhong <zhong2plus at gmail.com>
Jian Zhen <zhenjl at gmail.com> Jian Zhen <zhenjl at gmail.com>
Joshua Prunier <joshua.prunier at gmail.com> Joshua Prunier <joshua.prunier at gmail.com>
Julien Lefevre <julien.lefevr at gmail.com> Julien Lefevre <julien.lefevr at gmail.com>
@ -62,6 +64,7 @@ Lucas Liu <extrafliu at gmail.com>
Luke Scott <luke at webconnex.com> Luke Scott <luke at webconnex.com>
Maciej Zimnoch <maciej.zimnoch at codilime.com> Maciej Zimnoch <maciej.zimnoch at codilime.com>
Michael Woolnough <michael.woolnough at gmail.com> Michael Woolnough <michael.woolnough at gmail.com>
Nathanial Murphy <nathanial.murphy at gmail.com>
Nicola Peduzzi <thenikso at gmail.com> Nicola Peduzzi <thenikso at gmail.com>
Olivier Mengué <dolmen at cpan.org> Olivier Mengué <dolmen at cpan.org>
oscarzhao <oscarzhaosl at gmail.com> oscarzhao <oscarzhaosl at gmail.com>
@ -81,6 +84,7 @@ Steven Hartland <steven.hartland at multiplay.co.uk>
Thomas Wodarek <wodarekwebpage at gmail.com> Thomas Wodarek <wodarekwebpage at gmail.com>
Tim Ruffles <timruffles at gmail.com> Tim Ruffles <timruffles at gmail.com>
Tom Jenkinson <tom at tjenkinson.me> Tom Jenkinson <tom at tjenkinson.me>
Vladimir Kovpak <cn007b at gmail.com>
Xiangyu Hu <xiangyu.hu at outlook.com> Xiangyu Hu <xiangyu.hu at outlook.com>
Xiaobing Jiang <s7v7nislands at gmail.com> Xiaobing Jiang <s7v7nislands at gmail.com>
Xiuming Chen <cc at cxm.cc> Xiuming Chen <cc at cxm.cc>
@ -90,6 +94,7 @@ Zhenye Xie <xiezhenye at gmail.com>
Barracuda Networks, Inc. Barracuda Networks, Inc.
Counting Ltd. Counting Ltd.
DigitalOcean Inc.
Facebook Inc. Facebook Inc.
GitHub Inc. GitHub Inc.
Google Inc. Google Inc.

View File

@ -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) ## Version 1.4 (2018-06-03)
Changes: Changes:

View File

@ -40,7 +40,7 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
* Optional placeholder interpolation * Optional placeholder interpolation
## Requirements ## 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+) * MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
--------------------------------------- ---------------------------------------
@ -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. 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. 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` ##### `collation`
``` ```
@ -396,14 +407,9 @@ TCP on a remote host, e.g. Amazon RDS:
id:password@tcp(your-amazonaws-uri.com:3306)/dbname id:password@tcp(your-amazonaws-uri.com:3306)/dbname
``` ```
Google Cloud SQL on App Engine (First Generation MySQL Server): Google Cloud SQL on App Engine:
``` ```
user@cloudsql(project-id:instance-name)/dbname user:password@unix(/cloudsql/project-id:region-name:instance-name)/dbname
```
Google Cloud SQL on App Engine (Second Generation MySQL Server):
```
user@cloudsql(project-id:regionname:instance-name)/dbname
``` ```
TCP using default port (3306) on localhost: TCP using default port (3306) on localhost:
@ -457,13 +463,13 @@ Alternatively you can use the [`NullTime`](https://godoc.org/github.com/go-sql-d
### Unicode support ### 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. 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. 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 ## 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. 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.

View File

@ -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)
})
}

View File

@ -6,7 +6,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file, // 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/. // You can obtain one at http://mozilla.org/MPL/2.0/.
// +build !windows,!appengine // +build linux darwin dragonfly freebsd netbsd openbsd solaris illumos
package mysql package mysql
@ -19,35 +19,36 @@ import (
var errUnexpectedRead = errors.New("unexpected read from socket") var errUnexpectedRead = errors.New("unexpected read from socket")
func connCheck(c net.Conn) error { func connCheck(conn net.Conn) error {
var ( var sysErr error
n int
err error
buff [1]byte
)
sconn, ok := c.(syscall.Conn) sysConn, ok := conn.(syscall.Conn)
if !ok { if !ok {
return nil return nil
} }
rc, err := sconn.SyscallConn() rawConn, err := sysConn.SyscallConn()
if err != nil { if err != nil {
return err 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 return true
}) })
switch { if err != nil {
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 return err
} }
return sysErr
} }

View File

@ -6,12 +6,12 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file, // 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/. // You can obtain one at http://mozilla.org/MPL/2.0/.
// +build windows appengine // +build !linux,!darwin,!dragonfly,!freebsd,!netbsd,!openbsd,!solaris,!illumos
package mysql package mysql
import "net" import "net"
func connCheck(c net.Conn) error { func connCheck(conn net.Conn) error {
return nil return nil
} }

View File

@ -6,7 +6,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file, // 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/. // You can obtain one at http://mozilla.org/MPL/2.0/.
// +build go1.10,!windows // +build linux darwin dragonfly freebsd netbsd openbsd solaris illumos
package mysql package mysql

View File

@ -12,6 +12,7 @@ import (
"context" "context"
"database/sql" "database/sql"
"database/sql/driver" "database/sql/driver"
"encoding/json"
"io" "io"
"net" "net"
"strconv" "strconv"
@ -154,7 +155,9 @@ func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
// Send command // Send command
err := mc.writeCommandPacketStr(comStmtPrepare, query) err := mc.writeCommandPacketStr(comStmtPrepare, query)
if err != nil { 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{ stmt := &mysqlStmt{
@ -269,6 +272,14 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
} }
buf = append(buf, '\'') 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: case []byte:
if v == nil { if v == nil {
buf = append(buf, "NULL"...) buf = append(buf, "NULL"...)

View File

@ -11,6 +11,7 @@ package mysql
import ( import (
"context" "context"
"database/sql/driver" "database/sql/driver"
"encoding/json"
"errors" "errors"
"net" "net"
"testing" "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) { func TestInterpolateParamsTooManyPlaceholders(t *testing.T) {
mc := &mysqlConn{ mc := &mysqlConn{
buf: newBuffer(nil), buf: newBuffer(nil),

View File

@ -37,17 +37,19 @@ func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
dial, ok := dials[mc.cfg.Net] dial, ok := dials[mc.cfg.Net]
dialsLock.RUnlock() dialsLock.RUnlock()
if ok { 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 { } else {
nd := net.Dialer{Timeout: mc.cfg.Timeout} nd := net.Dialer{Timeout: mc.cfg.Timeout}
mc.netConn, err = nd.DialContext(ctx, mc.cfg.Net, mc.cfg.Addr) mc.netConn, err = nd.DialContext(ctx, mc.cfg.Net, mc.cfg.Addr)
} }
if err != nil { 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 return nil, err
} }
@ -64,6 +66,7 @@ func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
// Call startWatcher for context support (From Go 1.8) // Call startWatcher for context support (From Go 1.8)
mc.startWatcher() mc.startWatcher()
if err := mc.watchCancel(ctx); err != nil { if err := mc.watchCancel(ctx); err != nil {
mc.cleanup()
return nil, err return nil, err
} }
defer mc.finish() defer mc.finish()

View File

@ -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)
}
}

View File

@ -83,3 +83,25 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
func init() { func init() {
sql.Register("mysql", &MySQLDriver{}) 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
}

View File

@ -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
}

View File

@ -1,137 +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")
}
}

View File

@ -1874,7 +1874,7 @@ func TestDialNonRetryableNetErr(t *testing.T) {
func TestDialTemporaryNetErr(t *testing.T) { func TestDialTemporaryNetErr(t *testing.T) {
testErr := netErrorMock{temporary: true} testErr := netErrorMock{temporary: true}
testDialError(t, testErr, driver.ErrBadConn) testDialError(t, testErr, testErr)
} }
// Tests custom dial functions // Tests custom dial functions
@ -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")
}
}

View File

@ -55,6 +55,7 @@ type Config struct {
AllowCleartextPasswords bool // Allows the cleartext client side plugin AllowCleartextPasswords bool // Allows the cleartext client side plugin
AllowNativePasswords bool // Allows the native password authentication method AllowNativePasswords bool // Allows the native password authentication method
AllowOldPasswords bool // Allows the old insecure password 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 ClientFoundRows bool // Return number of matching rows instead of rows changed
ColumnsWithAlias bool // Prepend table alias to column names ColumnsWithAlias bool // Prepend table alias to column names
InterpolateParams bool // Interpolate placeholders into query string InterpolateParams bool // Interpolate placeholders into query string
@ -70,6 +71,7 @@ func NewConfig() *Config {
Loc: time.UTC, Loc: time.UTC,
MaxAllowedPacket: defaultMaxAllowedPacket, MaxAllowedPacket: defaultMaxAllowedPacket,
AllowNativePasswords: true, AllowNativePasswords: true,
CheckConnLiveness: true,
} }
} }
@ -148,6 +150,19 @@ func (cfg *Config) normalize() error {
return nil 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 // FormatDSN formats the given Config into a DSN string which can be passed to
// the driver. // the driver.
func (cfg *Config) FormatDSN() string { func (cfg *Config) FormatDSN() string {
@ -186,165 +201,75 @@ func (cfg *Config) FormatDSN() string {
} }
if cfg.AllowCleartextPasswords { if cfg.AllowCleartextPasswords {
if hasParam { writeDSNParam(&buf, &hasParam, "allowCleartextPasswords", "true")
buf.WriteString("&allowCleartextPasswords=true")
} else {
hasParam = true
buf.WriteString("?allowCleartextPasswords=true")
}
} }
if !cfg.AllowNativePasswords { if !cfg.AllowNativePasswords {
if hasParam { writeDSNParam(&buf, &hasParam, "allowNativePasswords", "false")
buf.WriteString("&allowNativePasswords=false")
} else {
hasParam = true
buf.WriteString("?allowNativePasswords=false")
}
} }
if cfg.AllowOldPasswords { if cfg.AllowOldPasswords {
if hasParam { writeDSNParam(&buf, &hasParam, "allowOldPasswords", "true")
buf.WriteString("&allowOldPasswords=true") }
} else {
hasParam = true if !cfg.CheckConnLiveness {
buf.WriteString("?allowOldPasswords=true") writeDSNParam(&buf, &hasParam, "checkConnLiveness", "false")
}
} }
if cfg.ClientFoundRows { if cfg.ClientFoundRows {
if hasParam { writeDSNParam(&buf, &hasParam, "clientFoundRows", "true")
buf.WriteString("&clientFoundRows=true")
} else {
hasParam = true
buf.WriteString("?clientFoundRows=true")
}
} }
if col := cfg.Collation; col != defaultCollation && len(col) > 0 { if col := cfg.Collation; col != defaultCollation && len(col) > 0 {
if hasParam { writeDSNParam(&buf, &hasParam, "collation", col)
buf.WriteString("&collation=")
} else {
hasParam = true
buf.WriteString("?collation=")
}
buf.WriteString(col)
} }
if cfg.ColumnsWithAlias { if cfg.ColumnsWithAlias {
if hasParam { writeDSNParam(&buf, &hasParam, "columnsWithAlias", "true")
buf.WriteString("&columnsWithAlias=true")
} else {
hasParam = true
buf.WriteString("?columnsWithAlias=true")
}
} }
if cfg.InterpolateParams { if cfg.InterpolateParams {
if hasParam { writeDSNParam(&buf, &hasParam, "interpolateParams", "true")
buf.WriteString("&interpolateParams=true")
} else {
hasParam = true
buf.WriteString("?interpolateParams=true")
}
} }
if cfg.Loc != time.UTC && cfg.Loc != nil { if cfg.Loc != time.UTC && cfg.Loc != nil {
if hasParam { writeDSNParam(&buf, &hasParam, "loc", url.QueryEscape(cfg.Loc.String()))
buf.WriteString("&loc=")
} else {
hasParam = true
buf.WriteString("?loc=")
}
buf.WriteString(url.QueryEscape(cfg.Loc.String()))
} }
if cfg.MultiStatements { if cfg.MultiStatements {
if hasParam { writeDSNParam(&buf, &hasParam, "multiStatements", "true")
buf.WriteString("&multiStatements=true")
} else {
hasParam = true
buf.WriteString("?multiStatements=true")
}
} }
if cfg.ParseTime { if cfg.ParseTime {
if hasParam { writeDSNParam(&buf, &hasParam, "parseTime", "true")
buf.WriteString("&parseTime=true")
} else {
hasParam = true
buf.WriteString("?parseTime=true")
}
} }
if cfg.ReadTimeout > 0 { if cfg.ReadTimeout > 0 {
if hasParam { writeDSNParam(&buf, &hasParam, "readTimeout", cfg.ReadTimeout.String())
buf.WriteString("&readTimeout=")
} else {
hasParam = true
buf.WriteString("?readTimeout=")
}
buf.WriteString(cfg.ReadTimeout.String())
} }
if cfg.RejectReadOnly { if cfg.RejectReadOnly {
if hasParam { writeDSNParam(&buf, &hasParam, "rejectReadOnly", "true")
buf.WriteString("&rejectReadOnly=true")
} else {
hasParam = true
buf.WriteString("?rejectReadOnly=true")
}
} }
if len(cfg.ServerPubKey) > 0 { if len(cfg.ServerPubKey) > 0 {
if hasParam { writeDSNParam(&buf, &hasParam, "serverPubKey", url.QueryEscape(cfg.ServerPubKey))
buf.WriteString("&serverPubKey=")
} else {
hasParam = true
buf.WriteString("?serverPubKey=")
}
buf.WriteString(url.QueryEscape(cfg.ServerPubKey))
} }
if cfg.Timeout > 0 { if cfg.Timeout > 0 {
if hasParam { writeDSNParam(&buf, &hasParam, "timeout", cfg.Timeout.String())
buf.WriteString("&timeout=")
} else {
hasParam = true
buf.WriteString("?timeout=")
}
buf.WriteString(cfg.Timeout.String())
} }
if len(cfg.TLSConfig) > 0 { if len(cfg.TLSConfig) > 0 {
if hasParam { writeDSNParam(&buf, &hasParam, "tls", url.QueryEscape(cfg.TLSConfig))
buf.WriteString("&tls=")
} else {
hasParam = true
buf.WriteString("?tls=")
}
buf.WriteString(url.QueryEscape(cfg.TLSConfig))
} }
if cfg.WriteTimeout > 0 { if cfg.WriteTimeout > 0 {
if hasParam { writeDSNParam(&buf, &hasParam, "writeTimeout", cfg.WriteTimeout.String())
buf.WriteString("&writeTimeout=")
} else {
hasParam = true
buf.WriteString("?writeTimeout=")
}
buf.WriteString(cfg.WriteTimeout.String())
} }
if cfg.MaxAllowedPacket != defaultMaxAllowedPacket { if cfg.MaxAllowedPacket != defaultMaxAllowedPacket {
if hasParam { writeDSNParam(&buf, &hasParam, "maxAllowedPacket", strconv.Itoa(cfg.MaxAllowedPacket))
buf.WriteString("&maxAllowedPacket=")
} else {
hasParam = true
buf.WriteString("?maxAllowedPacket=")
}
buf.WriteString(strconv.Itoa(cfg.MaxAllowedPacket))
} }
// other params // other params
@ -355,16 +280,7 @@ func (cfg *Config) FormatDSN() string {
} }
sort.Strings(params) sort.Strings(params)
for _, param := range params { for _, param := range params {
if hasParam { writeDSNParam(&buf, &hasParam, param, url.QueryEscape(cfg.Params[param]))
buf.WriteByte('&')
} else {
hasParam = true
buf.WriteByte('?')
}
buf.WriteString(param)
buf.WriteByte('=')
buf.WriteString(url.QueryEscape(cfg.Params[param]))
} }
} }
@ -491,6 +407,14 @@ func parseDSNParams(cfg *Config, params string) (err error) {
return errors.New("invalid bool value: " + value) 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 // Switch "rowsAffected" mode
case "clientFoundRows": case "clientFoundRows":
var isBool bool var isBool bool

View File

@ -22,55 +22,55 @@ var testDSNs = []struct {
out *Config out *Config
}{{ }{{
"username:password@protocol(address)/dbname?param=value", "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", "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", "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", "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", "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", "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", "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", "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}, &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", "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", "/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@/", "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", "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", "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", "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},
}, },
} }

3
vendor/github.com/go-sql-driver/mysql/go.mod generated vendored Normal file
View File

@ -0,0 +1,3 @@
module github.com/go-sql-driver/mysql
go 1.10

50
vendor/github.com/go-sql-driver/mysql/nulltime.go generated vendored Normal file
View File

@ -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
}

View File

@ -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

View File

@ -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
}

62
vendor/github.com/go-sql-driver/mysql/nulltime_test.go generated vendored Normal file
View File

@ -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)
}
}
}

View File

@ -115,7 +115,7 @@ func (mc *mysqlConn) writePacket(data []byte) error {
if mc.cfg.ReadTimeout != 0 { if mc.cfg.ReadTimeout != 0 {
err = conn.SetReadDeadline(time.Time{}) err = conn.SetReadDeadline(time.Time{})
} }
if err == nil { if err == nil && mc.cfg.CheckConnLiveness {
err = connCheck(conn) err = connCheck(conn)
} }
if err != nil { if err != nil {

View File

@ -106,60 +106,6 @@ func readBool(input string) (value bool, valid bool) {
* Time related utils * * 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) { func parseDateTime(str string, loc *time.Location) (t time.Time, err error) {
base := "0000-00-00 00:00:00.0000000" base := "0000-00-00 00:00:00.0000000"
switch len(str) { switch len(str) {

View File

@ -14,49 +14,8 @@ import (
"database/sql/driver" "database/sql/driver"
"encoding/binary" "encoding/binary"
"testing" "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) { func TestLengthEncodedInteger(t *testing.T) {
var integerTests = []struct { var integerTests = []struct {
num uint64 num uint64