@@ -71,6 +71,27 @@ func (c *JSONCodec) PlanEncode(m *Map, oid uint32, format int16, value any) Enco
71
71
}
72
72
}
73
73
74
+ // JSON needs its on scan plan for pointers to handle 'null'::json(b).
75
+ // Consider making pointerPointerScanPlan more flexible in the future.
76
+ type jsonPointerScanPlan struct {
77
+ next ScanPlan
78
+ }
79
+
80
+ func (p jsonPointerScanPlan ) Scan (src []byte , dst any ) error {
81
+ el := reflect .ValueOf (dst ).Elem ()
82
+ if src == nil || string (src ) == "null" {
83
+ el .SetZero ()
84
+ return nil
85
+ }
86
+
87
+ el .Set (reflect .New (el .Type ().Elem ()))
88
+ if p .next != nil {
89
+ return p .next .Scan (src , el .Interface ())
90
+ }
91
+
92
+ return nil
93
+ }
94
+
74
95
type encodePlanJSONCodecEitherFormatString struct {}
75
96
76
97
func (encodePlanJSONCodecEitherFormatString ) Encode (value any , buf []byte ) (newBuf []byte , err error ) {
@@ -117,64 +138,38 @@ func (e *encodePlanJSONCodecEitherFormatMarshal) Encode(value any, buf []byte) (
117
138
return buf , nil
118
139
}
119
140
120
- func (c * JSONCodec ) PlanScan (m * Map , oid uint32 , format int16 , target any ) ScanPlan {
141
+ func (c * JSONCodec ) PlanScan (m * Map , oid uint32 , formatCode int16 , target any ) ScanPlan {
142
+ return c .planScan (m , oid , formatCode , target , 0 )
143
+ }
144
+
145
+ // JSON cannot fallback to pointerPointerScanPlan because of 'null'::json(b),
146
+ // so we need to duplicate the logic here.
147
+ func (c * JSONCodec ) planScan (m * Map , oid uint32 , formatCode int16 , target any , depth int ) ScanPlan {
148
+ if depth > 8 {
149
+ return & scanPlanFail {m : m , oid : oid , formatCode : formatCode }
150
+ }
151
+
121
152
switch target .(type ) {
122
153
case * string :
123
- return scanPlanAnyToString {}
124
-
125
- case * * string :
126
- // This is to fix **string scanning. It seems wrong to special case **string, but it's not clear what a better
127
- // solution would be.
128
- //
129
- // https://github.com/jackc/pgx/issues/1470 -- **string
130
- // https://github.com/jackc/pgx/issues/1691 -- ** anything else
131
-
132
- if wrapperPlan , nextDst , ok := TryPointerPointerScanPlan (target ); ok {
133
- if nextPlan := m .planScan (oid , format , nextDst , 0 ); nextPlan != nil {
134
- if _ , failed := nextPlan .(* scanPlanFail ); ! failed {
135
- wrapperPlan .SetNext (nextPlan )
136
- return wrapperPlan
137
- }
138
- }
139
- }
140
-
154
+ return & scanPlanAnyToString {}
141
155
case * []byte :
142
- return scanPlanJSONToByteSlice {}
156
+ return & scanPlanJSONToByteSlice {}
143
157
case BytesScanner :
144
- return scanPlanBinaryBytesToBytesScanner {}
145
-
146
- }
147
-
148
- // Cannot rely on sql.Scanner being handled later because scanPlanJSONToJSONUnmarshal will take precedence.
149
- //
150
- // https://github.com/jackc/pgx/issues/1418
151
- if isSQLScanner (target ) {
152
- return & scanPlanSQLScanner {formatCode : format }
158
+ return & scanPlanBinaryBytesToBytesScanner {}
159
+ case sql.Scanner :
160
+ return & scanPlanSQLScanner {formatCode : formatCode }
153
161
}
154
162
155
- return & scanPlanJSONToJSONUnmarshal {
156
- unmarshal : c .Unmarshal ,
163
+ rv := reflect .ValueOf (target )
164
+ if rv .Kind () == reflect .Pointer && rv .Elem ().Kind () == reflect .Pointer {
165
+ var plan jsonPointerScanPlan
166
+ plan .next = c .planScan (m , oid , formatCode , rv .Elem ().Interface (), depth + 1 )
167
+ return plan
168
+ } else {
169
+ return & scanPlanJSONToJSONUnmarshal {unmarshal : c .Unmarshal }
157
170
}
158
171
}
159
172
160
- // we need to check if the target is a pointer to a sql.Scanner (or any of the pointer ref tree implements a sql.Scanner).
161
- //
162
- // https://github.com/jackc/pgx/issues/2146
163
- func isSQLScanner (v any ) bool {
164
- if _ , is := v .(sql.Scanner ); is {
165
- return true
166
- }
167
-
168
- val := reflect .ValueOf (v )
169
- for val .Kind () == reflect .Ptr {
170
- if _ , ok := val .Interface ().(sql.Scanner ); ok {
171
- return true
172
- }
173
- val = val .Elem ()
174
- }
175
- return false
176
- }
177
-
178
173
type scanPlanAnyToString struct {}
179
174
180
175
func (scanPlanAnyToString ) Scan (src []byte , dst any ) error {
@@ -202,7 +197,7 @@ type scanPlanJSONToJSONUnmarshal struct {
202
197
}
203
198
204
199
func (s * scanPlanJSONToJSONUnmarshal ) Scan (src []byte , dst any ) error {
205
- if src == nil {
200
+ if src == nil || string ( src ) == "null" {
206
201
dstValue := reflect .ValueOf (dst )
207
202
if dstValue .Kind () == reflect .Ptr {
208
203
el := dstValue .Elem ()
0 commit comments