forked from jmoiron/sqlx
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial attempt at supporting Go 1.8's context in database/sql
- Loading branch information
Showing
2 changed files
with
360 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
// +build go1.8 | ||
|
||
package sqlx | ||
|
||
import ( | ||
"context" | ||
"database/sql" | ||
) | ||
|
||
// A union interface of contextPreparer and binder, required to be able to | ||
// prepare named statements with context (as the bindtype must be determined). | ||
type namedPreparerContext interface { | ||
PreparerContext | ||
binder | ||
} | ||
|
||
func prepareNamedContext(ctx context.Context, p namedPreparerContext, query string) (*NamedStmt, error) { | ||
bindType := BindType(p.DriverName()) | ||
q, args, err := compileNamedQuery([]byte(query), bindType) | ||
if err != nil { | ||
return nil, err | ||
} | ||
stmt, err := PreparexContext(ctx, p, q) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &NamedStmt{ | ||
QueryString: q, | ||
Params: args, | ||
Stmt: stmt, | ||
}, nil | ||
} | ||
|
||
// ExecContext executes a named statement using the struct passed. | ||
// Any named placeholder parameters are replaced with fields from arg. | ||
func (n *NamedStmt) ExecContext(ctx context.Context, arg interface{}) (sql.Result, error) { | ||
args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper) | ||
if err != nil { | ||
return *new(sql.Result), err | ||
} | ||
return n.Stmt.ExecContext(ctx, args...) | ||
} | ||
|
||
// QueryContext executes a named statement using the struct argument, returning rows. | ||
// Any named placeholder parameters are replaced with fields from arg. | ||
func (n *NamedStmt) QueryContext(ctx context.Context, arg interface{}) (*sql.Rows, error) { | ||
args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return n.Stmt.QueryContext(ctx, args...) | ||
} | ||
|
||
// QueryRowContext executes a named statement against the database. Because sqlx cannot | ||
// create a *sql.Row with an error condition pre-set for binding errors, sqlx | ||
// returns a *sqlx.Row instead. | ||
// Any named placeholder parameters are replaced with fields from arg. | ||
func (n *NamedStmt) QueryRowContext(ctx context.Context, arg interface{}) *Row { | ||
args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper) | ||
if err != nil { | ||
return &Row{err: err} | ||
} | ||
return n.Stmt.QueryRowxContext(ctx, args...) | ||
} | ||
|
||
// MustExecContext execs a NamedStmt, panicing on error | ||
// Any named placeholder parameters are replaced with fields from arg. | ||
func (n *NamedStmt) MustExecContext(ctx context.Context, arg interface{}) sql.Result { | ||
res, err := n.ExecContext(ctx, arg) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return res | ||
} | ||
|
||
// QueryxContext using this NamedStmt | ||
// Any named placeholder parameters are replaced with fields from arg. | ||
func (n *NamedStmt) QueryxContext(ctx context.Context, arg interface{}) (*Rows, error) { | ||
r, err := n.QueryContext(ctx, arg) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &Rows{Rows: r, Mapper: n.Stmt.Mapper, unsafe: isUnsafe(n)}, err | ||
} | ||
|
||
// QueryRowxContext this NamedStmt. Because of limitations with QueryRow, this is | ||
// an alias for QueryRow. | ||
// Any named placeholder parameters are replaced with fields from arg. | ||
func (n *NamedStmt) QueryRowxContext(ctx context.Context, arg interface{}) *Row { | ||
return n.QueryRowContext(ctx, arg) | ||
} | ||
|
||
// SelectContext using this NamedStmt | ||
// Any named placeholder parameters are replaced with fields from arg. | ||
func (n *NamedStmt) SelectContext(ctx context.Context, dest interface{}, arg interface{}) error { | ||
rows, err := n.QueryxContext(ctx, arg) | ||
if err != nil { | ||
return err | ||
} | ||
// if something happens here, we want to make sure the rows are Closed | ||
defer rows.Close() | ||
return scanAll(rows, dest, false) | ||
} | ||
|
||
// GetContext using this NamedStmt | ||
// Any named placeholder parameters are replaced with fields from arg. | ||
func (n *NamedStmt) GetContext(ctx context.Context, dest interface{}, arg interface{}) error { | ||
r := n.QueryRowxContext(ctx, arg) | ||
return r.scanAny(dest, false) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,250 @@ | ||
// +build go1.8 | ||
|
||
package sqlx | ||
|
||
import ( | ||
"context" | ||
"database/sql" | ||
"fmt" | ||
"io/ioutil" | ||
"path/filepath" | ||
"reflect" | ||
) | ||
|
||
// QueryerContext is an interface used by GetContext and SelectContext | ||
type QueryerContext interface { | ||
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) | ||
QueryxContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) | ||
QueryRowxContext(ctx context.Context, query string, args ...interface{}) *Row | ||
} | ||
|
||
// PreparerContext is an interface used by PreparexContext. | ||
type PreparerContext interface { | ||
PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) | ||
} | ||
|
||
// ExecerContext is an interface used by MustExecContext and LoadFileContext | ||
type ExecerContext interface { | ||
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) | ||
} | ||
|
||
// SelectContext executes a query using the provided Queryer, and StructScans | ||
// each row into dest, which must be a slice. If the slice elements are | ||
// scannable, then the result set must have only one column. Otherwise, | ||
// StructScan is used. The *sql.Rows are closed automatically. | ||
// Any placeholder parameters are replaced with supplied args. | ||
func SelectContext(ctx context.Context, q QueryerContext, dest interface{}, query string, args ...interface{}) error { | ||
rows, err := q.QueryxContext(ctx, query, args...) | ||
if err != nil { | ||
return err | ||
} | ||
// if something happens here, we want to make sure the rows are Closed | ||
defer rows.Close() | ||
return scanAll(rows, dest, false) | ||
} | ||
|
||
// PreparexContext prepares a statement. | ||
// | ||
// The provided context is used for the preparation of the statement, not for | ||
// the execution of the statement. | ||
func PreparexContext(ctx context.Context, p PreparerContext, query string) (*Stmt, error) { | ||
s, err := p.PrepareContext(ctx, query) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &Stmt{Stmt: s, unsafe: isUnsafe(p), Mapper: mapperFor(p)}, err | ||
} | ||
|
||
// GetContext does a QueryRow using the provided Queryer, and scans the | ||
// resulting row to dest. If dest is scannable, the result must only have one | ||
// column. Otherwise, StructScan is used. Get will return sql.ErrNoRows like | ||
// row.Scan would. Any placeholder parameters are replaced with supplied args. | ||
// An error is returned if the result set is empty. | ||
func GetContext(ctx context.Context, q QueryerContext, dest interface{}, query string, args ...interface{}) error { | ||
r := q.QueryRowxContext(ctx, query, args...) | ||
return r.scanAny(dest, false) | ||
} | ||
|
||
// LoadFileContext exec's every statement in a file (as a single call to Exec). | ||
// LoadFileContext may return a nil *sql.Result if errors are encountered | ||
// locating or reading the file at path. LoadFile reads the entire file into | ||
// memory, so it is not suitable for loading large data dumps, but can be useful | ||
// for initializing schemas or loading indexes. | ||
// | ||
// FIXME: this does not really work with multi-statement files for mattn/go-sqlite3 | ||
// or the go-mysql-driver/mysql drivers; pq seems to be an exception here. Detecting | ||
// this by requiring something with DriverName() and then attempting to split the | ||
// queries will be difficult to get right, and its current driver-specific behavior | ||
// is deemed at least not complex in its incorrectness. | ||
func LoadFileContext(ctx context.Context, e ExecerContext, path string) (*sql.Result, error) { | ||
realpath, err := filepath.Abs(path) | ||
if err != nil { | ||
return nil, err | ||
} | ||
contents, err := ioutil.ReadFile(realpath) | ||
if err != nil { | ||
return nil, err | ||
} | ||
res, err := e.ExecContext(ctx, string(contents)) | ||
return &res, err | ||
} | ||
|
||
// MustExecContext execs the query using e and panics if there was an error. | ||
// Any placeholder parameters are replaced with supplied args. | ||
func MustExecContext(ctx context.Context, e ExecerContext, query string, args ...interface{}) sql.Result { | ||
res, err := e.ExecContext(ctx, query, args...) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return res | ||
} | ||
|
||
// PrepareNamedContext returns an sqlx.NamedStmt | ||
func (db *DB) PrepareNamedContext(ctx context.Context, query string) (*NamedStmt, error) { | ||
return prepareNamedContext(ctx, db, query) | ||
} | ||
|
||
// SelectContext using this DB. | ||
// Any placeholder parameters are replaced with supplied args. | ||
func (db *DB) SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error { | ||
return SelectContext(ctx, db, dest, query, args...) | ||
} | ||
|
||
// PreparexContext returns an sqlx.Stmt instead of a sql.Stmt. | ||
// | ||
// The provided context is used for the preparation of the statement, not for | ||
// the execution of the statement. | ||
func (db *DB) PreparexContext(ctx context.Context, query string) (*Stmt, error) { | ||
return PreparexContext(ctx, db, query) | ||
} | ||
|
||
// QueryxContext queries the database and returns an *sqlx.Rows. | ||
// Any placeholder parameters are replaced with supplied args. | ||
func (db *DB) QueryxContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) { | ||
r, err := db.DB.QueryContext(ctx, query, args...) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &Rows{Rows: r, unsafe: db.unsafe, Mapper: db.Mapper}, err | ||
} | ||
|
||
// QueryRowxContext queries the database and returns an *sqlx.Row. | ||
// Any placeholder parameters are replaced with supplied args. | ||
func (db *DB) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *Row { | ||
rows, err := db.DB.QueryContext(ctx, query, args...) | ||
return &Row{rows: rows, err: err, unsafe: db.unsafe, Mapper: db.Mapper} | ||
} | ||
|
||
// MustBeginTx starts a transaction, and panics on error. Returns an *sqlx.Tx instead | ||
// of an *sql.Tx. | ||
// | ||
// The provided context is used until the transaction is committed or rolled | ||
// back. If the context is canceled, the sql package will roll back the | ||
// transaction. Tx.Commit will return an error if the context provided to | ||
// MustBeginContext is canceled. | ||
func (db *DB) MustBeginTx(ctx context.Context, opts *sql.TxOptions) *Tx { | ||
tx, err := db.BeginTxx(ctx, opts) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return tx | ||
} | ||
|
||
// BeginTxx begins a transaction and returns an *sqlx.Tx instead of an | ||
// *sql.Tx. | ||
// | ||
// The provided context is used until the transaction is committed or rolled | ||
// back. If the context is canceled, the sql package will roll back the | ||
// transaction. Tx.Commit will return an error if the context provided to | ||
// BeginxContext is canceled. | ||
func (db *DB) BeginTxx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { | ||
tx, err := db.DB.BeginTx(ctx, opts) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &Tx{Tx: tx, driverName: db.driverName, unsafe: db.unsafe, Mapper: db.Mapper}, err | ||
} | ||
|
||
// StmtxContext returns a version of the prepared statement which runs within a | ||
// transaction. Provided stmt can be either *sql.Stmt or *sqlx.Stmt. | ||
func (tx *Tx) StmtxContext(ctx context.Context, stmt interface{}) *Stmt { | ||
var s *sql.Stmt | ||
switch v := stmt.(type) { | ||
case Stmt: | ||
s = v.Stmt | ||
case *Stmt: | ||
s = v.Stmt | ||
case sql.Stmt: | ||
s = &v | ||
case *sql.Stmt: | ||
s = v | ||
default: | ||
panic(fmt.Sprintf("non-statement type %v passed to Stmtx", reflect.ValueOf(stmt).Type())) | ||
} | ||
return &Stmt{Stmt: tx.StmtContext(ctx, s), Mapper: tx.Mapper} | ||
} | ||
|
||
// NamedStmtContext returns a version of the prepared statement which runs | ||
// within a transaction. | ||
func (tx *Tx) NamedStmtContext(ctx context.Context, stmt *NamedStmt) *NamedStmt { | ||
return &NamedStmt{ | ||
QueryString: stmt.QueryString, | ||
Params: stmt.Params, | ||
Stmt: tx.StmtxContext(ctx, stmt.Stmt), | ||
} | ||
} | ||
|
||
// SelectContext using the prepared statement. | ||
// Any placeholder parameters are replaced with supplied args. | ||
func (s *Stmt) SelectContext(ctx context.Context, dest interface{}, args ...interface{}) error { | ||
return SelectContext(ctx, &qStmt{s}, dest, "", args...) | ||
} | ||
|
||
// GetContext using the prepared statement. | ||
// Any placeholder parameters are replaced with supplied args. | ||
// An error is returned if the result set is empty. | ||
func (s *Stmt) GetContext(ctx context.Context, dest interface{}, args ...interface{}) error { | ||
return GetContext(ctx, &qStmt{s}, dest, "", args...) | ||
} | ||
|
||
// MustExecContext (panic) using this statement. Note that the query portion of | ||
// the error output will be blank, as Stmt does not expose its query. | ||
// Any placeholder parameters are replaced with supplied args. | ||
func (s *Stmt) MustExecContext(ctx context.Context, args ...interface{}) sql.Result { | ||
return MustExecContext(ctx, &qStmt{s}, "", args...) | ||
} | ||
|
||
// QueryRowxContext using this statement. | ||
// Any placeholder parameters are replaced with supplied args. | ||
func (s *Stmt) QueryRowxContext(ctx context.Context, args ...interface{}) *Row { | ||
qs := &qStmt{s} | ||
return qs.QueryRowxContext(ctx, "", args...) | ||
} | ||
|
||
// QueryxContext using this statement. | ||
// Any placeholder parameters are replaced with supplied args. | ||
func (s *Stmt) QueryxContext(ctx context.Context, args ...interface{}) (*Rows, error) { | ||
qs := &qStmt{s} | ||
return qs.QueryxContext(ctx, "", args...) | ||
} | ||
|
||
func (q *qStmt) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) { | ||
return q.Stmt.QueryContext(ctx, args...) | ||
} | ||
|
||
func (q *qStmt) QueryxContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) { | ||
r, err := q.Stmt.QueryContext(ctx, args...) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &Rows{Rows: r, unsafe: q.Stmt.unsafe, Mapper: q.Stmt.Mapper}, err | ||
} | ||
|
||
func (q *qStmt) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *Row { | ||
rows, err := q.Stmt.QueryContext(ctx, args...) | ||
return &Row{rows: rows, err: err, unsafe: q.Stmt.unsafe, Mapper: q.Stmt.Mapper} | ||
} | ||
|
||
func (q *qStmt) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { | ||
return q.Stmt.ExecContext(ctx, args...) | ||
} |