Skip to content

Commit

Permalink
database/sql: add option to use named parameter in query arguments
Browse files Browse the repository at this point in the history
Modify the new Context methods to take a name-value driver struct.
This will require more modifications to drivers to use, but will
reduce the overall number of structures that need to be maintained
over time.

Fixes #12381

Change-Id: I30747533ce418a1be5991a0c8767a26e8451adbd
Reviewed-on: https://go-review.googlesource.com/30166
Reviewed-by: Brad Fitzpatrick <[email protected]>
Run-TryBot: Brad Fitzpatrick <[email protected]>
TryBot-Result: Gobot Gobot <[email protected]>
  • Loading branch information
kardianos authored and bradfitz committed Oct 17, 2016
1 parent 99df54f commit 707a833
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 39 deletions.
27 changes: 19 additions & 8 deletions src/database/sql/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ var errNilPtr = errors.New("destination pointer is nil") // embedded in descript
// Stmt.Query into driver Values.
//
// The statement ds may be nil, if no statement is available.
func driverArgs(ds *driverStmt, args []interface{}) ([]driver.Value, error) {
dargs := make([]driver.Value, len(args))
func driverArgs(ds *driverStmt, args []interface{}) ([]driver.NamedValue, error) {
nvargs := make([]driver.NamedValue, len(args))
var si driver.Stmt
if ds != nil {
si = ds.si
Expand All @@ -33,16 +33,27 @@ func driverArgs(ds *driverStmt, args []interface{}) ([]driver.Value, error) {
if !ok {
for n, arg := range args {
var err error
dargs[n], err = driver.DefaultParameterConverter.ConvertValue(arg)
nvargs[n].Ordinal = n + 1
if np, ok := arg.(NamedParam); ok {
arg = np.Value
nvargs[n].Name = np.Name
}
nvargs[n].Value, err = driver.DefaultParameterConverter.ConvertValue(arg)

if err != nil {
return nil, fmt.Errorf("sql: converting Exec argument #%d's type: %v", n, err)
}
}
return dargs, nil
return nvargs, nil
}

// Let the Stmt convert its own arguments.
for n, arg := range args {
nvargs[n].Ordinal = n + 1
if np, ok := arg.(NamedParam); ok {
arg = np.Value
nvargs[n].Name = np.Name
}
// First, see if the value itself knows how to convert
// itself to a driver type. For example, a NullString
// struct changing into a string or nil.
Expand All @@ -66,18 +77,18 @@ func driverArgs(ds *driverStmt, args []interface{}) ([]driver.Value, error) {
// same error.
var err error
ds.Lock()
dargs[n], err = cc.ColumnConverter(n).ConvertValue(arg)
nvargs[n].Value, err = cc.ColumnConverter(n).ConvertValue(arg)
ds.Unlock()
if err != nil {
return nil, fmt.Errorf("sql: converting argument #%d's type: %v", n, err)
}
if !driver.IsValue(dargs[n]) {
if !driver.IsValue(nvargs[n].Value) {
return nil, fmt.Errorf("sql: driver ColumnConverter error converted %T to unsupported type %T",
arg, dargs[n])
arg, nvargs[n].Value)
}
}

return dargs, nil
return nvargs, nil
}

// convertAssign copies to dest the value in src, converting it if possible.
Expand Down
43 changes: 35 additions & 8 deletions src/database/sql/ctxutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,13 @@ func ctxDriverPrepare(ctx context.Context, ci driver.Conn, query string) (driver
}
}

func ctxDriverExec(ctx context.Context, execer driver.Execer, query string, dargs []driver.Value) (driver.Result, error) {
func ctxDriverExec(ctx context.Context, execer driver.Execer, query string, nvdargs []driver.NamedValue) (driver.Result, error) {
if execerCtx, is := execer.(driver.ExecerContext); is {
return execerCtx.ExecContext(ctx, query, dargs)
return execerCtx.ExecContext(ctx, query, nvdargs)
}
dargs, err := namedValueToValue(nvdargs)
if err != nil {
return nil, err
}
if ctx.Done() == context.Background().Done() {
return execer.Exec(query, dargs)
Expand Down Expand Up @@ -90,9 +94,13 @@ func ctxDriverExec(ctx context.Context, execer driver.Execer, query string, darg
}
}

func ctxDriverQuery(ctx context.Context, queryer driver.Queryer, query string, dargs []driver.Value) (driver.Rows, error) {
func ctxDriverQuery(ctx context.Context, queryer driver.Queryer, query string, nvdargs []driver.NamedValue) (driver.Rows, error) {
if queryerCtx, is := queryer.(driver.QueryerContext); is {
return queryerCtx.QueryContext(ctx, query, dargs)
return queryerCtx.QueryContext(ctx, query, nvdargs)
}
dargs, err := namedValueToValue(nvdargs)
if err != nil {
return nil, err
}
if ctx.Done() == context.Background().Done() {
return queryer.Query(query, dargs)
Expand Down Expand Up @@ -130,9 +138,13 @@ func ctxDriverQuery(ctx context.Context, queryer driver.Queryer, query string, d
}
}

func ctxDriverStmtExec(ctx context.Context, si driver.Stmt, dargs []driver.Value) (driver.Result, error) {
func ctxDriverStmtExec(ctx context.Context, si driver.Stmt, nvdargs []driver.NamedValue) (driver.Result, error) {
if siCtx, is := si.(driver.StmtExecContext); is {
return siCtx.ExecContext(ctx, dargs)
return siCtx.ExecContext(ctx, nvdargs)
}
dargs, err := namedValueToValue(nvdargs)
if err != nil {
return nil, err
}
if ctx.Done() == context.Background().Done() {
return si.Exec(dargs)
Expand Down Expand Up @@ -170,9 +182,13 @@ func ctxDriverStmtExec(ctx context.Context, si driver.Stmt, dargs []driver.Value
}
}

func ctxDriverStmtQuery(ctx context.Context, si driver.Stmt, dargs []driver.Value) (driver.Rows, error) {
func ctxDriverStmtQuery(ctx context.Context, si driver.Stmt, nvdargs []driver.NamedValue) (driver.Rows, error) {
if siCtx, is := si.(driver.StmtQueryContext); is {
return siCtx.QueryContext(ctx, dargs)
return siCtx.QueryContext(ctx, nvdargs)
}
dargs, err := namedValueToValue(nvdargs)
if err != nil {
return nil, err
}
if ctx.Done() == context.Background().Done() {
return si.Query(dargs)
Expand Down Expand Up @@ -253,3 +269,14 @@ func ctxDriverBegin(ctx context.Context, ci driver.Conn) (driver.Tx, error) {
return r.txi, r.err
}
}

func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) {
dargs := make([]driver.Value, len(named))
for n, param := range named {
if len(param.Name) > 0 {
return nil, errors.New("sql: driver does not support the use of Named Parameters")
}
dargs[n] = param.Value
}
return dargs, nil
}
18 changes: 14 additions & 4 deletions src/database/sql/driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ import (
// time.Time
type Value interface{}

// NamedValue holds both the value name and value.
// The Ordinal is the position of the parameter starting from one and is always set.
// If the Name is not empty it should be used for the parameter identifier and
// not the ordinal position.
type NamedValue struct {
Name string
Ordinal int
Value Value
}

// Driver is the interface that must be implemented by a database
// driver.
type Driver interface {
Expand Down Expand Up @@ -71,7 +81,7 @@ type Execer interface {
// ExecerContext is like execer, but must honor the context timeout and return
// when the context is cancelled.
type ExecerContext interface {
ExecContext(ctx context.Context, query string, args []Value) (Result, error)
ExecContext(ctx context.Context, query string, args []NamedValue) (Result, error)
}

// Queryer is an optional interface that may be implemented by a Conn.
Expand All @@ -88,7 +98,7 @@ type Queryer interface {
// QueryerContext is like Queryer, but most honor the context timeout and return
// when the context is cancelled.
type QueryerContext interface {
QueryContext(ctx context.Context, query string, args []Value) (Rows, error)
QueryContext(ctx context.Context, query string, args []NamedValue) (Rows, error)
}

// Conn is a connection to a database. It is not used concurrently
Expand Down Expand Up @@ -174,13 +184,13 @@ type Stmt interface {
// StmtExecContext enhances the Stmt interface by providing Exec with context.
type StmtExecContext interface {
// ExecContext must honor the context timeout and return when it is cancelled.
ExecContext(ctx context.Context, args []Value) (Result, error)
ExecContext(ctx context.Context, args []NamedValue) (Result, error)
}

// StmtQueryContext enhances the Stmt interface by providing Query with context.
type StmtQueryContext interface {
// QueryContext must honor the context timeout and return when it is cancelled.
QueryContext(ctx context.Context, args []Value) (Rows, error)
QueryContext(ctx context.Context, args []NamedValue) (Rows, error)
}

// ColumnConverter may be optionally implemented by Stmt if the
Expand Down
Loading

0 comments on commit 707a833

Please sign in to comment.