database/sql: accept nil pointers to Valuers implemented on value receivers

The driver.Valuer interface lets types map their Go representation to
a suitable database/sql/driver.Value.

If a user defines the Value method with a value receiver, such as:

    type MyStr string

    func (s MyStr) Value() (driver.Value, error) {
        return strings.ToUpper(string(s)), nil
    }

Then they can't use (*MyStr)(nil) as an argument to an SQL call via
database/sql, because *MyStr also implements driver.Value, but via a
compiler-generated wrapper which checks whether the pointer is nil and
panics if so.

We now accept (*MyStr)(nil) and map it to "nil" (an SQL "NULL")
if the Valuer method is implemented on MyStr instead of *MyStr.

If a user implements the driver.Value interface with a pointer
receiver, they retain full control of what nil means:

    type MyStr string

    func (s *MyStr) Value() (driver.Value, error) {
        if s == nil {
            return "missing MyStr", nil
        }
        return strings.ToUpper(string(*s)), nil
    }

Adds tests for both cases.

Fixes #8415

Change-Id: I897d609d80d46e2354d2669a8a3e090688eee3ad
Reviewed-on: https://go-review.googlesource.com/31259
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Daniel Theophanes <kardianos@gmail.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
Brad Fitzpatrick 2016-10-11 08:34:58 -07:00
parent 237d7e34bc
commit 0ce1d79a6a
3 changed files with 131 additions and 4 deletions

View file

@ -9,6 +9,7 @@ import (
"fmt"
"reflect"
"runtime"
"strings"
"testing"
"time"
)
@ -389,3 +390,85 @@ func TestUserDefinedBytes(t *testing.T) {
t.Fatal("userDefinedBytes got potentially dirty driver memory")
}
}
type Valuer_V string
func (v Valuer_V) Value() (driver.Value, error) {
return strings.ToUpper(string(v)), nil
}
type Valuer_P string
func (p *Valuer_P) Value() (driver.Value, error) {
if p == nil {
return "nil-to-str", nil
}
return strings.ToUpper(string(*p)), nil
}
func TestDriverArgs(t *testing.T) {
var nilValuerVPtr *Valuer_V
var nilValuerPPtr *Valuer_P
var nilStrPtr *string
tests := []struct {
args []interface{}
want []driver.NamedValue
}{
0: {
args: []interface{}{Valuer_V("foo")},
want: []driver.NamedValue{
driver.NamedValue{
Ordinal: 1,
Value: "FOO",
},
},
},
1: {
args: []interface{}{nilValuerVPtr},
want: []driver.NamedValue{
driver.NamedValue{
Ordinal: 1,
Value: nil,
},
},
},
2: {
args: []interface{}{nilValuerPPtr},
want: []driver.NamedValue{
driver.NamedValue{
Ordinal: 1,
Value: "nil-to-str",
},
},
},
3: {
args: []interface{}{"plain-str"},
want: []driver.NamedValue{
driver.NamedValue{
Ordinal: 1,
Value: "plain-str",
},
},
},
4: {
args: []interface{}{nilStrPtr},
want: []driver.NamedValue{
driver.NamedValue{
Ordinal: 1,
Value: nil,
},
},
},
}
for i, tt := range tests {
ds := new(driverStmt)
got, err := driverArgs(ds, tt.args)
if err != nil {
t.Errorf("test[%d]: %v", i, err)
continue
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("test[%d]: got %v, want %v", i, got, tt.want)
}
}
}