Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Named query for Conn #780

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
177 changes: 176 additions & 1 deletion named_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package sqlx

import (
"context"
"database/sql"
"fmt"
"testing"
Expand Down Expand Up @@ -298,7 +299,6 @@ func TestNamedQueries(t *testing.T) {
if p2.Email != sl.Email {
t.Errorf("expected %s, got %s", sl.Email, p2.Email)
}

})
}

Expand Down Expand Up @@ -433,3 +433,178 @@ func TestFixBounds(t *testing.T) {
})
}
}


func TestNamedConQueries(t *testing.T) {
RunWithSchema(defaultSchema, t, func(db *DB, t *testing.T, now string) {
var err error
test := Test{t}
c,err:=db.Connx(context.Background())
test.Error(err)
loadDefaultFixture(db, t)
var ns *NamedStmt

// Check that invalid preparations fail
ns, err = c.PrepareNamed("SELECT * FROM person WHERE first_name=:first:name")
if err == nil {
t.Error("Expected an error with invalid prepared statement.")
}

ns, err = c.PrepareNamed("invalid sql")
if err == nil {
t.Error("Expected an error with invalid prepared statement.")
}

// Check closing works as anticipated
ns, err = c.PrepareNamed("SELECT * FROM person WHERE first_name=:first_name")
test.Error(err)
err = ns.Close()
test.Error(err)

ns, err = c.PrepareNamed(`
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: "jmoiron@jmoiron.net"}

rows, err := ns.Queryx(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.Select(&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 struct batch inserts
sls := []Person{
{FirstName: "Ardie", LastName: "Savea", Email: "asavea@ab.co.nz"},
{FirstName: "Sonny Bill", LastName: "Williams", Email: "sbw@ab.co.nz"},
{FirstName: "Ngani", LastName: "Laumape", Email: "nlaumape@ab.co.nz"},
}

insert := fmt.Sprintf(
"INSERT INTO person (first_name, last_name, email, added_at) VALUES (:first_name, :last_name, :email, %v)\n",
now,
)
_, err = c.NamedExec(insert, sls)
test.Error(err)

// test map batch inserts
slsMap := []map[string]interface{}{
{"first_name": "Ardie", "last_name": "Savea", "email": "asavea@ab.co.nz"},
{"first_name": "Sonny Bill", "last_name": "Williams", "email": "sbw@ab.co.nz"},
{"first_name": "Ngani", "last_name": "Laumape", "email": "nlaumape@ab.co.nz"},
}

_, err = c.NamedExec(`INSERT INTO person (first_name, last_name, email)
VALUES (:first_name, :last_name, :email) ;--`, slsMap)
test.Error(err)

type A map[string]interface{}

typedMap := []A{
{"first_name": "Ardie", "last_name": "Savea", "email": "asavea@ab.co.nz"},
{"first_name": "Sonny Bill", "last_name": "Williams", "email": "sbw@ab.co.nz"},
{"first_name": "Ngani", "last_name": "Laumape", "email": "nlaumape@ab.co.nz"},
}

_, err = c.NamedExec(`INSERT INTO person (first_name, last_name, email)
VALUES (:first_name, :last_name, :email) ;--`, typedMap)
test.Error(err)

for _, p := range sls {
dest := Person{}
err = c.Get(&dest, c.Rebind("SELECT * FROM person WHERE email=?"), p.Email)
test.Error(err)
if dest.Email != p.Email {
t.Errorf("expected %s, got %s", p.Email, dest.Email)
}
}

// test Exec
ns, err = c.PrepareNamed(`
INSERT INTO person (first_name, last_name, email)
VALUES (:first_name, :last_name, :email)`)
test.Error(err)

js := Person{
FirstName: "Julien",
LastName: "Savea",
Email: "jsavea@ab.co.nz",
}
_, err = ns.Exec(js)
test.Error(err)

// Make sure we can pull him out again
p2 := Person{}
c.Get(&p2, c.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,err := c.BeginTxx(context.Background(),nil)
test.Error(err)
txns := tx.NamedStmt(ns)

// We're going to add Steven in this txn
sl := Person{
FirstName: "Steven",
LastName: "Luatua",
Email: "sluatua@ab.co.nz",
}

_, err = txns.Exec(sl)
test.Error(err)
// then rollback...
tx.Rollback()
// looking for Steven after a rollback should fail
err = c.Get(&p2, c.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,err = c.BeginTxx(context.Background(),nil)
test.Error(err)
txns = tx.NamedStmt(ns)
_, err = txns.Exec(sl)
test.Error(err)
tx.Commit()

// looking for Steven after a Commit should succeed
err = c.Get(&p2, c.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)
}
})
}
66 changes: 66 additions & 0 deletions sqlx.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package sqlx

import (
"context"
"database/sql"
"database/sql/driver"
"errors"
Expand Down Expand Up @@ -154,6 +155,10 @@ func mapperFor(i interface{}) *reflectx.Mapper {
return i.Mapper
case *Tx:
return i.Mapper
case Conn:
return i.Mapper
case *Conn:
return i.Mapper
default:
return mapper()
}
Expand Down Expand Up @@ -384,6 +389,67 @@ type Conn struct {
Mapper *reflectx.Mapper
}

func (c *Conn) Prepare(query string) (*sql.Stmt, error) {
return c.PrepareContext(context.Background(), query)
}

func (c *Conn) Query(query string, args ...interface{}) (*sql.Rows, error) {
return c.QueryContext(context.Background(), query, args...)
}

// Queryx queries the database and returns an *sqlx.Rows.
// Any placeholder parameters are replaced with supplied args.
func (c *Conn) Queryx(query string, args ...interface{}) (*Rows, error) {
r, err := c.Query(query, args...)
if err != nil {
return nil, err
}
return &Rows{Rows: r, unsafe: c.unsafe, Mapper: c.Mapper}, err
}

func (c *Conn) QueryRowx(query string, args ...interface{}) *Row {
rows, err := c.Query(query, args...)
return &Row{rows: rows, err: err, unsafe: c.unsafe, Mapper: c.Mapper}
}

func (c *Conn) Exec(query string, args ...interface{}) (sql.Result, error) {
return c.ExecContext(context.Background(), query, args...)
}

// PrepareNamed returns an sqlx.NamedStmt
func (c *Conn) PrepareNamed(query string) (*NamedStmt, error) {
return prepareNamed(c, query)
}

// DriverName returns the driverName used by the DB which began this transaction.
func (c *Conn) DriverName() string {
return c.driverName
}

// BindNamed binds a query within a connections bindvar type.
func (c *Conn) BindNamed(query string, arg interface{}) (string, []interface{}, error) {
return bindNamedMapper(BindType(c.driverName), query, arg, c.Mapper)
}

// NamedExec using this Conn.
// Any named placeholder parameters are replaced with fields from arg.
func (c *Conn) NamedExec(query string, arg interface{}) (sql.Result, error) {
return NamedExec(c, query, arg)
}

// NamedQuery using this Conn.
// Any named placeholder parameters are replaced with fields from arg.
func (c *Conn) NamedQuery(query string, arg interface{}) (*Rows, error) {
return NamedQuery(c, query, arg)
}

// Get using this Conn.
// Any placeholder parameters are replaced with supplied args.
// An error is returned if the result set is empty.
func (c *Conn) Get(dest interface{}, query string, args ...interface{}) error {
return Get(c, dest, query, args...)
}

// Tx is an sqlx wrapper around sql.Tx with extra functionality
type Tx struct {
*sql.Tx
Expand Down
12 changes: 12 additions & 0 deletions sqlx_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,18 @@ func (c *Conn) Rebind(query string) string {
return Rebind(BindType(c.driverName), query)
}

// NamedQueryContext using this Conn.
// Any named placeholder parameters are replaced with fields from arg.
func (c *Conn) NamedQueryContext(ctx context.Context, query string, arg interface{}) (*Rows, error) {
return NamedQueryContext(ctx, c, query, arg)
}

// NamedExecContext using this Conn.
// Any named placeholder parameters are replaced with fields from arg.
func (c *Conn) NamedExecContext(ctx context.Context, query string, arg interface{}) (sql.Result, error) {
return NamedExecContext(ctx, c, query, arg)
}

// 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 {
Expand Down