1 // Go MySQL Driver - A MySQL-Driver for Go's database/sql package
3 // Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this file,
7 // You can obtain one at http://mozilla.org/MPL/2.0/.
22 // a copy of context.Context for Go 1.7 and earlier
23 type mysqlContext interface {
24 Done() <-chan struct{}
27 // defined in context.Context, but not used in this driver:
28 // Deadline() (deadline time.Time, ok bool)
29 // Value(key interface{}) interface{}
32 type mysqlConn struct {
40 writeTimeout time.Duration
46 // for context support (Go 1.8+)
48 watcher chan<- mysqlContext
50 finished chan<- struct{}
51 canceled atomicError // set non-nil if conn is canceled
52 closed atomicBool // set when conn is closed, before closech is closed
55 // Handles parameters set in DSN after the connection is established
56 func (mc *mysqlConn) handleParams() (err error) {
57 for param, val := range mc.cfg.Params {
61 charsets := strings.Split(val, ",")
62 for i := range charsets {
63 // ignore errors here - a charset may not exist
64 err = mc.exec("SET NAMES " + charsets[i])
75 err = mc.exec("SET " + param + "=" + val + "")
85 func (mc *mysqlConn) markBadConn(err error) error {
89 if err != errBadConnNoWrite {
92 return driver.ErrBadConn
95 func (mc *mysqlConn) Begin() (driver.Tx, error) {
96 return mc.begin(false)
99 func (mc *mysqlConn) begin(readOnly bool) (driver.Tx, error) {
100 if mc.closed.IsSet() {
101 errLog.Print(ErrInvalidConn)
102 return nil, driver.ErrBadConn
106 q = "START TRANSACTION READ ONLY"
108 q = "START TRANSACTION"
112 return &mysqlTx{mc}, err
114 return nil, mc.markBadConn(err)
117 func (mc *mysqlConn) Close() (err error) {
118 // Makes Close idempotent
119 if !mc.closed.IsSet() {
120 err = mc.writeCommandPacket(comQuit)
128 // Closes the network connection and unsets internal variables. Do not call this
129 // function after successfully authentication, call Close instead. This function
130 // is called before auth or on auth failure because MySQL will have already
131 // closed the network connection.
132 func (mc *mysqlConn) cleanup() {
133 if !mc.closed.TrySet(true) {
137 // Makes cleanup idempotent
139 if mc.netConn == nil {
142 if err := mc.netConn.Close(); err != nil {
147 func (mc *mysqlConn) error() error {
148 if mc.closed.IsSet() {
149 if err := mc.canceled.Value(); err != nil {
152 return ErrInvalidConn
157 func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
158 if mc.closed.IsSet() {
159 errLog.Print(ErrInvalidConn)
160 return nil, driver.ErrBadConn
163 err := mc.writeCommandPacketStr(comStmtPrepare, query)
165 return nil, mc.markBadConn(err)
173 columnCount, err := stmt.readPrepareResultPacket()
175 if stmt.paramCount > 0 {
176 if err = mc.readUntilEOF(); err != nil {
182 err = mc.readUntilEOF()
189 func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (string, error) {
190 // Number of ? should be same to len(args)
191 if strings.Count(query, "?") != len(args) {
192 return "", driver.ErrSkip
195 buf := mc.buf.takeCompleteBuffer()
197 // can not take the buffer. Something must be wrong with the connection
198 errLog.Print(ErrBusyBuffer)
199 return "", ErrInvalidConn
204 for i := 0; i < len(query); i++ {
205 q := strings.IndexByte(query[i:], '?')
207 buf = append(buf, query[i:]...)
210 buf = append(buf, query[i:i+q]...)
217 buf = append(buf, "NULL"...)
221 switch v := arg.(type) {
223 buf = strconv.AppendInt(buf, v, 10)
225 buf = strconv.AppendFloat(buf, v, 'g', -1, 64)
228 buf = append(buf, '1')
230 buf = append(buf, '0')
234 buf = append(buf, "'0000-00-00'"...)
236 v := v.In(mc.cfg.Loc)
237 v = v.Add(time.Nanosecond * 500) // To round under microsecond
239 year100 := year / 100
246 micro := v.Nanosecond() / 1000
248 buf = append(buf, []byte{
250 digits10[year100], digits01[year100],
251 digits10[year1], digits01[year1],
253 digits10[month], digits01[month],
255 digits10[day], digits01[day],
257 digits10[hour], digits01[hour],
259 digits10[minute], digits01[minute],
261 digits10[second], digits01[second],
265 micro10000 := micro / 10000
266 micro100 := micro / 100 % 100
267 micro1 := micro % 100
268 buf = append(buf, []byte{
270 digits10[micro10000], digits01[micro10000],
271 digits10[micro100], digits01[micro100],
272 digits10[micro1], digits01[micro1],
275 buf = append(buf, '\'')
279 buf = append(buf, "NULL"...)
281 buf = append(buf, "_binary'"...)
282 if mc.status&statusNoBackslashEscapes == 0 {
283 buf = escapeBytesBackslash(buf, v)
285 buf = escapeBytesQuotes(buf, v)
287 buf = append(buf, '\'')
290 buf = append(buf, '\'')
291 if mc.status&statusNoBackslashEscapes == 0 {
292 buf = escapeStringBackslash(buf, v)
294 buf = escapeStringQuotes(buf, v)
296 buf = append(buf, '\'')
298 return "", driver.ErrSkip
301 if len(buf)+4 > mc.maxAllowedPacket {
302 return "", driver.ErrSkip
305 if argPos != len(args) {
306 return "", driver.ErrSkip
308 return string(buf), nil
311 func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
312 if mc.closed.IsSet() {
313 errLog.Print(ErrInvalidConn)
314 return nil, driver.ErrBadConn
317 if !mc.cfg.InterpolateParams {
318 return nil, driver.ErrSkip
320 // try to interpolate the parameters to save extra roundtrips for preparing and closing a statement
321 prepared, err := mc.interpolateParams(query, args)
330 err := mc.exec(query)
333 affectedRows: int64(mc.affectedRows),
334 insertId: int64(mc.insertId),
337 return nil, mc.markBadConn(err)
340 // Internal function to execute commands
341 func (mc *mysqlConn) exec(query string) error {
343 if err := mc.writeCommandPacketStr(comQuery, query); err != nil {
344 return mc.markBadConn(err)
348 resLen, err := mc.readResultSetHeaderPacket()
355 if err := mc.readUntilEOF(); err != nil {
360 if err := mc.readUntilEOF(); err != nil {
365 return mc.discardResults()
368 func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) {
369 return mc.query(query, args)
372 func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error) {
373 if mc.closed.IsSet() {
374 errLog.Print(ErrInvalidConn)
375 return nil, driver.ErrBadConn
378 if !mc.cfg.InterpolateParams {
379 return nil, driver.ErrSkip
381 // try client-side prepare to reduce roundtrip
382 prepared, err := mc.interpolateParams(query, args)
389 err := mc.writeCommandPacketStr(comQuery, query)
393 resLen, err = mc.readResultSetHeaderPacket()
395 rows := new(textRows)
401 switch err := rows.NextResultSet(); err {
410 rows.rs.columns, err = mc.readColumns(resLen)
414 return nil, mc.markBadConn(err)
417 // Gets the value of the given MySQL System Variable
418 // The returned byte slice is only valid until the next read
419 func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
421 if err := mc.writeCommandPacketStr(comQuery, "SELECT @@"+name); err != nil {
426 resLen, err := mc.readResultSetHeaderPacket()
428 rows := new(textRows)
430 rows.rs.columns = []mysqlField{{fieldType: fieldTypeVarChar}}
434 if err := mc.readUntilEOF(); err != nil {
439 dest := make([]driver.Value, resLen)
440 if err = rows.readRow(dest); err == nil {
441 return dest[0].([]byte), mc.readUntilEOF()
447 // finish is called when the query has canceled.
448 func (mc *mysqlConn) cancel(err error) {
453 // finish is called when the query has succeeded.
454 func (mc *mysqlConn) finish() {
455 if !mc.watching || mc.finished == nil {
459 case mc.finished <- struct{}{}:
465 // Ping implements driver.Pinger interface
466 func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
467 if mc.closed.IsSet() {
468 errLog.Print(ErrInvalidConn)
469 return driver.ErrBadConn
472 if err = mc.watchCancel(ctx); err != nil {
477 if err = mc.writeCommandPacket(comPing); err != nil {
481 return mc.readResultOK()
484 // BeginTx implements driver.ConnBeginTx interface
485 func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
486 if err := mc.watchCancel(ctx); err != nil {
491 if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault {
492 level, err := mapIsolationLevel(opts.Isolation)
496 err = mc.exec("SET TRANSACTION ISOLATION LEVEL " + level)
502 return mc.begin(opts.ReadOnly)
505 func (mc *mysqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
506 dargs, err := namedValueToValue(args)
511 if err := mc.watchCancel(ctx); err != nil {
515 rows, err := mc.query(query, dargs)
520 rows.finish = mc.finish
524 func (mc *mysqlConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
525 dargs, err := namedValueToValue(args)
530 if err := mc.watchCancel(ctx); err != nil {
535 return mc.Exec(query, dargs)
538 func (mc *mysqlConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
539 if err := mc.watchCancel(ctx); err != nil {
543 stmt, err := mc.Prepare(query)
553 return nil, ctx.Err()
558 func (stmt *mysqlStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
559 dargs, err := namedValueToValue(args)
564 if err := stmt.mc.watchCancel(ctx); err != nil {
568 rows, err := stmt.query(dargs)
573 rows.finish = stmt.mc.finish
577 func (stmt *mysqlStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
578 dargs, err := namedValueToValue(args)
583 if err := stmt.mc.watchCancel(ctx); err != nil {
586 defer stmt.mc.finish()
588 return stmt.Exec(dargs)
591 func (mc *mysqlConn) watchCancel(ctx context.Context) error {
593 // Reach here if canceled,
594 // so the connection is already invalid
598 if ctx.Done() == nil {
608 if mc.watcher == nil {
617 func (mc *mysqlConn) startWatcher() {
618 watcher := make(chan mysqlContext, 1)
620 finished := make(chan struct{})
621 mc.finished = finished
626 case ctx = <-watcher:
642 func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) {
643 nv.Value, err = converter{}.ConvertValue(nv.Value)
647 // ResetSession implements driver.SessionResetter.
649 func (mc *mysqlConn) ResetSession(ctx context.Context) error {
650 if mc.closed.IsSet() {
651 return driver.ErrBadConn