summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/go-sql-driver
diff options
context:
space:
mode:
authorChristopher Speller <crspeller@gmail.com>2018-01-29 14:17:40 -0800
committerGitHub <noreply@github.com>2018-01-29 14:17:40 -0800
commit961c04cae992eadb42d286d2f85f8a675bdc68c8 (patch)
tree3408f2d06f847e966c53485e2d54c692cdd037c1 /vendor/github.com/go-sql-driver
parent8d66523ba7d9a77129844be476732ebfd5272d64 (diff)
downloadchat-961c04cae992eadb42d286d2f85f8a675bdc68c8.tar.gz
chat-961c04cae992eadb42d286d2f85f8a675bdc68c8.tar.bz2
chat-961c04cae992eadb42d286d2f85f8a675bdc68c8.zip
Upgrading server dependancies (#8154)
Diffstat (limited to 'vendor/github.com/go-sql-driver')
-rw-r--r--vendor/github.com/go-sql-driver/mysql/.gitignore1
-rw-r--r--vendor/github.com/go-sql-driver/mysql/.travis.yml93
-rw-r--r--vendor/github.com/go-sql-driver/mysql/.travis/docker.cnf5
-rwxr-xr-xvendor/github.com/go-sql-driver/mysql/.travis/wait_mysql.sh8
-rw-r--r--vendor/github.com/go-sql-driver/mysql/AUTHORS27
-rw-r--r--vendor/github.com/go-sql-driver/mysql/README.md113
-rw-r--r--vendor/github.com/go-sql-driver/mysql/appengine.go2
-rw-r--r--vendor/github.com/go-sql-driver/mysql/benchmark_go18_test.go93
-rw-r--r--vendor/github.com/go-sql-driver/mysql/benchmark_test.go6
-rw-r--r--vendor/github.com/go-sql-driver/mysql/collations.go1
-rw-r--r--vendor/github.com/go-sql-driver/mysql/connection.go148
-rw-r--r--vendor/github.com/go-sql-driver/mysql/connection_go18.go202
-rw-r--r--vendor/github.com/go-sql-driver/mysql/connection_go18_test.go30
-rw-r--r--vendor/github.com/go-sql-driver/mysql/const.go9
-rw-r--r--vendor/github.com/go-sql-driver/mysql/driver.go14
-rw-r--r--vendor/github.com/go-sql-driver/mysql/driver_go18_test.go798
-rw-r--r--vendor/github.com/go-sql-driver/mysql/driver_test.go308
-rw-r--r--vendor/github.com/go-sql-driver/mysql/dsn.go132
-rw-r--r--vendor/github.com/go-sql-driver/mysql/dsn_test.go86
-rw-r--r--vendor/github.com/go-sql-driver/mysql/errors.go79
-rw-r--r--vendor/github.com/go-sql-driver/mysql/fields.go194
-rw-r--r--vendor/github.com/go-sql-driver/mysql/infile.go3
-rw-r--r--vendor/github.com/go-sql-driver/mysql/packets.go213
-rw-r--r--vendor/github.com/go-sql-driver/mysql/packets_test.go24
-rw-r--r--vendor/github.com/go-sql-driver/mysql/rows.go174
-rw-r--r--vendor/github.com/go-sql-driver/mysql/statement.go85
-rw-r--r--vendor/github.com/go-sql-driver/mysql/statement_test.go126
-rw-r--r--vendor/github.com/go-sql-driver/mysql/transaction.go4
-rw-r--r--vendor/github.com/go-sql-driver/mysql/utils.go84
-rw-r--r--vendor/github.com/go-sql-driver/mysql/utils_go17.go40
-rw-r--r--vendor/github.com/go-sql-driver/mysql/utils_go18.go49
-rw-r--r--vendor/github.com/go-sql-driver/mysql/utils_go18_test.go54
-rw-r--r--vendor/github.com/go-sql-driver/mysql/utils_test.go80
33 files changed, 2794 insertions, 491 deletions
diff --git a/vendor/github.com/go-sql-driver/mysql/.gitignore b/vendor/github.com/go-sql-driver/mysql/.gitignore
index ba8e0cb3a..2de28da16 100644
--- a/vendor/github.com/go-sql-driver/mysql/.gitignore
+++ b/vendor/github.com/go-sql-driver/mysql/.gitignore
@@ -6,3 +6,4 @@
Icon?
ehthumbs.db
Thumbs.db
+.idea
diff --git a/vendor/github.com/go-sql-driver/mysql/.travis.yml b/vendor/github.com/go-sql-driver/mysql/.travis.yml
index c1cc10aaf..e922f9187 100644
--- a/vendor/github.com/go-sql-driver/mysql/.travis.yml
+++ b/vendor/github.com/go-sql-driver/mysql/.travis.yml
@@ -1,13 +1,92 @@
sudo: false
language: go
go:
- - 1.2
- - 1.3
- - 1.4
- - 1.5
- - 1.6
- - 1.7
- - tip
+ - 1.7.x
+ - 1.8.x
+ - 1.9.x
+ - master
+
+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.9.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_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.9.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_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.9.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_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
+
+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
diff --git a/vendor/github.com/go-sql-driver/mysql/.travis/docker.cnf b/vendor/github.com/go-sql-driver/mysql/.travis/docker.cnf
new file mode 100644
index 000000000..e57754e5a
--- /dev/null
+++ b/vendor/github.com/go-sql-driver/mysql/.travis/docker.cnf
@@ -0,0 +1,5 @@
+[client]
+user = gotest
+password = secret
+host = 127.0.0.1
+port = 3307
diff --git a/vendor/github.com/go-sql-driver/mysql/.travis/wait_mysql.sh b/vendor/github.com/go-sql-driver/mysql/.travis/wait_mysql.sh
new file mode 100755
index 000000000..abcf5f0ae
--- /dev/null
+++ b/vendor/github.com/go-sql-driver/mysql/.travis/wait_mysql.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+while :
+do
+ sleep 3
+ if mysql -e 'select version()'; then
+ break
+ fi
+done
diff --git a/vendor/github.com/go-sql-driver/mysql/AUTHORS b/vendor/github.com/go-sql-driver/mysql/AUTHORS
index 692c186fd..4702c83ab 100644
--- a/vendor/github.com/go-sql-driver/mysql/AUTHORS
+++ b/vendor/github.com/go-sql-driver/mysql/AUTHORS
@@ -12,35 +12,58 @@
# 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>
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>
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>
+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>
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>
+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>
+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>
+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>
Soroush Pour <me at soroushjp.com>
Stan Putrya <root.vagner at gmail.com>
Stanley Gunawan <gunawan.stanley at gmail.com>
@@ -52,5 +75,9 @@ Zhenye Xie <xiezhenye at gmail.com>
# Organizations
Barracuda Networks, Inc.
+Counting Ltd.
Google Inc.
+InfoSum Ltd.
+Keybase Inc.
+Pivotal Inc.
Stripe Inc.
diff --git a/vendor/github.com/go-sql-driver/mysql/README.md b/vendor/github.com/go-sql-driver/mysql/README.md
index a16012f81..299198d53 100644
--- a/vendor/github.com/go-sql-driver/mysql/README.md
+++ b/vendor/github.com/go-sql-driver/mysql/README.md
@@ -1,6 +1,6 @@
# Go-MySQL-Driver
-A MySQL-Driver for Go's [database/sql](http://golang.org/pkg/database/sql) package
+A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) package
![Go-MySQL-Driver logo](https://raw.github.com/wiki/go-sql-driver/mysql/gomysql_m.png "Golang Gopher holding the MySQL Dolphin")
@@ -15,6 +15,9 @@ A MySQL-Driver for Go's [database/sql](http://golang.org/pkg/database/sql) packa
* [Address](#address)
* [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)
@@ -26,31 +29,31 @@ A MySQL-Driver for Go's [database/sql](http://golang.org/pkg/database/sql) packa
## Features
* Lightweight and [fast](https://github.com/go-sql-driver/sql-benchmark "golang MySQL-Driver performance")
* Native Go implementation. No C-bindings, just pure Go
- * Connections over TCP/IPv4, TCP/IPv6, Unix domain sockets or [custom protocols](http://godoc.org/github.com/go-sql-driver/mysql#DialFunc)
+ * Connections over TCP/IPv4, TCP/IPv6, Unix domain sockets or [custom protocols](https://godoc.org/github.com/go-sql-driver/mysql#DialFunc)
* Automatic handling of broken connections
* Automatic Connection Pooling *(by database/sql package)*
* Supports queries larger than 16MB
- * Full [`sql.RawBytes`](http://golang.org/pkg/database/sql/#RawBytes) support.
+ * 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
* Optional `time.Time` parsing
* Optional placeholder interpolation
## Requirements
- * Go 1.2 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+)
---------------------------------------
## Installation
-Simple install the package to your [$GOPATH](http://code.google.com/p/go-wiki/wiki/GOPATH "GOPATH") with the [go tool](http://golang.org/cmd/go/ "go command") from shell:
+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](http://git-scm.com/downloads) on your machine and in your system's `PATH`.
+Make sure [Git is installed](https://git-scm.com/downloads) on your machine and in your system's `PATH`.
## Usage
-_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`](http://golang.org/pkg/database/sql) API then.
+_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
@@ -95,13 +98,14 @@ Alternatively, [Config.FormatDSN](https://godoc.org/github.com/go-sql-driver/mys
Passwords can consist of any character. Escaping is **not** necessary.
#### Protocol
-See [net.Dial](http://golang.org/pkg/net/#Dial) for more information which networks are available.
+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.
#### 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](http://golang.org/pkg/net/#JoinHostPort) and [net.SplitHostPort](http://golang.org/pkg/net/#SplitHostPort) manipulate addresses in this form.
+The functions [net.JoinHostPort](https://golang.org/pkg/net/#JoinHostPort) and [net.SplitHostPort](https://golang.org/pkg/net/#SplitHostPort) manipulate addresses in this form.
For Unix domain sockets the address is the absolute path to the MySQL-Server-socket, e.g. `/var/run/mysqld/mysqld.sock` or `/tmp/mysql.sock`.
@@ -136,9 +140,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`
@@ -220,19 +224,19 @@ Valid Values: <escaped name>
Default: UTC
```
-Sets the location for time.Time values (when using `parseTime=true`). *"Local"* sets the system's location. See [time.LoadLocation](http://golang.org/pkg/time/#LoadLocation) for details.
+Sets the location for time.Time values (when using `parseTime=true`). *"Local"* sets the system's location. See [time.LoadLocation](https://golang.org/pkg/time/#LoadLocation) for details.
Note that this sets the location for time.Time values but does not change MySQL's [time_zone setting](https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html). For that see the [time_zone system variable](#system-variables), which can also be set as a DSN parameter.
-Please keep in mind, that param values must be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed. Alternatively you can manually replace the `/` with `%2F`. For example `US/Pacific` would be `loc=US%2FPacific`.
+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`.
##### `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`
@@ -260,13 +264,13 @@ 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`
+##### `rejectReadOnly`
```
Type: bool
@@ -274,20 +278,37 @@ 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.
+`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.
+
+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.
-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`
```
-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`
@@ -297,16 +318,17 @@ Valid Values: true, false, skip-verify, <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`](http://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 a custom value registered with [`mysql.RegisterTLSConfig`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig).
+
##### `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
@@ -317,9 +339,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`
@@ -380,6 +402,18 @@ 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.
+
+## `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 `_`):
```go
@@ -390,17 +424,17 @@ Files must be whitelisted by registering them with `mysql.RegisterLocalFile(file
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.
-See the [godoc of Go-MySQL-Driver](http://godoc.org/github.com/go-sql-driver/mysql "golang mysql driver documentation") for details.
+See the [godoc of Go-MySQL-Driver](https://godoc.org/github.com/go-sql-driver/mysql "golang mysql driver documentation") for details.
### `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](http://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 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.
**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`](http://godoc.org/github.com/go-sql-driver/mysql#NullTime) type as the scan destination, which works with both `time.Time` and `string` / `[]byte`.
+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
@@ -412,7 +446,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.
-
## 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.
@@ -431,13 +464,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](http://www.mozilla.org/MPL/2.0/FAQ.html) if you have further questions regarding the license.
+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")
diff --git a/vendor/github.com/go-sql-driver/mysql/appengine.go b/vendor/github.com/go-sql-driver/mysql/appengine.go
index 565614eef..be41f2ee6 100644
--- a/vendor/github.com/go-sql-driver/mysql/appengine.go
+++ b/vendor/github.com/go-sql-driver/mysql/appengine.go
@@ -11,7 +11,7 @@
package mysql
import (
- "appengine/cloudsql"
+ "google.golang.org/appengine/cloudsql"
)
func init() {
diff --git a/vendor/github.com/go-sql-driver/mysql/benchmark_go18_test.go b/vendor/github.com/go-sql-driver/mysql/benchmark_go18_test.go
new file mode 100644
index 000000000..d6a7e9d6e
--- /dev/null
+++ b/vendor/github.com/go-sql-driver/mysql/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/vendor/github.com/go-sql-driver/mysql/benchmark_test.go b/vendor/github.com/go-sql-driver/mysql/benchmark_test.go
index 7da833a2a..c1de8672b 100644
--- a/vendor/github.com/go-sql-driver/mysql/benchmark_test.go
+++ b/vendor/github.com/go-sql-driver/mysql/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/vendor/github.com/go-sql-driver/mysql/collations.go b/vendor/github.com/go-sql-driver/mysql/collations.go
index 82079cfb9..136c9e4d1 100644
--- a/vendor/github.com/go-sql-driver/mysql/collations.go
+++ b/vendor/github.com/go-sql-driver/mysql/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/vendor/github.com/go-sql-driver/mysql/connection.go b/vendor/github.com/go-sql-driver/mysql/connection.go
index d82c728f3..e57061412 100644
--- a/vendor/github.com/go-sql-driver/mysql/connection.go
+++ b/vendor/github.com/go-sql-driver/mysql/connection.go
@@ -10,12 +10,23 @@ package mysql
import (
"database/sql/driver"
+ "io"
"net"
"strconv"
"strings"
"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
@@ -29,7 +40,14 @@ type mysqlConn struct {
status statusFlag
sequence uint8
parseTime bool
- strict bool
+
+ // for context support (Go 1.8+)
+ watching bool
+ watcher chan<- mysqlContext
+ 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
}
// Handles parameters set in DSN after the connection is established
@@ -62,22 +80,41 @@ 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.netConn == nil {
+ 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
}
-
- return nil, err
+ return nil, mc.markBadConn(err)
}
func (mc *mysqlConn) Close() (err error) {
// Makes Close idempotent
- if mc.netConn != nil {
+ if !mc.closed.IsSet() {
err = mc.writeCommandPacket(comQuit)
}
@@ -91,26 +128,39 @@ 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) {
+ 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) error() error {
+ if mc.closed.IsSet() {
+ if err := mc.canceled.Value(); 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.closed.IsSet() {
errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn
}
// Send command
err := mc.writeCommandPacketStr(comStmtPrepare, query)
if err != nil {
- return nil, err
+ return nil, mc.markBadConn(err)
}
stmt := &mysqlStmt{
@@ -144,7 +194,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
@@ -257,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.netConn == nil {
+ if mc.closed.IsSet() {
errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn
}
@@ -271,7 +321,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
@@ -283,32 +332,43 @@ 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
- err := mc.writeCommandPacketStr(comQuery, query)
- if err != nil {
- return err
+ if err := mc.writeCommandPacketStr(comQuery, query); err != nil {
+ return mc.markBadConn(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) {
- if mc.netConn == nil {
+ return mc.query(query, args)
+}
+
+func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error) {
+ if mc.closed.IsSet() {
errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn
}
@@ -322,7 +382,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)
@@ -335,15 +394,22 @@ 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
}
}
- return nil, err
+ return nil, mc.markBadConn(err)
}
// Gets the value of the given MySQL System Variable
@@ -359,7 +425,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
@@ -375,3 +441,21 @@ 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.canceled.Set(err)
+ mc.cleanup()
+}
+
+// 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/vendor/github.com/go-sql-driver/mysql/connection_go18.go b/vendor/github.com/go-sql-driver/mysql/connection_go18.go
new file mode 100644
index 000000000..1306b70b7
--- /dev/null
+++ b/vendor/github.com/go-sql-driver/mysql/connection_go18.go
@@ -0,0 +1,202 @@
+// 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) error {
+ if mc.closed.IsSet() {
+ 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 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
+}
diff --git a/vendor/github.com/go-sql-driver/mysql/connection_go18_test.go b/vendor/github.com/go-sql-driver/mysql/connection_go18_test.go
new file mode 100644
index 000000000..2719ab3b7
--- /dev/null
+++ b/vendor/github.com/go-sql-driver/mysql/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/vendor/github.com/go-sql-driver/mysql/const.go b/vendor/github.com/go-sql-driver/mysql/const.go
index 88cfff3fd..4a19ca523 100644
--- a/vendor/github.com/go-sql-driver/mysql/const.go
+++ b/vendor/github.com/go-sql-driver/mysql/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"
)
@@ -87,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
@@ -107,7 +110,7 @@ const (
fieldTypeBit
)
const (
- fieldTypeJSON byte = iota + 0xf5
+ fieldTypeJSON fieldType = iota + 0xf5
fieldTypeNewDecimal
fieldTypeEnum
fieldTypeSet
diff --git a/vendor/github.com/go-sql-driver/mysql/driver.go b/vendor/github.com/go-sql-driver/mysql/driver.go
index 0022d1f1e..d42ce7a3d 100644
--- a/vendor/github.com/go-sql-driver/mysql/driver.go
+++ b/vendor/github.com/go-sql-driver/mysql/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:
//
@@ -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,13 +57,13 @@ 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 {
return nil, err
}
mc.parseTime = mc.cfg.ParseTime
- mc.strict = mc.cfg.Strict
// Connect to Server
if dial, ok := dials[mc.cfg.Net]; ok {
@@ -81,6 +86,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
diff --git a/vendor/github.com/go-sql-driver/mysql/driver_go18_test.go b/vendor/github.com/go-sql-driver/mysql/driver_go18_test.go
new file mode 100644
index 000000000..e461455dd
--- /dev/null
+++ b/vendor/github.com/go-sql-driver/mysql/driver_go18_test.go
@@ -0,0 +1,798 @@
+// 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(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)
+ }
+ })
+}
+
+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)
+ }
+ })
+ }
+}
diff --git a/vendor/github.com/go-sql-driver/mysql/driver_test.go b/vendor/github.com/go-sql-driver/mysql/driver_test.go
index 78e68f5d0..7877aa979 100644
--- a/vendor/github.com/go-sql-driver/mysql/driver_test.go
+++ b/vendor/github.com/go-sql-driver/mysql/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
@@ -63,7 +69,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
@@ -171,6 +177,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
@@ -482,6 +499,85 @@ 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 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
@@ -684,7 +780,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 +952,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
@@ -953,7 +1049,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 {
@@ -1054,22 +1150,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")
@@ -1145,84 +1255,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()
- }
- 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
- }
- 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 {
@@ -1422,7 +1454,6 @@ func TestTimezoneConversion(t *testing.T) {
// Regression test for timezone handling
tzTest := func(dbt *DBTest) {
-
// Create table
dbt.mustExec("CREATE TABLE test (ts TIMESTAMP)")
@@ -1638,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)
@@ -1647,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()
})
}
@@ -1739,7 +1781,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())
}
@@ -1836,7 +1878,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())
@@ -1902,3 +1944,77 @@ 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")
+ }
+}
+
+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")
+ })
+}
+
+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/vendor/github.com/go-sql-driver/mysql/dsn.go b/vendor/github.com/go-sql-driver/mysql/dsn.go
index ac00dcedd..47eab6945 100644
--- a/vendor/github.com/go-sql-driver/mysql/dsn.go
+++ b/vendor/github.com/go-sql-driver/mysql/dsn.go
@@ -15,6 +15,7 @@ import (
"fmt"
"net"
"net/url"
+ "sort"
"strconv"
"strings"
"time"
@@ -27,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)
@@ -53,7 +56,54 @@ 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
- Strict bool // Return warnings as errors
+ 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,
+ MaxAllowedPacket: defaultMaxAllowedPacket,
+ 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)
+ }
+
+ 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
}
// FormatDSN formats the given Config into a DSN string which can be passed to
@@ -102,12 +152,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")
}
}
@@ -195,12 +245,12 @@ func (cfg *Config) FormatDSN() string {
buf.WriteString(cfg.ReadTimeout.String())
}
- if cfg.Strict {
+ if cfg.RejectReadOnly {
if hasParam {
- buf.WriteString("&strict=true")
+ buf.WriteString("&rejectReadOnly=true")
} else {
hasParam = true
- buf.WriteString("?strict=true")
+ buf.WriteString("?rejectReadOnly=true")
}
}
@@ -234,7 +284,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 {
@@ -247,7 +297,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 {
@@ -257,7 +312,7 @@ func (cfg *Config) FormatDSN() string {
buf.WriteString(param)
buf.WriteByte('=')
- buf.WriteString(url.QueryEscape(value))
+ buf.WriteString(url.QueryEscape(cfg.Params[param]))
}
}
@@ -267,10 +322,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,
- }
+ cfg = NewConfig()
// [user[:password]@][net[(addr)]]/dbname[?param1=value1&paramN=valueN]
// Find the last '/' (since the password or the net addr might contain a '/')
@@ -338,28 +390,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")
- }
-
- }
-
return
}
@@ -374,7 +407,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
@@ -472,14 +504,18 @@ func parseDSNParams(cfg *Config, params string) (err error) {
return
}
- // Strict mode
- case "strict":
+ // Reject read-only connections
+ case "rejectReadOnly":
var isBool bool
- cfg.Strict, isBool = readBool(value)
+ cfg.RejectReadOnly, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
+ // Strict mode
+ case "strict":
+ panic("strict mode has been removed. See https://github.com/go-sql-driver/mysql/wiki/strict-mode")
+
// Dial Timeout
case "timeout":
cfg.Timeout, err = time.ParseDuration(value)
@@ -506,14 +542,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 {
- if len(tlsConfig.ServerName) == 0 && !tlsConfig.InsecureSkipVerify {
- host, _, err := net.SplitHostPort(cfg.Addr)
- if err == nil {
- tlsConfig.ServerName = host
- }
- }
-
+ if tlsConfig := getTLSConfigClone(name); tlsConfig != nil {
cfg.TLSConfig = name
cfg.tls = tlsConfig
} else {
@@ -546,3 +575,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/vendor/github.com/go-sql-driver/mysql/dsn_test.go b/vendor/github.com/go-sql-driver/mysql/dsn_test.go
index 0693192ad..7507d1201 100644
--- a/vendor/github.com/go-sql-driver/mysql/dsn_test.go
+++ b/vendor/github.com/go-sql-driver/mysql/dsn_test.go
@@ -22,47 +22,57 @@ 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, 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, 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, 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},
+ &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, 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, 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, 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&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},
+ &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},
+ &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},
+ &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},
+ &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},
+ &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},
+ &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},
-}}
+ &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, 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},
+},
+}
func TestDSNParser(t *testing.T) {
for i, tst := range testDSNs {
@@ -88,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",
}
@@ -159,11 +170,41 @@ 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")
}
+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)
@@ -218,6 +259,21 @@ func TestDSNUnsafeCollation(t *testing.T) {
}
}
+func TestParamsAreSorted(t *testing.T) {
+ expected := "/dbname?interpolateParams=true&foobar=baz&quux=loo"
+ cfg := NewConfig()
+ cfg.DBName = "dbname"
+ cfg.InterpolateParams = true
+ cfg.Params = map[string]string{
+ "quux": "loo",
+ "foobar": "baz",
+ }
+ actual := cfg.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()
diff --git a/vendor/github.com/go-sql-driver/mysql/errors.go b/vendor/github.com/go-sql-driver/mysql/errors.go
index 857854e14..760782ff2 100644
--- a/vendor/github.com/go-sql-driver/mysql/errors.go
+++ b/vendor/github.com/go-sql-driver/mysql/errors.go
@@ -9,10 +9,8 @@
package mysql
import (
- "database/sql/driver"
"errors"
"fmt"
- "io"
"log"
"os"
)
@@ -31,6 +29,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))
@@ -59,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/vendor/github.com/go-sql-driver/mysql/fields.go b/vendor/github.com/go-sql-driver/mysql/fields.go
new file mode 100644
index 000000000..e1e2ece4b
--- /dev/null
+++ b/vendor/github.com/go-sql-driver/mysql/fields.go
@@ -0,0 +1,194 @@
+// 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"
+)
+
+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 (
+ 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
+ charSet uint8
+}
+
+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/vendor/github.com/go-sql-driver/mysql/infile.go b/vendor/github.com/go-sql-driver/mysql/infile.go
index 547357cfa..4020f9192 100644
--- a/vendor/github.com/go-sql-driver/mysql/infile.go
+++ b/vendor/github.com/go-sql-driver/mysql/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 {
diff --git a/vendor/github.com/go-sql-driver/mysql/packets.go b/vendor/github.com/go-sql-driver/mysql/packets.go
index aafe9793e..afc3fcc46 100644
--- a/vendor/github.com/go-sql-driver/mysql/packets.go
+++ b/vendor/github.com/go-sql-driver/mysql/packets.go
@@ -30,9 +30,12 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
// read packet header
data, err := mc.buf.readNext(4)
if err != nil {
+ if cerr := mc.canceled.Value(); cerr != nil {
+ return nil, cerr
+ }
errLog.Print(err)
mc.Close()
- return nil, driver.ErrBadConn
+ return nil, ErrInvalidConn
}
// packet length [24 bit]
@@ -54,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
@@ -63,9 +66,12 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
// read packet body [pktLen bytes]
data, err = mc.buf.readNext(pktLen)
if err != nil {
+ if cerr := mc.canceled.Value(); cerr != nil {
+ return nil, cerr
+ }
errLog.Print(err)
mc.Close()
- return nil, driver.ErrBadConn
+ return nil, ErrInvalidConn
}
// return data if this was the last packet
@@ -125,11 +131,20 @@ 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.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
}
}
@@ -142,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
}
@@ -263,7 +283,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]
@@ -341,7 +361,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
@@ -349,7 +371,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]
@@ -368,7 +390,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]
@@ -381,7 +403,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)
@@ -389,7 +413,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
@@ -410,7 +434,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
@@ -429,7 +453,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
@@ -450,7 +474,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
@@ -484,25 +508,26 @@ 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:]
- 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)
}
@@ -550,6 +575,22 @@ 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
+ // 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
+ // 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]
@@ -584,19 +625,12 @@ 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]
- if !mc.strict {
- return nil
- }
- pos := 1 + n + m + 2
- if binary.LittleEndian.Uint16(data[pos:pos+2]) > 0 {
- return mc.getWarnings()
- }
return nil
}
@@ -668,14 +702,21 @@ func (mc *mysqlConn) readColumns(count int) ([]mysqlField, error) {
if err != nil {
return nil, err
}
+ pos += n
// Filler [uint8]
+ pos++
+
// Charset [charset, collation uint8]
+ columns[i].charSet = data[pos]
+ pos += 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]
@@ -698,6 +739,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 +752,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 +777,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(
@@ -808,14 +849,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
@@ -887,6 +921,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
@@ -900,7 +940,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]
@@ -959,7 +999,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
}
@@ -967,7 +1007,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 {
@@ -983,7 +1023,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 {
@@ -999,7 +1039,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 {
@@ -1011,10 +1051,10 @@ 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 {
+ if len(v) < longDataSize {
paramValues = appendLengthEncodedInteger(paramValues,
uint64(len(v)),
)
@@ -1029,14 +1069,14 @@ 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 {
+ if len(v) < longDataSize {
paramValues = appendLengthEncodedInteger(paramValues,
uint64(len(v)),
)
@@ -1048,20 +1088,22 @@ 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 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)
@@ -1097,8 +1139,6 @@ func (mc *mysqlConn) discardResults() error {
if err := mc.readUntilEOF(); err != nil {
return err
}
- } else {
- mc.status &^= statusMoreResultsExists
}
}
return nil
@@ -1116,20 +1156,17 @@ 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
}
+ 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]
@@ -1145,14 +1182,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 +1198,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 +1207,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 +1216,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)
@@ -1193,7 +1230,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
@@ -1233,10 +1270,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 +1281,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 +1289,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 +1300,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 +1316,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/vendor/github.com/go-sql-driver/mysql/packets_test.go b/vendor/github.com/go-sql-driver/mysql/packets_test.go
index 98404586a..2f8207511 100644
--- a/vendor/github.com/go-sql-driver/mysql/packets_test.go
+++ b/vendor/github.com/go-sql-driver/mysql/packets_test.go
@@ -9,7 +9,6 @@
package mysql
import (
- "database/sql/driver"
"errors"
"net"
"testing"
@@ -101,7 +100,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 +170,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 +204,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 +230,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])
@@ -244,15 +243,16 @@ 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
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
@@ -263,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
@@ -276,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/vendor/github.com/go-sql-driver/mysql/rows.go b/vendor/github.com/go-sql-driver/mysql/rows.go
index c08255eee..d3b1e2822 100644
--- a/vendor/github.com/go-sql-driver/mysql/rows.go
+++ b/vendor/github.com/go-sql-driver/mysql/rows.go
@@ -11,19 +11,20 @@ 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
+ done bool
}
type mysqlRows struct {
- mc *mysqlConn
- columns []mysqlField
+ mc *mysqlConn
+ rs resultSet
+ finish func()
}
type binaryRows struct {
@@ -34,37 +35,86 @@ type textRows struct {
mysqlRows
}
-type emptyRows struct{}
-
func (rows *mysqlRows) Columns() []string {
- columns := make([]string, len(rows.columns))
+ 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 {
- 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
}
}
+
+ rows.rs.columnNames = columns
return columns
}
-func (rows *mysqlRows) Close() error {
+func (rows *mysqlRows) ColumnTypeDatabaseTypeName(i int) string {
+ return rows.rs.columns[i].typeDatabaseName()
+}
+
+// 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()
+ 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
- err := mc.readUntilEOF()
+ if !rows.rs.done {
+ err = mc.readUntilEOF()
+ }
if err == nil {
if err = mc.discardResults(); err != nil {
return err
@@ -75,22 +125,66 @@ func (rows *mysqlRows) Close() error {
return err
}
-func (rows *binaryRows) Next(dest []driver.Value) error {
- if mc := rows.mc; mc != nil {
- if mc.netConn == nil {
- return ErrInvalidConn
+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 err := rows.mc.error(); err != nil {
+ return 0, err
+ }
+
+ // Remove unread packets from stream
+ if !rows.rs.done {
+ if err := rows.mc.readUntilEOF(); err != nil {
+ return 0, err
}
+ rows.rs.done = true
+ }
- // Fetch next row from stream
- return rows.readRow(dest)
+ if !rows.HasNextResultSet() {
+ rows.mc = nil
+ return 0, io.EOF
}
- return io.EOF
+ rows.rs = resultSet{}
+ return rows.mc.readResultSetHeaderPacket()
}
-func (rows *textRows) Next(dest []driver.Value) error {
+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() error {
+ resLen, err := rows.nextNotEmptyResultSet()
+ if err != nil {
+ return err
+ }
+
+ rows.rs.columns, err = rows.mc.readColumns(resLen)
+ return err
+}
+
+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
@@ -99,14 +193,24 @@ func (rows *textRows) Next(dest []driver.Value) error {
return io.EOF
}
-func (rows emptyRows) Columns() []string {
- return nil
-}
+func (rows *textRows) NextResultSet() (err error) {
+ resLen, err := rows.nextNotEmptyResultSet()
+ if err != nil {
+ return err
+ }
-func (rows emptyRows) Close() error {
- return nil
+ rows.rs.columns, err = rows.mc.readColumns(resLen)
+ return err
}
-func (rows emptyRows) Next(dest []driver.Value) error {
+func (rows *textRows) Next(dest []driver.Value) error {
+ if mc := rows.mc; mc != nil {
+ if err := mc.error(); err != nil {
+ return err
+ }
+
+ // Fetch next row from stream
+ return rows.readRow(dest)
+ }
return io.EOF
}
diff --git a/vendor/github.com/go-sql-driver/mysql/statement.go b/vendor/github.com/go-sql-driver/mysql/statement.go
index 7f9b04585..98e57bcd8 100644
--- a/vendor/github.com/go-sql-driver/mysql/statement.go
+++ b/vendor/github.com/go-sql-driver/mysql/statement.go
@@ -11,6 +11,7 @@ package mysql
import (
"database/sql/driver"
"fmt"
+ "io"
"reflect"
"strconv"
)
@@ -19,11 +20,10 @@ type mysqlStmt struct {
mc *mysqlConn
id uint32
paramCount int
- columns []mysqlField // cached from the first query
}
func (stmt *mysqlStmt) Close() error {
- if stmt.mc == nil || stmt.mc.netConn == nil {
+ 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,14 +45,14 @@ 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.closed.IsSet() {
errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn
}
// Send command
err := stmt.writeExecutePacket(args)
if err != nil {
- return nil, err
+ return nil, stmt.mc.markBadConn(err)
}
mc := stmt.mc
@@ -62,37 +62,45 @@ 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
- }
-
- // Rows
- err = mc.readUntilEOF()
+ if err != nil {
+ return nil, err
+ }
+
+ 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) {
- if stmt.mc.netConn == nil {
+ return stmt.query(args)
+}
+
+func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) {
+ if stmt.mc.closed.IsSet() {
errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn
}
// Send command
err := stmt.writeExecutePacket(args)
if err != nil {
- return nil, err
+ return nil, stmt.mc.markBadConn(err)
}
mc := stmt.mc
@@ -107,14 +115,15 @@ func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
if resLen > 0 {
rows.mc = mc
- // Columns
- // If not cached, read them and cache them
- if stmt.columns == nil {
- rows.columns, err = mc.readColumns(resLen)
- stmt.columns = rows.columns
- } else {
- rows.columns = stmt.columns
- err = mc.readUntilEOF()
+ rows.rs.columns, err = mc.readColumns(resLen)
+ } else {
+ rows.rs.done = true
+
+ switch err := rows.NextResultSet(); err {
+ case nil, io.EOF:
+ return rows, nil
+ default:
+ return nil, err
}
}
@@ -128,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:
@@ -148,6 +163,16 @@ 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
}
return nil, fmt.Errorf("unsupported type %T, a %s", v, rv.Kind())
}
diff --git a/vendor/github.com/go-sql-driver/mysql/statement_test.go b/vendor/github.com/go-sql-driver/mysql/statement_test.go
new file mode 100644
index 000000000..98a6c1933
--- /dev/null
+++ b/vendor/github.com/go-sql-driver/mysql/statement_test.go
@@ -0,0 +1,126 @@
+// 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 (
+ "bytes"
+ "testing"
+)
+
+func TestConvertDerivedString(t *testing.T) {
+ type derived string
+
+ 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)
+ }
+}
diff --git a/vendor/github.com/go-sql-driver/mysql/transaction.go b/vendor/github.com/go-sql-driver/mysql/transaction.go
index 33c749b35..417d72793 100644
--- a/vendor/github.com/go-sql-driver/mysql/transaction.go
+++ b/vendor/github.com/go-sql-driver/mysql/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.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.netConn == nil {
+ if tx.mc == nil || tx.mc.closed.IsSet() {
return ErrInvalidConn
}
err = tx.mc.exec("ROLLBACK")
diff --git a/vendor/github.com/go-sql-driver/mysql/utils.go b/vendor/github.com/go-sql-driver/mysql/utils.go
index d523b7ffd..a92a4029b 100644
--- a/vendor/github.com/go-sql-driver/mysql/utils.go
+++ b/vendor/github.com/go-sql-driver/mysql/utils.go
@@ -16,16 +16,21 @@ import (
"fmt"
"io"
"strings"
+ "sync"
+ "sync/atomic"
"time"
)
var (
+ tlsConfigLock sync.RWMutex
tlsConfigRegister map[string]*tls.Config // Register for custom tls.Configs
)
// 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 {
@@ -51,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.
@@ -548,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
@@ -738,3 +756,67 @@ 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/vendor/github.com/go-sql-driver/mysql/utils_go17.go b/vendor/github.com/go-sql-driver/mysql/utils_go17.go
new file mode 100644
index 000000000..f59563456
--- /dev/null
+++ b/vendor/github.com/go-sql-driver/mysql/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/vendor/github.com/go-sql-driver/mysql/utils_go18.go b/vendor/github.com/go-sql-driver/mysql/utils_go18.go
new file mode 100644
index 000000000..7d8c9b16e
--- /dev/null
+++ b/vendor/github.com/go-sql-driver/mysql/utils_go18.go
@@ -0,0 +1,49 @@
+// 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"
+)
+
+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 "", errors.New("mysql: unsupported isolation level: " + string(level))
+ }
+}
diff --git a/vendor/github.com/go-sql-driver/mysql/utils_go18_test.go b/vendor/github.com/go-sql-driver/mysql/utils_go18_test.go
new file mode 100644
index 000000000..856c25f56
--- /dev/null
+++ b/vendor/github.com/go-sql-driver/mysql/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")
+ }
+
+}
diff --git a/vendor/github.com/go-sql-driver/mysql/utils_test.go b/vendor/github.com/go-sql-driver/mysql/utils_test.go
index 0d6c6684f..0041892db 100644
--- a/vendor/github.com/go-sql-driver/mysql/utils_test.go
+++ b/vendor/github.com/go-sql-driver/mysql/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")
+ }
+}