forked from jmoiron/sqlx
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request jmoiron#270 from wyattjoh/master
Add experimental support for 1.8's new Context based database/sql functions
- Loading branch information
Showing
5 changed files
with
1,923 additions
and
1 deletion.
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,132 @@ | ||
// +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) | ||
} | ||
|
||
// NamedQueryContext binds a named query and then runs Query on the result using the | ||
// provided Ext (sqlx.Tx, sqlx.Db). It works with both structs and with | ||
// map[string]interface{} types. | ||
func NamedQueryContext(ctx context.Context, e ExtContext, query string, arg interface{}) (*Rows, error) { | ||
q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return e.QueryxContext(ctx, q, args...) | ||
} | ||
|
||
// NamedExecContext uses BindStruct to get a query executable by the driver and | ||
// then runs Exec on the result. Returns an error from the binding | ||
// or the query excution itself. | ||
func NamedExecContext(ctx context.Context, e ExtContext, query string, arg interface{}) (sql.Result, error) { | ||
q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return e.ExecContext(ctx, q, args...) | ||
} |
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,136 @@ | ||
// +build go1.8 | ||
|
||
package sqlx | ||
|
||
import ( | ||
"context" | ||
"database/sql" | ||
"testing" | ||
) | ||
|
||
func TestNamedContextQueries(t *testing.T) { | ||
RunWithSchema(defaultSchema, t, func(db *DB, t *testing.T) { | ||
loadDefaultFixture(db, t) | ||
test := Test{t} | ||
var ns *NamedStmt | ||
var err error | ||
|
||
ctx := context.Background() | ||
|
||
// Check that invalid preparations fail | ||
ns, err = db.PrepareNamedContext(ctx, "SELECT * FROM person WHERE first_name=:first:name") | ||
if err == nil { | ||
t.Error("Expected an error with invalid prepared statement.") | ||
} | ||
|
||
ns, err = db.PrepareNamedContext(ctx, "invalid sql") | ||
if err == nil { | ||
t.Error("Expected an error with invalid prepared statement.") | ||
} | ||
|
||
// Check closing works as anticipated | ||
ns, err = db.PrepareNamedContext(ctx, "SELECT * FROM person WHERE first_name=:first_name") | ||
test.Error(err) | ||
err = ns.Close() | ||
test.Error(err) | ||
|
||
ns, err = db.PrepareNamedContext(ctx, ` | ||
SELECT first_name, last_name, email | ||
FROM person WHERE first_name=:first_name AND email=:email`) | ||
test.Error(err) | ||
|
||
// test Queryx w/ uses Query | ||
p := Person{FirstName: "Jason", LastName: "Moiron", Email: "[email protected]"} | ||
|
||
rows, err := ns.QueryxContext(ctx, p) | ||
test.Error(err) | ||
for rows.Next() { | ||
var p2 Person | ||
rows.StructScan(&p2) | ||
if p.FirstName != p2.FirstName { | ||
t.Errorf("got %s, expected %s", p.FirstName, p2.FirstName) | ||
} | ||
if p.LastName != p2.LastName { | ||
t.Errorf("got %s, expected %s", p.LastName, p2.LastName) | ||
} | ||
if p.Email != p2.Email { | ||
t.Errorf("got %s, expected %s", p.Email, p2.Email) | ||
} | ||
} | ||
|
||
// test Select | ||
people := make([]Person, 0, 5) | ||
err = ns.SelectContext(ctx, &people, p) | ||
test.Error(err) | ||
|
||
if len(people) != 1 { | ||
t.Errorf("got %d results, expected %d", len(people), 1) | ||
} | ||
if p.FirstName != people[0].FirstName { | ||
t.Errorf("got %s, expected %s", p.FirstName, people[0].FirstName) | ||
} | ||
if p.LastName != people[0].LastName { | ||
t.Errorf("got %s, expected %s", p.LastName, people[0].LastName) | ||
} | ||
if p.Email != people[0].Email { | ||
t.Errorf("got %s, expected %s", p.Email, people[0].Email) | ||
} | ||
|
||
// test Exec | ||
ns, err = db.PrepareNamedContext(ctx, ` | ||
INSERT INTO person (first_name, last_name, email) | ||
VALUES (:first_name, :last_name, :email)`) | ||
test.Error(err) | ||
|
||
js := Person{ | ||
FirstName: "Julien", | ||
LastName: "Savea", | ||
Email: "[email protected]", | ||
} | ||
_, err = ns.ExecContext(ctx, js) | ||
test.Error(err) | ||
|
||
// Make sure we can pull him out again | ||
p2 := Person{} | ||
db.GetContext(ctx, &p2, db.Rebind("SELECT * FROM person WHERE email=?"), js.Email) | ||
if p2.Email != js.Email { | ||
t.Errorf("expected %s, got %s", js.Email, p2.Email) | ||
} | ||
|
||
// test Txn NamedStmts | ||
tx := db.MustBeginTx(ctx, nil) | ||
txns := tx.NamedStmtContext(ctx, ns) | ||
|
||
// We're going to add Steven in this txn | ||
sl := Person{ | ||
FirstName: "Steven", | ||
LastName: "Luatua", | ||
Email: "[email protected]", | ||
} | ||
|
||
_, err = txns.ExecContext(ctx, sl) | ||
test.Error(err) | ||
// then rollback... | ||
tx.Rollback() | ||
// looking for Steven after a rollback should fail | ||
err = db.GetContext(ctx, &p2, db.Rebind("SELECT * FROM person WHERE email=?"), sl.Email) | ||
if err != sql.ErrNoRows { | ||
t.Errorf("expected no rows error, got %v", err) | ||
} | ||
|
||
// now do the same, but commit | ||
tx = db.MustBeginTx(ctx, nil) | ||
txns = tx.NamedStmtContext(ctx, ns) | ||
_, err = txns.ExecContext(ctx, sl) | ||
test.Error(err) | ||
tx.Commit() | ||
|
||
// looking for Steven after a Commit should succeed | ||
err = db.GetContext(ctx, &p2, db.Rebind("SELECT * FROM person WHERE email=?"), sl.Email) | ||
test.Error(err) | ||
if p2.Email != sl.Email { | ||
t.Errorf("expected %s, got %s", sl.Email, p2.Email) | ||
} | ||
|
||
}) | ||
} |
Oops, something went wrong.