aboutsummaryrefslogtreecommitdiff
path: root/src/database/sql/convert.go
diff options
context:
space:
mode:
authorBrad Fitzpatrick <bradfitz@golang.org>2016-10-11 08:34:58 -0700
committerBrad Fitzpatrick <bradfitz@golang.org>2016-10-17 15:26:25 +0000
commit0ce1d79a6a771f7449ec493b993ed2a720917870 (patch)
treefc1ef0ac4bd678c24236278e40ff8964a98d4c2f /src/database/sql/convert.go
parent237d7e34bc7579ec499a029191c33106dc5476a1 (diff)
downloadgo-0ce1d79a6a771f7449ec493b993ed2a720917870.tar.xz
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>
Diffstat (limited to 'src/database/sql/convert.go')
-rw-r--r--src/database/sql/convert.go26
1 files changed, 24 insertions, 2 deletions
diff --git a/src/database/sql/convert.go b/src/database/sql/convert.go
index cee96319da..594a816966 100644
--- a/src/database/sql/convert.go
+++ b/src/database/sql/convert.go
@@ -57,8 +57,8 @@ func driverArgs(ds *driverStmt, args []interface{}) ([]driver.NamedValue, error)
// 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.
- if svi, ok := arg.(driver.Valuer); ok {
- sv, err := svi.Value()
+ if vr, ok := arg.(driver.Valuer); ok {
+ sv, err := callValuerValue(vr)
if err != nil {
return nil, fmt.Errorf("sql: argument index %d from Value: %v", n, err)
}
@@ -341,3 +341,25 @@ func asBytes(buf []byte, rv reflect.Value) (b []byte, ok bool) {
}
return
}
+
+var valuerReflectType = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
+
+// callValuerValue returns vr.Value(), with one exception:
+// If vr.Value is an auto-generated method on a pointer type and the
+// pointer is nil, it would panic at runtime in the panicwrap
+// method. Treat it like nil instead.
+// Issue 8415.
+//
+// This is so people can implement driver.Value on value types and
+// still use nil pointers to those types to mean nil/NULL, just like
+// string/*string.
+//
+// This function is mirrored in the database/sql/driver package.
+func callValuerValue(vr driver.Valuer) (v driver.Value, err error) {
+ if rv := reflect.ValueOf(vr); rv.Kind() == reflect.Ptr &&
+ rv.IsNil() &&
+ rv.Type().Elem().Implements(valuerReflectType) {
+ return nil, nil
+ }
+ return vr.Value()
+}