| // 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) error { |
| if mc.closed.IsSet() { |
| errLog.Print(ErrInvalidConn) |
| return driver.ErrBadConn |
| } |
| |
| if err := mc.watchCancel(ctx); err != nil { |
| return err |
| } |
| defer mc.finish() |
| |
| if err := mc.writeCommandPacket(comPing); err != nil { |
| return err |
| } |
| if _, err := mc.readResultOK(); err != nil { |
| return err |
| } |
| |
| return nil |
| } |
| |
| // 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 |
| } |