// 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/.package mysqlimport ()// Registry for custom tls.Configsvar ( tlsConfigLock sync.RWMutex tlsConfigRegistry map[string]*tls.Config)// RegisterTLSConfig registers a custom tls.Config to be used with sql.Open.// Use the key as a value in the DSN where tls=value.//// Note: The provided tls.Config is exclusively owned by the driver after// registering it.//// rootCertPool := x509.NewCertPool()// pem, err := ioutil.ReadFile("/path/ca-cert.pem")// if err != nil {// log.Fatal(err)// }// if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {// log.Fatal("Failed to append PEM.")// }// clientCert := make([]tls.Certificate, 0, 1)// certs, err := tls.LoadX509KeyPair("/path/client-cert.pem", "/path/client-key.pem")// if err != nil {// log.Fatal(err)// }// clientCert = append(clientCert, certs)// mysql.RegisterTLSConfig("custom", &tls.Config{// RootCAs: rootCertPool,// Certificates: clientCert,// })// db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom")func ( string, *tls.Config) error {if , := readBool(); || strings.ToLower() == "skip-verify" || strings.ToLower() == "preferred" {returnfmt.Errorf("key '%s' is reserved", ) }tlsConfigLock.Lock()iftlsConfigRegistry == nil {tlsConfigRegistry = make(map[string]*tls.Config) }tlsConfigRegistry[] = tlsConfigLock.Unlock()returnnil}// DeregisterTLSConfig removes the tls.Config associated with key.func ( string) {tlsConfigLock.Lock()iftlsConfigRegistry != nil {delete(tlsConfigRegistry, ) }tlsConfigLock.Unlock()}func getTLSConfigClone( string) ( *tls.Config) {tlsConfigLock.RLock()if , := tlsConfigRegistry[]; { = .Clone() }tlsConfigLock.RUnlock()return}// Returns the bool value of the input.// The 2nd return value indicates if the input was a valid bool valuefunc readBool( string) ( bool, bool) {switch {case"1", "true", "TRUE", "True":returntrue, truecase"0", "false", "FALSE", "False":returnfalse, true }// Not a valid bool valuereturn}/******************************************************************************* Time related utils *******************************************************************************/func parseDateTime( []byte, *time.Location) (time.Time, error) {const = "0000-00-00 00:00:00.000000"switchlen() {case10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM"ifstring() == [:len()] {returntime.Time{}, nil } , := parseByteYear()if != nil {returntime.Time{}, }if [4] != '-' {returntime.Time{}, fmt.Errorf("bad value for field: `%c`", [4]) } , := parseByte2Digits([5], [6])if != nil {returntime.Time{}, } := time.Month()if [7] != '-' {returntime.Time{}, fmt.Errorf("bad value for field: `%c`", [7]) } , := parseByte2Digits([8], [9])if != nil {returntime.Time{}, }iflen() == 10 {returntime.Date(, , , 0, 0, 0, 0, ), nil }if [10] != ' ' {returntime.Time{}, fmt.Errorf("bad value for field: `%c`", [10]) } , := parseByte2Digits([11], [12])if != nil {returntime.Time{}, }if [13] != ':' {returntime.Time{}, fmt.Errorf("bad value for field: `%c`", [13]) } , := parseByte2Digits([14], [15])if != nil {returntime.Time{}, }if [16] != ':' {returntime.Time{}, fmt.Errorf("bad value for field: `%c`", [16]) } , := parseByte2Digits([17], [18])if != nil {returntime.Time{}, }iflen() == 19 {returntime.Date(, , , , , , 0, ), nil }if [19] != '.' {returntime.Time{}, fmt.Errorf("bad value for field: `%c`", [19]) } , := parseByteNanoSec([20:])if != nil {returntime.Time{}, }returntime.Date(, , , , , , , ), nildefault:returntime.Time{}, fmt.Errorf("invalid time bytes: %s", ) }}func parseByteYear( []byte) (int, error) { , := 0, 1000for := 0; < 4; ++ { , := bToi([])if != nil {return0, } += * /= 10 }return , nil}func parseByte2Digits(, byte) (int, error) { , := bToi()if != nil {return0, } , := bToi()if != nil {return0, }return *10 + , nil}func parseByteNanoSec( []byte) (int, error) { , := 0, 100000// max is 6-digitsfor := 0; < len(); ++ { , := bToi([])if != nil {return0, } += * /= 10 }// nanoseconds has 10-digits. (needs to scale digits) // 10 - 6 = 4, so we have to multiple 1000.return * 1000, nil}func bToi( byte) (int, error) {if < '0' || > '9' {return0, errors.New("not [0-9]") }returnint( - '0'), nil}func parseBinaryDateTime( uint64, []byte, *time.Location) (driver.Value, error) {switch {case0:returntime.Time{}, nilcase4:returntime.Date(int(binary.LittleEndian.Uint16([:2])), // yeartime.Month([2]), // monthint([3]), // day0, 0, 0, 0, , ), nilcase7:returntime.Date(int(binary.LittleEndian.Uint16([:2])), // yeartime.Month([2]), // monthint([3]), // dayint([4]), // hourint([5]), // minutesint([6]), // seconds0, , ), nilcase11:returntime.Date(int(binary.LittleEndian.Uint16([:2])), // yeartime.Month([2]), // monthint([3]), // dayint([4]), // hourint([5]), // minutesint([6]), // secondsint(binary.LittleEndian.Uint32([7:11]))*1000, // nanoseconds , ), nil }returnnil, fmt.Errorf("invalid DATETIME packet length %d", )}func appendDateTime( []byte, time.Time) ([]byte, error) { , , := .Date() , , := .Clock() := .Nanosecond()if < 1 || > 9999 {return , errors.New("year is not in the range [1, 9999]: " + strconv.Itoa()) // use errors.New instead of fmt.Errorf to avoid year escape to heap } := / 100 := % 100var [len("2006-01-02T15:04:05.999999999")]byte// does not escape [0], [1], [2], [3] = digits10[], digits01[], digits10[], digits01[] [4] = '-' [5], [6] = digits10[], digits01[] [7] = '-' [8], [9] = digits10[], digits01[]if == 0 && == 0 && == 0 && == 0 {returnappend(, [:10]...), nil } [10] = ' ' [11], [12] = digits10[], digits01[] [13] = ':' [14], [15] = digits10[], digits01[] [16] = ':' [17], [18] = digits10[], digits01[]if == 0 {returnappend(, [:19]...), nil } := / 100000000 := ( / 1000000) % 100 := ( / 10000) % 100 := ( / 100) % 100 := % 100 [19] = '.'// milli second [20], [21], [22] =digits01[], digits10[], digits01[]// micro second [23], [24], [25] =digits10[], digits01[], digits10[]// nano second [26], [27], [28] =digits01[], digits10[], digits01[]// trim trailing zeros := len()for > 0 && [-1] == '0' { -- }returnappend(, [:]...), nil}// zeroDateTime is used in formatBinaryDateTime to avoid an allocation// if the DATE or DATETIME has the zero value.// It must never be changed.// The current behavior depends on database/sql copying the result.var zeroDateTime = []byte("0000-00-00 00:00:00.000000")const digits01 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"const digits10 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999"func appendMicrosecs(, []byte, int) []byte {if <= 0 {return }iflen() == 0 {returnappend(, ".000000"[:+1]...) } := binary.LittleEndian.Uint32([:4]) := byte( / 10000) -= 10000 * uint32() := byte( / 100) -= 100 * uint32() := byte()switch {default:returnappend(, '.',digits10[], digits01[],digits10[], digits01[],digits10[], digits01[], )case1:returnappend(, '.',digits10[], )case2:returnappend(, '.',digits10[], digits01[], )case3:returnappend(, '.',digits10[], digits01[],digits10[], )case4:returnappend(, '.',digits10[], digits01[],digits10[], digits01[], )case5:returnappend(, '.',digits10[], digits01[],digits10[], digits01[],digits10[], ) }}func formatBinaryDateTime( []byte, uint8) (driver.Value, error) {// length expects the deterministic length of the zero value, // negative time and 100+ hours are automatically added if needediflen() == 0 {returnzeroDateTime[:], nil }var []byte// return valuevar , , byte// current digit pairswitch {case10, 19, 21, 22, 23, 24, 25, 26:default: := "DATE"if > 10 { += "TIME" }returnnil, fmt.Errorf("illegal %s length %d", , ) }switchlen() {case4, 7, 11:default: := "DATE"if > 10 { += "TIME" }returnnil, fmt.Errorf("illegal %s packet length %d", , len()) } = make([]byte, 0, )// start with the date := binary.LittleEndian.Uint16([:2]) := / 100 = byte( - 100*uint16()) , = [2], [3] = append(,digits10[], digits01[],digits10[], digits01[], '-',digits10[], digits01[], '-',digits10[], digits01[], )if == 10 {return , nil }iflen() == 4 {returnappend(, zeroDateTime[10:]...), nil } = append(, ' ') = [4] // hour = [5:]// p1 is 2-digit hour, src is after hour , = [0], [1] = append(,digits10[], digits01[], ':',digits10[], digits01[], ':',digits10[], digits01[], )returnappendMicrosecs(, [2:], int()-20), nil}func formatBinaryTime( []byte, uint8) (driver.Value, error) {// length expects the deterministic length of the zero value, // negative time and 100+ hours are automatically added if needediflen() == 0 {returnzeroDateTime[11 : 11+], nil }var []byte// return valueswitch {case8, // time (can be up to 10 when negative and 100+ hours)10, 11, 12, 13, 14, 15: // time with fractional secondsdefault:returnnil, fmt.Errorf("illegal TIME length %d", ) }switchlen() {case8, 12:default:returnnil, fmt.Errorf("invalid TIME packet length %d", len()) }// +2 to enable negative time and 100+ hours = make([]byte, 0, +2)if [0] == 1 { = append(, '-') } := binary.LittleEndian.Uint32([1:5]) := int64()*24 + int64([5])if >= 100 { = strconv.AppendInt(, , 10) } else { = append(, digits10[], digits01[]) } , := [6], [7] = append(, ':',digits10[], digits01[], ':',digits10[], digits01[], )returnappendMicrosecs(, [8:], int()-9), nil}/******************************************************************************* Convert from and to bytes *******************************************************************************/func uint64ToBytes( uint64) []byte {return []byte{byte(),byte( >> 8),byte( >> 16),byte( >> 24),byte( >> 32),byte( >> 40),byte( >> 48),byte( >> 56), }}func uint64ToString( uint64) []byte {var [20]byte := 20// U+0030 = 0 // ... // U+0039 = 9varuint64for >= 10 { -- = / 10 [] = uint8(-*10) + 0x30 = } -- [] = uint8() + 0x30return [:]}// treats string value as unsigned integer representationfunc stringToInt( []byte) int { := 0for := range { *= 10 += int([] - 0x30) }return}// returns the string read as a bytes slice, whether the value is NULL,// the number of bytes read and an error, in case the string is longer than// the input slicefunc readLengthEncodedString( []byte) ([]byte, bool, int, error) {// Get length , , := readLengthEncodedInteger()if < 1 {return [:], , , nil } += int()// Check data lengthiflen() >= {return [-int() : : ], false, , nil }returnnil, false, , io.EOF}// returns the number of bytes skipped and an error, in case the string is// longer than the input slicefunc skipLengthEncodedString( []byte) (int, error) {// Get length , , := readLengthEncodedInteger()if < 1 {return , nil } += int()// Check data lengthiflen() >= {return , nil }return , io.EOF}// returns the number read, whether the value is NULL and the number of bytes readfunc readLengthEncodedInteger( []byte) (uint64, bool, int) {// See issue #349iflen() == 0 {return0, true, 1 }switch [0] {// 251: NULLcase0xfb:return0, true, 1// 252: value of following 2case0xfc:returnuint64([1]) | uint64([2])<<8, false, 3// 253: value of following 3case0xfd:returnuint64([1]) | uint64([2])<<8 | uint64([3])<<16, false, 4// 254: value of following 8case0xfe:returnuint64([1]) | uint64([2])<<8 | uint64([3])<<16 |uint64([4])<<24 | uint64([5])<<32 | uint64([6])<<40 |uint64([7])<<48 | uint64([8])<<56,false, 9 }// 0-250: value of first bytereturnuint64([0]), false, 1}// encodes a uint64 value and appends it to the given bytes slicefunc appendLengthEncodedInteger( []byte, uint64) []byte {switch {case <= 250:returnappend(, byte())case <= 0xffff:returnappend(, 0xfc, byte(), byte(>>8))case <= 0xffffff:returnappend(, 0xfd, byte(), byte(>>8), byte(>>16)) }returnappend(, 0xfe, byte(), byte(>>8), byte(>>16), byte(>>24),byte(>>32), byte(>>40), byte(>>48), byte(>>56))}// reserveBuffer checks cap(buf) and expand buffer to len(buf) + appendSize.// If cap(buf) is not enough, reallocate new buffer.func reserveBuffer( []byte, int) []byte { := len() + ifcap() < {// Grow buffer exponentially := make([]byte, len()*2+)copy(, ) = }return [:]}// escapeBytesBackslash escapes []byte with backslashes (\)// This escapes the contents of a string (provided as []byte) by adding backslashes before special// characters, and turning others into specific escape sequences, such as// turning newlines into \n and null bytes into \0.// https://github.com/mysql/mysql-server/blob/mysql-5.7.5/mysys/charset.c#L823-L932func escapeBytesBackslash(, []byte) []byte { := len() = reserveBuffer(, len()*2)for , := range {switch {case'\x00': [+1] = '0' [] = '\\' += 2case'\n': [+1] = 'n' [] = '\\' += 2case'\r': [+1] = 'r' [] = '\\' += 2case'\x1a': [+1] = 'Z' [] = '\\' += 2case'\'': [+1] = '\'' [] = '\\' += 2case'"': [+1] = '"' [] = '\\' += 2case'\\': [+1] = '\\' [] = '\\' += 2default: [] = ++ } }return [:]}// escapeStringBackslash is similar to escapeBytesBackslash but for string.func escapeStringBackslash( []byte, string) []byte { := len() = reserveBuffer(, len()*2)for := 0; < len(); ++ { := []switch {case'\x00': [+1] = '0' [] = '\\' += 2case'\n': [+1] = 'n' [] = '\\' += 2case'\r': [+1] = 'r' [] = '\\' += 2case'\x1a': [+1] = 'Z' [] = '\\' += 2case'\'': [+1] = '\'' [] = '\\' += 2case'"': [+1] = '"' [] = '\\' += 2case'\\': [+1] = '\\' [] = '\\' += 2default: [] = ++ } }return [:]}// escapeBytesQuotes escapes apostrophes in []byte by doubling them up.// This escapes the contents of a string by doubling up any apostrophes that// it contains. This is used when the NO_BACKSLASH_ESCAPES SQL_MODE is in// effect on the server.// https://github.com/mysql/mysql-server/blob/mysql-5.7.5/mysys/charset.c#L963-L1038func escapeBytesQuotes(, []byte) []byte { := len() = reserveBuffer(, len()*2)for , := range {if == '\'' { [+1] = '\'' [] = '\'' += 2 } else { [] = ++ } }return [:]}// escapeStringQuotes is similar to escapeBytesQuotes but for string.func escapeStringQuotes( []byte, string) []byte { := len() = reserveBuffer(, len()*2)for := 0; < len(); ++ { := []if == '\'' { [+1] = '\'' [] = '\'' += 2 } else { [] = ++ } }return [:]}/******************************************************************************* Sync utils *******************************************************************************/// noCopy may be embedded into structs which must not be copied// after the first use.//// See https://github.com/golang/go/issues/8005#issuecomment-190753527// for details.type noCopy struct{}// Lock is a no-op used by -copylocks checker from `go vet`.func (*noCopy) () {}// Unlock is a no-op used by -copylocks checker from `go vet`.// noCopy should implement sync.Locker from Go 1.11// https://github.com/golang/go/commit/c2eba53e7f80df21d51285879d51ab81bcfbf6bc// https://github.com/golang/go/issues/26165func (*noCopy) () {}// atomicError is a wrapper for atomically accessed error valuestype atomicError struct { _ noCopy value atomic.Value}// Set sets the error value regardless of the previous value.// The value must not be nilfunc ( *atomicError) ( error) { .value.Store()}// Value returns the current error valuefunc ( *atomicError) () error {if := .value.Load(); != nil {// this will panic if the value doesn't implement the error interfacereturn .(error) }returnnil}func namedValueToValue( []driver.NamedValue) ([]driver.Value, error) { := make([]driver.Value, len())for , := range {iflen(.Name) > 0 {// TODO: support the use of Named Parameters #561returnnil, errors.New("mysql: driver does not support the use of Named Parameters") } [] = .Value }return , nil}func mapIsolationLevel( driver.IsolationLevel) (string, error) {switchsql.IsolationLevel() {casesql.LevelRepeatableRead:return"REPEATABLE READ", nilcasesql.LevelReadCommitted:return"READ COMMITTED", nilcasesql.LevelReadUncommitted:return"READ UNCOMMITTED", nilcasesql.LevelSerializable:return"SERIALIZABLE", nildefault:return"", fmt.Errorf("mysql: unsupported isolation level: %v", ) }}
The pages are generated with Goldsv0.6.7. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds.