Update mysql to b4242ba (latest). (#4552)
The most recent tagged release of mysql is v1.4.1, from a year ago. It also happens to pull in an unwanted dependency (appengine) that the latest commit does not. Tests pass: $ go test -count=1 github.com/go-sql-driver/mysql ok github.com/go-sql-driver/mysql 0.068s Fixes #4530
This commit is contained in:
parent
f24fd0dfc8
commit
0804e97d88
2
go.mod
2
go.mod
|
@ -9,7 +9,7 @@ require (
|
||||||
github.com/cloudflare/cfssl v1.4.0
|
github.com/cloudflare/cfssl v1.4.0
|
||||||
github.com/eggsampler/acme/v2 v2.0.1
|
github.com/eggsampler/acme/v2 v2.0.1
|
||||||
github.com/go-gorp/gorp v2.0.0+incompatible // indirect
|
github.com/go-gorp/gorp v2.0.0+incompatible // indirect
|
||||||
github.com/go-sql-driver/mysql v1.3.1-0.20170715192408-3955978caca4
|
github.com/go-sql-driver/mysql v1.4.1-0.20191114115753-b4242bab7dc5
|
||||||
github.com/golang/mock v1.2.0
|
github.com/golang/mock v1.2.0
|
||||||
github.com/golang/protobuf v1.3.1
|
github.com/golang/protobuf v1.3.1
|
||||||
github.com/golang/snappy v0.0.0-20170215233205-553a64147049 // indirect
|
github.com/golang/snappy v0.0.0-20170215233205-553a64147049 // indirect
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -48,6 +48,8 @@ github.com/go-sql-driver/mysql v1.3.0 h1:pgwjLi/dvffoP9aabwkT3AKpXQM93QARkjFhDDq
|
||||||
github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
github.com/go-sql-driver/mysql v1.3.1-0.20170715192408-3955978caca4 h1:VR5tBQt9N1t7k1gLi3zDPVMJ7dNwrHewr8cH+r3hRCo=
|
github.com/go-sql-driver/mysql v1.3.1-0.20170715192408-3955978caca4 h1:VR5tBQt9N1t7k1gLi3zDPVMJ7dNwrHewr8cH+r3hRCo=
|
||||||
github.com/go-sql-driver/mysql v1.3.1-0.20170715192408-3955978caca4/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.3.1-0.20170715192408-3955978caca4/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.1-0.20191114115753-b4242bab7dc5 h1:TPdJVmaDpKVlxYKc2CTaU6iY51jeQqbRooWdI1ATYG4=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.1-0.20191114115753-b4242bab7dc5/go.mod h1:XIaZU7xtUgusUqDPXOOPcmC5Dyyw3F1pbh54fHzaehk=
|
||||||
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||||
|
|
|
@ -1,22 +1,48 @@
|
||||||
sudo: false
|
sudo: false
|
||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.5
|
- 1.9.x
|
||||||
- 1.6
|
- 1.10.x
|
||||||
- 1.7
|
- 1.11.x
|
||||||
- 1.8
|
- 1.12.x
|
||||||
- tip
|
- master
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- go get golang.org/x/tools/cmd/cover
|
- go get golang.org/x/tools/cmd/cover
|
||||||
- go get github.com/mattn/goveralls
|
- 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:
|
matrix:
|
||||||
include:
|
include:
|
||||||
|
- env: DB=MYSQL8
|
||||||
|
sudo: required
|
||||||
|
dist: trusty
|
||||||
|
go: 1.10.x
|
||||||
|
services:
|
||||||
|
- docker
|
||||||
|
before_install:
|
||||||
|
- go get golang.org/x/tools/cmd/cover
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
- docker pull mysql:8.0
|
||||||
|
- docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
|
||||||
|
mysql:8.0 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
|
||||||
|
- cp .travis/docker.cnf ~/.my.cnf
|
||||||
|
- .travis/wait_mysql.sh
|
||||||
|
before_script:
|
||||||
|
- export MYSQL_TEST_USER=gotest
|
||||||
|
- export MYSQL_TEST_PASS=secret
|
||||||
|
- export MYSQL_TEST_ADDR=127.0.0.1:3307
|
||||||
|
- export MYSQL_TEST_CONCURRENT=1
|
||||||
|
|
||||||
- env: DB=MYSQL57
|
- env: DB=MYSQL57
|
||||||
sudo: required
|
sudo: required
|
||||||
dist: trusty
|
dist: trusty
|
||||||
go: 1.8
|
go: 1.10.x
|
||||||
services:
|
services:
|
||||||
- docker
|
- docker
|
||||||
before_install:
|
before_install:
|
||||||
|
@ -24,10 +50,8 @@ matrix:
|
||||||
- go get github.com/mattn/goveralls
|
- go get github.com/mattn/goveralls
|
||||||
- docker pull mysql:5.7
|
- docker pull mysql:5.7
|
||||||
- docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
|
- docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
|
||||||
mysql:5.7 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB
|
mysql:5.7 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
|
||||||
- sleep 30
|
|
||||||
- cp .travis/docker.cnf ~/.my.cnf
|
- cp .travis/docker.cnf ~/.my.cnf
|
||||||
- mysql --print-defaults
|
|
||||||
- .travis/wait_mysql.sh
|
- .travis/wait_mysql.sh
|
||||||
before_script:
|
before_script:
|
||||||
- export MYSQL_TEST_USER=gotest
|
- export MYSQL_TEST_USER=gotest
|
||||||
|
@ -38,7 +62,7 @@ matrix:
|
||||||
- env: DB=MARIA55
|
- env: DB=MARIA55
|
||||||
sudo: required
|
sudo: required
|
||||||
dist: trusty
|
dist: trusty
|
||||||
go: 1.8
|
go: 1.10.x
|
||||||
services:
|
services:
|
||||||
- docker
|
- docker
|
||||||
before_install:
|
before_install:
|
||||||
|
@ -46,10 +70,8 @@ matrix:
|
||||||
- go get github.com/mattn/goveralls
|
- go get github.com/mattn/goveralls
|
||||||
- docker pull mariadb:5.5
|
- docker pull mariadb:5.5
|
||||||
- docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
|
- docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
|
||||||
mariadb:5.5 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB
|
mariadb:5.5 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
|
||||||
- sleep 30
|
|
||||||
- cp .travis/docker.cnf ~/.my.cnf
|
- cp .travis/docker.cnf ~/.my.cnf
|
||||||
- mysql --print-defaults
|
|
||||||
- .travis/wait_mysql.sh
|
- .travis/wait_mysql.sh
|
||||||
before_script:
|
before_script:
|
||||||
- export MYSQL_TEST_USER=gotest
|
- export MYSQL_TEST_USER=gotest
|
||||||
|
@ -60,7 +82,7 @@ matrix:
|
||||||
- env: DB=MARIA10_1
|
- env: DB=MARIA10_1
|
||||||
sudo: required
|
sudo: required
|
||||||
dist: trusty
|
dist: trusty
|
||||||
go: 1.8
|
go: 1.10.x
|
||||||
services:
|
services:
|
||||||
- docker
|
- docker
|
||||||
before_install:
|
before_install:
|
||||||
|
@ -68,10 +90,8 @@ matrix:
|
||||||
- go get github.com/mattn/goveralls
|
- go get github.com/mattn/goveralls
|
||||||
- docker pull mariadb:10.1
|
- docker pull mariadb:10.1
|
||||||
- docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
|
- docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
|
||||||
mariadb:10.1 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB
|
mariadb:10.1 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
|
||||||
- sleep 30
|
|
||||||
- cp .travis/docker.cnf ~/.my.cnf
|
- cp .travis/docker.cnf ~/.my.cnf
|
||||||
- mysql --print-defaults
|
|
||||||
- .travis/wait_mysql.sh
|
- .travis/wait_mysql.sh
|
||||||
before_script:
|
before_script:
|
||||||
- export MYSQL_TEST_USER=gotest
|
- export MYSQL_TEST_USER=gotest
|
||||||
|
@ -79,11 +99,31 @@ matrix:
|
||||||
- export MYSQL_TEST_ADDR=127.0.0.1:3307
|
- export MYSQL_TEST_ADDR=127.0.0.1:3307
|
||||||
- export MYSQL_TEST_CONCURRENT=1
|
- export MYSQL_TEST_CONCURRENT=1
|
||||||
|
|
||||||
|
- os: osx
|
||||||
|
osx_image: xcode10.1
|
||||||
|
addons:
|
||||||
|
homebrew:
|
||||||
|
packages:
|
||||||
|
- mysql
|
||||||
|
update: true
|
||||||
|
go: 1.12.x
|
||||||
|
before_install:
|
||||||
|
- go get golang.org/x/tools/cmd/cover
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
before_script:
|
||||||
|
- echo -e "[server]\ninnodb_log_file_size=256MB\ninnodb_buffer_pool_size=512MB\nmax_allowed_packet=16MB\nlocal_infile=1" >> /usr/local/etc/my.cnf
|
||||||
|
- mysql.server start
|
||||||
|
- mysql -uroot -e 'CREATE USER gotest IDENTIFIED BY "secret"'
|
||||||
|
- mysql -uroot -e 'GRANT ALL ON *.* TO gotest'
|
||||||
|
- mysql -uroot -e 'create database gotest;'
|
||||||
|
- export MYSQL_TEST_USER=gotest
|
||||||
|
- export MYSQL_TEST_PASS=secret
|
||||||
|
- export MYSQL_TEST_ADDR=127.0.0.1:3306
|
||||||
|
- export MYSQL_TEST_CONCURRENT=1
|
||||||
|
|
||||||
before_script:
|
|
||||||
- mysql -e 'create database gotest;'
|
|
||||||
script:
|
script:
|
||||||
- go test -v -covermode=count -coverprofile=coverage.out
|
- go test -v -covermode=count -coverprofile=coverage.out
|
||||||
- go vet ./...
|
- go vet ./...
|
||||||
- test -z "$(gofmt -d -s . | tee /dev/stderr)"
|
- .travis/gofmt.sh
|
||||||
|
after_script:
|
||||||
- $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci
|
- $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci
|
||||||
|
|
|
@ -13,41 +13,54 @@
|
||||||
|
|
||||||
Aaron Hopkins <go-sql-driver at die.net>
|
Aaron Hopkins <go-sql-driver at die.net>
|
||||||
Achille Roussel <achille.roussel at gmail.com>
|
Achille Roussel <achille.roussel at gmail.com>
|
||||||
|
Alexey Palazhchenko <alexey.palazhchenko at gmail.com>
|
||||||
|
Andrew Reid <andrew.reid at tixtrack.com>
|
||||||
Arne Hormann <arnehormann at gmail.com>
|
Arne Hormann <arnehormann at gmail.com>
|
||||||
Asta Xie <xiemengjun at gmail.com>
|
Asta Xie <xiemengjun at gmail.com>
|
||||||
Bulat Gaifullin <gaifullinbf at gmail.com>
|
Bulat Gaifullin <gaifullinbf at gmail.com>
|
||||||
Carlos Nieto <jose.carlos at menteslibres.net>
|
Carlos Nieto <jose.carlos at menteslibres.net>
|
||||||
Chris Moos <chris at tech9computers.com>
|
Chris Moos <chris at tech9computers.com>
|
||||||
|
Craig Wilson <craiggwilson at gmail.com>
|
||||||
|
Daniel Montoya <dsmontoyam at gmail.com>
|
||||||
Daniel Nichter <nil at codenode.com>
|
Daniel Nichter <nil at codenode.com>
|
||||||
Daniël van Eeden <git at myname.nl>
|
Daniël van Eeden <git at myname.nl>
|
||||||
Dave Protasowski <dprotaso at gmail.com>
|
Dave Protasowski <dprotaso at gmail.com>
|
||||||
DisposaBoy <disposaboy at dby.me>
|
DisposaBoy <disposaboy at dby.me>
|
||||||
Egor Smolyakov <egorsmkv at gmail.com>
|
Egor Smolyakov <egorsmkv at gmail.com>
|
||||||
|
Erwan Martin <hello at erwan.io>
|
||||||
Evan Shaw <evan at vendhq.com>
|
Evan Shaw <evan at vendhq.com>
|
||||||
Frederick Mayle <frederickmayle at gmail.com>
|
Frederick Mayle <frederickmayle at gmail.com>
|
||||||
Gustavo Kristic <gkristic at gmail.com>
|
Gustavo Kristic <gkristic at gmail.com>
|
||||||
|
Hajime Nakagami <nakagami at gmail.com>
|
||||||
Hanno Braun <mail at hannobraun.com>
|
Hanno Braun <mail at hannobraun.com>
|
||||||
Henri Yandell <flamefew at gmail.com>
|
Henri Yandell <flamefew at gmail.com>
|
||||||
Hirotaka Yamamoto <ymmt2005 at gmail.com>
|
Hirotaka Yamamoto <ymmt2005 at gmail.com>
|
||||||
|
Huyiguang <hyg at webterren.com>
|
||||||
ICHINOSE Shogo <shogo82148 at gmail.com>
|
ICHINOSE Shogo <shogo82148 at gmail.com>
|
||||||
|
Ilia Cimpoes <ichimpoesh at gmail.com>
|
||||||
INADA Naoki <songofacandy at gmail.com>
|
INADA Naoki <songofacandy at gmail.com>
|
||||||
Jacek Szwec <szwec.jacek at gmail.com>
|
Jacek Szwec <szwec.jacek at gmail.com>
|
||||||
James Harr <james.harr at gmail.com>
|
James Harr <james.harr at gmail.com>
|
||||||
Jeff Hodges <jeff at somethingsimilar.com>
|
Jeff Hodges <jeff at somethingsimilar.com>
|
||||||
|
Jeffrey Charles <jeffreycharles at gmail.com>
|
||||||
|
Jerome Meyer <jxmeyer at gmail.com>
|
||||||
Jian Zhen <zhenjl at gmail.com>
|
Jian Zhen <zhenjl at gmail.com>
|
||||||
Joshua Prunier <joshua.prunier at gmail.com>
|
Joshua Prunier <joshua.prunier at gmail.com>
|
||||||
Julien Lefevre <julien.lefevr at gmail.com>
|
Julien Lefevre <julien.lefevr at gmail.com>
|
||||||
Julien Schmidt <go-sql-driver at julienschmidt.com>
|
Julien Schmidt <go-sql-driver at julienschmidt.com>
|
||||||
|
Justin Li <jli at j-li.net>
|
||||||
Justin Nuß <nuss.justin at gmail.com>
|
Justin Nuß <nuss.justin at gmail.com>
|
||||||
Kamil Dziedzic <kamil at klecza.pl>
|
Kamil Dziedzic <kamil at klecza.pl>
|
||||||
Kevin Malachowski <kevin at chowski.com>
|
Kevin Malachowski <kevin at chowski.com>
|
||||||
|
Kieron Woodhouse <kieron.woodhouse at infosum.com>
|
||||||
Lennart Rudolph <lrudolph at hmc.edu>
|
Lennart Rudolph <lrudolph at hmc.edu>
|
||||||
Leonardo YongUk Kim <dalinaum at gmail.com>
|
Leonardo YongUk Kim <dalinaum at gmail.com>
|
||||||
|
Linh Tran Tuan <linhduonggnu at gmail.com>
|
||||||
Lion Yang <lion at aosc.xyz>
|
Lion Yang <lion at aosc.xyz>
|
||||||
Luca Looz <luca.looz92 at gmail.com>
|
Luca Looz <luca.looz92 at gmail.com>
|
||||||
Lucas Liu <extrafliu at gmail.com>
|
Lucas Liu <extrafliu at gmail.com>
|
||||||
Luke Scott <luke at webconnex.com>
|
Luke Scott <luke at webconnex.com>
|
||||||
Maciej Zimnoch <maciej.zimnoch@codilime.com>
|
Maciej Zimnoch <maciej.zimnoch at codilime.com>
|
||||||
Michael Woolnough <michael.woolnough at gmail.com>
|
Michael Woolnough <michael.woolnough at gmail.com>
|
||||||
Nicola Peduzzi <thenikso at gmail.com>
|
Nicola Peduzzi <thenikso at gmail.com>
|
||||||
Olivier Mengué <dolmen at cpan.org>
|
Olivier Mengué <dolmen at cpan.org>
|
||||||
|
@ -55,10 +68,20 @@ oscarzhao <oscarzhaosl at gmail.com>
|
||||||
Paul Bonser <misterpib at gmail.com>
|
Paul Bonser <misterpib at gmail.com>
|
||||||
Peter Schultz <peter.schultz at classmarkets.com>
|
Peter Schultz <peter.schultz at classmarkets.com>
|
||||||
Rebecca Chin <rchin at pivotal.io>
|
Rebecca Chin <rchin at pivotal.io>
|
||||||
|
Reed Allman <rdallman10 at gmail.com>
|
||||||
|
Richard Wilkes <wilkes at me.com>
|
||||||
|
Robert Russell <robert at rrbrussell.com>
|
||||||
Runrioter Wung <runrioter at gmail.com>
|
Runrioter Wung <runrioter at gmail.com>
|
||||||
|
Shuode Li <elemount at qq.com>
|
||||||
|
Simon J Mudd <sjmudd at pobox.com>
|
||||||
Soroush Pour <me at soroushjp.com>
|
Soroush Pour <me at soroushjp.com>
|
||||||
Stan Putrya <root.vagner at gmail.com>
|
Stan Putrya <root.vagner at gmail.com>
|
||||||
Stanley Gunawan <gunawan.stanley at gmail.com>
|
Stanley Gunawan <gunawan.stanley at gmail.com>
|
||||||
|
Steven Hartland <steven.hartland at multiplay.co.uk>
|
||||||
|
Thomas Wodarek <wodarekwebpage at gmail.com>
|
||||||
|
Tim Ruffles <timruffles at gmail.com>
|
||||||
|
Tom Jenkinson <tom at tjenkinson.me>
|
||||||
|
Vladimir Kovpak <cn007b at gmail.com>
|
||||||
Xiangyu Hu <xiangyu.hu at outlook.com>
|
Xiangyu Hu <xiangyu.hu at outlook.com>
|
||||||
Xiaobing Jiang <s7v7nislands at gmail.com>
|
Xiaobing Jiang <s7v7nislands at gmail.com>
|
||||||
Xiuming Chen <cc at cxm.cc>
|
Xiuming Chen <cc at cxm.cc>
|
||||||
|
@ -67,7 +90,14 @@ Zhenye Xie <xiezhenye at gmail.com>
|
||||||
# Organizations
|
# Organizations
|
||||||
|
|
||||||
Barracuda Networks, Inc.
|
Barracuda Networks, Inc.
|
||||||
|
Counting Ltd.
|
||||||
|
DigitalOcean Inc.
|
||||||
|
Facebook Inc.
|
||||||
|
GitHub Inc.
|
||||||
Google Inc.
|
Google Inc.
|
||||||
|
InfoSum Ltd.
|
||||||
Keybase Inc.
|
Keybase Inc.
|
||||||
|
Multiplay Ltd.
|
||||||
|
Percona LLC
|
||||||
Pivotal Inc.
|
Pivotal Inc.
|
||||||
Stripe Inc.
|
Stripe Inc.
|
||||||
|
|
|
@ -1,3 +1,51 @@
|
||||||
|
## Version 1.4 (2018-06-03)
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
- Documentation fixes (#530, #535, #567)
|
||||||
|
- Refactoring (#575, #579, #580, #581, #603, #615, #704)
|
||||||
|
- Cache column names (#444)
|
||||||
|
- Sort the DSN parameters in DSNs generated from a config (#637)
|
||||||
|
- Allow native password authentication by default (#644)
|
||||||
|
- Use the default port if it is missing in the DSN (#668)
|
||||||
|
- Removed the `strict` mode (#676)
|
||||||
|
- Do not query `max_allowed_packet` by default (#680)
|
||||||
|
- Dropped support Go 1.6 and lower (#696)
|
||||||
|
- Updated `ConvertValue()` to match the database/sql/driver implementation (#760)
|
||||||
|
- Document the usage of `0000-00-00T00:00:00` as the time.Time zero value (#783)
|
||||||
|
- Improved the compatibility of the authentication system (#807)
|
||||||
|
|
||||||
|
New Features:
|
||||||
|
|
||||||
|
- Multi-Results support (#537)
|
||||||
|
- `rejectReadOnly` DSN option (#604)
|
||||||
|
- `context.Context` support (#608, #612, #627, #761)
|
||||||
|
- Transaction isolation level support (#619, #744)
|
||||||
|
- Read-Only transactions support (#618, #634)
|
||||||
|
- `NewConfig` function which initializes a config with default values (#679)
|
||||||
|
- Implemented the `ColumnType` interfaces (#667, #724)
|
||||||
|
- Support for custom string types in `ConvertValue` (#623)
|
||||||
|
- Implemented `NamedValueChecker`, improving support for uint64 with high bit set (#690, #709, #710)
|
||||||
|
- `caching_sha2_password` authentication plugin support (#794, #800, #801, #802)
|
||||||
|
- Implemented `driver.SessionResetter` (#779)
|
||||||
|
- `sha256_password` authentication plugin support (#808)
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
|
||||||
|
- Use the DSN hostname as TLS default ServerName if `tls=true` (#564, #718)
|
||||||
|
- Fixed LOAD LOCAL DATA INFILE for empty files (#590)
|
||||||
|
- Removed columns definition cache since it sometimes cached invalid data (#592)
|
||||||
|
- Don't mutate registered TLS configs (#600)
|
||||||
|
- Make RegisterTLSConfig concurrency-safe (#613)
|
||||||
|
- Handle missing auth data in the handshake packet correctly (#646)
|
||||||
|
- Do not retry queries when data was written to avoid data corruption (#302, #736)
|
||||||
|
- Cache the connection pointer for error handling before invalidating it (#678)
|
||||||
|
- Fixed imports for appengine/cloudsql (#700)
|
||||||
|
- Fix sending STMT_LONG_DATA for 0 byte data (#734)
|
||||||
|
- Set correct capacity for []bytes read from length-encoded strings (#766)
|
||||||
|
- Make RegisterDial concurrency-safe (#773)
|
||||||
|
|
||||||
|
|
||||||
## Version 1.3 (2016-12-01)
|
## Version 1.3 (2016-12-01)
|
||||||
|
|
||||||
Changes:
|
Changes:
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
# Contributing Guidelines
|
|
||||||
|
|
||||||
## Reporting Issues
|
|
||||||
|
|
||||||
Before creating a new Issue, please check first if a similar Issue [already exists](https://github.com/go-sql-driver/mysql/issues?state=open) or was [recently closed](https://github.com/go-sql-driver/mysql/issues?direction=desc&page=1&sort=updated&state=closed).
|
|
||||||
|
|
||||||
## Contributing Code
|
|
||||||
|
|
||||||
By contributing to this project, you share your code under the Mozilla Public License 2, as specified in the LICENSE file.
|
|
||||||
Don't forget to add yourself to the AUTHORS file.
|
|
||||||
|
|
||||||
### Code Review
|
|
||||||
|
|
||||||
Everyone is invited to review and comment on pull requests.
|
|
||||||
If it looks fine to you, comment with "LGTM" (Looks good to me).
|
|
||||||
|
|
||||||
If changes are required, notice the reviewers with "PTAL" (Please take another look) after committing the fixes.
|
|
||||||
|
|
||||||
Before merging the Pull Request, at least one [team member](https://github.com/go-sql-driver?tab=members) must have commented with "LGTM".
|
|
||||||
|
|
||||||
## Development Ideas
|
|
||||||
|
|
||||||
If you are looking for ideas for code contributions, please check our [Development Ideas](https://github.com/go-sql-driver/mysql/wiki/Development-Ideas) Wiki page.
|
|
|
@ -16,10 +16,11 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
|
||||||
* [Parameters](#parameters)
|
* [Parameters](#parameters)
|
||||||
* [Examples](#examples)
|
* [Examples](#examples)
|
||||||
* [Connection pool and timeouts](#connection-pool-and-timeouts)
|
* [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)
|
* [LOAD DATA LOCAL INFILE support](#load-data-local-infile-support)
|
||||||
* [time.Time support](#timetime-support)
|
* [time.Time support](#timetime-support)
|
||||||
* [Unicode support](#unicode-support)
|
* [Unicode support](#unicode-support)
|
||||||
* [context.Context Support](#contextcontext-support)
|
|
||||||
* [Testing / Development](#testing--development)
|
* [Testing / Development](#testing--development)
|
||||||
* [License](#license)
|
* [License](#license)
|
||||||
|
|
||||||
|
@ -39,7 +40,7 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
|
||||||
* Optional placeholder interpolation
|
* Optional placeholder interpolation
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
* Go 1.5 or higher
|
* Go 1.9 or higher. We aim to support the 3 latest versions of Go.
|
||||||
* MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
|
* MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
|
||||||
|
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
@ -47,7 +48,7 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
|
||||||
## Installation
|
## Installation
|
||||||
Simple install the package to your [$GOPATH](https://github.com/golang/go/wiki/GOPATH "GOPATH") with the [go tool](https://golang.org/cmd/go/ "go command") from shell:
|
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
|
```bash
|
||||||
$ go get github.com/go-sql-driver/mysql
|
$ go get -u github.com/go-sql-driver/mysql
|
||||||
```
|
```
|
||||||
Make sure [Git is installed](https://git-scm.com/downloads) on your machine and in your system's `PATH`.
|
Make sure [Git is installed](https://git-scm.com/downloads) on your machine and in your system's `PATH`.
|
||||||
|
|
||||||
|
@ -101,7 +102,8 @@ See [net.Dial](https://golang.org/pkg/net/#Dial) for more information which netw
|
||||||
In general you should use an Unix domain socket if available and TCP otherwise for best performance.
|
In general you should use an Unix domain socket if available and TCP otherwise for best performance.
|
||||||
|
|
||||||
#### Address
|
#### 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.
|
If `host` is a literal IPv6 address, it must be enclosed in square brackets.
|
||||||
The functions [net.JoinHostPort](https://golang.org/pkg/net/#JoinHostPort) and [net.SplitHostPort](https://golang.org/pkg/net/#SplitHostPort) manipulate addresses in this form.
|
The functions [net.JoinHostPort](https://golang.org/pkg/net/#JoinHostPort) and [net.SplitHostPort](https://golang.org/pkg/net/#SplitHostPort) manipulate addresses in this form.
|
||||||
|
|
||||||
|
@ -138,9 +140,9 @@ Default: false
|
||||||
```
|
```
|
||||||
Type: bool
|
Type: bool
|
||||||
Valid Values: true, false
|
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`
|
##### `allowOldPasswords`
|
||||||
|
|
||||||
|
@ -169,13 +171,18 @@ Unless you need the fallback behavior, please use `collation` instead.
|
||||||
```
|
```
|
||||||
Type: string
|
Type: string
|
||||||
Valid Values: <name>
|
Valid Values: <name>
|
||||||
Default: utf8_general_ci
|
Default: utf8mb4_general_ci
|
||||||
```
|
```
|
||||||
|
|
||||||
Sets the collation used for client-server interaction on connection. In contrast to `charset`, `collation` does not issue additional queries. If the specified collation is unavailable on the target server, the connection will fail.
|
Sets the collation used for client-server interaction on connection. In contrast to `charset`, `collation` does not issue additional queries. If the specified collation is unavailable on the target server, the connection will fail.
|
||||||
|
|
||||||
A list of valid charsets for a server is retrievable with `SHOW COLLATION`.
|
A list of valid charsets for a server is retrievable with `SHOW COLLATION`.
|
||||||
|
|
||||||
|
The default collation (`utf8mb4_general_ci`) is supported from MySQL 5.5. You should use an older collation (e.g. `utf8_general_ci`) for older MySQL.
|
||||||
|
|
||||||
|
Collations for charset "ucs2", "utf16", "utf16le", and "utf32" can not be used ([ref](https://dev.mysql.com/doc/refman/5.7/en/charset-connection.html#charset-connection-impermissible-client-charset)).
|
||||||
|
|
||||||
|
|
||||||
##### `clientFoundRows`
|
##### `clientFoundRows`
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -231,10 +238,10 @@ Please keep in mind, that param values must be [url.QueryEscape](https://golang.
|
||||||
##### `maxAllowedPacket`
|
##### `maxAllowedPacket`
|
||||||
```
|
```
|
||||||
Type: decimal number
|
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`
|
##### `multiStatements`
|
||||||
|
|
||||||
|
@ -257,6 +264,7 @@ Default: false
|
||||||
```
|
```
|
||||||
|
|
||||||
`parseTime=true` changes the output type of `DATE` and `DATETIME` values to `time.Time` instead of `[]byte` / `string`
|
`parseTime=true` changes the output type of `DATE` and `DATETIME` values to `time.Time` instead of `[]byte` / `string`
|
||||||
|
The date or datetime like `0000-00-00 00:00:00` is converted into zero value of `time.Time`.
|
||||||
|
|
||||||
|
|
||||||
##### `readTimeout`
|
##### `readTimeout`
|
||||||
|
@ -277,7 +285,7 @@ Default: false
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
`rejectreadOnly=true` causes the driver to reject read-only connections. This
|
`rejectReadOnly=true` causes the driver to reject read-only connections. This
|
||||||
is for a possible race condition during an automatic failover, where the mysql
|
is for a possible race condition during an automatic failover, where the mysql
|
||||||
client gets connected to a read-only replica after the failover.
|
client gets connected to a read-only replica after the failover.
|
||||||
|
|
||||||
|
@ -292,20 +300,24 @@ 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)
|
supposed to happen, setting this on some MySQL providers (such as AWS Aurora)
|
||||||
is safer for failovers.
|
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.
|
||||||
|
|
||||||
##### `strict`
|
|
||||||
|
##### `serverPubKey`
|
||||||
|
|
||||||
```
|
```
|
||||||
Type: bool
|
Type: string
|
||||||
Valid Values: true, false
|
Valid Values: <name>
|
||||||
Default: false
|
Default: none
|
||||||
```
|
```
|
||||||
|
|
||||||
`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.
|
Server public keys can be registered with [`mysql.RegisterServerPubKey`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterServerPubKey), which can then be used by the assigned name in the DSN.
|
||||||
|
Public keys are used to transmit encrypted data, e.g. for authentication.
|
||||||
|
If the server's public key is known, it should be set manually to avoid expensive and potentially insecure transmissions of the public key from the server to the client each time it is required.
|
||||||
|
|
||||||
A server-side strict mode, which is safe for production use, can be set via the [`sql_mode`](https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html) system variable.
|
|
||||||
|
|
||||||
By default MySQL also treats notes as warnings. Use [`sql_notes=false`](http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_sql_notes) to ignore notes.
|
|
||||||
|
|
||||||
##### `timeout`
|
##### `timeout`
|
||||||
|
|
||||||
|
@ -316,15 +328,17 @@ Default: OS default
|
||||||
|
|
||||||
Timeout for establishing connections, aka dial timeout. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*.
|
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`
|
##### `tls`
|
||||||
|
|
||||||
```
|
```
|
||||||
Type: bool / string
|
Type: bool / string
|
||||||
Valid Values: true, false, skip-verify, <name>
|
Valid Values: true, false, skip-verify, preferred, <name>
|
||||||
Default: false
|
Default: false
|
||||||
```
|
```
|
||||||
|
|
||||||
`tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side). Use a custom value registered with [`mysql.RegisterTLSConfig`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig).
|
`tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side) or use `preferred` to use TLS only when advertised by the server. This is similar to `skip-verify`, but additionally allows a fallback to a connection which is not encrypted. Neither `skip-verify` nor `preferred` add any reliable security. You can use a custom TLS config after registering it with [`mysql.RegisterTLSConfig`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig).
|
||||||
|
|
||||||
|
|
||||||
##### `writeTimeout`
|
##### `writeTimeout`
|
||||||
|
|
||||||
|
@ -344,9 +358,9 @@ Any other parameters are interpreted as system variables:
|
||||||
* `<string_var>=%27<value>%27`: `SET <string_var>='<value>'`
|
* `<string_var>=%27<value>%27`: `SET <string_var>='<value>'`
|
||||||
|
|
||||||
Rules:
|
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!
|
* 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:
|
Examples:
|
||||||
* `autocommit=1`: `SET autocommit=1`
|
* `autocommit=1`: `SET autocommit=1`
|
||||||
|
@ -382,14 +396,9 @@ TCP on a remote host, e.g. Amazon RDS:
|
||||||
id:password@tcp(your-amazonaws-uri.com:3306)/dbname
|
id:password@tcp(your-amazonaws-uri.com:3306)/dbname
|
||||||
```
|
```
|
||||||
|
|
||||||
Google Cloud SQL on App Engine (First Generation MySQL Server):
|
Google Cloud SQL on App Engine:
|
||||||
```
|
```
|
||||||
user@cloudsql(project-id:instance-name)/dbname
|
user:password@unix(/cloudsql/project-id:region-name:instance-name)/dbname
|
||||||
```
|
|
||||||
|
|
||||||
Google Cloud SQL on App Engine (Second Generation MySQL Server):
|
|
||||||
```
|
|
||||||
user@cloudsql(project-id:regionname:instance-name)/dbname
|
|
||||||
```
|
```
|
||||||
|
|
||||||
TCP using default port (3306) on localhost:
|
TCP using default port (3306) on localhost:
|
||||||
|
@ -411,6 +420,13 @@ user:password@/
|
||||||
### Connection pool and timeouts
|
### 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.
|
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
|
### `LOAD DATA LOCAL INFILE` support
|
||||||
For this feature you need direct access to the package. Therefore you must change the import path (no `_`):
|
For this feature you need direct access to the package. Therefore you must change the import path (no `_`):
|
||||||
|
@ -426,9 +442,9 @@ See the [godoc of Go-MySQL-Driver](https://godoc.org/github.com/go-sql-driver/my
|
||||||
|
|
||||||
|
|
||||||
### `time.Time` support
|
### `time.Time` support
|
||||||
The default internal output type of MySQL `DATE` and `DATETIME` values is `[]byte` which allows you to scan the value into a `[]byte`, `string` or `sql.RawBytes` variable in your programm.
|
The default internal output type of MySQL `DATE` and `DATETIME` values is `[]byte` which allows you to scan the value into a `[]byte`, `string` or `sql.RawBytes` variable in your program.
|
||||||
|
|
||||||
However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` variables, which is the logical opposite in Go to `DATE` and `DATETIME` in MySQL. You can do that by changing the internal output type from `[]byte` to `time.Time` with the DSN parameter `parseTime=true`. You can set the default [`time.Time` location](https://golang.org/pkg/time/#Location) with the `loc` DSN parameter.
|
However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` variables, which is the logical equivalent in Go to `DATE` and `DATETIME` in MySQL. You can do that by changing the internal output type from `[]byte` to `time.Time` with the DSN parameter `parseTime=true`. You can set the default [`time.Time` location](https://golang.org/pkg/time/#Location) with the `loc` DSN parameter.
|
||||||
|
|
||||||
**Caution:** As of Go 1.1, this makes `time.Time` the only variable type you can scan `DATE` and `DATETIME` values into. This breaks for example [`sql.RawBytes` support](https://github.com/go-sql-driver/mysql/wiki/Examples#rawbytes).
|
**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).
|
||||||
|
|
||||||
|
@ -444,10 +460,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.
|
See http://dev.mysql.com/doc/refman/5.7/en/charset-unicode.html for more details on MySQL's Unicode support.
|
||||||
|
|
||||||
## `context.Context` Support
|
|
||||||
Go 1.8 added `database/sql` support for `context.Context`. This driver supports query timeouts and cancellation via contexts.
|
|
||||||
See [context support in the database/sql package](https://golang.org/doc/go1.8#database_sql) for more details.
|
|
||||||
|
|
||||||
## Testing / Development
|
## Testing / Development
|
||||||
To run the driver tests you may need to adjust the configuration. See the [Testing Wiki-Page](https://github.com/go-sql-driver/mysql/wiki/Testing "Testing") for details.
|
To run the driver tests you may need to adjust the configuration. See the [Testing Wiki-Page](https://github.com/go-sql-driver/mysql/wiki/Testing "Testing") for details.
|
||||||
|
|
||||||
|
@ -466,13 +478,13 @@ Mozilla summarizes the license scope as follows:
|
||||||
|
|
||||||
|
|
||||||
That means:
|
That means:
|
||||||
* You can **use** the **unchanged** source code both in private and commercially
|
* 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)
|
* 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 **needn't publish** the source code of your library as long as the files licensed under the MPL 2.0 are **unchanged**.
|
||||||
|
|
||||||
Please read the [MPL 2.0 FAQ](https://www.mozilla.org/en-US/MPL/2.0/FAQ/) if you have further questions regarding the license.
|
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).
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,422 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2018 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// server pub keys registry
|
||||||
|
var (
|
||||||
|
serverPubKeyLock sync.RWMutex
|
||||||
|
serverPubKeyRegistry map[string]*rsa.PublicKey
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterServerPubKey registers a server RSA public key which can be used to
|
||||||
|
// send data in a secure manner to the server without receiving the public key
|
||||||
|
// in a potentially insecure way from the server first.
|
||||||
|
// Registered keys can afterwards be used adding serverPubKey=<name> to the DSN.
|
||||||
|
//
|
||||||
|
// Note: The provided rsa.PublicKey instance is exclusively owned by the driver
|
||||||
|
// after registering it and may not be modified.
|
||||||
|
//
|
||||||
|
// data, err := ioutil.ReadFile("mykey.pem")
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// block, _ := pem.Decode(data)
|
||||||
|
// if block == nil || block.Type != "PUBLIC KEY" {
|
||||||
|
// log.Fatal("failed to decode PEM block containing public key")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// pub, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if rsaPubKey, ok := pub.(*rsa.PublicKey); ok {
|
||||||
|
// mysql.RegisterServerPubKey("mykey", rsaPubKey)
|
||||||
|
// } else {
|
||||||
|
// log.Fatal("not a RSA public key")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
func RegisterServerPubKey(name string, pubKey *rsa.PublicKey) {
|
||||||
|
serverPubKeyLock.Lock()
|
||||||
|
if serverPubKeyRegistry == nil {
|
||||||
|
serverPubKeyRegistry = make(map[string]*rsa.PublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
serverPubKeyRegistry[name] = pubKey
|
||||||
|
serverPubKeyLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeregisterServerPubKey removes the public key registered with the given name.
|
||||||
|
func DeregisterServerPubKey(name string) {
|
||||||
|
serverPubKeyLock.Lock()
|
||||||
|
if serverPubKeyRegistry != nil {
|
||||||
|
delete(serverPubKeyRegistry, name)
|
||||||
|
}
|
||||||
|
serverPubKeyLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getServerPubKey(name string) (pubKey *rsa.PublicKey) {
|
||||||
|
serverPubKeyLock.RLock()
|
||||||
|
if v, ok := serverPubKeyRegistry[name]; ok {
|
||||||
|
pubKey = v
|
||||||
|
}
|
||||||
|
serverPubKeyLock.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash password using pre 4.1 (old password) method
|
||||||
|
// https://github.com/atcurtis/mariadb/blob/master/mysys/my_rnd.c
|
||||||
|
type myRnd struct {
|
||||||
|
seed1, seed2 uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
const myRndMaxVal = 0x3FFFFFFF
|
||||||
|
|
||||||
|
// Pseudo random number generator
|
||||||
|
func newMyRnd(seed1, seed2 uint32) *myRnd {
|
||||||
|
return &myRnd{
|
||||||
|
seed1: seed1 % myRndMaxVal,
|
||||||
|
seed2: seed2 % myRndMaxVal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tested to be equivalent to MariaDB's floating point variant
|
||||||
|
// http://play.golang.org/p/QHvhd4qved
|
||||||
|
// http://play.golang.org/p/RG0q4ElWDx
|
||||||
|
func (r *myRnd) NextByte() byte {
|
||||||
|
r.seed1 = (r.seed1*3 + r.seed2) % myRndMaxVal
|
||||||
|
r.seed2 = (r.seed1 + r.seed2 + 33) % myRndMaxVal
|
||||||
|
|
||||||
|
return byte(uint64(r.seed1) * 31 / myRndMaxVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate binary hash from byte string using insecure pre 4.1 method
|
||||||
|
func pwHash(password []byte) (result [2]uint32) {
|
||||||
|
var add uint32 = 7
|
||||||
|
var tmp uint32
|
||||||
|
|
||||||
|
result[0] = 1345345333
|
||||||
|
result[1] = 0x12345671
|
||||||
|
|
||||||
|
for _, c := range password {
|
||||||
|
// skip spaces and tabs in password
|
||||||
|
if c == ' ' || c == '\t' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp = uint32(c)
|
||||||
|
result[0] ^= (((result[0] & 63) + add) * tmp) + (result[0] << 8)
|
||||||
|
result[1] += (result[1] << 8) ^ result[0]
|
||||||
|
add += tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove sign bit (1<<31)-1)
|
||||||
|
result[0] &= 0x7FFFFFFF
|
||||||
|
result[1] &= 0x7FFFFFFF
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash password using insecure pre 4.1 method
|
||||||
|
func scrambleOldPassword(scramble []byte, password string) []byte {
|
||||||
|
if len(password) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
scramble = scramble[:8]
|
||||||
|
|
||||||
|
hashPw := pwHash([]byte(password))
|
||||||
|
hashSc := pwHash(scramble)
|
||||||
|
|
||||||
|
r := newMyRnd(hashPw[0]^hashSc[0], hashPw[1]^hashSc[1])
|
||||||
|
|
||||||
|
var out [8]byte
|
||||||
|
for i := range out {
|
||||||
|
out[i] = r.NextByte() + 64
|
||||||
|
}
|
||||||
|
|
||||||
|
mask := r.NextByte()
|
||||||
|
for i := range out {
|
||||||
|
out[i] ^= mask
|
||||||
|
}
|
||||||
|
|
||||||
|
return out[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash password using 4.1+ method (SHA1)
|
||||||
|
func scramblePassword(scramble []byte, password string) []byte {
|
||||||
|
if len(password) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// stage1Hash = SHA1(password)
|
||||||
|
crypt := sha1.New()
|
||||||
|
crypt.Write([]byte(password))
|
||||||
|
stage1 := crypt.Sum(nil)
|
||||||
|
|
||||||
|
// scrambleHash = SHA1(scramble + SHA1(stage1Hash))
|
||||||
|
// inner Hash
|
||||||
|
crypt.Reset()
|
||||||
|
crypt.Write(stage1)
|
||||||
|
hash := crypt.Sum(nil)
|
||||||
|
|
||||||
|
// outer Hash
|
||||||
|
crypt.Reset()
|
||||||
|
crypt.Write(scramble)
|
||||||
|
crypt.Write(hash)
|
||||||
|
scramble = crypt.Sum(nil)
|
||||||
|
|
||||||
|
// token = scrambleHash XOR stage1Hash
|
||||||
|
for i := range scramble {
|
||||||
|
scramble[i] ^= stage1[i]
|
||||||
|
}
|
||||||
|
return scramble
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash password using MySQL 8+ method (SHA256)
|
||||||
|
func scrambleSHA256Password(scramble []byte, password string) []byte {
|
||||||
|
if len(password) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// XOR(SHA256(password), SHA256(SHA256(SHA256(password)), scramble))
|
||||||
|
|
||||||
|
crypt := sha256.New()
|
||||||
|
crypt.Write([]byte(password))
|
||||||
|
message1 := crypt.Sum(nil)
|
||||||
|
|
||||||
|
crypt.Reset()
|
||||||
|
crypt.Write(message1)
|
||||||
|
message1Hash := crypt.Sum(nil)
|
||||||
|
|
||||||
|
crypt.Reset()
|
||||||
|
crypt.Write(message1Hash)
|
||||||
|
crypt.Write(scramble)
|
||||||
|
message2 := crypt.Sum(nil)
|
||||||
|
|
||||||
|
for i := range message1 {
|
||||||
|
message1[i] ^= message2[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return message1
|
||||||
|
}
|
||||||
|
|
||||||
|
func encryptPassword(password string, seed []byte, pub *rsa.PublicKey) ([]byte, error) {
|
||||||
|
plain := make([]byte, len(password)+1)
|
||||||
|
copy(plain, password)
|
||||||
|
for i := range plain {
|
||||||
|
j := i % len(seed)
|
||||||
|
plain[i] ^= seed[j]
|
||||||
|
}
|
||||||
|
sha1 := sha1.New()
|
||||||
|
return rsa.EncryptOAEP(sha1, rand.Reader, pub, plain, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) sendEncryptedPassword(seed []byte, pub *rsa.PublicKey) error {
|
||||||
|
enc, err := encryptPassword(mc.cfg.Passwd, seed, pub)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return mc.writeAuthSwitchPacket(enc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, error) {
|
||||||
|
switch plugin {
|
||||||
|
case "caching_sha2_password":
|
||||||
|
authResp := scrambleSHA256Password(authData, mc.cfg.Passwd)
|
||||||
|
return authResp, nil
|
||||||
|
|
||||||
|
case "mysql_old_password":
|
||||||
|
if !mc.cfg.AllowOldPasswords {
|
||||||
|
return nil, ErrOldPassword
|
||||||
|
}
|
||||||
|
// Note: there are edge cases where this should work but doesn't;
|
||||||
|
// this is currently "wontfix":
|
||||||
|
// https://github.com/go-sql-driver/mysql/issues/184
|
||||||
|
authResp := append(scrambleOldPassword(authData[:8], mc.cfg.Passwd), 0)
|
||||||
|
return authResp, nil
|
||||||
|
|
||||||
|
case "mysql_clear_password":
|
||||||
|
if !mc.cfg.AllowCleartextPasswords {
|
||||||
|
return nil, ErrCleartextPassword
|
||||||
|
}
|
||||||
|
// http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html
|
||||||
|
// http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html
|
||||||
|
return append([]byte(mc.cfg.Passwd), 0), nil
|
||||||
|
|
||||||
|
case "mysql_native_password":
|
||||||
|
if !mc.cfg.AllowNativePasswords {
|
||||||
|
return nil, ErrNativePassword
|
||||||
|
}
|
||||||
|
// https://dev.mysql.com/doc/internals/en/secure-password-authentication.html
|
||||||
|
// Native password authentication only need and will need 20-byte challenge.
|
||||||
|
authResp := scramblePassword(authData[:20], mc.cfg.Passwd)
|
||||||
|
return authResp, nil
|
||||||
|
|
||||||
|
case "sha256_password":
|
||||||
|
if len(mc.cfg.Passwd) == 0 {
|
||||||
|
return []byte{0}, nil
|
||||||
|
}
|
||||||
|
if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
|
||||||
|
// write cleartext auth packet
|
||||||
|
return append([]byte(mc.cfg.Passwd), 0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKey := mc.cfg.pubKey
|
||||||
|
if pubKey == nil {
|
||||||
|
// request public key from server
|
||||||
|
return []byte{1}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encrypted password
|
||||||
|
enc, err := encryptPassword(mc.cfg.Passwd, authData, pubKey)
|
||||||
|
return enc, err
|
||||||
|
|
||||||
|
default:
|
||||||
|
errLog.Print("unknown auth plugin:", plugin)
|
||||||
|
return nil, ErrUnknownPlugin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
|
||||||
|
// Read Result Packet
|
||||||
|
authData, newPlugin, err := mc.readAuthResult()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle auth plugin switch, if requested
|
||||||
|
if newPlugin != "" {
|
||||||
|
// If CLIENT_PLUGIN_AUTH capability is not supported, no new cipher is
|
||||||
|
// sent and we have to keep using the cipher sent in the init packet.
|
||||||
|
if authData == nil {
|
||||||
|
authData = oldAuthData
|
||||||
|
} else {
|
||||||
|
// copy data from read buffer to owned slice
|
||||||
|
copy(oldAuthData, authData)
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin = newPlugin
|
||||||
|
|
||||||
|
authResp, err := mc.auth(authData, plugin)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = mc.writeAuthSwitchPacket(authResp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read Result Packet
|
||||||
|
authData, newPlugin, err = mc.readAuthResult()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not allow to change the auth plugin more than once
|
||||||
|
if newPlugin != "" {
|
||||||
|
return ErrMalformPkt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch plugin {
|
||||||
|
|
||||||
|
// https://insidemysql.com/preparing-your-community-connector-for-mysql-8-part-2-sha256/
|
||||||
|
case "caching_sha2_password":
|
||||||
|
switch len(authData) {
|
||||||
|
case 0:
|
||||||
|
return nil // auth successful
|
||||||
|
case 1:
|
||||||
|
switch authData[0] {
|
||||||
|
case cachingSha2PasswordFastAuthSuccess:
|
||||||
|
if err = mc.readResultOK(); err == nil {
|
||||||
|
return nil // auth successful
|
||||||
|
}
|
||||||
|
|
||||||
|
case cachingSha2PasswordPerformFullAuthentication:
|
||||||
|
if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
|
||||||
|
// write cleartext auth packet
|
||||||
|
err = mc.writeAuthSwitchPacket(append([]byte(mc.cfg.Passwd), 0))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pubKey := mc.cfg.pubKey
|
||||||
|
if pubKey == nil {
|
||||||
|
// request public key from server
|
||||||
|
data, err := mc.buf.takeSmallBuffer(4 + 1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data[4] = cachingSha2PasswordRequestPublicKey
|
||||||
|
mc.writePacket(data)
|
||||||
|
|
||||||
|
// parse public key
|
||||||
|
if data, err = mc.readPacket(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
block, _ := pem.Decode(data[1:])
|
||||||
|
pkix, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pubKey = pkix.(*rsa.PublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// send encrypted password
|
||||||
|
err = mc.sendEncryptedPassword(oldAuthData, pubKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mc.readResultOK()
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ErrMalformPkt
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return ErrMalformPkt
|
||||||
|
}
|
||||||
|
|
||||||
|
case "sha256_password":
|
||||||
|
switch len(authData) {
|
||||||
|
case 0:
|
||||||
|
return nil // auth successful
|
||||||
|
default:
|
||||||
|
block, _ := pem.Decode(authData)
|
||||||
|
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// send encrypted password
|
||||||
|
err = mc.sendEncryptedPassword(oldAuthData, pub.(*rsa.PublicKey))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return mc.readResultOK()
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil // auth successful
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
|
@ -15,47 +15,69 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultBufSize = 4096
|
const defaultBufSize = 4096
|
||||||
|
const maxCachedBufSize = 256 * 1024
|
||||||
|
|
||||||
// A buffer which is used for both reading and writing.
|
// A buffer which is used for both reading and writing.
|
||||||
// This is possible since communication on each connection is synchronous.
|
// This is possible since communication on each connection is synchronous.
|
||||||
// In other words, we can't write and read simultaneously on the same connection.
|
// In other words, we can't write and read simultaneously on the same connection.
|
||||||
// The buffer is similar to bufio.Reader / Writer but zero-copy-ish
|
// The buffer is similar to bufio.Reader / Writer but zero-copy-ish
|
||||||
// Also highly optimized for this particular use case.
|
// Also highly optimized for this particular use case.
|
||||||
|
// This buffer is backed by two byte slices in a double-buffering scheme
|
||||||
type buffer struct {
|
type buffer struct {
|
||||||
buf []byte
|
buf []byte // buf is a byte buffer who's length and capacity are equal.
|
||||||
nc net.Conn
|
nc net.Conn
|
||||||
idx int
|
idx int
|
||||||
length int
|
length int
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
|
dbuf [2][]byte // dbuf is an array with the two byte slices that back this buffer
|
||||||
|
flipcnt uint // flipccnt is the current buffer counter for double-buffering
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newBuffer allocates and returns a new buffer.
|
||||||
func newBuffer(nc net.Conn) buffer {
|
func newBuffer(nc net.Conn) buffer {
|
||||||
var b [defaultBufSize]byte
|
fg := make([]byte, defaultBufSize)
|
||||||
return buffer{
|
return buffer{
|
||||||
buf: b[:],
|
buf: fg,
|
||||||
nc: nc,
|
nc: nc,
|
||||||
|
dbuf: [2][]byte{fg, nil},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// flip replaces the active buffer with the background buffer
|
||||||
|
// this is a delayed flip that simply increases the buffer counter;
|
||||||
|
// the actual flip will be performed the next time we call `buffer.fill`
|
||||||
|
func (b *buffer) flip() {
|
||||||
|
b.flipcnt += 1
|
||||||
|
}
|
||||||
|
|
||||||
// fill reads into the buffer until at least _need_ bytes are in it
|
// fill reads into the buffer until at least _need_ bytes are in it
|
||||||
func (b *buffer) fill(need int) error {
|
func (b *buffer) fill(need int) error {
|
||||||
n := b.length
|
n := b.length
|
||||||
|
// fill data into its double-buffering target: if we've called
|
||||||
|
// flip on this buffer, we'll be copying to the background buffer,
|
||||||
|
// and then filling it with network data; otherwise we'll just move
|
||||||
|
// the contents of the current buffer to the front before filling it
|
||||||
|
dest := b.dbuf[b.flipcnt&1]
|
||||||
|
|
||||||
// move existing data to the beginning
|
// grow buffer if necessary to fit the whole packet.
|
||||||
if n > 0 && b.idx > 0 {
|
if need > len(dest) {
|
||||||
copy(b.buf[0:n], b.buf[b.idx:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// grow buffer if necessary
|
|
||||||
// TODO: let the buffer shrink again at some point
|
|
||||||
// Maybe keep the org buf slice and swap back?
|
|
||||||
if need > len(b.buf) {
|
|
||||||
// Round up to the next multiple of the default size
|
// Round up to the next multiple of the default size
|
||||||
newBuf := make([]byte, ((need/defaultBufSize)+1)*defaultBufSize)
|
dest = make([]byte, ((need/defaultBufSize)+1)*defaultBufSize)
|
||||||
copy(newBuf, b.buf)
|
|
||||||
b.buf = newBuf
|
// if the allocated buffer is not too large, move it to backing storage
|
||||||
|
// to prevent extra allocations on applications that perform large reads
|
||||||
|
if len(dest) <= maxCachedBufSize {
|
||||||
|
b.dbuf[b.flipcnt&1] = dest
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if we're filling the fg buffer, move the existing data to the start of it.
|
||||||
|
// if we're filling the bg buffer, copy over the data
|
||||||
|
if n > 0 {
|
||||||
|
copy(dest[:n], b.buf[b.idx:])
|
||||||
|
}
|
||||||
|
|
||||||
|
b.buf = dest
|
||||||
b.idx = 0
|
b.idx = 0
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
@ -105,43 +127,56 @@ func (b *buffer) readNext(need int) ([]byte, error) {
|
||||||
return b.buf[offset:b.idx], nil
|
return b.buf[offset:b.idx], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns a buffer with the requested size.
|
// takeBuffer returns a buffer with the requested size.
|
||||||
// If possible, a slice from the existing buffer is returned.
|
// If possible, a slice from the existing buffer is returned.
|
||||||
// Otherwise a bigger buffer is made.
|
// Otherwise a bigger buffer is made.
|
||||||
// Only one buffer (total) can be used at a time.
|
// Only one buffer (total) can be used at a time.
|
||||||
func (b *buffer) takeBuffer(length int) []byte {
|
func (b *buffer) takeBuffer(length int) ([]byte, error) {
|
||||||
if b.length > 0 {
|
if b.length > 0 {
|
||||||
return nil
|
return nil, ErrBusyBuffer
|
||||||
}
|
}
|
||||||
|
|
||||||
// test (cheap) general case first
|
// test (cheap) general case first
|
||||||
if length <= defaultBufSize || length <= cap(b.buf) {
|
if length <= cap(b.buf) {
|
||||||
return b.buf[:length]
|
return b.buf[:length], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if length < maxPacketSize {
|
if length < maxPacketSize {
|
||||||
b.buf = make([]byte, length)
|
b.buf = make([]byte, length)
|
||||||
return b.buf
|
return b.buf, nil
|
||||||
}
|
}
|
||||||
return make([]byte, length)
|
|
||||||
|
// buffer is larger than we want to store.
|
||||||
|
return make([]byte, length), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// shortcut which can be used if the requested buffer is guaranteed to be
|
// takeSmallBuffer is shortcut which can be used if length is
|
||||||
// smaller than defaultBufSize
|
// known to be smaller than defaultBufSize.
|
||||||
// Only one buffer (total) can be used at a time.
|
// Only one buffer (total) can be used at a time.
|
||||||
func (b *buffer) takeSmallBuffer(length int) []byte {
|
func (b *buffer) takeSmallBuffer(length int) ([]byte, error) {
|
||||||
if b.length == 0 {
|
if b.length > 0 {
|
||||||
return b.buf[:length]
|
return nil, ErrBusyBuffer
|
||||||
}
|
}
|
||||||
return nil
|
return b.buf[:length], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// takeCompleteBuffer returns the complete existing buffer.
|
// takeCompleteBuffer returns the complete existing buffer.
|
||||||
// This can be used if the necessary buffer size is unknown.
|
// This can be used if the necessary buffer size is unknown.
|
||||||
|
// cap and len of the returned buffer will be equal.
|
||||||
// Only one buffer (total) can be used at a time.
|
// Only one buffer (total) can be used at a time.
|
||||||
func (b *buffer) takeCompleteBuffer() []byte {
|
func (b *buffer) takeCompleteBuffer() ([]byte, error) {
|
||||||
if b.length == 0 {
|
if b.length > 0 {
|
||||||
return b.buf
|
return nil, ErrBusyBuffer
|
||||||
|
}
|
||||||
|
return b.buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// store stores buf, an updated buffer, if its suitable to do so.
|
||||||
|
func (b *buffer) store(buf []byte) error {
|
||||||
|
if b.length > 0 {
|
||||||
|
return ErrBusyBuffer
|
||||||
|
} else if cap(buf) <= maxPacketSize && cap(buf) > cap(b.buf) {
|
||||||
|
b.buf = buf[:cap(buf)]
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,182 +8,190 @@
|
||||||
|
|
||||||
package mysql
|
package mysql
|
||||||
|
|
||||||
const defaultCollation = "utf8_general_ci"
|
const defaultCollation = "utf8mb4_general_ci"
|
||||||
|
const binaryCollation = "binary"
|
||||||
|
|
||||||
// A list of available collations mapped to the internal ID.
|
// A list of available collations mapped to the internal ID.
|
||||||
// To update this map use the following MySQL query:
|
// To update this map use the following MySQL query:
|
||||||
// SELECT COLLATION_NAME, ID FROM information_schema.COLLATIONS
|
// SELECT COLLATION_NAME, ID FROM information_schema.COLLATIONS WHERE ID<256 ORDER BY ID
|
||||||
|
//
|
||||||
|
// Handshake packet have only 1 byte for collation_id. So we can't use collations with ID > 255.
|
||||||
|
//
|
||||||
|
// ucs2, utf16, and utf32 can't be used for connection charset.
|
||||||
|
// https://dev.mysql.com/doc/refman/5.7/en/charset-connection.html#charset-connection-impermissible-client-charset
|
||||||
|
// They are commented out to reduce this map.
|
||||||
var collations = map[string]byte{
|
var collations = map[string]byte{
|
||||||
"big5_chinese_ci": 1,
|
"big5_chinese_ci": 1,
|
||||||
"latin2_czech_cs": 2,
|
"latin2_czech_cs": 2,
|
||||||
"dec8_swedish_ci": 3,
|
"dec8_swedish_ci": 3,
|
||||||
"cp850_general_ci": 4,
|
"cp850_general_ci": 4,
|
||||||
"latin1_german1_ci": 5,
|
"latin1_german1_ci": 5,
|
||||||
"hp8_english_ci": 6,
|
"hp8_english_ci": 6,
|
||||||
"koi8r_general_ci": 7,
|
"koi8r_general_ci": 7,
|
||||||
"latin1_swedish_ci": 8,
|
"latin1_swedish_ci": 8,
|
||||||
"latin2_general_ci": 9,
|
"latin2_general_ci": 9,
|
||||||
"swe7_swedish_ci": 10,
|
"swe7_swedish_ci": 10,
|
||||||
"ascii_general_ci": 11,
|
"ascii_general_ci": 11,
|
||||||
"ujis_japanese_ci": 12,
|
"ujis_japanese_ci": 12,
|
||||||
"sjis_japanese_ci": 13,
|
"sjis_japanese_ci": 13,
|
||||||
"cp1251_bulgarian_ci": 14,
|
"cp1251_bulgarian_ci": 14,
|
||||||
"latin1_danish_ci": 15,
|
"latin1_danish_ci": 15,
|
||||||
"hebrew_general_ci": 16,
|
"hebrew_general_ci": 16,
|
||||||
"tis620_thai_ci": 18,
|
"tis620_thai_ci": 18,
|
||||||
"euckr_korean_ci": 19,
|
"euckr_korean_ci": 19,
|
||||||
"latin7_estonian_cs": 20,
|
"latin7_estonian_cs": 20,
|
||||||
"latin2_hungarian_ci": 21,
|
"latin2_hungarian_ci": 21,
|
||||||
"koi8u_general_ci": 22,
|
"koi8u_general_ci": 22,
|
||||||
"cp1251_ukrainian_ci": 23,
|
"cp1251_ukrainian_ci": 23,
|
||||||
"gb2312_chinese_ci": 24,
|
"gb2312_chinese_ci": 24,
|
||||||
"greek_general_ci": 25,
|
"greek_general_ci": 25,
|
||||||
"cp1250_general_ci": 26,
|
"cp1250_general_ci": 26,
|
||||||
"latin2_croatian_ci": 27,
|
"latin2_croatian_ci": 27,
|
||||||
"gbk_chinese_ci": 28,
|
"gbk_chinese_ci": 28,
|
||||||
"cp1257_lithuanian_ci": 29,
|
"cp1257_lithuanian_ci": 29,
|
||||||
"latin5_turkish_ci": 30,
|
"latin5_turkish_ci": 30,
|
||||||
"latin1_german2_ci": 31,
|
"latin1_german2_ci": 31,
|
||||||
"armscii8_general_ci": 32,
|
"armscii8_general_ci": 32,
|
||||||
"utf8_general_ci": 33,
|
"utf8_general_ci": 33,
|
||||||
"cp1250_czech_cs": 34,
|
"cp1250_czech_cs": 34,
|
||||||
"ucs2_general_ci": 35,
|
//"ucs2_general_ci": 35,
|
||||||
"cp866_general_ci": 36,
|
"cp866_general_ci": 36,
|
||||||
"keybcs2_general_ci": 37,
|
"keybcs2_general_ci": 37,
|
||||||
"macce_general_ci": 38,
|
"macce_general_ci": 38,
|
||||||
"macroman_general_ci": 39,
|
"macroman_general_ci": 39,
|
||||||
"cp852_general_ci": 40,
|
"cp852_general_ci": 40,
|
||||||
"latin7_general_ci": 41,
|
"latin7_general_ci": 41,
|
||||||
"latin7_general_cs": 42,
|
"latin7_general_cs": 42,
|
||||||
"macce_bin": 43,
|
"macce_bin": 43,
|
||||||
"cp1250_croatian_ci": 44,
|
"cp1250_croatian_ci": 44,
|
||||||
"utf8mb4_general_ci": 45,
|
"utf8mb4_general_ci": 45,
|
||||||
"utf8mb4_bin": 46,
|
"utf8mb4_bin": 46,
|
||||||
"latin1_bin": 47,
|
"latin1_bin": 47,
|
||||||
"latin1_general_ci": 48,
|
"latin1_general_ci": 48,
|
||||||
"latin1_general_cs": 49,
|
"latin1_general_cs": 49,
|
||||||
"cp1251_bin": 50,
|
"cp1251_bin": 50,
|
||||||
"cp1251_general_ci": 51,
|
"cp1251_general_ci": 51,
|
||||||
"cp1251_general_cs": 52,
|
"cp1251_general_cs": 52,
|
||||||
"macroman_bin": 53,
|
"macroman_bin": 53,
|
||||||
"utf16_general_ci": 54,
|
//"utf16_general_ci": 54,
|
||||||
"utf16_bin": 55,
|
//"utf16_bin": 55,
|
||||||
"utf16le_general_ci": 56,
|
//"utf16le_general_ci": 56,
|
||||||
"cp1256_general_ci": 57,
|
"cp1256_general_ci": 57,
|
||||||
"cp1257_bin": 58,
|
"cp1257_bin": 58,
|
||||||
"cp1257_general_ci": 59,
|
"cp1257_general_ci": 59,
|
||||||
"utf32_general_ci": 60,
|
//"utf32_general_ci": 60,
|
||||||
"utf32_bin": 61,
|
//"utf32_bin": 61,
|
||||||
"utf16le_bin": 62,
|
//"utf16le_bin": 62,
|
||||||
"binary": 63,
|
"binary": 63,
|
||||||
"armscii8_bin": 64,
|
"armscii8_bin": 64,
|
||||||
"ascii_bin": 65,
|
"ascii_bin": 65,
|
||||||
"cp1250_bin": 66,
|
"cp1250_bin": 66,
|
||||||
"cp1256_bin": 67,
|
"cp1256_bin": 67,
|
||||||
"cp866_bin": 68,
|
"cp866_bin": 68,
|
||||||
"dec8_bin": 69,
|
"dec8_bin": 69,
|
||||||
"greek_bin": 70,
|
"greek_bin": 70,
|
||||||
"hebrew_bin": 71,
|
"hebrew_bin": 71,
|
||||||
"hp8_bin": 72,
|
"hp8_bin": 72,
|
||||||
"keybcs2_bin": 73,
|
"keybcs2_bin": 73,
|
||||||
"koi8r_bin": 74,
|
"koi8r_bin": 74,
|
||||||
"koi8u_bin": 75,
|
"koi8u_bin": 75,
|
||||||
"latin2_bin": 77,
|
"utf8_tolower_ci": 76,
|
||||||
"latin5_bin": 78,
|
"latin2_bin": 77,
|
||||||
"latin7_bin": 79,
|
"latin5_bin": 78,
|
||||||
"cp850_bin": 80,
|
"latin7_bin": 79,
|
||||||
"cp852_bin": 81,
|
"cp850_bin": 80,
|
||||||
"swe7_bin": 82,
|
"cp852_bin": 81,
|
||||||
"utf8_bin": 83,
|
"swe7_bin": 82,
|
||||||
"big5_bin": 84,
|
"utf8_bin": 83,
|
||||||
"euckr_bin": 85,
|
"big5_bin": 84,
|
||||||
"gb2312_bin": 86,
|
"euckr_bin": 85,
|
||||||
"gbk_bin": 87,
|
"gb2312_bin": 86,
|
||||||
"sjis_bin": 88,
|
"gbk_bin": 87,
|
||||||
"tis620_bin": 89,
|
"sjis_bin": 88,
|
||||||
"ucs2_bin": 90,
|
"tis620_bin": 89,
|
||||||
"ujis_bin": 91,
|
//"ucs2_bin": 90,
|
||||||
"geostd8_general_ci": 92,
|
"ujis_bin": 91,
|
||||||
"geostd8_bin": 93,
|
"geostd8_general_ci": 92,
|
||||||
"latin1_spanish_ci": 94,
|
"geostd8_bin": 93,
|
||||||
"cp932_japanese_ci": 95,
|
"latin1_spanish_ci": 94,
|
||||||
"cp932_bin": 96,
|
"cp932_japanese_ci": 95,
|
||||||
"eucjpms_japanese_ci": 97,
|
"cp932_bin": 96,
|
||||||
"eucjpms_bin": 98,
|
"eucjpms_japanese_ci": 97,
|
||||||
"cp1250_polish_ci": 99,
|
"eucjpms_bin": 98,
|
||||||
"utf16_unicode_ci": 101,
|
"cp1250_polish_ci": 99,
|
||||||
"utf16_icelandic_ci": 102,
|
//"utf16_unicode_ci": 101,
|
||||||
"utf16_latvian_ci": 103,
|
//"utf16_icelandic_ci": 102,
|
||||||
"utf16_romanian_ci": 104,
|
//"utf16_latvian_ci": 103,
|
||||||
"utf16_slovenian_ci": 105,
|
//"utf16_romanian_ci": 104,
|
||||||
"utf16_polish_ci": 106,
|
//"utf16_slovenian_ci": 105,
|
||||||
"utf16_estonian_ci": 107,
|
//"utf16_polish_ci": 106,
|
||||||
"utf16_spanish_ci": 108,
|
//"utf16_estonian_ci": 107,
|
||||||
"utf16_swedish_ci": 109,
|
//"utf16_spanish_ci": 108,
|
||||||
"utf16_turkish_ci": 110,
|
//"utf16_swedish_ci": 109,
|
||||||
"utf16_czech_ci": 111,
|
//"utf16_turkish_ci": 110,
|
||||||
"utf16_danish_ci": 112,
|
//"utf16_czech_ci": 111,
|
||||||
"utf16_lithuanian_ci": 113,
|
//"utf16_danish_ci": 112,
|
||||||
"utf16_slovak_ci": 114,
|
//"utf16_lithuanian_ci": 113,
|
||||||
"utf16_spanish2_ci": 115,
|
//"utf16_slovak_ci": 114,
|
||||||
"utf16_roman_ci": 116,
|
//"utf16_spanish2_ci": 115,
|
||||||
"utf16_persian_ci": 117,
|
//"utf16_roman_ci": 116,
|
||||||
"utf16_esperanto_ci": 118,
|
//"utf16_persian_ci": 117,
|
||||||
"utf16_hungarian_ci": 119,
|
//"utf16_esperanto_ci": 118,
|
||||||
"utf16_sinhala_ci": 120,
|
//"utf16_hungarian_ci": 119,
|
||||||
"utf16_german2_ci": 121,
|
//"utf16_sinhala_ci": 120,
|
||||||
"utf16_croatian_ci": 122,
|
//"utf16_german2_ci": 121,
|
||||||
"utf16_unicode_520_ci": 123,
|
//"utf16_croatian_ci": 122,
|
||||||
"utf16_vietnamese_ci": 124,
|
//"utf16_unicode_520_ci": 123,
|
||||||
"ucs2_unicode_ci": 128,
|
//"utf16_vietnamese_ci": 124,
|
||||||
"ucs2_icelandic_ci": 129,
|
//"ucs2_unicode_ci": 128,
|
||||||
"ucs2_latvian_ci": 130,
|
//"ucs2_icelandic_ci": 129,
|
||||||
"ucs2_romanian_ci": 131,
|
//"ucs2_latvian_ci": 130,
|
||||||
"ucs2_slovenian_ci": 132,
|
//"ucs2_romanian_ci": 131,
|
||||||
"ucs2_polish_ci": 133,
|
//"ucs2_slovenian_ci": 132,
|
||||||
"ucs2_estonian_ci": 134,
|
//"ucs2_polish_ci": 133,
|
||||||
"ucs2_spanish_ci": 135,
|
//"ucs2_estonian_ci": 134,
|
||||||
"ucs2_swedish_ci": 136,
|
//"ucs2_spanish_ci": 135,
|
||||||
"ucs2_turkish_ci": 137,
|
//"ucs2_swedish_ci": 136,
|
||||||
"ucs2_czech_ci": 138,
|
//"ucs2_turkish_ci": 137,
|
||||||
"ucs2_danish_ci": 139,
|
//"ucs2_czech_ci": 138,
|
||||||
"ucs2_lithuanian_ci": 140,
|
//"ucs2_danish_ci": 139,
|
||||||
"ucs2_slovak_ci": 141,
|
//"ucs2_lithuanian_ci": 140,
|
||||||
"ucs2_spanish2_ci": 142,
|
//"ucs2_slovak_ci": 141,
|
||||||
"ucs2_roman_ci": 143,
|
//"ucs2_spanish2_ci": 142,
|
||||||
"ucs2_persian_ci": 144,
|
//"ucs2_roman_ci": 143,
|
||||||
"ucs2_esperanto_ci": 145,
|
//"ucs2_persian_ci": 144,
|
||||||
"ucs2_hungarian_ci": 146,
|
//"ucs2_esperanto_ci": 145,
|
||||||
"ucs2_sinhala_ci": 147,
|
//"ucs2_hungarian_ci": 146,
|
||||||
"ucs2_german2_ci": 148,
|
//"ucs2_sinhala_ci": 147,
|
||||||
"ucs2_croatian_ci": 149,
|
//"ucs2_german2_ci": 148,
|
||||||
"ucs2_unicode_520_ci": 150,
|
//"ucs2_croatian_ci": 149,
|
||||||
"ucs2_vietnamese_ci": 151,
|
//"ucs2_unicode_520_ci": 150,
|
||||||
"ucs2_general_mysql500_ci": 159,
|
//"ucs2_vietnamese_ci": 151,
|
||||||
"utf32_unicode_ci": 160,
|
//"ucs2_general_mysql500_ci": 159,
|
||||||
"utf32_icelandic_ci": 161,
|
//"utf32_unicode_ci": 160,
|
||||||
"utf32_latvian_ci": 162,
|
//"utf32_icelandic_ci": 161,
|
||||||
"utf32_romanian_ci": 163,
|
//"utf32_latvian_ci": 162,
|
||||||
"utf32_slovenian_ci": 164,
|
//"utf32_romanian_ci": 163,
|
||||||
"utf32_polish_ci": 165,
|
//"utf32_slovenian_ci": 164,
|
||||||
"utf32_estonian_ci": 166,
|
//"utf32_polish_ci": 165,
|
||||||
"utf32_spanish_ci": 167,
|
//"utf32_estonian_ci": 166,
|
||||||
"utf32_swedish_ci": 168,
|
//"utf32_spanish_ci": 167,
|
||||||
"utf32_turkish_ci": 169,
|
//"utf32_swedish_ci": 168,
|
||||||
"utf32_czech_ci": 170,
|
//"utf32_turkish_ci": 169,
|
||||||
"utf32_danish_ci": 171,
|
//"utf32_czech_ci": 170,
|
||||||
"utf32_lithuanian_ci": 172,
|
//"utf32_danish_ci": 171,
|
||||||
"utf32_slovak_ci": 173,
|
//"utf32_lithuanian_ci": 172,
|
||||||
"utf32_spanish2_ci": 174,
|
//"utf32_slovak_ci": 173,
|
||||||
"utf32_roman_ci": 175,
|
//"utf32_spanish2_ci": 174,
|
||||||
"utf32_persian_ci": 176,
|
//"utf32_roman_ci": 175,
|
||||||
"utf32_esperanto_ci": 177,
|
//"utf32_persian_ci": 176,
|
||||||
"utf32_hungarian_ci": 178,
|
//"utf32_esperanto_ci": 177,
|
||||||
"utf32_sinhala_ci": 179,
|
//"utf32_hungarian_ci": 178,
|
||||||
"utf32_german2_ci": 180,
|
//"utf32_sinhala_ci": 179,
|
||||||
"utf32_croatian_ci": 181,
|
//"utf32_german2_ci": 180,
|
||||||
"utf32_unicode_520_ci": 182,
|
//"utf32_croatian_ci": 181,
|
||||||
"utf32_vietnamese_ci": 183,
|
//"utf32_unicode_520_ci": 182,
|
||||||
|
//"utf32_vietnamese_ci": 183,
|
||||||
"utf8_unicode_ci": 192,
|
"utf8_unicode_ci": 192,
|
||||||
"utf8_icelandic_ci": 193,
|
"utf8_icelandic_ci": 193,
|
||||||
"utf8_latvian_ci": 194,
|
"utf8_latvian_ci": 194,
|
||||||
|
@ -233,18 +241,25 @@ var collations = map[string]byte{
|
||||||
"utf8mb4_croatian_ci": 245,
|
"utf8mb4_croatian_ci": 245,
|
||||||
"utf8mb4_unicode_520_ci": 246,
|
"utf8mb4_unicode_520_ci": 246,
|
||||||
"utf8mb4_vietnamese_ci": 247,
|
"utf8mb4_vietnamese_ci": 247,
|
||||||
|
"gb18030_chinese_ci": 248,
|
||||||
|
"gb18030_bin": 249,
|
||||||
|
"gb18030_unicode_520_ci": 250,
|
||||||
|
"utf8mb4_0900_ai_ci": 255,
|
||||||
}
|
}
|
||||||
|
|
||||||
// A blacklist of collations which is unsafe to interpolate parameters.
|
// A blacklist of collations which is unsafe to interpolate parameters.
|
||||||
// These multibyte encodings may contains 0x5c (`\`) in their trailing bytes.
|
// These multibyte encodings may contains 0x5c (`\`) in their trailing bytes.
|
||||||
var unsafeCollations = map[string]bool{
|
var unsafeCollations = map[string]bool{
|
||||||
"big5_chinese_ci": true,
|
"big5_chinese_ci": true,
|
||||||
"sjis_japanese_ci": true,
|
"sjis_japanese_ci": true,
|
||||||
"gbk_chinese_ci": true,
|
"gbk_chinese_ci": true,
|
||||||
"big5_bin": true,
|
"big5_bin": true,
|
||||||
"gb2312_bin": true,
|
"gb2312_bin": true,
|
||||||
"gbk_bin": true,
|
"gbk_bin": true,
|
||||||
"sjis_bin": true,
|
"sjis_bin": true,
|
||||||
"cp932_japanese_ci": true,
|
"cp932_japanese_ci": true,
|
||||||
"cp932_bin": true,
|
"cp932_bin": true,
|
||||||
|
"gb18030_chinese_ci": true,
|
||||||
|
"gb18030_bin": true,
|
||||||
|
"gb18030_unicode_520_ci": true,
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2019 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
// +build !windows,!appengine
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errUnexpectedRead = errors.New("unexpected read from socket")
|
||||||
|
|
||||||
|
func connCheck(conn net.Conn) error {
|
||||||
|
var sysErr error
|
||||||
|
|
||||||
|
sysConn, ok := conn.(syscall.Conn)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
rawConn, err := sysConn.SyscallConn()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rawConn.Read(func(fd uintptr) bool {
|
||||||
|
var buf [1]byte
|
||||||
|
n, err := syscall.Read(int(fd), buf[:])
|
||||||
|
switch {
|
||||||
|
case n == 0 && err == nil:
|
||||||
|
sysErr = io.EOF
|
||||||
|
case n > 0:
|
||||||
|
sysErr = errUnexpectedRead
|
||||||
|
case err == syscall.EAGAIN || err == syscall.EWOULDBLOCK:
|
||||||
|
sysErr = nil
|
||||||
|
default:
|
||||||
|
sysErr = err
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sysErr
|
||||||
|
}
|
|
@ -1,19 +1,17 @@
|
||||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
//
|
//
|
||||||
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
// Copyright 2019 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
//
|
//
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
// 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,
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
// +build appengine
|
// +build windows appengine
|
||||||
|
|
||||||
package mysql
|
package mysql
|
||||||
|
|
||||||
import (
|
import "net"
|
||||||
"appengine/cloudsql"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func connCheck(conn net.Conn) error {
|
||||||
RegisterDial("cloudsql", cloudsql.Dial)
|
return nil
|
||||||
}
|
}
|
|
@ -9,6 +9,8 @@
|
||||||
package mysql
|
package mysql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
@ -17,19 +19,10 @@ import (
|
||||||
"time"
|
"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 {
|
type mysqlConn struct {
|
||||||
buf buffer
|
buf buffer
|
||||||
netConn net.Conn
|
netConn net.Conn
|
||||||
|
rawConn net.Conn // underlying connection when netConn is TLS connection.
|
||||||
affectedRows uint64
|
affectedRows uint64
|
||||||
insertId uint64
|
insertId uint64
|
||||||
cfg *Config
|
cfg *Config
|
||||||
|
@ -40,11 +33,11 @@ type mysqlConn struct {
|
||||||
status statusFlag
|
status statusFlag
|
||||||
sequence uint8
|
sequence uint8
|
||||||
parseTime bool
|
parseTime bool
|
||||||
strict bool
|
reset bool // set when the Go SQL package calls ResetSession
|
||||||
|
|
||||||
// for context support (Go 1.8+)
|
// for context support (Go 1.8+)
|
||||||
watching bool
|
watching bool
|
||||||
watcher chan<- mysqlContext
|
watcher chan<- context.Context
|
||||||
closech chan struct{}
|
closech chan struct{}
|
||||||
finished chan<- struct{}
|
finished chan<- struct{}
|
||||||
canceled atomicError // set non-nil if conn is canceled
|
canceled atomicError // set non-nil if conn is canceled
|
||||||
|
@ -81,17 +74,36 @@ func (mc *mysqlConn) handleParams() (err error) {
|
||||||
return
|
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) {
|
func (mc *mysqlConn) Begin() (driver.Tx, error) {
|
||||||
|
return mc.begin(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) begin(readOnly bool) (driver.Tx, error) {
|
||||||
if mc.closed.IsSet() {
|
if mc.closed.IsSet() {
|
||||||
errLog.Print(ErrInvalidConn)
|
errLog.Print(ErrInvalidConn)
|
||||||
return nil, driver.ErrBadConn
|
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 {
|
if err == nil {
|
||||||
return &mysqlTx{mc}, err
|
return &mysqlTx{mc}, err
|
||||||
}
|
}
|
||||||
|
return nil, mc.markBadConn(err)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc *mysqlConn) Close() (err error) {
|
func (mc *mysqlConn) Close() (err error) {
|
||||||
|
@ -142,7 +154,9 @@ func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
|
||||||
// Send command
|
// Send command
|
||||||
err := mc.writeCommandPacketStr(comStmtPrepare, query)
|
err := mc.writeCommandPacketStr(comStmtPrepare, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
// STMT_PREPARE is safe to retry. So we can return ErrBadConn here.
|
||||||
|
errLog.Print(err)
|
||||||
|
return nil, driver.ErrBadConn
|
||||||
}
|
}
|
||||||
|
|
||||||
stmt := &mysqlStmt{
|
stmt := &mysqlStmt{
|
||||||
|
@ -172,11 +186,11 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
|
||||||
return "", driver.ErrSkip
|
return "", driver.ErrSkip
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := mc.buf.takeCompleteBuffer()
|
buf, err := mc.buf.takeCompleteBuffer()
|
||||||
if buf == nil {
|
if err != nil {
|
||||||
// can not take the buffer. Something must be wrong with the connection
|
// can not take the buffer. Something must be wrong with the connection
|
||||||
errLog.Print(ErrBusyBuffer)
|
errLog.Print(err)
|
||||||
return "", driver.ErrBadConn
|
return "", ErrInvalidConn
|
||||||
}
|
}
|
||||||
buf = buf[:0]
|
buf = buf[:0]
|
||||||
argPos := 0
|
argPos := 0
|
||||||
|
@ -201,6 +215,9 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
|
||||||
switch v := arg.(type) {
|
switch v := arg.(type) {
|
||||||
case int64:
|
case int64:
|
||||||
buf = strconv.AppendInt(buf, v, 10)
|
buf = strconv.AppendInt(buf, v, 10)
|
||||||
|
case uint64:
|
||||||
|
// Handle uint64 explicitly because our custom ConvertValue emits unsigned values
|
||||||
|
buf = strconv.AppendUint(buf, v, 10)
|
||||||
case float64:
|
case float64:
|
||||||
buf = strconv.AppendFloat(buf, v, 'g', -1, 64)
|
buf = strconv.AppendFloat(buf, v, 'g', -1, 64)
|
||||||
case bool:
|
case bool:
|
||||||
|
@ -314,14 +331,14 @@ func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, err
|
||||||
insertId: int64(mc.insertId),
|
insertId: int64(mc.insertId),
|
||||||
}, err
|
}, err
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, mc.markBadConn(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal function to execute commands
|
// Internal function to execute commands
|
||||||
func (mc *mysqlConn) exec(query string) error {
|
func (mc *mysqlConn) exec(query string) error {
|
||||||
// Send command
|
// Send command
|
||||||
if err := mc.writeCommandPacketStr(comQuery, query); err != nil {
|
if err := mc.writeCommandPacketStr(comQuery, query); err != nil {
|
||||||
return err
|
return mc.markBadConn(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read Result
|
// Read Result
|
||||||
|
@ -385,12 +402,13 @@ func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Columns
|
// Columns
|
||||||
rows.rs.columns, err = mc.readColumns(resLen)
|
rows.rs.columns, err = mc.readColumns(resLen)
|
||||||
return rows, err
|
return rows, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, mc.markBadConn(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the value of the given MySQL System Variable
|
// Gets the value of the given MySQL System Variable
|
||||||
|
@ -440,3 +458,194 @@ func (mc *mysqlConn) finish() {
|
||||||
case <-mc.closech:
|
case <-mc.closech:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ping implements driver.Pinger interface
|
||||||
|
func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
|
||||||
|
if mc.closed.IsSet() {
|
||||||
|
errLog.Print(ErrInvalidConn)
|
||||||
|
return driver.ErrBadConn
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = mc.watchCancel(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer mc.finish()
|
||||||
|
|
||||||
|
if err = mc.writeCommandPacket(comPing); err != nil {
|
||||||
|
return mc.markBadConn(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mc.readResultOK()
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeginTx implements driver.ConnBeginTx interface
|
||||||
|
func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
|
||||||
|
if err := mc.watchCancel(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer mc.finish()
|
||||||
|
|
||||||
|
if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault {
|
||||||
|
level, err := mapIsolationLevel(opts.Isolation)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = mc.exec("SET TRANSACTION ISOLATION LEVEL " + level)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mc.begin(opts.ReadOnly)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
|
||||||
|
dargs, err := namedValueToValue(args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := mc.watchCancel(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := mc.query(query, dargs)
|
||||||
|
if err != nil {
|
||||||
|
mc.finish()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rows.finish = mc.finish
|
||||||
|
return rows, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
|
||||||
|
dargs, err := namedValueToValue(args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := mc.watchCancel(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer mc.finish()
|
||||||
|
|
||||||
|
return mc.Exec(query, dargs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
|
||||||
|
if err := mc.watchCancel(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, err := mc.Prepare(query)
|
||||||
|
mc.finish()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
default:
|
||||||
|
case <-ctx.Done():
|
||||||
|
stmt.Close()
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
return stmt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stmt *mysqlStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
|
||||||
|
dargs, err := namedValueToValue(args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := stmt.mc.watchCancel(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := stmt.query(dargs)
|
||||||
|
if err != nil {
|
||||||
|
stmt.mc.finish()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rows.finish = stmt.mc.finish
|
||||||
|
return rows, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stmt *mysqlStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
|
||||||
|
dargs, err := namedValueToValue(args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := stmt.mc.watchCancel(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer stmt.mc.finish()
|
||||||
|
|
||||||
|
return stmt.Exec(dargs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) watchCancel(ctx context.Context) error {
|
||||||
|
if mc.watching {
|
||||||
|
// Reach here if canceled,
|
||||||
|
// so the connection is already invalid
|
||||||
|
mc.cleanup()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// When ctx is already cancelled, don't watch it.
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// When ctx is not cancellable, don't watch it.
|
||||||
|
if ctx.Done() == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// When watcher is not alive, can't watch it.
|
||||||
|
if mc.watcher == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mc.watching = true
|
||||||
|
mc.watcher <- ctx
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) startWatcher() {
|
||||||
|
watcher := make(chan context.Context, 1)
|
||||||
|
mc.watcher = watcher
|
||||||
|
finished := make(chan struct{})
|
||||||
|
mc.finished = finished
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
var ctx context.Context
|
||||||
|
select {
|
||||||
|
case ctx = <-watcher:
|
||||||
|
case <-mc.closech:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
mc.cancel(ctx.Err())
|
||||||
|
case <-finished:
|
||||||
|
case <-mc.closech:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) {
|
||||||
|
nv.Value, err = converter{}.ConvertValue(nv.Value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetSession implements driver.SessionResetter.
|
||||||
|
// (From Go 1.10)
|
||||||
|
func (mc *mysqlConn) ResetSession(ctx context.Context) error {
|
||||||
|
if mc.closed.IsSet() {
|
||||||
|
return driver.ErrBadConn
|
||||||
|
}
|
||||||
|
mc.reset = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -1,204 +0,0 @@
|
||||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
|
||||||
//
|
|
||||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
||||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
// +build go1.8
|
|
||||||
|
|
||||||
package mysql
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"database/sql/driver"
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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 opts.ReadOnly {
|
|
||||||
// TODO: support read-only transactions
|
|
||||||
return nil, errors.New("mysql: read-only transactions not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := mc.watchCancel(ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer mc.finish()
|
|
||||||
|
|
||||||
if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault {
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
|
@ -0,0 +1,140 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2018 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql/driver"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type connector struct {
|
||||||
|
cfg *Config // immutable private copy.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect implements driver.Connector interface.
|
||||||
|
// Connect returns a connection to the database.
|
||||||
|
func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// New mysqlConn
|
||||||
|
mc := &mysqlConn{
|
||||||
|
maxAllowedPacket: maxPacketSize,
|
||||||
|
maxWriteSize: maxPacketSize - 1,
|
||||||
|
closech: make(chan struct{}),
|
||||||
|
cfg: c.cfg,
|
||||||
|
}
|
||||||
|
mc.parseTime = mc.cfg.ParseTime
|
||||||
|
|
||||||
|
// Connect to Server
|
||||||
|
dialsLock.RLock()
|
||||||
|
dial, ok := dials[mc.cfg.Net]
|
||||||
|
dialsLock.RUnlock()
|
||||||
|
if ok {
|
||||||
|
mc.netConn, err = dial(ctx, mc.cfg.Addr)
|
||||||
|
} else {
|
||||||
|
nd := net.Dialer{Timeout: mc.cfg.Timeout}
|
||||||
|
mc.netConn, err = nd.DialContext(ctx, mc.cfg.Net, mc.cfg.Addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable TCP Keepalives on TCP connections
|
||||||
|
if tc, ok := mc.netConn.(*net.TCPConn); ok {
|
||||||
|
if err := tc.SetKeepAlive(true); err != nil {
|
||||||
|
// Don't send COM_QUIT before handshake.
|
||||||
|
mc.netConn.Close()
|
||||||
|
mc.netConn = nil
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call startWatcher for context support (From Go 1.8)
|
||||||
|
mc.startWatcher()
|
||||||
|
if err := mc.watchCancel(ctx); err != nil {
|
||||||
|
mc.cleanup()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer mc.finish()
|
||||||
|
|
||||||
|
mc.buf = newBuffer(mc.netConn)
|
||||||
|
|
||||||
|
// Set I/O timeouts
|
||||||
|
mc.buf.timeout = mc.cfg.ReadTimeout
|
||||||
|
mc.writeTimeout = mc.cfg.WriteTimeout
|
||||||
|
|
||||||
|
// Reading Handshake Initialization Packet
|
||||||
|
authData, plugin, err := mc.readHandshakePacket()
|
||||||
|
if err != nil {
|
||||||
|
mc.cleanup()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if plugin == "" {
|
||||||
|
plugin = defaultAuthPlugin
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send Client Authentication Packet
|
||||||
|
authResp, err := mc.auth(authData, plugin)
|
||||||
|
if err != nil {
|
||||||
|
// try the default auth plugin, if using the requested plugin failed
|
||||||
|
errLog.Print("could not use requested auth plugin '"+plugin+"': ", err.Error())
|
||||||
|
plugin = defaultAuthPlugin
|
||||||
|
authResp, err = mc.auth(authData, plugin)
|
||||||
|
if err != nil {
|
||||||
|
mc.cleanup()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = mc.writeHandshakeResponsePacket(authResp, plugin); err != nil {
|
||||||
|
mc.cleanup()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle response to auth packet, switch methods if possible
|
||||||
|
if err = mc.handleAuthResult(authData, plugin); err != nil {
|
||||||
|
// Authentication failed and MySQL has already closed the connection
|
||||||
|
// (https://dev.mysql.com/doc/internals/en/authentication-fails.html).
|
||||||
|
// Do not send COM_QUIT, just cleanup and return the error.
|
||||||
|
mc.cleanup()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if mc.cfg.MaxAllowedPacket > 0 {
|
||||||
|
mc.maxAllowedPacket = mc.cfg.MaxAllowedPacket
|
||||||
|
} else {
|
||||||
|
// Get max allowed packet size
|
||||||
|
maxap, err := mc.getSystemVar("max_allowed_packet")
|
||||||
|
if err != nil {
|
||||||
|
mc.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mc.maxAllowedPacket = stringToInt(maxap) - 1
|
||||||
|
}
|
||||||
|
if mc.maxAllowedPacket < maxPacketSize {
|
||||||
|
mc.maxWriteSize = mc.maxAllowedPacket
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle DSN Params
|
||||||
|
err = mc.handleParams()
|
||||||
|
if err != nil {
|
||||||
|
mc.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Driver implements driver.Connector interface.
|
||||||
|
// Driver returns &MySQLDriver{}.
|
||||||
|
func (c *connector) Driver() driver.Driver {
|
||||||
|
return &MySQLDriver{}
|
||||||
|
}
|
|
@ -9,7 +9,9 @@
|
||||||
package mysql
|
package mysql
|
||||||
|
|
||||||
const (
|
const (
|
||||||
minProtocolVersion byte = 10
|
defaultAuthPlugin = "mysql_native_password"
|
||||||
|
defaultMaxAllowedPacket = 4 << 20 // 4 MiB
|
||||||
|
minProtocolVersion = 10
|
||||||
maxPacketSize = 1<<24 - 1
|
maxPacketSize = 1<<24 - 1
|
||||||
timeFormat = "2006-01-02 15:04:05.999999"
|
timeFormat = "2006-01-02 15:04:05.999999"
|
||||||
)
|
)
|
||||||
|
@ -18,10 +20,11 @@ const (
|
||||||
// http://dev.mysql.com/doc/internals/en/client-server-protocol.html
|
// http://dev.mysql.com/doc/internals/en/client-server-protocol.html
|
||||||
|
|
||||||
const (
|
const (
|
||||||
iOK byte = 0x00
|
iOK byte = 0x00
|
||||||
iLocalInFile byte = 0xfb
|
iAuthMoreData byte = 0x01
|
||||||
iEOF byte = 0xfe
|
iLocalInFile byte = 0xfb
|
||||||
iERR byte = 0xff
|
iEOF byte = 0xfe
|
||||||
|
iERR byte = 0xff
|
||||||
)
|
)
|
||||||
|
|
||||||
// https://dev.mysql.com/doc/internals/en/capability-flags.html#packet-Protocol::CapabilityFlags
|
// https://dev.mysql.com/doc/internals/en/capability-flags.html#packet-Protocol::CapabilityFlags
|
||||||
|
@ -87,8 +90,10 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// https://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnType
|
// https://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnType
|
||||||
|
type fieldType byte
|
||||||
|
|
||||||
const (
|
const (
|
||||||
fieldTypeDecimal byte = iota
|
fieldTypeDecimal fieldType = iota
|
||||||
fieldTypeTiny
|
fieldTypeTiny
|
||||||
fieldTypeShort
|
fieldTypeShort
|
||||||
fieldTypeLong
|
fieldTypeLong
|
||||||
|
@ -107,7 +112,7 @@ const (
|
||||||
fieldTypeBit
|
fieldTypeBit
|
||||||
)
|
)
|
||||||
const (
|
const (
|
||||||
fieldTypeJSON byte = iota + 0xf5
|
fieldTypeJSON fieldType = iota + 0xf5
|
||||||
fieldTypeNewDecimal
|
fieldTypeNewDecimal
|
||||||
fieldTypeEnum
|
fieldTypeEnum
|
||||||
fieldTypeSet
|
fieldTypeSet
|
||||||
|
@ -161,3 +166,9 @@ const (
|
||||||
statusInTransReadonly
|
statusInTransReadonly
|
||||||
statusSessionStateChanged
|
statusSessionStateChanged
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
cachingSha2PasswordRequestPublicKey = 2
|
||||||
|
cachingSha2PasswordFastAuthSuccess = 3
|
||||||
|
cachingSha2PasswordPerformFullAuthentication = 4
|
||||||
|
)
|
||||||
|
|
|
@ -17,176 +17,67 @@
|
||||||
package mysql
|
package mysql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// watcher interface is used for context support (From Go 1.8)
|
|
||||||
type watcher interface {
|
|
||||||
startWatcher()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MySQLDriver is exported to make the driver directly accessible.
|
// MySQLDriver is exported to make the driver directly accessible.
|
||||||
// In general the driver is used via the database/sql package.
|
// In general the driver is used via the database/sql package.
|
||||||
type MySQLDriver struct{}
|
type MySQLDriver struct{}
|
||||||
|
|
||||||
// DialFunc is a function which can be used to establish the network connection.
|
// DialFunc is a function which can be used to establish the network connection.
|
||||||
// Custom dial functions must be registered with RegisterDial
|
// Custom dial functions must be registered with RegisterDial
|
||||||
|
//
|
||||||
|
// Deprecated: users should register a DialContextFunc instead
|
||||||
type DialFunc func(addr string) (net.Conn, error)
|
type DialFunc func(addr string) (net.Conn, error)
|
||||||
|
|
||||||
var dials map[string]DialFunc
|
// DialContextFunc is a function which can be used to establish the network connection.
|
||||||
|
// Custom dial functions must be registered with RegisterDialContext
|
||||||
|
type DialContextFunc func(ctx context.Context, addr string) (net.Conn, error)
|
||||||
|
|
||||||
// RegisterDial registers a custom dial function. It can then be used by the
|
var (
|
||||||
|
dialsLock sync.RWMutex
|
||||||
|
dials map[string]DialContextFunc
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterDialContext registers a custom dial function. It can then be used by the
|
||||||
// network address mynet(addr), where mynet is the registered new network.
|
// network address mynet(addr), where mynet is the registered new network.
|
||||||
// addr is passed as a parameter to the dial function.
|
// The current context for the connection and its address is passed to the dial function.
|
||||||
func RegisterDial(net string, dial DialFunc) {
|
func RegisterDialContext(net string, dial DialContextFunc) {
|
||||||
|
dialsLock.Lock()
|
||||||
|
defer dialsLock.Unlock()
|
||||||
if dials == nil {
|
if dials == nil {
|
||||||
dials = make(map[string]DialFunc)
|
dials = make(map[string]DialContextFunc)
|
||||||
}
|
}
|
||||||
dials[net] = dial
|
dials[net] = dial
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open new Connection.
|
// RegisterDial registers a custom dial function. It can then be used by the
|
||||||
// See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how
|
// network address mynet(addr), where mynet is the registered new network.
|
||||||
// the DSN string is formated
|
// addr is passed as a parameter to the dial function.
|
||||||
func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
|
//
|
||||||
var err error
|
// Deprecated: users should call RegisterDialContext instead
|
||||||
|
func RegisterDial(network string, dial DialFunc) {
|
||||||
// New mysqlConn
|
RegisterDialContext(network, func(_ context.Context, addr string) (net.Conn, error) {
|
||||||
mc := &mysqlConn{
|
return dial(addr)
|
||||||
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 {
|
|
||||||
mc.netConn, err = dial(mc.cfg.Addr)
|
|
||||||
} else {
|
|
||||||
nd := net.Dialer{Timeout: mc.cfg.Timeout}
|
|
||||||
mc.netConn, err = nd.Dial(mc.cfg.Net, mc.cfg.Addr)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable TCP Keepalives on TCP connections
|
|
||||||
if tc, ok := mc.netConn.(*net.TCPConn); ok {
|
|
||||||
if err := tc.SetKeepAlive(true); err != nil {
|
|
||||||
// Don't send COM_QUIT before handshake.
|
|
||||||
mc.netConn.Close()
|
|
||||||
mc.netConn = nil
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call startWatcher for context support (From Go 1.8)
|
|
||||||
if s, ok := interface{}(mc).(watcher); ok {
|
|
||||||
s.startWatcher()
|
|
||||||
}
|
|
||||||
|
|
||||||
mc.buf = newBuffer(mc.netConn)
|
|
||||||
|
|
||||||
// Set I/O timeouts
|
|
||||||
mc.buf.timeout = mc.cfg.ReadTimeout
|
|
||||||
mc.writeTimeout = mc.cfg.WriteTimeout
|
|
||||||
|
|
||||||
// Reading Handshake Initialization Packet
|
|
||||||
cipher, err := mc.readInitPacket()
|
|
||||||
if err != nil {
|
|
||||||
mc.cleanup()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send Client Authentication Packet
|
|
||||||
if err = mc.writeAuthPacket(cipher); err != nil {
|
|
||||||
mc.cleanup()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle response to auth packet, switch methods if possible
|
|
||||||
if err = handleAuthResult(mc, cipher); err != nil {
|
|
||||||
// Authentication failed and MySQL has already closed the connection
|
|
||||||
// (https://dev.mysql.com/doc/internals/en/authentication-fails.html).
|
|
||||||
// Do not send COM_QUIT, just cleanup and return the error.
|
|
||||||
mc.cleanup()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if mc.cfg.MaxAllowedPacket > 0 {
|
|
||||||
mc.maxAllowedPacket = mc.cfg.MaxAllowedPacket
|
|
||||||
} else {
|
|
||||||
// Get max allowed packet size
|
|
||||||
maxap, err := mc.getSystemVar("max_allowed_packet")
|
|
||||||
if err != nil {
|
|
||||||
mc.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mc.maxAllowedPacket = stringToInt(maxap) - 1
|
|
||||||
}
|
|
||||||
if mc.maxAllowedPacket < maxPacketSize {
|
|
||||||
mc.maxWriteSize = mc.maxAllowedPacket
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle DSN Params
|
|
||||||
err = mc.handleParams()
|
|
||||||
if err != nil {
|
|
||||||
mc.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return mc, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleAuthResult(mc *mysqlConn, oldCipher []byte) error {
|
// Open new Connection.
|
||||||
// Read Result Packet
|
// See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how
|
||||||
cipher, err := mc.readResultOK()
|
// the DSN string is formatted
|
||||||
if err == nil {
|
func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
|
||||||
return nil // auth successful
|
cfg, err := ParseDSN(dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
c := &connector{
|
||||||
if mc.cfg == nil {
|
cfg: cfg,
|
||||||
return err // auth failed and retry not possible
|
|
||||||
}
|
}
|
||||||
|
return c.Connect(context.Background())
|
||||||
// Retry auth if configured to do so.
|
|
||||||
if mc.cfg.AllowOldPasswords && err == ErrOldPassword {
|
|
||||||
// Retry with old authentication method. Note: there are edge cases
|
|
||||||
// where this should work but doesn't; this is currently "wontfix":
|
|
||||||
// https://github.com/go-sql-driver/mysql/issues/184
|
|
||||||
|
|
||||||
// If CLIENT_PLUGIN_AUTH capability is not supported, no new cipher is
|
|
||||||
// sent and we have to keep using the cipher sent in the init packet.
|
|
||||||
if cipher == nil {
|
|
||||||
cipher = oldCipher
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = mc.writeOldAuthPacket(cipher); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = mc.readResultOK()
|
|
||||||
} else if mc.cfg.AllowCleartextPasswords && err == ErrCleartextPassword {
|
|
||||||
// Retry with clear text password for
|
|
||||||
// http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html
|
|
||||||
// http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html
|
|
||||||
if err = mc.writeClearAuthPacket(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = mc.readResultOK()
|
|
||||||
} else if mc.cfg.AllowNativePasswords && err == ErrNativePassword {
|
|
||||||
if err = mc.writeNativeAuthPacket(cipher); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = mc.readResultOK()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2018 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
// +build go1.10
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewConnector returns new driver.Connector.
|
||||||
|
func NewConnector(cfg *Config) (driver.Connector, error) {
|
||||||
|
cfg = cfg.Clone()
|
||||||
|
// normalize the contents of cfg so calls to NewConnector have the same
|
||||||
|
// behavior as MySQLDriver.OpenConnector
|
||||||
|
if err := cfg.normalize(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &connector{cfg: cfg}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenConnector implements driver.DriverContext.
|
||||||
|
func (d MySQLDriver) OpenConnector(dsn string) (driver.Connector, error) {
|
||||||
|
cfg, err := ParseDSN(dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &connector{
|
||||||
|
cfg: cfg,
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -10,9 +10,11 @@ package mysql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/rsa"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -28,7 +30,9 @@ var (
|
||||||
errInvalidDSNUnsafeCollation = errors.New("invalid DSN: interpolateParams can not be used with unsafe collations")
|
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 {
|
type Config struct {
|
||||||
User string // Username
|
User string // Username
|
||||||
Passwd string // Password (requires User)
|
Passwd string // Password (requires User)
|
||||||
|
@ -39,6 +43,8 @@ type Config struct {
|
||||||
Collation string // Connection collation
|
Collation string // Connection collation
|
||||||
Loc *time.Location // Location for time.Time values
|
Loc *time.Location // Location for time.Time values
|
||||||
MaxAllowedPacket int // Max packet size allowed
|
MaxAllowedPacket int // Max packet size allowed
|
||||||
|
ServerPubKey string // Server public key name
|
||||||
|
pubKey *rsa.PublicKey // Server public key
|
||||||
TLSConfig string // TLS configuration name
|
TLSConfig string // TLS configuration name
|
||||||
tls *tls.Config // TLS configuration
|
tls *tls.Config // TLS configuration
|
||||||
Timeout time.Duration // Dial timeout
|
Timeout time.Duration // Dial timeout
|
||||||
|
@ -55,7 +61,91 @@ type Config struct {
|
||||||
MultiStatements bool // Allow multiple statements in one query
|
MultiStatements bool // Allow multiple statements in one query
|
||||||
ParseTime bool // Parse time values to time.Time
|
ParseTime bool // Parse time values to time.Time
|
||||||
RejectReadOnly bool // Reject read-only connections
|
RejectReadOnly bool // Reject read-only connections
|
||||||
Strict bool // Return warnings as errors
|
}
|
||||||
|
|
||||||
|
// 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) Clone() *Config {
|
||||||
|
cp := *cfg
|
||||||
|
if cp.tls != nil {
|
||||||
|
cp.tls = cfg.tls.Clone()
|
||||||
|
}
|
||||||
|
if len(cp.Params) > 0 {
|
||||||
|
cp.Params = make(map[string]string, len(cfg.Params))
|
||||||
|
for k, v := range cfg.Params {
|
||||||
|
cp.Params[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cfg.pubKey != nil {
|
||||||
|
cp.pubKey = &rsa.PublicKey{
|
||||||
|
N: new(big.Int).Set(cfg.pubKey.N),
|
||||||
|
E: cfg.pubKey.E,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &cp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) normalize() error {
|
||||||
|
if cfg.InterpolateParams && unsafeCollations[cfg.Collation] {
|
||||||
|
return errInvalidDSNUnsafeCollation
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch cfg.TLSConfig {
|
||||||
|
case "false", "":
|
||||||
|
// don't set anything
|
||||||
|
case "true":
|
||||||
|
cfg.tls = &tls.Config{}
|
||||||
|
case "skip-verify", "preferred":
|
||||||
|
cfg.tls = &tls.Config{InsecureSkipVerify: true}
|
||||||
|
default:
|
||||||
|
cfg.tls = getTLSConfigClone(cfg.TLSConfig)
|
||||||
|
if cfg.tls == nil {
|
||||||
|
return errors.New("invalid value / unknown config name: " + cfg.TLSConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.tls != nil && cfg.tls.ServerName == "" && !cfg.tls.InsecureSkipVerify {
|
||||||
|
host, _, err := net.SplitHostPort(cfg.Addr)
|
||||||
|
if err == nil {
|
||||||
|
cfg.tls.ServerName = host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.ServerPubKey != "" {
|
||||||
|
cfg.pubKey = getServerPubKey(cfg.ServerPubKey)
|
||||||
|
if cfg.pubKey == nil {
|
||||||
|
return errors.New("invalid value / unknown server pub key name: " + cfg.ServerPubKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormatDSN formats the given Config into a DSN string which can be passed to
|
// FormatDSN formats the given Config into a DSN string which can be passed to
|
||||||
|
@ -104,12 +194,12 @@ func (cfg *Config) FormatDSN() string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.AllowNativePasswords {
|
if !cfg.AllowNativePasswords {
|
||||||
if hasParam {
|
if hasParam {
|
||||||
buf.WriteString("&allowNativePasswords=true")
|
buf.WriteString("&allowNativePasswords=false")
|
||||||
} else {
|
} else {
|
||||||
hasParam = true
|
hasParam = true
|
||||||
buf.WriteString("?allowNativePasswords=true")
|
buf.WriteString("?allowNativePasswords=false")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,13 +296,14 @@ func (cfg *Config) FormatDSN() string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Strict {
|
if len(cfg.ServerPubKey) > 0 {
|
||||||
if hasParam {
|
if hasParam {
|
||||||
buf.WriteString("&strict=true")
|
buf.WriteString("&serverPubKey=")
|
||||||
} else {
|
} else {
|
||||||
hasParam = true
|
hasParam = true
|
||||||
buf.WriteString("?strict=true")
|
buf.WriteString("?serverPubKey=")
|
||||||
}
|
}
|
||||||
|
buf.WriteString(url.QueryEscape(cfg.ServerPubKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Timeout > 0 {
|
if cfg.Timeout > 0 {
|
||||||
|
@ -245,7 +336,7 @@ func (cfg *Config) FormatDSN() string {
|
||||||
buf.WriteString(cfg.WriteTimeout.String())
|
buf.WriteString(cfg.WriteTimeout.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.MaxAllowedPacket > 0 {
|
if cfg.MaxAllowedPacket != defaultMaxAllowedPacket {
|
||||||
if hasParam {
|
if hasParam {
|
||||||
buf.WriteString("&maxAllowedPacket=")
|
buf.WriteString("&maxAllowedPacket=")
|
||||||
} else {
|
} else {
|
||||||
|
@ -283,10 +374,7 @@ func (cfg *Config) FormatDSN() string {
|
||||||
// ParseDSN parses the DSN string to a Config
|
// ParseDSN parses the DSN string to a Config
|
||||||
func ParseDSN(dsn string) (cfg *Config, err error) {
|
func ParseDSN(dsn string) (cfg *Config, err error) {
|
||||||
// New config with some default values
|
// New config with some default values
|
||||||
cfg = &Config{
|
cfg = NewConfig()
|
||||||
Loc: time.UTC,
|
|
||||||
Collation: defaultCollation,
|
|
||||||
}
|
|
||||||
|
|
||||||
// [user[:password]@][net[(addr)]]/dbname[?param1=value1¶mN=valueN]
|
// [user[:password]@][net[(addr)]]/dbname[?param1=value1¶mN=valueN]
|
||||||
// Find the last '/' (since the password or the net addr might contain a '/')
|
// Find the last '/' (since the password or the net addr might contain a '/')
|
||||||
|
@ -354,28 +442,9 @@ func ParseDSN(dsn string) (cfg *Config, err error) {
|
||||||
return nil, errInvalidDSNNoSlash
|
return nil, errInvalidDSNNoSlash
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.InterpolateParams && unsafeCollations[cfg.Collation] {
|
if err = cfg.normalize(); err != nil {
|
||||||
return nil, errInvalidDSNUnsafeCollation
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 nil, errors.New("default addr for network '" + cfg.Net + "' unknown")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -390,7 +459,6 @@ func parseDSNParams(cfg *Config, params string) (err error) {
|
||||||
|
|
||||||
// cfg params
|
// cfg params
|
||||||
switch value := param[1]; param[0] {
|
switch value := param[1]; param[0] {
|
||||||
|
|
||||||
// Disable INFILE whitelist / enable all files
|
// Disable INFILE whitelist / enable all files
|
||||||
case "allowAllFiles":
|
case "allowAllFiles":
|
||||||
var isBool bool
|
var isBool bool
|
||||||
|
@ -496,13 +564,17 @@ func parseDSNParams(cfg *Config, params string) (err error) {
|
||||||
return errors.New("invalid bool value: " + value)
|
return errors.New("invalid bool value: " + value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Server public key
|
||||||
|
case "serverPubKey":
|
||||||
|
name, err := url.QueryUnescape(value)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid value for server pub key name: %v", err)
|
||||||
|
}
|
||||||
|
cfg.ServerPubKey = name
|
||||||
|
|
||||||
// Strict mode
|
// Strict mode
|
||||||
case "strict":
|
case "strict":
|
||||||
var isBool bool
|
panic("strict mode has been removed. See https://github.com/go-sql-driver/mysql/wiki/strict-mode")
|
||||||
cfg.Strict, isBool = readBool(value)
|
|
||||||
if !isBool {
|
|
||||||
return errors.New("invalid bool value: " + value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dial Timeout
|
// Dial Timeout
|
||||||
case "timeout":
|
case "timeout":
|
||||||
|
@ -517,36 +589,17 @@ func parseDSNParams(cfg *Config, params string) (err error) {
|
||||||
if isBool {
|
if isBool {
|
||||||
if boolValue {
|
if boolValue {
|
||||||
cfg.TLSConfig = "true"
|
cfg.TLSConfig = "true"
|
||||||
cfg.tls = &tls.Config{}
|
|
||||||
host, _, err := net.SplitHostPort(cfg.Addr)
|
|
||||||
if err == nil {
|
|
||||||
cfg.tls.ServerName = host
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
cfg.TLSConfig = "false"
|
cfg.TLSConfig = "false"
|
||||||
}
|
}
|
||||||
} else if vl := strings.ToLower(value); vl == "skip-verify" {
|
} else if vl := strings.ToLower(value); vl == "skip-verify" || vl == "preferred" {
|
||||||
cfg.TLSConfig = vl
|
cfg.TLSConfig = vl
|
||||||
cfg.tls = &tls.Config{InsecureSkipVerify: true}
|
|
||||||
} else {
|
} else {
|
||||||
name, err := url.QueryUnescape(value)
|
name, err := url.QueryUnescape(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid value for TLS config name: %v", err)
|
return fmt.Errorf("invalid value for TLS config name: %v", err)
|
||||||
}
|
}
|
||||||
|
cfg.TLSConfig = name
|
||||||
if tlsConfig := getTLSConfigClone(name); tlsConfig != nil {
|
|
||||||
if len(tlsConfig.ServerName) == 0 && !tlsConfig.InsecureSkipVerify {
|
|
||||||
host, _, err := net.SplitHostPort(cfg.Addr)
|
|
||||||
if err == nil {
|
|
||||||
tlsConfig.ServerName = host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.TLSConfig = name
|
|
||||||
cfg.tls = tlsConfig
|
|
||||||
} else {
|
|
||||||
return errors.New("invalid value / unknown config name: " + name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// I/O write Timeout
|
// I/O write Timeout
|
||||||
|
@ -574,3 +627,10 @@ func parseDSNParams(cfg *Config, params string) (err error) {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ensureHavePort(addr string) string {
|
||||||
|
if _, _, err := net.SplitHostPort(addr); err != nil {
|
||||||
|
return net.JoinHostPort(addr, "3306")
|
||||||
|
}
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
|
@ -9,10 +9,8 @@
|
||||||
package mysql
|
package mysql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql/driver"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
@ -31,6 +29,12 @@ var (
|
||||||
ErrPktSyncMul = errors.New("commands out of sync. Did you run multiple statements at once?")
|
ErrPktSyncMul = errors.New("commands out of sync. Did you run multiple statements at once?")
|
||||||
ErrPktTooLarge = errors.New("packet for query is too large. Try adjusting the 'max_allowed_packet' variable on the server")
|
ErrPktTooLarge = errors.New("packet for query is too large. Try adjusting the 'max_allowed_packet' variable on the server")
|
||||||
ErrBusyBuffer = errors.New("busy buffer")
|
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))
|
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 {
|
func (me *MySQLError) Error() string {
|
||||||
return fmt.Sprintf("Error %d: %s", me.Number, me.Message)
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
module github.com/go-sql-driver/mysql
|
||||||
|
|
||||||
|
go 1.9
|
|
@ -174,8 +174,7 @@ func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
|
||||||
|
|
||||||
// read OK packet
|
// read OK packet
|
||||||
if err == nil {
|
if err == nil {
|
||||||
_, err = mc.readResultOK()
|
return mc.readResultOK()
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mc.readPacket()
|
mc.readPacket()
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Scan implements the Scanner interface.
|
||||||
|
// The value type must be time.Time or string / []byte (formatted time-string),
|
||||||
|
// otherwise Scan fails.
|
||||||
|
func (nt *NullTime) Scan(value interface{}) (err error) {
|
||||||
|
if value == nil {
|
||||||
|
nt.Time, nt.Valid = time.Time{}, false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := value.(type) {
|
||||||
|
case time.Time:
|
||||||
|
nt.Time, nt.Valid = v, true
|
||||||
|
return
|
||||||
|
case []byte:
|
||||||
|
nt.Time, err = parseDateTime(string(v), time.UTC)
|
||||||
|
nt.Valid = (err == nil)
|
||||||
|
return
|
||||||
|
case string:
|
||||||
|
nt.Time, err = parseDateTime(v, time.UTC)
|
||||||
|
nt.Valid = (err == nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nt.Valid = false
|
||||||
|
return fmt.Errorf("Can't convert %T to time.Time", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver Valuer interface.
|
||||||
|
func (nt NullTime) Value() (driver.Value, error) {
|
||||||
|
if !nt.Valid {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nt.Time, nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
// +build go1.13
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NullTime represents a time.Time that may be NULL.
|
||||||
|
// NullTime implements the Scanner interface so
|
||||||
|
// it can be used as a scan destination:
|
||||||
|
//
|
||||||
|
// var nt NullTime
|
||||||
|
// err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
|
||||||
|
// ...
|
||||||
|
// if nt.Valid {
|
||||||
|
// // use nt.Time
|
||||||
|
// } else {
|
||||||
|
// // NULL value
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// This NullTime implementation is not driver-specific
|
||||||
|
type NullTime sql.NullTime
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
// +build !go1.13
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NullTime represents a time.Time that may be NULL.
|
||||||
|
// NullTime implements the Scanner interface so
|
||||||
|
// it can be used as a scan destination:
|
||||||
|
//
|
||||||
|
// var nt NullTime
|
||||||
|
// err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
|
||||||
|
// ...
|
||||||
|
// if nt.Valid {
|
||||||
|
// // use nt.Time
|
||||||
|
// } else {
|
||||||
|
// // NULL value
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// This NullTime implementation is not driver-specific
|
||||||
|
type NullTime struct {
|
||||||
|
Time time.Time
|
||||||
|
Valid bool // Valid is true if Time is not NULL
|
||||||
|
}
|
|
@ -35,7 +35,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
|
||||||
}
|
}
|
||||||
errLog.Print(err)
|
errLog.Print(err)
|
||||||
mc.Close()
|
mc.Close()
|
||||||
return nil, driver.ErrBadConn
|
return nil, ErrInvalidConn
|
||||||
}
|
}
|
||||||
|
|
||||||
// packet length [24 bit]
|
// packet length [24 bit]
|
||||||
|
@ -51,13 +51,13 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
|
||||||
mc.sequence++
|
mc.sequence++
|
||||||
|
|
||||||
// packets with length 0 terminate a previous packet which is a
|
// packets with length 0 terminate a previous packet which is a
|
||||||
// multiple of (2^24)−1 bytes long
|
// multiple of (2^24)-1 bytes long
|
||||||
if pktLen == 0 {
|
if pktLen == 0 {
|
||||||
// there was no previous packet
|
// there was no previous packet
|
||||||
if prevData == nil {
|
if prevData == nil {
|
||||||
errLog.Print(ErrMalformPkt)
|
errLog.Print(ErrMalformPkt)
|
||||||
mc.Close()
|
mc.Close()
|
||||||
return nil, driver.ErrBadConn
|
return nil, ErrInvalidConn
|
||||||
}
|
}
|
||||||
|
|
||||||
return prevData, nil
|
return prevData, nil
|
||||||
|
@ -71,7 +71,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
|
||||||
}
|
}
|
||||||
errLog.Print(err)
|
errLog.Print(err)
|
||||||
mc.Close()
|
mc.Close()
|
||||||
return nil, driver.ErrBadConn
|
return nil, ErrInvalidConn
|
||||||
}
|
}
|
||||||
|
|
||||||
// return data if this was the last packet
|
// return data if this was the last packet
|
||||||
|
@ -96,6 +96,35 @@ func (mc *mysqlConn) writePacket(data []byte) error {
|
||||||
return ErrPktTooLarge
|
return ErrPktTooLarge
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Perform a stale connection check. We only perform this check for
|
||||||
|
// the first query on a connection that has been checked out of the
|
||||||
|
// connection pool: a fresh connection from the pool is more likely
|
||||||
|
// to be stale, and it has not performed any previous writes that
|
||||||
|
// could cause data corruption, so it's safe to return ErrBadConn
|
||||||
|
// if the check fails.
|
||||||
|
if mc.reset {
|
||||||
|
mc.reset = false
|
||||||
|
conn := mc.netConn
|
||||||
|
if mc.rawConn != nil {
|
||||||
|
conn = mc.rawConn
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
// If this connection has a ReadTimeout which we've been setting on
|
||||||
|
// reads, reset it to its default value before we attempt a non-blocking
|
||||||
|
// read, otherwise the scheduler will just time us out before we can read
|
||||||
|
if mc.cfg.ReadTimeout != 0 {
|
||||||
|
err = conn.SetReadDeadline(time.Time{})
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
err = connCheck(conn)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
errLog.Print("closing bad idle connection: ", err)
|
||||||
|
mc.Close()
|
||||||
|
return driver.ErrBadConn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
var size int
|
var size int
|
||||||
if pktLen >= maxPacketSize {
|
if pktLen >= maxPacketSize {
|
||||||
|
@ -137,32 +166,41 @@ func (mc *mysqlConn) writePacket(data []byte) error {
|
||||||
if cerr := mc.canceled.Value(); cerr != nil {
|
if cerr := mc.canceled.Value(); cerr != nil {
|
||||||
return cerr
|
return cerr
|
||||||
}
|
}
|
||||||
|
if n == 0 && pktLen == len(data)-4 {
|
||||||
|
// only for the first loop iteration when nothing was written yet
|
||||||
|
return errBadConnNoWrite
|
||||||
|
}
|
||||||
mc.cleanup()
|
mc.cleanup()
|
||||||
errLog.Print(err)
|
errLog.Print(err)
|
||||||
}
|
}
|
||||||
return driver.ErrBadConn
|
return ErrInvalidConn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
* Initialisation Process *
|
* Initialization Process *
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
// Handshake Initialization Packet
|
// Handshake Initialization Packet
|
||||||
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::Handshake
|
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::Handshake
|
||||||
func (mc *mysqlConn) readInitPacket() ([]byte, error) {
|
func (mc *mysqlConn) readHandshakePacket() (data []byte, plugin string, err error) {
|
||||||
data, err := mc.readPacket()
|
data, err = mc.readPacket()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
if data[0] == iERR {
|
if data[0] == iERR {
|
||||||
return nil, mc.handleErrorPacket(data)
|
return nil, "", mc.handleErrorPacket(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// protocol version [1 byte]
|
// protocol version [1 byte]
|
||||||
if data[0] < minProtocolVersion {
|
if data[0] < minProtocolVersion {
|
||||||
return nil, fmt.Errorf(
|
return nil, "", fmt.Errorf(
|
||||||
"unsupported protocol version %d. Version %d or higher is required",
|
"unsupported protocol version %d. Version %d or higher is required",
|
||||||
data[0],
|
data[0],
|
||||||
minProtocolVersion,
|
minProtocolVersion,
|
||||||
|
@ -174,7 +212,7 @@ func (mc *mysqlConn) readInitPacket() ([]byte, error) {
|
||||||
pos := 1 + bytes.IndexByte(data[1:], 0x00) + 1 + 4
|
pos := 1 + bytes.IndexByte(data[1:], 0x00) + 1 + 4
|
||||||
|
|
||||||
// first part of the password cipher [8 bytes]
|
// first part of the password cipher [8 bytes]
|
||||||
cipher := data[pos : pos+8]
|
authData := data[pos : pos+8]
|
||||||
|
|
||||||
// (filler) always 0x00 [1 byte]
|
// (filler) always 0x00 [1 byte]
|
||||||
pos += 8 + 1
|
pos += 8 + 1
|
||||||
|
@ -182,10 +220,14 @@ func (mc *mysqlConn) readInitPacket() ([]byte, error) {
|
||||||
// capability flags (lower 2 bytes) [2 bytes]
|
// capability flags (lower 2 bytes) [2 bytes]
|
||||||
mc.flags = clientFlag(binary.LittleEndian.Uint16(data[pos : pos+2]))
|
mc.flags = clientFlag(binary.LittleEndian.Uint16(data[pos : pos+2]))
|
||||||
if mc.flags&clientProtocol41 == 0 {
|
if mc.flags&clientProtocol41 == 0 {
|
||||||
return nil, ErrOldProtocol
|
return nil, "", ErrOldProtocol
|
||||||
}
|
}
|
||||||
if mc.flags&clientSSL == 0 && mc.cfg.tls != nil {
|
if mc.flags&clientSSL == 0 && mc.cfg.tls != nil {
|
||||||
return nil, ErrNoTLS
|
if mc.cfg.TLSConfig == "preferred" {
|
||||||
|
mc.cfg.tls = nil
|
||||||
|
} else {
|
||||||
|
return nil, "", ErrNoTLS
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pos += 2
|
pos += 2
|
||||||
|
|
||||||
|
@ -209,32 +251,32 @@ func (mc *mysqlConn) readInitPacket() ([]byte, error) {
|
||||||
//
|
//
|
||||||
// The official Python library uses the fixed length 12
|
// The official Python library uses the fixed length 12
|
||||||
// which seems to work but technically could have a hidden bug.
|
// which seems to work but technically could have a hidden bug.
|
||||||
cipher = append(cipher, data[pos:pos+12]...)
|
authData = append(authData, data[pos:pos+12]...)
|
||||||
|
pos += 13
|
||||||
|
|
||||||
// TODO: Verify string termination
|
|
||||||
// EOF if version (>= 5.5.7 and < 5.5.10) or (>= 5.6.0 and < 5.6.2)
|
// EOF if version (>= 5.5.7 and < 5.5.10) or (>= 5.6.0 and < 5.6.2)
|
||||||
// \NUL otherwise
|
// \NUL otherwise
|
||||||
//
|
if end := bytes.IndexByte(data[pos:], 0x00); end != -1 {
|
||||||
//if data[len(data)-1] == 0 {
|
plugin = string(data[pos : pos+end])
|
||||||
// return
|
} else {
|
||||||
//}
|
plugin = string(data[pos:])
|
||||||
//return ErrMalformPkt
|
}
|
||||||
|
|
||||||
// make a memory safe copy of the cipher slice
|
// make a memory safe copy of the cipher slice
|
||||||
var b [20]byte
|
var b [20]byte
|
||||||
copy(b[:], cipher)
|
copy(b[:], authData)
|
||||||
return b[:], nil
|
return b[:], plugin, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// make a memory safe copy of the cipher slice
|
// make a memory safe copy of the cipher slice
|
||||||
var b [8]byte
|
var b [8]byte
|
||||||
copy(b[:], cipher)
|
copy(b[:], authData)
|
||||||
return b[:], nil
|
return b[:], plugin, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client Authentication Packet
|
// Client Authentication Packet
|
||||||
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeResponse
|
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeResponse
|
||||||
func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
|
func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string) error {
|
||||||
// Adjust client flags based on server support
|
// Adjust client flags based on server support
|
||||||
clientFlags := clientProtocol41 |
|
clientFlags := clientProtocol41 |
|
||||||
clientSecureConn |
|
clientSecureConn |
|
||||||
|
@ -258,10 +300,17 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
|
||||||
clientFlags |= clientMultiStatements
|
clientFlags |= clientMultiStatements
|
||||||
}
|
}
|
||||||
|
|
||||||
// User Password
|
// encode length of the auth plugin data
|
||||||
scrambleBuff := scramblePassword(cipher, []byte(mc.cfg.Passwd))
|
var authRespLEIBuf [9]byte
|
||||||
|
authRespLen := len(authResp)
|
||||||
|
authRespLEI := appendLengthEncodedInteger(authRespLEIBuf[:0], uint64(authRespLen))
|
||||||
|
if len(authRespLEI) > 1 {
|
||||||
|
// if the length can not be written in 1 byte, it must be written as a
|
||||||
|
// length encoded integer
|
||||||
|
clientFlags |= clientPluginAuthLenEncClientData
|
||||||
|
}
|
||||||
|
|
||||||
pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1 + 1 + len(scrambleBuff) + 21 + 1
|
pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1 + len(authRespLEI) + len(authResp) + 21 + 1
|
||||||
|
|
||||||
// To specify a db name
|
// To specify a db name
|
||||||
if n := len(mc.cfg.DBName); n > 0 {
|
if n := len(mc.cfg.DBName); n > 0 {
|
||||||
|
@ -270,11 +319,11 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate packet length and get buffer with that size
|
// Calculate packet length and get buffer with that size
|
||||||
data := mc.buf.takeSmallBuffer(pktLen + 4)
|
data, err := mc.buf.takeSmallBuffer(pktLen + 4)
|
||||||
if data == nil {
|
if err != nil {
|
||||||
// can not take the buffer. Something must be wrong with the connection
|
// cannot take the buffer. Something must be wrong with the connection
|
||||||
errLog.Print(ErrBusyBuffer)
|
errLog.Print(err)
|
||||||
return driver.ErrBadConn
|
return errBadConnNoWrite
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientFlags [32 bit]
|
// ClientFlags [32 bit]
|
||||||
|
@ -312,6 +361,7 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
|
||||||
if err := tlsConn.Handshake(); err != nil {
|
if err := tlsConn.Handshake(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
mc.rawConn = mc.netConn
|
||||||
mc.netConn = tlsConn
|
mc.netConn = tlsConn
|
||||||
mc.buf.nc = tlsConn
|
mc.buf.nc = tlsConn
|
||||||
}
|
}
|
||||||
|
@ -329,9 +379,9 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
|
||||||
data[pos] = 0x00
|
data[pos] = 0x00
|
||||||
pos++
|
pos++
|
||||||
|
|
||||||
// ScrambleBuffer [length encoded integer]
|
// Auth Data [length encoded integer]
|
||||||
data[pos] = byte(len(scrambleBuff))
|
pos += copy(data[pos:], authRespLEI)
|
||||||
pos += 1 + copy(data[pos+1:], scrambleBuff)
|
pos += copy(data[pos:], authResp)
|
||||||
|
|
||||||
// Databasename [null terminated string]
|
// Databasename [null terminated string]
|
||||||
if len(mc.cfg.DBName) > 0 {
|
if len(mc.cfg.DBName) > 0 {
|
||||||
|
@ -340,72 +390,26 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
|
||||||
pos++
|
pos++
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assume native client during response
|
pos += copy(data[pos:], plugin)
|
||||||
pos += copy(data[pos:], "mysql_native_password")
|
|
||||||
data[pos] = 0x00
|
data[pos] = 0x00
|
||||||
|
pos++
|
||||||
|
|
||||||
// Send Auth packet
|
// Send Auth packet
|
||||||
return mc.writePacket(data)
|
return mc.writePacket(data[:pos])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client old authentication packet
|
|
||||||
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
|
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
|
||||||
func (mc *mysqlConn) writeOldAuthPacket(cipher []byte) error {
|
func (mc *mysqlConn) writeAuthSwitchPacket(authData []byte) error {
|
||||||
// User password
|
pktLen := 4 + len(authData)
|
||||||
scrambleBuff := scrambleOldPassword(cipher, []byte(mc.cfg.Passwd))
|
data, err := mc.buf.takeSmallBuffer(pktLen)
|
||||||
|
if err != nil {
|
||||||
// Calculate the packet length and add a tailing 0
|
// cannot take the buffer. Something must be wrong with the connection
|
||||||
pktLen := len(scrambleBuff) + 1
|
errLog.Print(err)
|
||||||
data := mc.buf.takeSmallBuffer(4 + pktLen)
|
return errBadConnNoWrite
|
||||||
if data == nil {
|
|
||||||
// can not take the buffer. Something must be wrong with the connection
|
|
||||||
errLog.Print(ErrBusyBuffer)
|
|
||||||
return driver.ErrBadConn
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the scrambled password [null terminated string]
|
// Add the auth data [EOF]
|
||||||
copy(data[4:], scrambleBuff)
|
copy(data[4:], authData)
|
||||||
data[4+pktLen-1] = 0x00
|
|
||||||
|
|
||||||
return mc.writePacket(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client clear text authentication packet
|
|
||||||
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
|
|
||||||
func (mc *mysqlConn) writeClearAuthPacket() error {
|
|
||||||
// Calculate the packet length and add a tailing 0
|
|
||||||
pktLen := len(mc.cfg.Passwd) + 1
|
|
||||||
data := mc.buf.takeSmallBuffer(4 + pktLen)
|
|
||||||
if data == nil {
|
|
||||||
// can not take the buffer. Something must be wrong with the connection
|
|
||||||
errLog.Print(ErrBusyBuffer)
|
|
||||||
return driver.ErrBadConn
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the clear password [null terminated string]
|
|
||||||
copy(data[4:], mc.cfg.Passwd)
|
|
||||||
data[4+pktLen-1] = 0x00
|
|
||||||
|
|
||||||
return mc.writePacket(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Native password authentication method
|
|
||||||
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
|
|
||||||
func (mc *mysqlConn) writeNativeAuthPacket(cipher []byte) error {
|
|
||||||
scrambleBuff := scramblePassword(cipher, []byte(mc.cfg.Passwd))
|
|
||||||
|
|
||||||
// Calculate the packet length and add a tailing 0
|
|
||||||
pktLen := len(scrambleBuff)
|
|
||||||
data := mc.buf.takeSmallBuffer(4 + pktLen)
|
|
||||||
if data == nil {
|
|
||||||
// can not take the buffer. Something must be wrong with the connection
|
|
||||||
errLog.Print(ErrBusyBuffer)
|
|
||||||
return driver.ErrBadConn
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the scramble
|
|
||||||
copy(data[4:], scrambleBuff)
|
|
||||||
|
|
||||||
return mc.writePacket(data)
|
return mc.writePacket(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -417,11 +421,11 @@ func (mc *mysqlConn) writeCommandPacket(command byte) error {
|
||||||
// Reset Packet Sequence
|
// Reset Packet Sequence
|
||||||
mc.sequence = 0
|
mc.sequence = 0
|
||||||
|
|
||||||
data := mc.buf.takeSmallBuffer(4 + 1)
|
data, err := mc.buf.takeSmallBuffer(4 + 1)
|
||||||
if data == nil {
|
if err != nil {
|
||||||
// can not take the buffer. Something must be wrong with the connection
|
// cannot take the buffer. Something must be wrong with the connection
|
||||||
errLog.Print(ErrBusyBuffer)
|
errLog.Print(err)
|
||||||
return driver.ErrBadConn
|
return errBadConnNoWrite
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add command byte
|
// Add command byte
|
||||||
|
@ -436,11 +440,11 @@ func (mc *mysqlConn) writeCommandPacketStr(command byte, arg string) error {
|
||||||
mc.sequence = 0
|
mc.sequence = 0
|
||||||
|
|
||||||
pktLen := 1 + len(arg)
|
pktLen := 1 + len(arg)
|
||||||
data := mc.buf.takeBuffer(pktLen + 4)
|
data, err := mc.buf.takeBuffer(pktLen + 4)
|
||||||
if data == nil {
|
if err != nil {
|
||||||
// can not take the buffer. Something must be wrong with the connection
|
// cannot take the buffer. Something must be wrong with the connection
|
||||||
errLog.Print(ErrBusyBuffer)
|
errLog.Print(err)
|
||||||
return driver.ErrBadConn
|
return errBadConnNoWrite
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add command byte
|
// Add command byte
|
||||||
|
@ -457,11 +461,11 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
|
||||||
// Reset Packet Sequence
|
// Reset Packet Sequence
|
||||||
mc.sequence = 0
|
mc.sequence = 0
|
||||||
|
|
||||||
data := mc.buf.takeSmallBuffer(4 + 1 + 4)
|
data, err := mc.buf.takeSmallBuffer(4 + 1 + 4)
|
||||||
if data == nil {
|
if err != nil {
|
||||||
// can not take the buffer. Something must be wrong with the connection
|
// cannot take the buffer. Something must be wrong with the connection
|
||||||
errLog.Print(ErrBusyBuffer)
|
errLog.Print(err)
|
||||||
return driver.ErrBadConn
|
return errBadConnNoWrite
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add command byte
|
// Add command byte
|
||||||
|
@ -481,45 +485,50 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
|
||||||
* Result Packets *
|
* Result Packets *
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
// Returns error if Packet is not an 'Result OK'-Packet
|
func (mc *mysqlConn) readAuthResult() ([]byte, string, error) {
|
||||||
func (mc *mysqlConn) readResultOK() ([]byte, error) {
|
|
||||||
data, err := mc.readPacket()
|
data, err := mc.readPacket()
|
||||||
if err == nil {
|
if err != nil {
|
||||||
// packet indicator
|
return nil, "", err
|
||||||
switch data[0] {
|
|
||||||
|
|
||||||
case iOK:
|
|
||||||
return nil, mc.handleOkPacket(data)
|
|
||||||
|
|
||||||
case iEOF:
|
|
||||||
if len(data) > 1 {
|
|
||||||
pluginEndIndex := bytes.IndexByte(data, 0x00)
|
|
||||||
plugin := string(data[1:pluginEndIndex])
|
|
||||||
cipher := data[pluginEndIndex+1 : len(data)-1]
|
|
||||||
|
|
||||||
switch plugin {
|
|
||||||
case "mysql_old_password":
|
|
||||||
// using old_passwords
|
|
||||||
return cipher, ErrOldPassword
|
|
||||||
case "mysql_clear_password":
|
|
||||||
// using clear text password
|
|
||||||
return cipher, ErrCleartextPassword
|
|
||||||
case "mysql_native_password":
|
|
||||||
// using mysql default authentication method
|
|
||||||
return cipher, ErrNativePassword
|
|
||||||
default:
|
|
||||||
return cipher, ErrUnknownPlugin
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest
|
|
||||||
return nil, ErrOldPassword
|
|
||||||
|
|
||||||
default: // Error otherwise
|
|
||||||
return nil, mc.handleErrorPacket(data)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil, err
|
|
||||||
|
// packet indicator
|
||||||
|
switch data[0] {
|
||||||
|
|
||||||
|
case iOK:
|
||||||
|
return nil, "", mc.handleOkPacket(data)
|
||||||
|
|
||||||
|
case iAuthMoreData:
|
||||||
|
return data[1:], "", err
|
||||||
|
|
||||||
|
case iEOF:
|
||||||
|
if len(data) == 1 {
|
||||||
|
// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest
|
||||||
|
return nil, "mysql_old_password", nil
|
||||||
|
}
|
||||||
|
pluginEndIndex := bytes.IndexByte(data, 0x00)
|
||||||
|
if pluginEndIndex < 0 {
|
||||||
|
return nil, "", ErrMalformPkt
|
||||||
|
}
|
||||||
|
plugin := string(data[1:pluginEndIndex])
|
||||||
|
authData := data[pluginEndIndex+1:]
|
||||||
|
return authData, plugin, nil
|
||||||
|
|
||||||
|
default: // Error otherwise
|
||||||
|
return nil, "", mc.handleErrorPacket(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns error if Packet is not an 'Result OK'-Packet
|
||||||
|
func (mc *mysqlConn) readResultOK() error {
|
||||||
|
data, err := mc.readPacket()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if data[0] == iOK {
|
||||||
|
return mc.handleOkPacket(data)
|
||||||
|
}
|
||||||
|
return mc.handleErrorPacket(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Result Set Header Packet
|
// Result Set Header Packet
|
||||||
|
@ -563,7 +572,8 @@ func (mc *mysqlConn) handleErrorPacket(data []byte) error {
|
||||||
errno := binary.LittleEndian.Uint16(data[1:3])
|
errno := binary.LittleEndian.Uint16(data[1:3])
|
||||||
|
|
||||||
// 1792: ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION
|
// 1792: ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION
|
||||||
if errno == 1792 && mc.cfg.RejectReadOnly {
|
// 1290: ER_OPTION_PREVENTS_STATEMENT (returned by Aurora during failover)
|
||||||
|
if (errno == 1792 || errno == 1290) && mc.cfg.RejectReadOnly {
|
||||||
// Oops; we are connected to a read-only connection, and won't be able
|
// Oops; we are connected to a read-only connection, and won't be able
|
||||||
// to issue any write statements. Since RejectReadOnly is configured,
|
// to issue any write statements. Since RejectReadOnly is configured,
|
||||||
// we throw away this connection hoping this one would have write
|
// we throw away this connection hoping this one would have write
|
||||||
|
@ -616,14 +626,7 @@ func (mc *mysqlConn) handleOkPacket(data []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// warning count [2 bytes]
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -695,14 +698,21 @@ func (mc *mysqlConn) readColumns(count int) ([]mysqlField, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
pos += n
|
||||||
|
|
||||||
// Filler [uint8]
|
// Filler [uint8]
|
||||||
|
pos++
|
||||||
|
|
||||||
// Charset [charset, collation uint8]
|
// Charset [charset, collation uint8]
|
||||||
|
columns[i].charSet = data[pos]
|
||||||
|
pos += 2
|
||||||
|
|
||||||
// Length [uint32]
|
// Length [uint32]
|
||||||
pos += n + 1 + 2 + 4
|
columns[i].length = binary.LittleEndian.Uint32(data[pos : pos+4])
|
||||||
|
pos += 4
|
||||||
|
|
||||||
// Field type [uint8]
|
// Field type [uint8]
|
||||||
columns[i].fieldType = data[pos]
|
columns[i].fieldType = fieldType(data[pos])
|
||||||
pos++
|
pos++
|
||||||
|
|
||||||
// Flags [uint16]
|
// Flags [uint16]
|
||||||
|
@ -835,14 +845,7 @@ func (stmt *mysqlStmt) readPrepareResultPacket() (uint16, error) {
|
||||||
// Reserved [8 bit]
|
// Reserved [8 bit]
|
||||||
|
|
||||||
// Warning count [16 bit uint]
|
// 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 columnCount, nil
|
||||||
}
|
}
|
||||||
return 0, err
|
return 0, err
|
||||||
|
@ -859,7 +862,7 @@ func (stmt *mysqlStmt) writeCommandLongData(paramID int, arg []byte) error {
|
||||||
// 2 bytes paramID
|
// 2 bytes paramID
|
||||||
const dataOffset = 1 + 4 + 2
|
const dataOffset = 1 + 4 + 2
|
||||||
|
|
||||||
// Can not use the write buffer since
|
// Cannot use the write buffer since
|
||||||
// a) the buffer is too small
|
// a) the buffer is too small
|
||||||
// b) it is in use
|
// b) it is in use
|
||||||
data := make([]byte, 4+1+4+2+len(arg))
|
data := make([]byte, 4+1+4+2+len(arg))
|
||||||
|
@ -914,20 +917,28 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
|
||||||
const minPktLen = 4 + 1 + 4 + 1 + 4
|
const minPktLen = 4 + 1 + 4 + 1 + 4
|
||||||
mc := stmt.mc
|
mc := stmt.mc
|
||||||
|
|
||||||
|
// Determine threshold dynamically to avoid packet size shortage.
|
||||||
|
longDataSize := mc.maxAllowedPacket / (stmt.paramCount + 1)
|
||||||
|
if longDataSize < 64 {
|
||||||
|
longDataSize = 64
|
||||||
|
}
|
||||||
|
|
||||||
// Reset packet-sequence
|
// Reset packet-sequence
|
||||||
mc.sequence = 0
|
mc.sequence = 0
|
||||||
|
|
||||||
var data []byte
|
var data []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
data = mc.buf.takeBuffer(minPktLen)
|
data, err = mc.buf.takeBuffer(minPktLen)
|
||||||
} else {
|
} else {
|
||||||
data = mc.buf.takeCompleteBuffer()
|
data, err = mc.buf.takeCompleteBuffer()
|
||||||
|
// In this case the len(data) == cap(data) which is used to optimise the flow below.
|
||||||
}
|
}
|
||||||
if data == nil {
|
if err != nil {
|
||||||
// can not take the buffer. Something must be wrong with the connection
|
// cannot take the buffer. Something must be wrong with the connection
|
||||||
errLog.Print(ErrBusyBuffer)
|
errLog.Print(err)
|
||||||
return driver.ErrBadConn
|
return errBadConnNoWrite
|
||||||
}
|
}
|
||||||
|
|
||||||
// command [1 byte]
|
// command [1 byte]
|
||||||
|
@ -952,7 +963,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
|
||||||
pos := minPktLen
|
pos := minPktLen
|
||||||
|
|
||||||
var nullMask []byte
|
var nullMask []byte
|
||||||
if maskLen, typesLen := (len(args)+7)/8, 1+2*len(args); pos+maskLen+typesLen >= len(data) {
|
if maskLen, typesLen := (len(args)+7)/8, 1+2*len(args); pos+maskLen+typesLen >= cap(data) {
|
||||||
// buffer has to be extended but we don't know by how much so
|
// buffer has to be extended but we don't know by how much so
|
||||||
// we depend on append after all data with known sizes fit.
|
// we depend on append after all data with known sizes fit.
|
||||||
// We stop at that because we deal with a lot of columns here
|
// We stop at that because we deal with a lot of columns here
|
||||||
|
@ -961,10 +972,11 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
|
||||||
copy(tmp[:pos], data[:pos])
|
copy(tmp[:pos], data[:pos])
|
||||||
data = tmp
|
data = tmp
|
||||||
nullMask = data[pos : pos+maskLen]
|
nullMask = data[pos : pos+maskLen]
|
||||||
|
// No need to clean nullMask as make ensures that.
|
||||||
pos += maskLen
|
pos += maskLen
|
||||||
} else {
|
} else {
|
||||||
nullMask = data[pos : pos+maskLen]
|
nullMask = data[pos : pos+maskLen]
|
||||||
for i := 0; i < maskLen; i++ {
|
for i := range nullMask {
|
||||||
nullMask[i] = 0
|
nullMask[i] = 0
|
||||||
}
|
}
|
||||||
pos += maskLen
|
pos += maskLen
|
||||||
|
@ -986,7 +998,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
|
||||||
// build NULL-bitmap
|
// build NULL-bitmap
|
||||||
if arg == nil {
|
if arg == nil {
|
||||||
nullMask[i/8] |= 1 << (uint(i) & 7)
|
nullMask[i/8] |= 1 << (uint(i) & 7)
|
||||||
paramTypes[i+i] = fieldTypeNULL
|
paramTypes[i+i] = byte(fieldTypeNULL)
|
||||||
paramTypes[i+i+1] = 0x00
|
paramTypes[i+i+1] = 0x00
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -994,7 +1006,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
|
||||||
// cache types and values
|
// cache types and values
|
||||||
switch v := arg.(type) {
|
switch v := arg.(type) {
|
||||||
case int64:
|
case int64:
|
||||||
paramTypes[i+i] = fieldTypeLongLong
|
paramTypes[i+i] = byte(fieldTypeLongLong)
|
||||||
paramTypes[i+i+1] = 0x00
|
paramTypes[i+i+1] = 0x00
|
||||||
|
|
||||||
if cap(paramValues)-len(paramValues)-8 >= 0 {
|
if cap(paramValues)-len(paramValues)-8 >= 0 {
|
||||||
|
@ -1009,8 +1021,24 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case uint64:
|
||||||
|
paramTypes[i+i] = byte(fieldTypeLongLong)
|
||||||
|
paramTypes[i+i+1] = 0x80 // type is unsigned
|
||||||
|
|
||||||
|
if cap(paramValues)-len(paramValues)-8 >= 0 {
|
||||||
|
paramValues = paramValues[:len(paramValues)+8]
|
||||||
|
binary.LittleEndian.PutUint64(
|
||||||
|
paramValues[len(paramValues)-8:],
|
||||||
|
uint64(v),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
paramValues = append(paramValues,
|
||||||
|
uint64ToBytes(uint64(v))...,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
case float64:
|
case float64:
|
||||||
paramTypes[i+i] = fieldTypeDouble
|
paramTypes[i+i] = byte(fieldTypeDouble)
|
||||||
paramTypes[i+i+1] = 0x00
|
paramTypes[i+i+1] = 0x00
|
||||||
|
|
||||||
if cap(paramValues)-len(paramValues)-8 >= 0 {
|
if cap(paramValues)-len(paramValues)-8 >= 0 {
|
||||||
|
@ -1026,7 +1054,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
case bool:
|
case bool:
|
||||||
paramTypes[i+i] = fieldTypeTiny
|
paramTypes[i+i] = byte(fieldTypeTiny)
|
||||||
paramTypes[i+i+1] = 0x00
|
paramTypes[i+i+1] = 0x00
|
||||||
|
|
||||||
if v {
|
if v {
|
||||||
|
@ -1038,10 +1066,10 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
|
||||||
case []byte:
|
case []byte:
|
||||||
// Common case (non-nil value) first
|
// Common case (non-nil value) first
|
||||||
if v != nil {
|
if v != nil {
|
||||||
paramTypes[i+i] = fieldTypeString
|
paramTypes[i+i] = byte(fieldTypeString)
|
||||||
paramTypes[i+i+1] = 0x00
|
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,
|
paramValues = appendLengthEncodedInteger(paramValues,
|
||||||
uint64(len(v)),
|
uint64(len(v)),
|
||||||
)
|
)
|
||||||
|
@ -1056,14 +1084,14 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
|
||||||
|
|
||||||
// Handle []byte(nil) as a NULL value
|
// Handle []byte(nil) as a NULL value
|
||||||
nullMask[i/8] |= 1 << (uint(i) & 7)
|
nullMask[i/8] |= 1 << (uint(i) & 7)
|
||||||
paramTypes[i+i] = fieldTypeNULL
|
paramTypes[i+i] = byte(fieldTypeNULL)
|
||||||
paramTypes[i+i+1] = 0x00
|
paramTypes[i+i+1] = 0x00
|
||||||
|
|
||||||
case string:
|
case string:
|
||||||
paramTypes[i+i] = fieldTypeString
|
paramTypes[i+i] = byte(fieldTypeString)
|
||||||
paramTypes[i+i+1] = 0x00
|
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,
|
paramValues = appendLengthEncodedInteger(paramValues,
|
||||||
uint64(len(v)),
|
uint64(len(v)),
|
||||||
)
|
)
|
||||||
|
@ -1075,7 +1103,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
case time.Time:
|
case time.Time:
|
||||||
paramTypes[i+i] = fieldTypeString
|
paramTypes[i+i] = byte(fieldTypeString)
|
||||||
paramTypes[i+i+1] = 0x00
|
paramTypes[i+i+1] = 0x00
|
||||||
|
|
||||||
var a [64]byte
|
var a [64]byte
|
||||||
|
@ -1093,7 +1121,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
|
||||||
paramValues = append(paramValues, b...)
|
paramValues = append(paramValues, b...)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("can not convert type: %T", arg)
|
return fmt.Errorf("cannot convert type: %T", arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1101,7 +1129,10 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
|
||||||
// In that case we must build the data packet with the new values buffer
|
// In that case we must build the data packet with the new values buffer
|
||||||
if valuesCap != cap(paramValues) {
|
if valuesCap != cap(paramValues) {
|
||||||
data = append(data[:pos], paramValues...)
|
data = append(data[:pos], paramValues...)
|
||||||
mc.buf.buf = data
|
if err = mc.buf.store(data); err != nil {
|
||||||
|
errLog.Print(err)
|
||||||
|
return errBadConnNoWrite
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pos += len(paramValues)
|
pos += len(paramValues)
|
||||||
|
@ -1149,10 +1180,11 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
|
||||||
}
|
}
|
||||||
return io.EOF
|
return io.EOF
|
||||||
}
|
}
|
||||||
|
mc := rows.mc
|
||||||
rows.mc = nil
|
rows.mc = nil
|
||||||
|
|
||||||
// Error otherwise
|
// Error otherwise
|
||||||
return rows.mc.handleErrorPacket(data)
|
return mc.handleErrorPacket(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NULL-bitmap, [(column-count + 7 + 2) / 8 bytes]
|
// NULL-bitmap, [(column-count + 7 + 2) / 8 bytes]
|
||||||
|
@ -1270,7 +1302,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
|
||||||
rows.rs.columns[i].decimals,
|
rows.rs.columns[i].decimals,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, true)
|
dest[i], err = formatBinaryTime(data[pos:pos+int(num)], dstlen)
|
||||||
case rows.mc.parseTime:
|
case rows.mc.parseTime:
|
||||||
dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.Loc)
|
dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.Loc)
|
||||||
default:
|
default:
|
||||||
|
@ -1290,7 +1322,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, false)
|
dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
|
@ -11,16 +11,10 @@ package mysql
|
||||||
import (
|
import (
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"io"
|
"io"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mysqlField struct {
|
|
||||||
tableName string
|
|
||||||
name string
|
|
||||||
flags fieldFlag
|
|
||||||
fieldType byte
|
|
||||||
decimals byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type resultSet struct {
|
type resultSet struct {
|
||||||
columns []mysqlField
|
columns []mysqlField
|
||||||
columnNames []string
|
columnNames []string
|
||||||
|
@ -65,6 +59,44 @@ func (rows *mysqlRows) Columns() []string {
|
||||||
return columns
|
return columns
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
func (rows *mysqlRows) Close() (err error) {
|
||||||
if f := rows.finish; f != nil {
|
if f := rows.finish; f != nil {
|
||||||
f()
|
f()
|
||||||
|
@ -79,6 +111,13 @@ func (rows *mysqlRows) Close() (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// flip the buffer for this connection if we need to drain it.
|
||||||
|
// note that for a successful query (i.e. one where rows.next()
|
||||||
|
// has been called until it returns false), `rows.mc` will be nil
|
||||||
|
// by the time the user calls `(*Rows).Close`, so we won't reach this
|
||||||
|
// see: https://github.com/golang/go/commit/651ddbdb5056ded455f47f9c494c67b389622a47
|
||||||
|
mc.buf.flip()
|
||||||
|
|
||||||
// Remove unread packets from stream
|
// Remove unread packets from stream
|
||||||
if !rows.rs.done {
|
if !rows.rs.done {
|
||||||
err = mc.readUntilEOF()
|
err = mc.readUntilEOF()
|
||||||
|
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type mysqlStmt struct {
|
type mysqlStmt struct {
|
||||||
|
@ -52,7 +51,7 @@ func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
|
||||||
// Send command
|
// Send command
|
||||||
err := stmt.writeExecutePacket(args)
|
err := stmt.writeExecutePacket(args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, stmt.mc.markBadConn(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mc := stmt.mc
|
mc := stmt.mc
|
||||||
|
@ -100,7 +99,7 @@ func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) {
|
||||||
// Send command
|
// Send command
|
||||||
err := stmt.writeExecutePacket(args)
|
err := stmt.writeExecutePacket(args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, stmt.mc.markBadConn(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mc := stmt.mc
|
mc := stmt.mc
|
||||||
|
@ -132,31 +131,74 @@ func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) {
|
||||||
|
|
||||||
type converter struct{}
|
type converter struct{}
|
||||||
|
|
||||||
|
// ConvertValue mirrors the reference/default converter in database/sql/driver
|
||||||
|
// with _one_ exception. We support uint64 with their high bit and the default
|
||||||
|
// implementation does not. This function should be kept in sync with
|
||||||
|
// database/sql/driver defaultConverter.ConvertValue() except for that
|
||||||
|
// deliberate difference.
|
||||||
func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
|
func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
|
||||||
if driver.IsValue(v) {
|
if driver.IsValue(v) {
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if vr, ok := v.(driver.Valuer); ok {
|
||||||
|
sv, err := callValuerValue(vr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !driver.IsValue(sv) {
|
||||||
|
return nil, fmt.Errorf("non-Value type %T returned from Value", sv)
|
||||||
|
}
|
||||||
|
return sv, nil
|
||||||
|
}
|
||||||
|
|
||||||
rv := reflect.ValueOf(v)
|
rv := reflect.ValueOf(v)
|
||||||
switch rv.Kind() {
|
switch rv.Kind() {
|
||||||
case reflect.Ptr:
|
case reflect.Ptr:
|
||||||
// indirect pointers
|
// indirect pointers
|
||||||
if rv.IsNil() {
|
if rv.IsNil() {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
} else {
|
||||||
|
return c.ConvertValue(rv.Elem().Interface())
|
||||||
}
|
}
|
||||||
return c.ConvertValue(rv.Elem().Interface())
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
return rv.Int(), nil
|
return rv.Int(), nil
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
return int64(rv.Uint()), nil
|
return rv.Uint(), nil
|
||||||
case reflect.Uint64:
|
|
||||||
u64 := rv.Uint()
|
|
||||||
if u64 >= 1<<63 {
|
|
||||||
return strconv.FormatUint(u64, 10), nil
|
|
||||||
}
|
|
||||||
return int64(u64), nil
|
|
||||||
case reflect.Float32, reflect.Float64:
|
case reflect.Float32, reflect.Float64:
|
||||||
return rv.Float(), nil
|
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())
|
return nil, fmt.Errorf("unsupported type %T, a %s", v, rv.Kind())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var valuerReflectType = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
|
||||||
|
|
||||||
|
// callValuerValue returns vr.Value(), with one exception:
|
||||||
|
// If vr.Value is an auto-generated method on a pointer type and the
|
||||||
|
// pointer is nil, it would panic at runtime in the panicwrap
|
||||||
|
// method. Treat it like nil instead.
|
||||||
|
//
|
||||||
|
// This is so people can implement driver.Value on value types and
|
||||||
|
// still use nil pointers to those types to mean nil/NULL, just like
|
||||||
|
// string/*string.
|
||||||
|
//
|
||||||
|
// This is an exact copy of the same-named unexported function from the
|
||||||
|
// database/sql package.
|
||||||
|
func callValuerValue(vr driver.Valuer) (v driver.Value, err error) {
|
||||||
|
if rv := reflect.ValueOf(vr); rv.Kind() == reflect.Ptr &&
|
||||||
|
rv.IsNil() &&
|
||||||
|
rv.Type().Elem().Implements(valuerReflectType) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return vr.Value()
|
||||||
|
}
|
||||||
|
|
|
@ -9,27 +9,31 @@
|
||||||
package mysql
|
package mysql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha1"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"database/sql"
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Registry for custom tls.Configs
|
||||||
var (
|
var (
|
||||||
tlsConfigLock sync.RWMutex
|
tlsConfigLock sync.RWMutex
|
||||||
tlsConfigRegister map[string]*tls.Config // Register for custom tls.Configs
|
tlsConfigRegistry map[string]*tls.Config
|
||||||
)
|
)
|
||||||
|
|
||||||
// RegisterTLSConfig registers a custom tls.Config to be used with sql.Open.
|
// RegisterTLSConfig registers a custom tls.Config to be used with sql.Open.
|
||||||
// Use the key as a value in the DSN where tls=value.
|
// Use the key as a value in the DSN where tls=value.
|
||||||
//
|
//
|
||||||
// Note: The tls.Config provided to needs to be exclusively owned by the driver after registering.
|
// Note: The provided tls.Config is exclusively owned by the driver after
|
||||||
|
// registering it.
|
||||||
//
|
//
|
||||||
// rootCertPool := x509.NewCertPool()
|
// rootCertPool := x509.NewCertPool()
|
||||||
// pem, err := ioutil.ReadFile("/path/ca-cert.pem")
|
// pem, err := ioutil.ReadFile("/path/ca-cert.pem")
|
||||||
|
@ -52,16 +56,16 @@ var (
|
||||||
// db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom")
|
// db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom")
|
||||||
//
|
//
|
||||||
func RegisterTLSConfig(key string, config *tls.Config) error {
|
func RegisterTLSConfig(key string, config *tls.Config) error {
|
||||||
if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" {
|
if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" || strings.ToLower(key) == "preferred" {
|
||||||
return fmt.Errorf("key '%s' is reserved", key)
|
return fmt.Errorf("key '%s' is reserved", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConfigLock.Lock()
|
tlsConfigLock.Lock()
|
||||||
if tlsConfigRegister == nil {
|
if tlsConfigRegistry == nil {
|
||||||
tlsConfigRegister = make(map[string]*tls.Config)
|
tlsConfigRegistry = make(map[string]*tls.Config)
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConfigRegister[key] = config
|
tlsConfigRegistry[key] = config
|
||||||
tlsConfigLock.Unlock()
|
tlsConfigLock.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -69,16 +73,16 @@ func RegisterTLSConfig(key string, config *tls.Config) error {
|
||||||
// DeregisterTLSConfig removes the tls.Config associated with key.
|
// DeregisterTLSConfig removes the tls.Config associated with key.
|
||||||
func DeregisterTLSConfig(key string) {
|
func DeregisterTLSConfig(key string) {
|
||||||
tlsConfigLock.Lock()
|
tlsConfigLock.Lock()
|
||||||
if tlsConfigRegister != nil {
|
if tlsConfigRegistry != nil {
|
||||||
delete(tlsConfigRegister, key)
|
delete(tlsConfigRegistry, key)
|
||||||
}
|
}
|
||||||
tlsConfigLock.Unlock()
|
tlsConfigLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTLSConfigClone(key string) (config *tls.Config) {
|
func getTLSConfigClone(key string) (config *tls.Config) {
|
||||||
tlsConfigLock.RLock()
|
tlsConfigLock.RLock()
|
||||||
if v, ok := tlsConfigRegister[key]; ok {
|
if v, ok := tlsConfigRegistry[key]; ok {
|
||||||
config = cloneTLSConfig(v)
|
config = v.Clone()
|
||||||
}
|
}
|
||||||
tlsConfigLock.RUnlock()
|
tlsConfigLock.RUnlock()
|
||||||
return
|
return
|
||||||
|
@ -98,177 +102,10 @@ func readBool(input string) (value bool, valid bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************
|
|
||||||
* Authentication *
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
// Encrypt password using 4.1+ method
|
|
||||||
func scramblePassword(scramble, password []byte) []byte {
|
|
||||||
if len(password) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// stage1Hash = SHA1(password)
|
|
||||||
crypt := sha1.New()
|
|
||||||
crypt.Write(password)
|
|
||||||
stage1 := crypt.Sum(nil)
|
|
||||||
|
|
||||||
// scrambleHash = SHA1(scramble + SHA1(stage1Hash))
|
|
||||||
// inner Hash
|
|
||||||
crypt.Reset()
|
|
||||||
crypt.Write(stage1)
|
|
||||||
hash := crypt.Sum(nil)
|
|
||||||
|
|
||||||
// outer Hash
|
|
||||||
crypt.Reset()
|
|
||||||
crypt.Write(scramble)
|
|
||||||
crypt.Write(hash)
|
|
||||||
scramble = crypt.Sum(nil)
|
|
||||||
|
|
||||||
// token = scrambleHash XOR stage1Hash
|
|
||||||
for i := range scramble {
|
|
||||||
scramble[i] ^= stage1[i]
|
|
||||||
}
|
|
||||||
return scramble
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encrypt password using pre 4.1 (old password) method
|
|
||||||
// https://github.com/atcurtis/mariadb/blob/master/mysys/my_rnd.c
|
|
||||||
type myRnd struct {
|
|
||||||
seed1, seed2 uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
const myRndMaxVal = 0x3FFFFFFF
|
|
||||||
|
|
||||||
// Pseudo random number generator
|
|
||||||
func newMyRnd(seed1, seed2 uint32) *myRnd {
|
|
||||||
return &myRnd{
|
|
||||||
seed1: seed1 % myRndMaxVal,
|
|
||||||
seed2: seed2 % myRndMaxVal,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tested to be equivalent to MariaDB's floating point variant
|
|
||||||
// http://play.golang.org/p/QHvhd4qved
|
|
||||||
// http://play.golang.org/p/RG0q4ElWDx
|
|
||||||
func (r *myRnd) NextByte() byte {
|
|
||||||
r.seed1 = (r.seed1*3 + r.seed2) % myRndMaxVal
|
|
||||||
r.seed2 = (r.seed1 + r.seed2 + 33) % myRndMaxVal
|
|
||||||
|
|
||||||
return byte(uint64(r.seed1) * 31 / myRndMaxVal)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate binary hash from byte string using insecure pre 4.1 method
|
|
||||||
func pwHash(password []byte) (result [2]uint32) {
|
|
||||||
var add uint32 = 7
|
|
||||||
var tmp uint32
|
|
||||||
|
|
||||||
result[0] = 1345345333
|
|
||||||
result[1] = 0x12345671
|
|
||||||
|
|
||||||
for _, c := range password {
|
|
||||||
// skip spaces and tabs in password
|
|
||||||
if c == ' ' || c == '\t' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
tmp = uint32(c)
|
|
||||||
result[0] ^= (((result[0] & 63) + add) * tmp) + (result[0] << 8)
|
|
||||||
result[1] += (result[1] << 8) ^ result[0]
|
|
||||||
add += tmp
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove sign bit (1<<31)-1)
|
|
||||||
result[0] &= 0x7FFFFFFF
|
|
||||||
result[1] &= 0x7FFFFFFF
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encrypt password using insecure pre 4.1 method
|
|
||||||
func scrambleOldPassword(scramble, password []byte) []byte {
|
|
||||||
if len(password) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
scramble = scramble[:8]
|
|
||||||
|
|
||||||
hashPw := pwHash(password)
|
|
||||||
hashSc := pwHash(scramble)
|
|
||||||
|
|
||||||
r := newMyRnd(hashPw[0]^hashSc[0], hashPw[1]^hashSc[1])
|
|
||||||
|
|
||||||
var out [8]byte
|
|
||||||
for i := range out {
|
|
||||||
out[i] = r.NextByte() + 64
|
|
||||||
}
|
|
||||||
|
|
||||||
mask := r.NextByte()
|
|
||||||
for i := range out {
|
|
||||||
out[i] ^= mask
|
|
||||||
}
|
|
||||||
|
|
||||||
return out[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
* Time related utils *
|
* Time related utils *
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
// NullTime represents a time.Time that may be NULL.
|
|
||||||
// NullTime implements the Scanner interface so
|
|
||||||
// it can be used as a scan destination:
|
|
||||||
//
|
|
||||||
// var nt NullTime
|
|
||||||
// err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
|
|
||||||
// ...
|
|
||||||
// if nt.Valid {
|
|
||||||
// // use nt.Time
|
|
||||||
// } else {
|
|
||||||
// // NULL value
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// This NullTime implementation is not driver-specific
|
|
||||||
type NullTime struct {
|
|
||||||
Time time.Time
|
|
||||||
Valid bool // Valid is true if Time is not NULL
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan implements the Scanner interface.
|
|
||||||
// The value type must be time.Time or string / []byte (formatted time-string),
|
|
||||||
// otherwise Scan fails.
|
|
||||||
func (nt *NullTime) Scan(value interface{}) (err error) {
|
|
||||||
if value == nil {
|
|
||||||
nt.Time, nt.Valid = time.Time{}, false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v := value.(type) {
|
|
||||||
case time.Time:
|
|
||||||
nt.Time, nt.Valid = v, true
|
|
||||||
return
|
|
||||||
case []byte:
|
|
||||||
nt.Time, err = parseDateTime(string(v), time.UTC)
|
|
||||||
nt.Valid = (err == nil)
|
|
||||||
return
|
|
||||||
case string:
|
|
||||||
nt.Time, err = parseDateTime(v, time.UTC)
|
|
||||||
nt.Valid = (err == nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
nt.Valid = false
|
|
||||||
return fmt.Errorf("Can't convert %T to time.Time", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value implements the driver Valuer interface.
|
|
||||||
func (nt NullTime) Value() (driver.Value, error) {
|
|
||||||
if !nt.Valid {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return nt.Time, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDateTime(str string, loc *time.Location) (t time.Time, err error) {
|
func parseDateTime(str string, loc *time.Location) (t time.Time, err error) {
|
||||||
base := "0000-00-00 00:00:00.0000000"
|
base := "0000-00-00 00:00:00.0000000"
|
||||||
switch len(str) {
|
switch len(str) {
|
||||||
|
@ -339,87 +176,104 @@ var zeroDateTime = []byte("0000-00-00 00:00:00.000000")
|
||||||
const digits01 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
|
const digits01 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
|
||||||
const digits10 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999"
|
const digits10 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999"
|
||||||
|
|
||||||
func formatBinaryDateTime(src []byte, length uint8, justTime bool) (driver.Value, error) {
|
func appendMicrosecs(dst, src []byte, decimals int) []byte {
|
||||||
|
if decimals <= 0 {
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
if len(src) == 0 {
|
||||||
|
return append(dst, ".000000"[:decimals+1]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
microsecs := binary.LittleEndian.Uint32(src[:4])
|
||||||
|
p1 := byte(microsecs / 10000)
|
||||||
|
microsecs -= 10000 * uint32(p1)
|
||||||
|
p2 := byte(microsecs / 100)
|
||||||
|
microsecs -= 100 * uint32(p2)
|
||||||
|
p3 := byte(microsecs)
|
||||||
|
|
||||||
|
switch decimals {
|
||||||
|
default:
|
||||||
|
return append(dst, '.',
|
||||||
|
digits10[p1], digits01[p1],
|
||||||
|
digits10[p2], digits01[p2],
|
||||||
|
digits10[p3], digits01[p3],
|
||||||
|
)
|
||||||
|
case 1:
|
||||||
|
return append(dst, '.',
|
||||||
|
digits10[p1],
|
||||||
|
)
|
||||||
|
case 2:
|
||||||
|
return append(dst, '.',
|
||||||
|
digits10[p1], digits01[p1],
|
||||||
|
)
|
||||||
|
case 3:
|
||||||
|
return append(dst, '.',
|
||||||
|
digits10[p1], digits01[p1],
|
||||||
|
digits10[p2],
|
||||||
|
)
|
||||||
|
case 4:
|
||||||
|
return append(dst, '.',
|
||||||
|
digits10[p1], digits01[p1],
|
||||||
|
digits10[p2], digits01[p2],
|
||||||
|
)
|
||||||
|
case 5:
|
||||||
|
return append(dst, '.',
|
||||||
|
digits10[p1], digits01[p1],
|
||||||
|
digits10[p2], digits01[p2],
|
||||||
|
digits10[p3],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatBinaryDateTime(src []byte, length uint8) (driver.Value, error) {
|
||||||
// length expects the deterministic length of the zero value,
|
// length expects the deterministic length of the zero value,
|
||||||
// negative time and 100+ hours are automatically added if needed
|
// negative time and 100+ hours are automatically added if needed
|
||||||
if len(src) == 0 {
|
if len(src) == 0 {
|
||||||
if justTime {
|
|
||||||
return zeroDateTime[11 : 11+length], nil
|
|
||||||
}
|
|
||||||
return zeroDateTime[:length], nil
|
return zeroDateTime[:length], nil
|
||||||
}
|
}
|
||||||
var dst []byte // return value
|
var dst []byte // return value
|
||||||
var pt, p1, p2, p3 byte // current digit pair
|
var p1, p2, p3 byte // current digit pair
|
||||||
var zOffs byte // offset of value in zeroDateTime
|
|
||||||
if justTime {
|
switch length {
|
||||||
switch length {
|
case 10, 19, 21, 22, 23, 24, 25, 26:
|
||||||
case
|
default:
|
||||||
8, // time (can be up to 10 when negative and 100+ hours)
|
t := "DATE"
|
||||||
10, 11, 12, 13, 14, 15: // time with fractional seconds
|
if length > 10 {
|
||||||
default:
|
t += "TIME"
|
||||||
return nil, fmt.Errorf("illegal TIME length %d", length)
|
|
||||||
}
|
}
|
||||||
switch len(src) {
|
return nil, fmt.Errorf("illegal %s length %d", t, length)
|
||||||
case 8, 12:
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("invalid TIME packet length %d", len(src))
|
|
||||||
}
|
|
||||||
// +2 to enable negative time and 100+ hours
|
|
||||||
dst = make([]byte, 0, length+2)
|
|
||||||
if src[0] == 1 {
|
|
||||||
dst = append(dst, '-')
|
|
||||||
}
|
|
||||||
if src[1] != 0 {
|
|
||||||
hour := uint16(src[1])*24 + uint16(src[5])
|
|
||||||
pt = byte(hour / 100)
|
|
||||||
p1 = byte(hour - 100*uint16(pt))
|
|
||||||
dst = append(dst, digits01[pt])
|
|
||||||
} else {
|
|
||||||
p1 = src[5]
|
|
||||||
}
|
|
||||||
zOffs = 11
|
|
||||||
src = src[6:]
|
|
||||||
} else {
|
|
||||||
switch length {
|
|
||||||
case 10, 19, 21, 22, 23, 24, 25, 26:
|
|
||||||
default:
|
|
||||||
t := "DATE"
|
|
||||||
if length > 10 {
|
|
||||||
t += "TIME"
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("illegal %s length %d", t, length)
|
|
||||||
}
|
|
||||||
switch len(src) {
|
|
||||||
case 4, 7, 11:
|
|
||||||
default:
|
|
||||||
t := "DATE"
|
|
||||||
if length > 10 {
|
|
||||||
t += "TIME"
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("illegal %s packet length %d", t, len(src))
|
|
||||||
}
|
|
||||||
dst = make([]byte, 0, length)
|
|
||||||
// start with the date
|
|
||||||
year := binary.LittleEndian.Uint16(src[:2])
|
|
||||||
pt = byte(year / 100)
|
|
||||||
p1 = byte(year - 100*uint16(pt))
|
|
||||||
p2, p3 = src[2], src[3]
|
|
||||||
dst = append(dst,
|
|
||||||
digits10[pt], digits01[pt],
|
|
||||||
digits10[p1], digits01[p1], '-',
|
|
||||||
digits10[p2], digits01[p2], '-',
|
|
||||||
digits10[p3], digits01[p3],
|
|
||||||
)
|
|
||||||
if length == 10 {
|
|
||||||
return dst, nil
|
|
||||||
}
|
|
||||||
if len(src) == 4 {
|
|
||||||
return append(dst, zeroDateTime[10:length]...), nil
|
|
||||||
}
|
|
||||||
dst = append(dst, ' ')
|
|
||||||
p1 = src[4] // hour
|
|
||||||
src = src[5:]
|
|
||||||
}
|
}
|
||||||
|
switch len(src) {
|
||||||
|
case 4, 7, 11:
|
||||||
|
default:
|
||||||
|
t := "DATE"
|
||||||
|
if length > 10 {
|
||||||
|
t += "TIME"
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("illegal %s packet length %d", t, len(src))
|
||||||
|
}
|
||||||
|
dst = make([]byte, 0, length)
|
||||||
|
// start with the date
|
||||||
|
year := binary.LittleEndian.Uint16(src[:2])
|
||||||
|
pt := year / 100
|
||||||
|
p1 = byte(year - 100*uint16(pt))
|
||||||
|
p2, p3 = src[2], src[3]
|
||||||
|
dst = append(dst,
|
||||||
|
digits10[pt], digits01[pt],
|
||||||
|
digits10[p1], digits01[p1], '-',
|
||||||
|
digits10[p2], digits01[p2], '-',
|
||||||
|
digits10[p3], digits01[p3],
|
||||||
|
)
|
||||||
|
if length == 10 {
|
||||||
|
return dst, nil
|
||||||
|
}
|
||||||
|
if len(src) == 4 {
|
||||||
|
return append(dst, zeroDateTime[10:length]...), nil
|
||||||
|
}
|
||||||
|
dst = append(dst, ' ')
|
||||||
|
p1 = src[4] // hour
|
||||||
|
src = src[5:]
|
||||||
|
|
||||||
// p1 is 2-digit hour, src is after hour
|
// p1 is 2-digit hour, src is after hour
|
||||||
p2, p3 = src[0], src[1]
|
p2, p3 = src[0], src[1]
|
||||||
dst = append(dst,
|
dst = append(dst,
|
||||||
|
@ -427,51 +281,49 @@ func formatBinaryDateTime(src []byte, length uint8, justTime bool) (driver.Value
|
||||||
digits10[p2], digits01[p2], ':',
|
digits10[p2], digits01[p2], ':',
|
||||||
digits10[p3], digits01[p3],
|
digits10[p3], digits01[p3],
|
||||||
)
|
)
|
||||||
if length <= byte(len(dst)) {
|
return appendMicrosecs(dst, src[2:], int(length)-20), nil
|
||||||
return dst, nil
|
}
|
||||||
}
|
|
||||||
src = src[2:]
|
func formatBinaryTime(src []byte, length uint8) (driver.Value, error) {
|
||||||
|
// length expects the deterministic length of the zero value,
|
||||||
|
// negative time and 100+ hours are automatically added if needed
|
||||||
if len(src) == 0 {
|
if len(src) == 0 {
|
||||||
return append(dst, zeroDateTime[19:zOffs+length]...), nil
|
return zeroDateTime[11 : 11+length], nil
|
||||||
}
|
}
|
||||||
microsecs := binary.LittleEndian.Uint32(src[:4])
|
var dst []byte // return value
|
||||||
p1 = byte(microsecs / 10000)
|
|
||||||
microsecs -= 10000 * uint32(p1)
|
switch length {
|
||||||
p2 = byte(microsecs / 100)
|
case
|
||||||
microsecs -= 100 * uint32(p2)
|
8, // time (can be up to 10 when negative and 100+ hours)
|
||||||
p3 = byte(microsecs)
|
10, 11, 12, 13, 14, 15: // time with fractional seconds
|
||||||
switch decimals := zOffs + length - 20; decimals {
|
|
||||||
default:
|
default:
|
||||||
return append(dst, '.',
|
return nil, fmt.Errorf("illegal TIME length %d", length)
|
||||||
digits10[p1], digits01[p1],
|
|
||||||
digits10[p2], digits01[p2],
|
|
||||||
digits10[p3], digits01[p3],
|
|
||||||
), nil
|
|
||||||
case 1:
|
|
||||||
return append(dst, '.',
|
|
||||||
digits10[p1],
|
|
||||||
), nil
|
|
||||||
case 2:
|
|
||||||
return append(dst, '.',
|
|
||||||
digits10[p1], digits01[p1],
|
|
||||||
), nil
|
|
||||||
case 3:
|
|
||||||
return append(dst, '.',
|
|
||||||
digits10[p1], digits01[p1],
|
|
||||||
digits10[p2],
|
|
||||||
), nil
|
|
||||||
case 4:
|
|
||||||
return append(dst, '.',
|
|
||||||
digits10[p1], digits01[p1],
|
|
||||||
digits10[p2], digits01[p2],
|
|
||||||
), nil
|
|
||||||
case 5:
|
|
||||||
return append(dst, '.',
|
|
||||||
digits10[p1], digits01[p1],
|
|
||||||
digits10[p2], digits01[p2],
|
|
||||||
digits10[p3],
|
|
||||||
), nil
|
|
||||||
}
|
}
|
||||||
|
switch len(src) {
|
||||||
|
case 8, 12:
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid TIME packet length %d", len(src))
|
||||||
|
}
|
||||||
|
// +2 to enable negative time and 100+ hours
|
||||||
|
dst = make([]byte, 0, length+2)
|
||||||
|
if src[0] == 1 {
|
||||||
|
dst = append(dst, '-')
|
||||||
|
}
|
||||||
|
days := binary.LittleEndian.Uint32(src[1:5])
|
||||||
|
hours := int64(days)*24 + int64(src[5])
|
||||||
|
|
||||||
|
if hours >= 100 {
|
||||||
|
dst = strconv.AppendInt(dst, hours, 10)
|
||||||
|
} else {
|
||||||
|
dst = append(dst, digits10[hours], digits01[hours])
|
||||||
|
}
|
||||||
|
|
||||||
|
min, sec := src[6], src[7]
|
||||||
|
dst = append(dst, ':',
|
||||||
|
digits10[min], digits01[min], ':',
|
||||||
|
digits10[sec], digits01[sec],
|
||||||
|
)
|
||||||
|
return appendMicrosecs(dst, src[8:], int(length)-9), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
|
@ -537,7 +389,7 @@ func readLengthEncodedString(b []byte) ([]byte, bool, int, error) {
|
||||||
|
|
||||||
// Check data length
|
// Check data length
|
||||||
if len(b) >= n {
|
if len(b) >= n {
|
||||||
return b[n-int(num) : n], false, n, nil
|
return b[n-int(num) : n : n], false, n, nil
|
||||||
}
|
}
|
||||||
return nil, false, n, io.EOF
|
return nil, false, n, io.EOF
|
||||||
}
|
}
|
||||||
|
@ -566,8 +418,8 @@ func readLengthEncodedInteger(b []byte) (uint64, bool, int) {
|
||||||
if len(b) == 0 {
|
if len(b) == 0 {
|
||||||
return 0, true, 1
|
return 0, true, 1
|
||||||
}
|
}
|
||||||
switch b[0] {
|
|
||||||
|
|
||||||
|
switch b[0] {
|
||||||
// 251: NULL
|
// 251: NULL
|
||||||
case 0xfb:
|
case 0xfb:
|
||||||
return 0, true, 1
|
return 0, true, 1
|
||||||
|
@ -778,7 +630,7 @@ type atomicBool struct {
|
||||||
value uint32
|
value uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsSet returns wether the current boolean value is true
|
// IsSet returns whether the current boolean value is true
|
||||||
func (ab *atomicBool) IsSet() bool {
|
func (ab *atomicBool) IsSet() bool {
|
||||||
return atomic.LoadUint32(&ab.value) > 0
|
return atomic.LoadUint32(&ab.value) > 0
|
||||||
}
|
}
|
||||||
|
@ -792,7 +644,7 @@ func (ab *atomicBool) Set(value bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrySet sets the value of the bool and returns wether the value changed
|
// TrySet sets the value of the bool and returns whether the value changed
|
||||||
func (ab *atomicBool) TrySet(value bool) bool {
|
func (ab *atomicBool) TrySet(value bool) bool {
|
||||||
if value {
|
if value {
|
||||||
return atomic.SwapUint32(&ab.value, 1) == 0
|
return atomic.SwapUint32(&ab.value, 1) == 0
|
||||||
|
@ -800,7 +652,7 @@ func (ab *atomicBool) TrySet(value bool) bool {
|
||||||
return atomic.SwapUint32(&ab.value, 0) > 0
|
return atomic.SwapUint32(&ab.value, 0) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// atomicBool is a wrapper for atomically accessed error values
|
// atomicError is a wrapper for atomically accessed error values
|
||||||
type atomicError struct {
|
type atomicError struct {
|
||||||
_noCopy noCopy
|
_noCopy noCopy
|
||||||
value atomic.Value
|
value atomic.Value
|
||||||
|
@ -820,3 +672,30 @@ func (ae *atomicError) Value() error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) {
|
||||||
|
dargs := make([]driver.Value, len(named))
|
||||||
|
for n, param := range named {
|
||||||
|
if len(param.Name) > 0 {
|
||||||
|
// TODO: support the use of Named Parameters #561
|
||||||
|
return nil, errors.New("mysql: driver does not support the use of Named Parameters")
|
||||||
|
}
|
||||||
|
dargs[n] = param.Value
|
||||||
|
}
|
||||||
|
return dargs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapIsolationLevel(level driver.IsolationLevel) (string, error) {
|
||||||
|
switch sql.IsolationLevel(level) {
|
||||||
|
case sql.LevelRepeatableRead:
|
||||||
|
return "REPEATABLE READ", nil
|
||||||
|
case sql.LevelReadCommitted:
|
||||||
|
return "READ COMMITTED", nil
|
||||||
|
case sql.LevelReadUncommitted:
|
||||||
|
return "READ UNCOMMITTED", nil
|
||||||
|
case sql.LevelSerializable:
|
||||||
|
return "SERIALIZABLE", nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("mysql: unsupported isolation level: %v", level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
|
||||||
//
|
|
||||||
// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
||||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
// +build go1.7
|
|
||||||
// +build !go1.8
|
|
||||||
|
|
||||||
package mysql
|
|
||||||
|
|
||||||
import "crypto/tls"
|
|
||||||
|
|
||||||
func cloneTLSConfig(c *tls.Config) *tls.Config {
|
|
||||||
return &tls.Config{
|
|
||||||
Rand: c.Rand,
|
|
||||||
Time: c.Time,
|
|
||||||
Certificates: c.Certificates,
|
|
||||||
NameToCertificate: c.NameToCertificate,
|
|
||||||
GetCertificate: c.GetCertificate,
|
|
||||||
RootCAs: c.RootCAs,
|
|
||||||
NextProtos: c.NextProtos,
|
|
||||||
ServerName: c.ServerName,
|
|
||||||
ClientAuth: c.ClientAuth,
|
|
||||||
ClientCAs: c.ClientCAs,
|
|
||||||
InsecureSkipVerify: c.InsecureSkipVerify,
|
|
||||||
CipherSuites: c.CipherSuites,
|
|
||||||
PreferServerCipherSuites: c.PreferServerCipherSuites,
|
|
||||||
SessionTicketsDisabled: c.SessionTicketsDisabled,
|
|
||||||
SessionTicketKey: c.SessionTicketKey,
|
|
||||||
ClientSessionCache: c.ClientSessionCache,
|
|
||||||
MinVersion: c.MinVersion,
|
|
||||||
MaxVersion: c.MaxVersion,
|
|
||||||
CurvePreferences: c.CurvePreferences,
|
|
||||||
DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled,
|
|
||||||
Renegotiation: c.Renegotiation,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
|
||||||
//
|
|
||||||
// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
||||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
// +build go1.8
|
|
||||||
|
|
||||||
package mysql
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"database/sql"
|
|
||||||
"database/sql/driver"
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
|
||||||
//
|
|
||||||
// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
||||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
// +build !go1.7
|
|
||||||
|
|
||||||
package mysql
|
|
||||||
|
|
||||||
import "crypto/tls"
|
|
||||||
|
|
||||||
func cloneTLSConfig(c *tls.Config) *tls.Config {
|
|
||||||
clone := *c
|
|
||||||
return &clone
|
|
||||||
}
|
|
|
@ -18,7 +18,7 @@ github.com/cloudflare/cfssl/signer
|
||||||
github.com/cloudflare/cfssl/signer/local
|
github.com/cloudflare/cfssl/signer/local
|
||||||
# github.com/eggsampler/acme/v2 v2.0.1
|
# github.com/eggsampler/acme/v2 v2.0.1
|
||||||
github.com/eggsampler/acme/v2
|
github.com/eggsampler/acme/v2
|
||||||
# github.com/go-sql-driver/mysql v1.3.1-0.20170715192408-3955978caca4
|
# github.com/go-sql-driver/mysql v1.4.1-0.20191114115753-b4242bab7dc5
|
||||||
github.com/go-sql-driver/mysql
|
github.com/go-sql-driver/mysql
|
||||||
# github.com/golang/mock v1.2.0
|
# github.com/golang/mock v1.2.0
|
||||||
github.com/golang/mock/gomock
|
github.com/golang/mock/gomock
|
||||||
|
|
Loading…
Reference in New Issue