mirror of
https://github.com/jackc/pgx.git
synced 2025-05-31 11:42:24 +00:00
pgx v5 introduced nil normalization for typed nils. This means that []byte(nil) is normalized to nil at the edge of the encoding system. This simplified encoding logic as nil could be encoded as NULL and type specific handling was unneeded. However, database/sql compatibility requires Value to be called on a nil pointer that implements driver.Valuer. This was broken by normalizing to nil. This commit changes the normalization logic to not normalize pointers that directly implement driver.Valuer to nil. It still normalizes pointers that implement driver.Valuer through implicit derefence. e.g. type T struct{} func (t *T) Value() (driver.Value, error) { return nil, nil } type S struct{} func (s S) Value() (driver.Value, error) { return nil, nil } (*T)(nil) will not be normalized to nil but (*S)(nil) will be. https://github.com/jackc/pgx/issues/1566
67 lines
1.9 KiB
Go
67 lines
1.9 KiB
Go
package anynil
|
|
|
|
import (
|
|
"database/sql/driver"
|
|
"reflect"
|
|
)
|
|
|
|
// valuerReflectType is a reflect.Type for driver.Valuer. It has confusing syntax because reflect.TypeOf returns nil
|
|
// when it's argument is a nil interface value. So we use a pointer to the interface and call Elem to get the actual
|
|
// type. Yuck.
|
|
//
|
|
// This can be simplified in Go 1.22 with reflect.TypeFor.
|
|
//
|
|
// var valuerReflectType = reflect.TypeFor[driver.Valuer]()
|
|
var valuerReflectType = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
|
|
|
|
// Is returns true if value is any type of nil except a pointer that directly implements driver.Valuer. e.g. nil,
|
|
// []byte(nil), and a *T where T implements driver.Valuer get normalized to nil but a *T where *T implements
|
|
// driver.Valuer does not.
|
|
func Is(value any) bool {
|
|
if value == nil {
|
|
return true
|
|
}
|
|
|
|
refVal := reflect.ValueOf(value)
|
|
kind := refVal.Kind()
|
|
switch kind {
|
|
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.UnsafePointer, reflect.Interface, reflect.Slice:
|
|
if !refVal.IsNil() {
|
|
return false
|
|
}
|
|
|
|
if kind == reflect.Ptr {
|
|
if _, ok := value.(driver.Valuer); ok {
|
|
// The pointer will be considered to implement driver.Valuer even if it is actually implemented on the value.
|
|
// But we only want to consider it nil if it is implemented on the pointer. So check if what the pointer points
|
|
// to implements driver.Valuer.
|
|
if !refVal.Type().Elem().Implements(valuerReflectType) {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Normalize converts typed nils (e.g. []byte(nil)) into untyped nil. Other values are returned unmodified.
|
|
func Normalize(v any) any {
|
|
if Is(v) {
|
|
return nil
|
|
}
|
|
return v
|
|
}
|
|
|
|
// NormalizeSlice converts all typed nils (e.g. []byte(nil)) in s into untyped nils. Other values are unmodified. s is
|
|
// mutated in place.
|
|
func NormalizeSlice(s []any) {
|
|
for i := range s {
|
|
if Is(s[i]) {
|
|
s[i] = nil
|
|
}
|
|
}
|
|
}
|