However, the enum tree is not supported since they do not support them. But other than that, mysql and maria DB seem to both be supported.
198 lines
5.3 KiB
Go
198 lines
5.3 KiB
Go
// 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"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type connector struct {
|
|
cfg *Config // immutable private copy.
|
|
encodedAttributes string // Encoded connection attributes.
|
|
}
|
|
|
|
func encodeConnectionAttributes(cfg *Config) string {
|
|
connAttrsBuf := make([]byte, 0)
|
|
|
|
// default connection attributes
|
|
connAttrsBuf = appendLengthEncodedString(connAttrsBuf, connAttrClientName)
|
|
connAttrsBuf = appendLengthEncodedString(connAttrsBuf, connAttrClientNameValue)
|
|
connAttrsBuf = appendLengthEncodedString(connAttrsBuf, connAttrOS)
|
|
connAttrsBuf = appendLengthEncodedString(connAttrsBuf, connAttrOSValue)
|
|
connAttrsBuf = appendLengthEncodedString(connAttrsBuf, connAttrPlatform)
|
|
connAttrsBuf = appendLengthEncodedString(connAttrsBuf, connAttrPlatformValue)
|
|
connAttrsBuf = appendLengthEncodedString(connAttrsBuf, connAttrPid)
|
|
connAttrsBuf = appendLengthEncodedString(connAttrsBuf, strconv.Itoa(os.Getpid()))
|
|
serverHost, _, _ := net.SplitHostPort(cfg.Addr)
|
|
if serverHost != "" {
|
|
connAttrsBuf = appendLengthEncodedString(connAttrsBuf, connAttrServerHost)
|
|
connAttrsBuf = appendLengthEncodedString(connAttrsBuf, serverHost)
|
|
}
|
|
|
|
// user-defined connection attributes
|
|
for _, connAttr := range strings.Split(cfg.ConnectionAttributes, ",") {
|
|
k, v, found := strings.Cut(connAttr, ":")
|
|
if !found {
|
|
continue
|
|
}
|
|
connAttrsBuf = appendLengthEncodedString(connAttrsBuf, k)
|
|
connAttrsBuf = appendLengthEncodedString(connAttrsBuf, v)
|
|
}
|
|
|
|
return string(connAttrsBuf)
|
|
}
|
|
|
|
func newConnector(cfg *Config) *connector {
|
|
encodedAttributes := encodeConnectionAttributes(cfg)
|
|
return &connector{
|
|
cfg: cfg,
|
|
encodedAttributes: encodedAttributes,
|
|
}
|
|
}
|
|
|
|
// 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
|
|
|
|
// Invoke beforeConnect if present, with a copy of the configuration
|
|
cfg := c.cfg
|
|
if c.cfg.beforeConnect != nil {
|
|
cfg = c.cfg.Clone()
|
|
err = c.cfg.beforeConnect(ctx, cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// New mysqlConn
|
|
mc := &mysqlConn{
|
|
maxAllowedPacket: maxPacketSize,
|
|
maxWriteSize: maxPacketSize - 1,
|
|
closech: make(chan struct{}),
|
|
cfg: cfg,
|
|
connector: c,
|
|
}
|
|
mc.parseTime = mc.cfg.ParseTime
|
|
|
|
// Connect to Server
|
|
dialsLock.RLock()
|
|
dial, ok := dials[mc.cfg.Net]
|
|
dialsLock.RUnlock()
|
|
if ok {
|
|
dctx := ctx
|
|
if mc.cfg.Timeout > 0 {
|
|
var cancel context.CancelFunc
|
|
dctx, cancel = context.WithTimeout(ctx, c.cfg.Timeout)
|
|
defer cancel()
|
|
}
|
|
mc.netConn, err = dial(dctx, 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
|
|
}
|
|
mc.rawConn = mc.netConn
|
|
|
|
// Enable TCP Keepalives on TCP connections
|
|
if tc, ok := mc.netConn.(*net.TCPConn); ok {
|
|
if err := tc.SetKeepAlive(true); err != nil {
|
|
c.cfg.Logger.Print(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
|
|
c.cfg.Logger.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{}
|
|
}
|