Skip to content

Commit 1a5fa7f

Browse files
committed
Array and Composite codecs handle typed nils
fixes: #2453
1 parent 4c1308c commit 1a5fa7f

4 files changed

Lines changed: 173 additions & 4 deletions

File tree

pgtype/array_codec.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ func (p *encodePlanArrayCodecText) Encode(value any, buf []byte) (newBuf []byte,
131131

132132
elem := array.Index(i)
133133
var elemBuf []byte
134-
if elem != nil {
134+
if isNil, _ := isNilDriverValuer(elem); !isNil {
135135
elemType := reflect.TypeOf(elem)
136136
if lastElemType != elemType {
137137
lastElemType = elemType
@@ -195,7 +195,7 @@ func (p *encodePlanArrayCodecBinary) Encode(value any, buf []byte) (newBuf []byt
195195

196196
elem := array.Index(i)
197197
var elemBuf []byte
198-
if elem != nil {
198+
if isNil, _ := isNilDriverValuer(elem); !isNil {
199199
elemType := reflect.TypeOf(elem)
200200
if lastElemType != elemType {
201201
lastElemType = elemType

pgtype/array_codec_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,21 @@ func TestArrayCodecArray(t *testing.T) {
116116
})
117117
}
118118

119+
func TestArrayCodecPointerToNil(t *testing.T) {
120+
pgxtest.RunWithQueryExecModes(context.Background(), t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
121+
n := int32(42)
122+
input := []*int32{&n, nil}
123+
var actual []*int32
124+
err := conn.QueryRow(
125+
ctx,
126+
"select $1::int[]",
127+
input,
128+
).Scan(&actual)
129+
require.NoError(t, err)
130+
require.Equal(t, input, actual)
131+
})
132+
}
133+
119134
func TestArrayCodecNamedSliceType(t *testing.T) {
120135
defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
121136
type _int16Slice []int16

pgtype/composite.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ func (b *CompositeBinaryBuilder) AppendValue(oid uint32, field any) {
476476
return
477477
}
478478

479-
if field == nil {
479+
if isNil, _ := isNilDriverValuer(field); isNil {
480480
b.buf = pgio.AppendUint32(b.buf, oid)
481481
b.buf = pgio.AppendInt32(b.buf, -1)
482482
b.fieldCount++
@@ -533,7 +533,7 @@ func (b *CompositeTextBuilder) AppendValue(oid uint32, field any) {
533533
return
534534
}
535535

536-
if field == nil {
536+
if isNil, _ := isNilDriverValuer(field); isNil {
537537
b.buf = append(b.buf, ',')
538538
return
539539
}

pgtype/composite_test.go

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,160 @@ create type point3d as (
159159
})
160160
}
161161

162+
type parent struct {
163+
Name string
164+
Location *point3d
165+
}
166+
167+
func (p parent) IsNull() bool {
168+
return false
169+
}
170+
171+
func (p parent) Index(i int) any {
172+
switch i {
173+
case 0:
174+
return p.Name
175+
case 1:
176+
return p.Location
177+
default:
178+
panic("invalid index")
179+
}
180+
}
181+
182+
func (p *parent) ScanNull() error {
183+
return fmt.Errorf("cannot scan NULL into parent")
184+
}
185+
186+
func (p *parent) ScanIndex(i int) any {
187+
switch i {
188+
case 0:
189+
return &p.Name
190+
case 1:
191+
return &p.Location
192+
default:
193+
panic("invalid index")
194+
}
195+
}
196+
197+
// https://github.com/jackc/pgx/issues/2453
198+
func TestCompositeCodecTranscodeStructWithNilPointer(t *testing.T) {
199+
defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
200+
_, err := conn.Exec(ctx, `drop type if exists parent;
201+
drop type if exists point3d;
202+
203+
create type point3d as (
204+
x float8,
205+
y float8,
206+
z float8
207+
);
208+
209+
create type parent as (
210+
name text,
211+
location point3d
212+
);`)
213+
require.NoError(t, err)
214+
defer conn.Exec(ctx, "drop type parent")
215+
defer conn.Exec(ctx, "drop type point3d")
216+
217+
dataTypes, err := conn.LoadTypes(ctx, []string{"point3d", "parent"})
218+
require.NoError(t, err)
219+
conn.TypeMap().RegisterTypes(dataTypes)
220+
221+
formats := []struct {
222+
name string
223+
code int16
224+
}{
225+
{name: "TextFormat", code: pgx.TextFormatCode},
226+
{name: "BinaryFormat", code: pgx.BinaryFormatCode},
227+
}
228+
229+
for _, format := range formats {
230+
input := parent{Name: "test", Location: nil}
231+
var output parent
232+
err := conn.QueryRow(ctx, "select $1::parent", pgx.QueryResultFormats{format.code}, input).Scan(&output)
233+
require.NoErrorf(t, err, "%v", format.name)
234+
require.Equalf(t, input, output, "%v", format.name)
235+
}
236+
})
237+
}
238+
239+
type parentWithSlice struct {
240+
Name string
241+
Locations []*point3d
242+
}
243+
244+
func (p parentWithSlice) IsNull() bool {
245+
return false
246+
}
247+
248+
func (p parentWithSlice) Index(i int) any {
249+
switch i {
250+
case 0:
251+
return p.Name
252+
case 1:
253+
return p.Locations
254+
default:
255+
panic("invalid index")
256+
}
257+
}
258+
259+
func (p *parentWithSlice) ScanNull() error {
260+
return fmt.Errorf("cannot scan NULL into parent")
261+
}
262+
263+
func (p *parentWithSlice) ScanIndex(i int) any {
264+
switch i {
265+
case 0:
266+
return &p.Name
267+
case 1:
268+
return &p.Locations
269+
default:
270+
panic("invalid index")
271+
}
272+
}
273+
274+
// https://github.com/jackc/pgx/issues/2453
275+
func TestCompositeCodecTranscodeStructWithSliceOfNilPointer(t *testing.T) {
276+
defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
277+
_, err := conn.Exec(ctx, `drop type if exists parent;
278+
drop type if exists point3d;
279+
280+
create type point3d as (
281+
x float8,
282+
y float8,
283+
z float8
284+
);
285+
286+
create type parent_with_slice as (
287+
name text,
288+
locations point3d[]
289+
);`)
290+
require.NoError(t, err)
291+
defer conn.Exec(ctx, "drop type parent_with_slice")
292+
defer conn.Exec(ctx, "drop type point3d")
293+
294+
dataTypes, err := conn.LoadTypes(ctx, []string{"point3d", "parent_with_slice"})
295+
require.NoError(t, err)
296+
conn.TypeMap().RegisterTypes(dataTypes)
297+
298+
formats := []struct {
299+
name string
300+
code int16
301+
}{
302+
{name: "TextFormat", code: pgx.TextFormatCode},
303+
{name: "BinaryFormat", code: pgx.BinaryFormatCode},
304+
}
305+
306+
for _, format := range formats {
307+
input := parentWithSlice{Name: "test", Locations: []*point3d{nil}}
308+
var output parentWithSlice
309+
err := conn.QueryRow(ctx, "select $1::parent_with_slice", pgx.QueryResultFormats{format.code}, input).Scan(&output)
310+
require.NoErrorf(t, err, "%v", format.name)
311+
require.Equalf(t, input, output, "%v", format.name)
312+
}
313+
})
314+
}
315+
162316
func TestCompositeCodecDecodeValue(t *testing.T) {
163317
defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
164318
_, err := conn.Exec(ctx, `drop type if exists point3d;

0 commit comments

Comments
 (0)