|  | // 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 mysql provides a MySQL driver for Go's database/sql package. | 
|  | // | 
|  | // The driver should be used via the database/sql package: | 
|  | // | 
|  | //  import "database/sql" | 
|  | //  import _ "github.com/go-sql-driver/mysql" | 
|  | // | 
|  | //  db, err := sql.Open("mysql", "user:password@/dbname") | 
|  | // | 
|  | // See https://github.com/go-sql-driver/mysql#usage for details | 
|  | package mysql | 
|  |  | 
|  | import ( | 
|  | "database/sql" | 
|  | "database/sql/driver" | 
|  | "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. | 
|  | // In general the driver is used via the database/sql package. | 
|  | type MySQLDriver struct{} | 
|  |  | 
|  | // DialFunc is a function which can be used to establish the network connection. | 
|  | // Custom dial functions must be registered with RegisterDial | 
|  | type DialFunc func(addr string) (net.Conn, error) | 
|  |  | 
|  | var ( | 
|  | dialsLock sync.RWMutex | 
|  | dials     map[string]DialFunc | 
|  | ) | 
|  |  | 
|  | // RegisterDial registers a custom dial function. It can then be used by the | 
|  | // network address mynet(addr), where mynet is the registered new network. | 
|  | // addr is passed as a parameter to the dial function. | 
|  | func RegisterDial(net string, dial DialFunc) { | 
|  | dialsLock.Lock() | 
|  | defer dialsLock.Unlock() | 
|  | if dials == nil { | 
|  | dials = make(map[string]DialFunc) | 
|  | } | 
|  | dials[net] = dial | 
|  | } | 
|  |  | 
|  | // Open new Connection. | 
|  | // See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how | 
|  | // the DSN string is formated | 
|  | func (d MySQLDriver) Open(dsn string) (driver.Conn, error) { | 
|  | var err error | 
|  |  | 
|  | // New mysqlConn | 
|  | mc := &mysqlConn{ | 
|  | maxAllowedPacket: maxPacketSize, | 
|  | maxWriteSize:     maxPacketSize - 1, | 
|  | closech:          make(chan struct{}), | 
|  | } | 
|  | mc.cfg, err = ParseDSN(dsn) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | mc.parseTime = mc.cfg.ParseTime | 
|  |  | 
|  | // Connect to Server | 
|  | dialsLock.RLock() | 
|  | dial, ok := dials[mc.cfg.Net] | 
|  | dialsLock.RUnlock() | 
|  | if 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 | 
|  | 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 | 
|  | } | 
|  |  | 
|  | func init() { | 
|  | sql.Register("mysql", &MySQLDriver{}) | 
|  | } |