should fix #5736tags/v1.9.0-dev
revision = "a77f45a7ce909c0ff14b28279fa1a2b674acb70f" | revision = "a77f45a7ce909c0ff14b28279fa1a2b674acb70f" | ||||
[[projects]] | [[projects]] | ||||
digest = "1:747c1fcb10f8f6734551465ab73c6ed9c551aa6e66250fb6683d1624f554546a" | |||||
digest = "1:dce58f88343bd78f4d32dd9601aab4fa5d9994fd2cafa185c51bbd858851cdf9" | |||||
name = "github.com/go-sql-driver/mysql" | name = "github.com/go-sql-driver/mysql" | ||||
packages = ["."] | packages = ["."] | ||||
pruneopts = "NUT" | pruneopts = "NUT" | ||||
revision = "d523deb1b23d913de5bdada721a6071e71283618" | |||||
revision = "c45f530f8e7fe40f4687eaa50d0c8c5f1b66f9e0" | |||||
[[projects]] | [[projects]] | ||||
digest = "1:06d21295033f211588d0ad7ff391cc1b27e72b60cb6d4b7db0d70cffae4cf228" | digest = "1:06d21295033f211588d0ad7ff391cc1b27e72b60cb6d4b7db0d70cffae4cf228" |
[[override]] | [[override]] | ||||
name = "github.com/go-sql-driver/mysql" | name = "github.com/go-sql-driver/mysql" | ||||
revision = "d523deb1b23d913de5bdada721a6071e71283618" | |||||
revision = "c45f530f8e7fe40f4687eaa50d0c8c5f1b66f9e0" | |||||
[[override]] | [[override]] | ||||
name = "github.com/mattn/go-sqlite3" | name = "github.com/mattn/go-sqlite3" |
Henri Yandell <flamefew at gmail.com> | Henri Yandell <flamefew at gmail.com> | ||||
Hirotaka Yamamoto <ymmt2005 at gmail.com> | Hirotaka Yamamoto <ymmt2005 at gmail.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> | ||||
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> | |||||
Tom Jenkinson <tom at tjenkinson.me> | |||||
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> | ||||
Percona LLC | Percona LLC | ||||
Pivotal Inc. | Pivotal Inc. | ||||
Stripe Inc. | Stripe Inc. | ||||
Multiplay Ltd. |
if err != nil { | if err != nil { | ||||
return err | return err | ||||
} | } | ||||
return mc.writeAuthSwitchPacket(enc, false) | |||||
return mc.writeAuthSwitchPacket(enc) | |||||
} | } | ||||
func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, bool, error) { | |||||
func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, error) { | |||||
switch plugin { | switch plugin { | ||||
case "caching_sha2_password": | case "caching_sha2_password": | ||||
authResp := scrambleSHA256Password(authData, mc.cfg.Passwd) | authResp := scrambleSHA256Password(authData, mc.cfg.Passwd) | ||||
return authResp, (authResp == nil), nil | |||||
return authResp, nil | |||||
case "mysql_old_password": | case "mysql_old_password": | ||||
if !mc.cfg.AllowOldPasswords { | if !mc.cfg.AllowOldPasswords { | ||||
return nil, false, ErrOldPassword | |||||
return nil, ErrOldPassword | |||||
} | } | ||||
// Note: there are edge cases where this should work but doesn't; | // Note: there are edge cases where this should work but doesn't; | ||||
// this is currently "wontfix": | // this is currently "wontfix": | ||||
// https://github.com/go-sql-driver/mysql/issues/184 | // https://github.com/go-sql-driver/mysql/issues/184 | ||||
authResp := scrambleOldPassword(authData[:8], mc.cfg.Passwd) | |||||
return authResp, true, nil | |||||
authResp := append(scrambleOldPassword(authData[:8], mc.cfg.Passwd), 0) | |||||
return authResp, nil | |||||
case "mysql_clear_password": | case "mysql_clear_password": | ||||
if !mc.cfg.AllowCleartextPasswords { | if !mc.cfg.AllowCleartextPasswords { | ||||
return nil, false, ErrCleartextPassword | |||||
return nil, ErrCleartextPassword | |||||
} | } | ||||
// http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html | // http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html | ||||
// http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html | // http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html | ||||
return []byte(mc.cfg.Passwd), true, nil | |||||
return append([]byte(mc.cfg.Passwd), 0), nil | |||||
case "mysql_native_password": | case "mysql_native_password": | ||||
if !mc.cfg.AllowNativePasswords { | if !mc.cfg.AllowNativePasswords { | ||||
return nil, false, ErrNativePassword | |||||
return nil, ErrNativePassword | |||||
} | } | ||||
// https://dev.mysql.com/doc/internals/en/secure-password-authentication.html | // https://dev.mysql.com/doc/internals/en/secure-password-authentication.html | ||||
// Native password authentication only need and will need 20-byte challenge. | // Native password authentication only need and will need 20-byte challenge. | ||||
authResp := scramblePassword(authData[:20], mc.cfg.Passwd) | authResp := scramblePassword(authData[:20], mc.cfg.Passwd) | ||||
return authResp, false, nil | |||||
return authResp, nil | |||||
case "sha256_password": | case "sha256_password": | ||||
if len(mc.cfg.Passwd) == 0 { | if len(mc.cfg.Passwd) == 0 { | ||||
return nil, true, nil | |||||
return []byte{0}, nil | |||||
} | } | ||||
if mc.cfg.tls != nil || mc.cfg.Net == "unix" { | if mc.cfg.tls != nil || mc.cfg.Net == "unix" { | ||||
// write cleartext auth packet | // write cleartext auth packet | ||||
return []byte(mc.cfg.Passwd), true, nil | |||||
return append([]byte(mc.cfg.Passwd), 0), nil | |||||
} | } | ||||
pubKey := mc.cfg.pubKey | pubKey := mc.cfg.pubKey | ||||
if pubKey == nil { | if pubKey == nil { | ||||
// request public key from server | // request public key from server | ||||
return []byte{1}, false, nil | |||||
return []byte{1}, nil | |||||
} | } | ||||
// encrypted password | // encrypted password | ||||
enc, err := encryptPassword(mc.cfg.Passwd, authData, pubKey) | enc, err := encryptPassword(mc.cfg.Passwd, authData, pubKey) | ||||
return enc, false, err | |||||
return enc, err | |||||
default: | default: | ||||
errLog.Print("unknown auth plugin:", plugin) | errLog.Print("unknown auth plugin:", plugin) | ||||
return nil, false, ErrUnknownPlugin | |||||
return nil, ErrUnknownPlugin | |||||
} | } | ||||
} | } | ||||
plugin = newPlugin | plugin = newPlugin | ||||
authResp, addNUL, err := mc.auth(authData, plugin) | |||||
authResp, err := mc.auth(authData, plugin) | |||||
if err != nil { | if err != nil { | ||||
return err | return err | ||||
} | } | ||||
if err = mc.writeAuthSwitchPacket(authResp, addNUL); err != nil { | |||||
if err = mc.writeAuthSwitchPacket(authResp); err != nil { | |||||
return err | return err | ||||
} | } | ||||
case cachingSha2PasswordPerformFullAuthentication: | case cachingSha2PasswordPerformFullAuthentication: | ||||
if mc.cfg.tls != nil || mc.cfg.Net == "unix" { | if mc.cfg.tls != nil || mc.cfg.Net == "unix" { | ||||
// write cleartext auth packet | // write cleartext auth packet | ||||
err = mc.writeAuthSwitchPacket([]byte(mc.cfg.Passwd), true) | |||||
err = mc.writeAuthSwitchPacket(append([]byte(mc.cfg.Passwd), 0)) | |||||
if err != nil { | if err != nil { | ||||
return err | return err | ||||
} | } | ||||
pubKey := mc.cfg.pubKey | pubKey := mc.cfg.pubKey | ||||
if pubKey == nil { | if pubKey == nil { | ||||
// request public key from server | // request public key from server | ||||
data := mc.buf.takeSmallBuffer(4 + 1) | |||||
data, err := mc.buf.takeSmallBuffer(4 + 1) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
data[4] = cachingSha2PasswordRequestPublicKey | data[4] = cachingSha2PasswordRequestPublicKey | ||||
mc.writePacket(data) | mc.writePacket(data) | ||||
// parse public key | // parse public key | ||||
data, err := mc.readPacket() | |||||
if err != nil { | |||||
if data, err = mc.readPacket(); err != nil { | |||||
return err | return err | ||||
} | } | ||||
// 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. | ||||
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 | ||||
} | } | ||||
// newBuffer allocates and returns a new buffer. | |||||
func newBuffer(nc net.Conn) buffer { | func newBuffer(nc net.Conn) buffer { | ||||
var b [defaultBufSize]byte | |||||
return buffer{ | return buffer{ | ||||
buf: b[:], | |||||
buf: make([]byte, defaultBufSize), | |||||
nc: nc, | nc: nc, | ||||
} | } | ||||
} | } | ||||
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) { | |||||
return b.buf[:length] | |||||
if length <= cap(b.buf) { | |||||
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 | |||||
// smaller than defaultBufSize | |||||
// takeSmallBuffer is shortcut which can be used if length is | |||||
// known to be smaller than defaultBufSize. | |||||
// Only one buffer (total) can be used at a time. | // 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 nil | |||||
return nil, ErrBusyBuffer | |||||
} | } | ||||
return b.buf[:length] | |||||
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 { | |||||
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 { | if b.length > 0 { | ||||
return nil | |||||
return ErrBusyBuffer | |||||
} else if cap(buf) <= maxPacketSize && cap(buf) > cap(b.buf) { | |||||
b.buf = buf[:cap(buf)] | |||||
} | } | ||||
return b.buf | |||||
return nil | |||||
} | } |
package mysql | package mysql | ||||
import ( | import ( | ||||
"context" | |||||
"database/sql" | |||||
"database/sql/driver" | "database/sql/driver" | ||||
"io" | "io" | ||||
"net" | "net" | ||||
"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 | ||||
// 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 | ||||
return "", driver.ErrSkip | return "", driver.ErrSkip | ||||
} | } | ||||
buf := mc.buf.takeCompleteBuffer() | |||||
if buf == nil { | |||||
buf, err := mc.buf.takeCompleteBuffer() | |||||
if err != nil { | |||||
// can not take the buffer. Something must be wrong with the connection | // can not take the buffer. Something must be wrong with the connection | ||||
errLog.Print(ErrBusyBuffer) | |||||
errLog.Print(err) | |||||
return "", ErrInvalidConn | return "", ErrInvalidConn | ||||
} | } | ||||
buf = buf[:0] | buf = buf[:0] | ||||
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 | |||||
} | |||||
return nil | |||||
} |
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package | |||||
// | |||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. | |||||
// | |||||
// This Source Code Form is subject to the terms of the Mozilla Public | |||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file, | |||||
// You can obtain one at http://mozilla.org/MPL/2.0/. | |||||
// +build go1.8 | |||||
package mysql | |||||
import ( | |||||
"context" | |||||
"database/sql" | |||||
"database/sql/driver" | |||||
) | |||||
// Ping implements driver.Pinger interface | |||||
func (mc *mysqlConn) Ping(ctx context.Context) (err error) { | |||||
if mc.closed.IsSet() { | |||||
errLog.Print(ErrInvalidConn) | |||||
return driver.ErrBadConn | |||||
} | |||||
if err = mc.watchCancel(ctx); err != nil { | |||||
return | |||||
} | |||||
defer mc.finish() | |||||
if err = mc.writeCommandPacket(comPing); err != nil { | |||||
return | |||||
} | |||||
return mc.readResultOK() | |||||
} | |||||
// BeginTx implements driver.ConnBeginTx interface | |||||
func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) { | |||||
if err := mc.watchCancel(ctx); err != nil { | |||||
return nil, err | |||||
} | |||||
defer mc.finish() | |||||
if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault { | |||||
level, err := mapIsolationLevel(opts.Isolation) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
err = mc.exec("SET TRANSACTION ISOLATION LEVEL " + level) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
} | |||||
return mc.begin(opts.ReadOnly) | |||||
} | |||||
func (mc *mysqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { | |||||
dargs, err := namedValueToValue(args) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
if err := mc.watchCancel(ctx); err != nil { | |||||
return nil, err | |||||
} | |||||
rows, err := mc.query(query, dargs) | |||||
if err != nil { | |||||
mc.finish() | |||||
return nil, err | |||||
} | |||||
rows.finish = mc.finish | |||||
return rows, err | |||||
} | |||||
func (mc *mysqlConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { | |||||
dargs, err := namedValueToValue(args) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
if err := mc.watchCancel(ctx); err != nil { | |||||
return nil, err | |||||
} | |||||
defer mc.finish() | |||||
return mc.Exec(query, dargs) | |||||
} | |||||
func (mc *mysqlConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) { | |||||
if err := mc.watchCancel(ctx); err != nil { | |||||
return nil, err | |||||
} | |||||
stmt, err := mc.Prepare(query) | |||||
mc.finish() | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
select { | |||||
default: | |||||
case <-ctx.Done(): | |||||
stmt.Close() | |||||
return nil, ctx.Err() | |||||
} | |||||
return stmt, nil | |||||
} | |||||
func (stmt *mysqlStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) { | |||||
dargs, err := namedValueToValue(args) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
if err := stmt.mc.watchCancel(ctx); err != nil { | |||||
return nil, err | |||||
} | |||||
rows, err := stmt.query(dargs) | |||||
if err != nil { | |||||
stmt.mc.finish() | |||||
return nil, err | |||||
} | |||||
rows.finish = stmt.mc.finish | |||||
return rows, err | |||||
} | |||||
func (stmt *mysqlStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) { | |||||
dargs, err := namedValueToValue(args) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
if err := stmt.mc.watchCancel(ctx); err != nil { | |||||
return nil, err | |||||
} | |||||
defer stmt.mc.finish() | |||||
return stmt.Exec(dargs) | |||||
} | |||||
func (mc *mysqlConn) watchCancel(ctx context.Context) error { | |||||
if mc.watching { | |||||
// Reach here if canceled, | |||||
// so the connection is already invalid | |||||
mc.cleanup() | |||||
return nil | |||||
} | |||||
if ctx.Done() == nil { | |||||
return nil | |||||
} | |||||
mc.watching = true | |||||
select { | |||||
default: | |||||
case <-ctx.Done(): | |||||
return ctx.Err() | |||||
} | |||||
if mc.watcher == nil { | |||||
return nil | |||||
} | |||||
mc.watcher <- ctx | |||||
return nil | |||||
} | |||||
func (mc *mysqlConn) startWatcher() { | |||||
watcher := make(chan mysqlContext, 1) | |||||
mc.watcher = watcher | |||||
finished := make(chan struct{}) | |||||
mc.finished = finished | |||||
go func() { | |||||
for { | |||||
var ctx mysqlContext | |||||
select { | |||||
case ctx = <-watcher: | |||||
case <-mc.closech: | |||||
return | |||||
} | |||||
select { | |||||
case <-ctx.Done(): | |||||
mc.cancel(ctx.Err()) | |||||
case <-finished: | |||||
case <-mc.closech: | |||||
return | |||||
} | |||||
} | |||||
}() | |||||
} | |||||
func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) { | |||||
nv.Value, err = converter{}.ConvertValue(nv.Value) | |||||
return | |||||
} | |||||
// ResetSession implements driver.SessionResetter. | |||||
// (From Go 1.10) | |||||
func (mc *mysqlConn) ResetSession(ctx context.Context) error { | |||||
if mc.closed.IsSet() { | |||||
return driver.ErrBadConn | |||||
} | |||||
return nil | |||||
} |
"sync" | "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{} | ||||
// Open new Connection. | // Open new Connection. | ||||
// See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how | // See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how | ||||
// the DSN string is formated | |||||
// the DSN string is formatted | |||||
func (d MySQLDriver) Open(dsn string) (driver.Conn, error) { | func (d MySQLDriver) Open(dsn string) (driver.Conn, error) { | ||||
var err error | var err error | ||||
mc.netConn, err = nd.Dial(mc.cfg.Net, mc.cfg.Addr) | mc.netConn, err = nd.Dial(mc.cfg.Net, mc.cfg.Addr) | ||||
} | } | ||||
if err != nil { | if err != nil { | ||||
if nerr, ok := err.(net.Error); ok && nerr.Temporary() { | |||||
errLog.Print("net.Error from Dial()': ", nerr.Error()) | |||||
return nil, driver.ErrBadConn | |||||
} | |||||
return nil, err | return nil, err | ||||
} | } | ||||
} | } | ||||
// Call startWatcher for context support (From Go 1.8) | // Call startWatcher for context support (From Go 1.8) | ||||
if s, ok := interface{}(mc).(watcher); ok { | |||||
s.startWatcher() | |||||
} | |||||
mc.startWatcher() | |||||
mc.buf = newBuffer(mc.netConn) | mc.buf = newBuffer(mc.netConn) | ||||
mc.cleanup() | mc.cleanup() | ||||
return nil, err | return nil, err | ||||
} | } | ||||
if plugin == "" { | |||||
plugin = defaultAuthPlugin | |||||
} | |||||
// Send Client Authentication Packet | // Send Client Authentication Packet | ||||
authResp, addNUL, err := mc.auth(authData, plugin) | |||||
authResp, err := mc.auth(authData, plugin) | |||||
if err != nil { | if err != nil { | ||||
// try the default auth plugin, if using the requested plugin failed | // try the default auth plugin, if using the requested plugin failed | ||||
errLog.Print("could not use requested auth plugin '"+plugin+"': ", err.Error()) | errLog.Print("could not use requested auth plugin '"+plugin+"': ", err.Error()) | ||||
plugin = defaultAuthPlugin | plugin = defaultAuthPlugin | ||||
authResp, addNUL, err = mc.auth(authData, plugin) | |||||
authResp, err = mc.auth(authData, plugin) | |||||
if err != nil { | if err != nil { | ||||
mc.cleanup() | mc.cleanup() | ||||
return nil, err | return nil, err | ||||
} | } | ||||
} | } | ||||
if err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin); err != nil { | |||||
if err = mc.writeHandshakeResponsePacket(authResp, plugin); err != nil { | |||||
mc.cleanup() | mc.cleanup() | ||||
return nil, err | return nil, err | ||||
} | } |
} 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} | cfg.tls = &tls.Config{InsecureSkipVerify: true} | ||||
} else { | } else { |
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 { | ||||
// 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) readHandshakePacket() ([]byte, string, error) { | |||||
data, err := mc.readPacket() | |||||
func (mc *mysqlConn) readHandshakePacket() (data []byte, plugin string, err error) { | |||||
data, err = mc.readPacket() | |||||
if err != nil { | if err != nil { | ||||
// for init we can rewrite this to ErrBadConn for sql.Driver to retry, since | // 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. | // in connection initialization we don't risk retrying non-idempotent actions. | ||||
if err == ErrInvalidConn { | if err == ErrInvalidConn { | ||||
return nil, "", driver.ErrBadConn | return nil, "", driver.ErrBadConn | ||||
} | } | ||||
return nil, "", err | |||||
return | |||||
} | } | ||||
if data[0] == iERR { | if data[0] == iERR { | ||||
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 | ||||
plugin := "" | |||||
if len(data) > pos { | if len(data) > pos { | ||||
// character set [1 byte] | // character set [1 byte] | ||||
// status flags [2 bytes] | // status flags [2 bytes] | ||||
return b[:], plugin, nil | return b[:], plugin, nil | ||||
} | } | ||||
plugin = defaultAuthPlugin | |||||
// 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[:], authData) | copy(b[:], authData) | ||||
// 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) writeHandshakeResponsePacket(authResp []byte, addNUL bool, plugin string) 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 | | ||||
// encode length of the auth plugin data | // encode length of the auth plugin data | ||||
var authRespLEIBuf [9]byte | var authRespLEIBuf [9]byte | ||||
authRespLEI := appendLengthEncodedInteger(authRespLEIBuf[:0], uint64(len(authResp))) | |||||
authRespLen := len(authResp) | |||||
authRespLEI := appendLengthEncodedInteger(authRespLEIBuf[:0], uint64(authRespLen)) | |||||
if len(authRespLEI) > 1 { | if len(authRespLEI) > 1 { | ||||
// if the length can not be written in 1 byte, it must be written as a | // if the length can not be written in 1 byte, it must be written as a | ||||
// length encoded integer | // length encoded integer | ||||
} | } | ||||
pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1 + len(authRespLEI) + len(authResp) + 21 + 1 | pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1 + len(authRespLEI) + len(authResp) + 21 + 1 | ||||
if addNUL { | |||||
pktLen++ | |||||
} | |||||
// 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 { | ||||
} | } | ||||
// Calculate packet length and get buffer with that size | // Calculate packet length and get buffer with that size | ||||
data := mc.buf.takeSmallBuffer(pktLen + 4) | |||||
if data == nil { | |||||
data, err := mc.buf.takeSmallBuffer(pktLen + 4) | |||||
if err != nil { | |||||
// cannot take the buffer. Something must be wrong with the connection | // cannot take the buffer. Something must be wrong with the connection | ||||
errLog.Print(ErrBusyBuffer) | |||||
errLog.Print(err) | |||||
return errBadConnNoWrite | return errBadConnNoWrite | ||||
} | } | ||||
// Auth Data [length encoded integer] | // Auth Data [length encoded integer] | ||||
pos += copy(data[pos:], authRespLEI) | pos += copy(data[pos:], authRespLEI) | ||||
pos += copy(data[pos:], authResp) | pos += copy(data[pos:], authResp) | ||||
if addNUL { | |||||
data[pos] = 0x00 | |||||
pos++ | |||||
} | |||||
// Databasename [null terminated string] | // Databasename [null terminated string] | ||||
if len(mc.cfg.DBName) > 0 { | if len(mc.cfg.DBName) > 0 { | ||||
pos += copy(data[pos:], plugin) | pos += copy(data[pos:], plugin) | ||||
data[pos] = 0x00 | data[pos] = 0x00 | ||||
pos++ | |||||
// Send Auth packet | // Send Auth packet | ||||
return mc.writePacket(data) | |||||
return mc.writePacket(data[:pos]) | |||||
} | } | ||||
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse | // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse | ||||
func (mc *mysqlConn) writeAuthSwitchPacket(authData []byte, addNUL bool) error { | |||||
func (mc *mysqlConn) writeAuthSwitchPacket(authData []byte) error { | |||||
pktLen := 4 + len(authData) | pktLen := 4 + len(authData) | ||||
if addNUL { | |||||
pktLen++ | |||||
} | |||||
data := mc.buf.takeSmallBuffer(pktLen) | |||||
if data == nil { | |||||
data, err := mc.buf.takeSmallBuffer(pktLen) | |||||
if err != nil { | |||||
// cannot take the buffer. Something must be wrong with the connection | // cannot take the buffer. Something must be wrong with the connection | ||||
errLog.Print(ErrBusyBuffer) | |||||
errLog.Print(err) | |||||
return errBadConnNoWrite | return errBadConnNoWrite | ||||
} | } | ||||
// Add the auth data [EOF] | // Add the auth data [EOF] | ||||
copy(data[4:], authData) | copy(data[4:], authData) | ||||
if addNUL { | |||||
data[pktLen-1] = 0x00 | |||||
} | |||||
return mc.writePacket(data) | return mc.writePacket(data) | ||||
} | } | ||||
// Reset Packet Sequence | // Reset Packet Sequence | ||||
mc.sequence = 0 | mc.sequence = 0 | ||||
data := mc.buf.takeSmallBuffer(4 + 1) | |||||
if data == nil { | |||||
data, err := mc.buf.takeSmallBuffer(4 + 1) | |||||
if err != nil { | |||||
// cannot take the buffer. Something must be wrong with the connection | // cannot take the buffer. Something must be wrong with the connection | ||||
errLog.Print(ErrBusyBuffer) | |||||
errLog.Print(err) | |||||
return errBadConnNoWrite | return errBadConnNoWrite | ||||
} | } | ||||
mc.sequence = 0 | mc.sequence = 0 | ||||
pktLen := 1 + len(arg) | pktLen := 1 + len(arg) | ||||
data := mc.buf.takeBuffer(pktLen + 4) | |||||
if data == nil { | |||||
data, err := mc.buf.takeBuffer(pktLen + 4) | |||||
if err != nil { | |||||
// cannot take the buffer. Something must be wrong with the connection | // cannot take the buffer. Something must be wrong with the connection | ||||
errLog.Print(ErrBusyBuffer) | |||||
errLog.Print(err) | |||||
return errBadConnNoWrite | return errBadConnNoWrite | ||||
} | } | ||||
// Reset Packet Sequence | // Reset Packet Sequence | ||||
mc.sequence = 0 | mc.sequence = 0 | ||||
data := mc.buf.takeSmallBuffer(4 + 1 + 4) | |||||
if data == nil { | |||||
data, err := mc.buf.takeSmallBuffer(4 + 1 + 4) | |||||
if err != nil { | |||||
// cannot take the buffer. Something must be wrong with the connection | // cannot take the buffer. Something must be wrong with the connection | ||||
errLog.Print(ErrBusyBuffer) | |||||
errLog.Print(err) | |||||
return errBadConnNoWrite | return errBadConnNoWrite | ||||
} | } | ||||
return data[1:], "", err | return data[1:], "", err | ||||
case iEOF: | case iEOF: | ||||
if len(data) < 1 { | |||||
if len(data) == 1 { | |||||
// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest | // https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest | ||||
return nil, "mysql_old_password", nil | return nil, "mysql_old_password", nil | ||||
} | } | ||||
const minPktLen = 4 + 1 + 4 + 1 + 4 | const minPktLen = 4 + 1 + 4 + 1 + 4 | ||||
mc := stmt.mc | mc := stmt.mc | ||||
// Determine threshould dynamically to avoid packet size shortage. | |||||
// Determine threshold dynamically to avoid packet size shortage. | |||||
longDataSize := mc.maxAllowedPacket / (stmt.paramCount + 1) | longDataSize := mc.maxAllowedPacket / (stmt.paramCount + 1) | ||||
if longDataSize < 64 { | if longDataSize < 64 { | ||||
longDataSize = 64 | longDataSize = 64 | ||||
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 { | |||||
// cannot 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 errBadConnNoWrite | return errBadConnNoWrite | ||||
} | } | ||||
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 | ||||
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 | ||||
// 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) | ||||
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: | ||||
) | ) | ||||
} | } | ||||
} | } | ||||
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 { |
import ( | import ( | ||||
"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" | ||||
func getTLSConfigClone(key string) (config *tls.Config) { | func getTLSConfigClone(key string) (config *tls.Config) { | ||||
tlsConfigLock.RLock() | tlsConfigLock.RLock() | ||||
if v, ok := tlsConfigRegistry[key]; ok { | if v, ok := tlsConfigRegistry[key]; ok { | ||||
config = cloneTLSConfig(v) | |||||
config = v.Clone() | |||||
} | } | ||||
tlsConfigLock.RUnlock() | tlsConfigLock.RUnlock() | ||||
return | return | ||||
const digits01 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" | const digits01 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" | ||||
const digits10 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999" | const digits10 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999" | ||||
func formatBinaryDateTime(src []byte, length uint8, justTime bool) (driver.Value, error) { | |||||
// length expects the deterministic length of the zero value, | |||||
// negative time and 100+ hours are automatically added if needed | |||||
if len(src) == 0 { | |||||
if justTime { | |||||
return zeroDateTime[11 : 11+length], nil | |||||
} | |||||
return zeroDateTime[:length], nil | |||||
func appendMicrosecs(dst, src []byte, decimals int) []byte { | |||||
if decimals <= 0 { | |||||
return dst | |||||
} | } | ||||
var dst []byte // return value | |||||
var pt, p1, p2, p3 byte // current digit pair | |||||
var zOffs byte // offset of value in zeroDateTime | |||||
if justTime { | |||||
switch length { | |||||
case | |||||
8, // time (can be up to 10 when negative and 100+ hours) | |||||
10, 11, 12, 13, 14, 15: // time with fractional seconds | |||||
default: | |||||
return nil, fmt.Errorf("illegal TIME length %d", length) | |||||
} | |||||
switch len(src) { | |||||
case 8, 12: | |||||
default: | |||||
return nil, fmt.Errorf("invalid TIME packet length %d", len(src)) | |||||
} | |||||
// +2 to enable negative time and 100+ hours | |||||
dst = make([]byte, 0, length+2) | |||||
if src[0] == 1 { | |||||
dst = append(dst, '-') | |||||
} | |||||
if src[1] != 0 { | |||||
hour := uint16(src[1])*24 + uint16(src[5]) | |||||
pt = byte(hour / 100) | |||||
p1 = byte(hour - 100*uint16(pt)) | |||||
dst = append(dst, digits01[pt]) | |||||
} else { | |||||
p1 = src[5] | |||||
} | |||||
zOffs = 11 | |||||
src = src[6:] | |||||
} else { | |||||
switch length { | |||||
case 10, 19, 21, 22, 23, 24, 25, 26: | |||||
default: | |||||
t := "DATE" | |||||
if length > 10 { | |||||
t += "TIME" | |||||
} | |||||
return nil, fmt.Errorf("illegal %s length %d", t, length) | |||||
} | |||||
switch len(src) { | |||||
case 4, 7, 11: | |||||
default: | |||||
t := "DATE" | |||||
if length > 10 { | |||||
t += "TIME" | |||||
} | |||||
return nil, fmt.Errorf("illegal %s packet length %d", t, len(src)) | |||||
} | |||||
dst = make([]byte, 0, length) | |||||
// start with the date | |||||
year := binary.LittleEndian.Uint16(src[:2]) | |||||
pt = byte(year / 100) | |||||
p1 = byte(year - 100*uint16(pt)) | |||||
p2, p3 = src[2], src[3] | |||||
dst = append(dst, | |||||
digits10[pt], digits01[pt], | |||||
digits10[p1], digits01[p1], '-', | |||||
digits10[p2], digits01[p2], '-', | |||||
digits10[p3], digits01[p3], | |||||
) | |||||
if length == 10 { | |||||
return dst, nil | |||||
} | |||||
if len(src) == 4 { | |||||
return append(dst, zeroDateTime[10:length]...), nil | |||||
} | |||||
dst = append(dst, ' ') | |||||
p1 = src[4] // hour | |||||
src = src[5:] | |||||
} | |||||
// p1 is 2-digit hour, src is after hour | |||||
p2, p3 = src[0], src[1] | |||||
dst = append(dst, | |||||
digits10[p1], digits01[p1], ':', | |||||
digits10[p2], digits01[p2], ':', | |||||
digits10[p3], digits01[p3], | |||||
) | |||||
if length <= byte(len(dst)) { | |||||
return dst, nil | |||||
} | |||||
src = src[2:] | |||||
if len(src) == 0 { | if len(src) == 0 { | ||||
return append(dst, zeroDateTime[19:zOffs+length]...), nil | |||||
return append(dst, ".000000"[:decimals+1]...) | |||||
} | } | ||||
microsecs := binary.LittleEndian.Uint32(src[:4]) | microsecs := binary.LittleEndian.Uint32(src[:4]) | ||||
p1 = byte(microsecs / 10000) | |||||
p1 := byte(microsecs / 10000) | |||||
microsecs -= 10000 * uint32(p1) | microsecs -= 10000 * uint32(p1) | ||||
p2 = byte(microsecs / 100) | |||||
p2 := byte(microsecs / 100) | |||||
microsecs -= 100 * uint32(p2) | microsecs -= 100 * uint32(p2) | ||||
p3 = byte(microsecs) | |||||
switch decimals := zOffs + length - 20; decimals { | |||||
p3 := byte(microsecs) | |||||
switch decimals { | |||||
default: | default: | ||||
return append(dst, '.', | return append(dst, '.', | ||||
digits10[p1], digits01[p1], | digits10[p1], digits01[p1], | ||||
digits10[p2], digits01[p2], | digits10[p2], digits01[p2], | ||||
digits10[p3], digits01[p3], | digits10[p3], digits01[p3], | ||||
), nil | |||||
) | |||||
case 1: | case 1: | ||||
return append(dst, '.', | return append(dst, '.', | ||||
digits10[p1], | digits10[p1], | ||||
), nil | |||||
) | |||||
case 2: | case 2: | ||||
return append(dst, '.', | return append(dst, '.', | ||||
digits10[p1], digits01[p1], | digits10[p1], digits01[p1], | ||||
), nil | |||||
) | |||||
case 3: | case 3: | ||||
return append(dst, '.', | return append(dst, '.', | ||||
digits10[p1], digits01[p1], | digits10[p1], digits01[p1], | ||||
digits10[p2], | digits10[p2], | ||||
), nil | |||||
) | |||||
case 4: | case 4: | ||||
return append(dst, '.', | return append(dst, '.', | ||||
digits10[p1], digits01[p1], | digits10[p1], digits01[p1], | ||||
digits10[p2], digits01[p2], | digits10[p2], digits01[p2], | ||||
), nil | |||||
) | |||||
case 5: | case 5: | ||||
return append(dst, '.', | return append(dst, '.', | ||||
digits10[p1], digits01[p1], | digits10[p1], digits01[p1], | ||||
digits10[p2], digits01[p2], | digits10[p2], digits01[p2], | ||||
digits10[p3], | digits10[p3], | ||||
), nil | |||||
) | |||||
} | } | ||||
} | } | ||||
func formatBinaryDateTime(src []byte, length uint8) (driver.Value, error) { | |||||
// length expects the deterministic length of the zero value, | |||||
// negative time and 100+ hours are automatically added if needed | |||||
if len(src) == 0 { | |||||
return zeroDateTime[:length], nil | |||||
} | |||||
var dst []byte // return value | |||||
var p1, p2, p3 byte // current digit pair | |||||
switch length { | |||||
case 10, 19, 21, 22, 23, 24, 25, 26: | |||||
default: | |||||
t := "DATE" | |||||
if length > 10 { | |||||
t += "TIME" | |||||
} | |||||
return nil, fmt.Errorf("illegal %s length %d", t, length) | |||||
} | |||||
switch len(src) { | |||||
case 4, 7, 11: | |||||
default: | |||||
t := "DATE" | |||||
if length > 10 { | |||||
t += "TIME" | |||||
} | |||||
return nil, fmt.Errorf("illegal %s packet length %d", t, len(src)) | |||||
} | |||||
dst = make([]byte, 0, length) | |||||
// start with the date | |||||
year := binary.LittleEndian.Uint16(src[:2]) | |||||
pt := year / 100 | |||||
p1 = byte(year - 100*uint16(pt)) | |||||
p2, p3 = src[2], src[3] | |||||
dst = append(dst, | |||||
digits10[pt], digits01[pt], | |||||
digits10[p1], digits01[p1], '-', | |||||
digits10[p2], digits01[p2], '-', | |||||
digits10[p3], digits01[p3], | |||||
) | |||||
if length == 10 { | |||||
return dst, nil | |||||
} | |||||
if len(src) == 4 { | |||||
return append(dst, zeroDateTime[10:length]...), nil | |||||
} | |||||
dst = append(dst, ' ') | |||||
p1 = src[4] // hour | |||||
src = src[5:] | |||||
// p1 is 2-digit hour, src is after hour | |||||
p2, p3 = src[0], src[1] | |||||
dst = append(dst, | |||||
digits10[p1], digits01[p1], ':', | |||||
digits10[p2], digits01[p2], ':', | |||||
digits10[p3], digits01[p3], | |||||
) | |||||
return appendMicrosecs(dst, src[2:], int(length)-20), nil | |||||
} | |||||
func formatBinaryTime(src []byte, length uint8) (driver.Value, error) { | |||||
// length expects the deterministic length of the zero value, | |||||
// negative time and 100+ hours are automatically added if needed | |||||
if len(src) == 0 { | |||||
return zeroDateTime[11 : 11+length], nil | |||||
} | |||||
var dst []byte // return value | |||||
switch length { | |||||
case | |||||
8, // time (can be up to 10 when negative and 100+ hours) | |||||
10, 11, 12, 13, 14, 15: // time with fractional seconds | |||||
default: | |||||
return nil, fmt.Errorf("illegal TIME length %d", length) | |||||
} | |||||
switch len(src) { | |||||
case 8, 12: | |||||
default: | |||||
return nil, fmt.Errorf("invalid TIME packet length %d", len(src)) | |||||
} | |||||
// +2 to enable negative time and 100+ hours | |||||
dst = make([]byte, 0, length+2) | |||||
if src[0] == 1 { | |||||
dst = append(dst, '-') | |||||
} | |||||
days := binary.LittleEndian.Uint32(src[1:5]) | |||||
hours := int64(days)*24 + int64(src[5]) | |||||
if hours >= 100 { | |||||
dst = strconv.AppendInt(dst, hours, 10) | |||||
} else { | |||||
dst = append(dst, digits10[hours], digits01[hours]) | |||||
} | |||||
min, sec := src[6], src[7] | |||||
dst = append(dst, ':', | |||||
digits10[min], digits01[min], ':', | |||||
digits10[sec], digits01[sec], | |||||
) | |||||
return appendMicrosecs(dst, src[8:], int(length)-9), nil | |||||
} | |||||
/****************************************************************************** | /****************************************************************************** | ||||
* Convert from and to bytes * | * Convert from and to bytes * | ||||
******************************************************************************/ | ******************************************************************************/ | ||||
} | } | ||||
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) | |||||
} | |||||
} |
// 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, | |||||
} | |||||
} |
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package | |||||
// | |||||
// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved. | |||||
// | |||||
// This Source Code Form is subject to the terms of the Mozilla Public | |||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file, | |||||
// You can obtain one at http://mozilla.org/MPL/2.0/. | |||||
// +build go1.8 | |||||
package mysql | |||||
import ( | |||||
"crypto/tls" | |||||
"database/sql" | |||||
"database/sql/driver" | |||||
"errors" | |||||
"fmt" | |||||
) | |||||
func cloneTLSConfig(c *tls.Config) *tls.Config { | |||||
return c.Clone() | |||||
} | |||||
func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) { | |||||
dargs := make([]driver.Value, len(named)) | |||||
for n, param := range named { | |||||
if len(param.Name) > 0 { | |||||
// TODO: support the use of Named Parameters #561 | |||||
return nil, errors.New("mysql: driver does not support the use of Named Parameters") | |||||
} | |||||
dargs[n] = param.Value | |||||
} | |||||
return dargs, nil | |||||
} | |||||
func mapIsolationLevel(level driver.IsolationLevel) (string, error) { | |||||
switch sql.IsolationLevel(level) { | |||||
case sql.LevelRepeatableRead: | |||||
return "REPEATABLE READ", nil | |||||
case sql.LevelReadCommitted: | |||||
return "READ COMMITTED", nil | |||||
case sql.LevelReadUncommitted: | |||||
return "READ UNCOMMITTED", nil | |||||
case sql.LevelSerializable: | |||||
return "SERIALIZABLE", nil | |||||
default: | |||||
return "", fmt.Errorf("mysql: unsupported isolation level: %v", level) | |||||
} | |||||
} |