Skip to content

Commit 9ca9203

Browse files
committed
Move typed nil handling to Map.Encode from anynil
The new logic checks for any type of nil at the beginning of Encode and then either treats it as NULL or calls the driver.Valuer method if appropriate. This should preserve the existing nil normalization while restoring the ability to encode nil driver.Valuer values.
1 parent 79cab46 commit 9ca9203

File tree

4 files changed

+67
-55
lines changed

4 files changed

+67
-55
lines changed

internal/anynil/anynil.go

Lines changed: 0 additions & 46 deletions
This file was deleted.

pgtype/array_codec.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"fmt"
77
"reflect"
88

9-
"github.com/jackc/pgx/v5/internal/anynil"
109
"github.com/jackc/pgx/v5/internal/pgio"
1110
)
1211

@@ -230,7 +229,7 @@ func (c *ArrayCodec) PlanScan(m *Map, oid uint32, format int16, target any) Scan
230229

231230
// target / arrayScanner might be a pointer to a nil. If it is create one so we can call ScanIndexType to plan the
232231
// scan of the elements.
233-
if anynil.Is(target) {
232+
if isNil, _ := isNilDriverValuer(target); isNil {
234233
arrayScanner = reflect.New(reflect.TypeOf(target).Elem()).Interface().(ArraySetter)
235234
}
236235

pgtype/doc.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,11 @@ interfaces.
141141
142142
Encoding Typed Nils
143143
144-
pgtype normalizes typed nils (e.g. []byte(nil)) into nil. nil is always encoded is the SQL NULL value without going
145-
through the Codec system. This means that Codecs and other encoding logic does not have to handle nil or *T(nil).
144+
pgtype encodes untyped and typed nils (e.g. nil and []byte(nil)) to the SQL NULL value without going through the Codec
145+
system. This means that Codecs and other encoding logic do not have to handle nil or *T(nil).
146146
147147
However, database/sql compatibility requires Value to be called on T(nil) when T implements driver.Valuer. Therefore,
148-
driver.Valuer values are not normalized to nil unless it is a *T(nil) where driver.Valuer is implemented on T. See
148+
driver.Valuer values are only considered NULL when *T(nil) where driver.Valuer is implemented on T not on *T. See
149149
https://github.com/golang/go/issues/8415 and
150150
https://github.com/golang/go/commit/0ce1d79a6a771f7449ec493b993ed2a720917870.
151151

pgtype/pgtype.go

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import (
99
"net/netip"
1010
"reflect"
1111
"time"
12-
13-
"github.com/jackc/pgx/v5/internal/anynil"
1412
)
1513

1614
// PostgreSQL oids for common types
@@ -1914,8 +1912,17 @@ func newEncodeError(value any, m *Map, oid uint32, formatCode int16, err error)
19141912
// (nil, nil). The caller of Encode is responsible for writing the correct NULL value or the length of the data
19151913
// written.
19161914
func (m *Map) Encode(oid uint32, formatCode int16, value any, buf []byte) (newBuf []byte, err error) {
1917-
if anynil.Is(value) {
1918-
return nil, nil
1915+
if isNil, callNilDriverValuer := isNilDriverValuer(value); isNil {
1916+
if callNilDriverValuer {
1917+
newBuf, err = (&encodePlanDriverValuer{m: m, oid: oid, formatCode: formatCode}).Encode(value, buf)
1918+
if err != nil {
1919+
return nil, newEncodeError(value, m, oid, formatCode, err)
1920+
}
1921+
1922+
return newBuf, nil
1923+
} else {
1924+
return nil, nil
1925+
}
19191926
}
19201927

19211928
plan := m.PlanEncode(oid, formatCode, value)
@@ -1970,3 +1977,55 @@ func (w *sqlScannerWrapper) Scan(src any) error {
19701977

19711978
return w.m.Scan(t.OID, TextFormatCode, bufSrc, w.v)
19721979
}
1980+
1981+
// canBeNil returns true if value can be nil.
1982+
func canBeNil(value any) bool {
1983+
refVal := reflect.ValueOf(value)
1984+
kind := refVal.Kind()
1985+
switch kind {
1986+
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.UnsafePointer, reflect.Interface, reflect.Slice:
1987+
return true
1988+
default:
1989+
return false
1990+
}
1991+
}
1992+
1993+
// valuerReflectType is a reflect.Type for driver.Valuer. It has confusing syntax because reflect.TypeOf returns nil
1994+
// when it's argument is a nil interface value. So we use a pointer to the interface and call Elem to get the actual
1995+
// type. Yuck.
1996+
//
1997+
// This can be simplified in Go 1.22 with reflect.TypeFor.
1998+
//
1999+
// var valuerReflectType = reflect.TypeFor[driver.Valuer]()
2000+
var valuerReflectType = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
2001+
2002+
// isNilDriverValuer returns true if value is any type of nil unless it implements driver.Valuer. *T is not considered to implement
2003+
// driver.Valuer if it is only implemented by T.
2004+
func isNilDriverValuer(value any) (isNil bool, callNilDriverValuer bool) {
2005+
if value == nil {
2006+
return true, false
2007+
}
2008+
2009+
refVal := reflect.ValueOf(value)
2010+
kind := refVal.Kind()
2011+
switch kind {
2012+
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.UnsafePointer, reflect.Interface, reflect.Slice:
2013+
if !refVal.IsNil() {
2014+
return false, false
2015+
}
2016+
2017+
if _, ok := value.(driver.Valuer); ok {
2018+
if kind == reflect.Ptr {
2019+
// The type assertion will succeed if driver.Valuer is implemented on T or *T. Check if it is implemented on *T
2020+
// by checking if it is not implemented on *T.
2021+
return true, !refVal.Type().Elem().Implements(valuerReflectType)
2022+
} else {
2023+
return true, true
2024+
}
2025+
}
2026+
2027+
return true, false
2028+
default:
2029+
return false, false
2030+
}
2031+
}

0 commit comments

Comments
 (0)