1 package pgtype_test
2
3 import (
4 "context"
5 "database/sql"
6 "database/sql/driver"
7 "encoding/json"
8 "errors"
9 "fmt"
10 "net"
11 "os"
12 "regexp"
13 "strconv"
14 "testing"
15
16 "github.com/jackc/pgx/v5"
17 "github.com/jackc/pgx/v5/pgtype"
18 "github.com/jackc/pgx/v5/pgxtest"
19 _ "github.com/jackc/pgx/v5/stdlib"
20 "github.com/stretchr/testify/assert"
21 "github.com/stretchr/testify/require"
22 )
23
24 var defaultConnTestRunner pgxtest.ConnTestRunner
25
26 func init() {
27 defaultConnTestRunner = pgxtest.DefaultConnTestRunner()
28 defaultConnTestRunner.CreateConfig = func(ctx context.Context, t testing.TB) *pgx.ConnConfig {
29 config, err := pgx.ParseConfig(os.Getenv("PGX_TEST_DATABASE"))
30 require.NoError(t, err)
31 return config
32 }
33 }
34
35
36 type _string string
37 type _bool bool
38 type _uint8 uint8
39 type _int8 int8
40 type _int16 int16
41 type _int16Slice []int16
42 type _int32Slice []int32
43 type _int64Slice []int64
44 type _float32Slice []float32
45 type _float64Slice []float64
46 type _byteSlice []byte
47
48
49
50 const unregisteredOID = uint32(1)
51
52 func mustParseInet(t testing.TB, s string) *net.IPNet {
53 ip, ipnet, err := net.ParseCIDR(s)
54 if err == nil {
55 if ipv4 := ip.To4(); ipv4 != nil {
56 ipnet.IP = ipv4
57 } else {
58 ipnet.IP = ip
59 }
60 return ipnet
61 }
62
63
64
65 ip = net.ParseIP(s)
66 if ip == nil {
67 t.Fatal(errors.New("unable to parse inet address"))
68 }
69 ipnet = &net.IPNet{IP: ip, Mask: net.CIDRMask(128, 128)}
70 if ipv4 := ip.To4(); ipv4 != nil {
71 ipnet.IP = ipv4
72 ipnet.Mask = net.CIDRMask(32, 32)
73 }
74 return ipnet
75 }
76
77 func mustParseMacaddr(t testing.TB, s string) net.HardwareAddr {
78 addr, err := net.ParseMAC(s)
79 if err != nil {
80 t.Fatal(err)
81 }
82
83 return addr
84 }
85
86 func skipCockroachDB(t testing.TB, msg string) {
87 conn, err := pgx.Connect(context.Background(), os.Getenv("PGX_TEST_DATABASE"))
88 if err != nil {
89 t.Fatal(err)
90 }
91 defer conn.Close(context.Background())
92
93 if conn.PgConn().ParameterStatus("crdb_version") != "" {
94 t.Skip(msg)
95 }
96 }
97
98 func skipPostgreSQLVersionLessThan(t testing.TB, minVersion int64) {
99 conn, err := pgx.Connect(context.Background(), os.Getenv("PGX_TEST_DATABASE"))
100 if err != nil {
101 t.Fatal(err)
102 }
103 defer conn.Close(context.Background())
104
105 serverVersionStr := conn.PgConn().ParameterStatus("server_version")
106 serverVersionStr = regexp.MustCompile(`^[0-9]+`).FindString(serverVersionStr)
107
108 if serverVersionStr == "" {
109 return
110 }
111
112 serverVersion, err := strconv.ParseInt(serverVersionStr, 10, 64)
113 require.NoError(t, err)
114
115 if serverVersion < minVersion {
116 t.Skipf("Test requires PostgreSQL v%d+", minVersion)
117 }
118 }
119
120
121 type sqlScannerFunc func(src any) error
122
123 func (f sqlScannerFunc) Scan(src any) error {
124 return f(src)
125 }
126
127
128 type driverValuerFunc func() (driver.Value, error)
129
130 func (f driverValuerFunc) Value() (driver.Value, error) {
131 return f()
132 }
133
134 func TestMapScanNilIsNoOp(t *testing.T) {
135 m := pgtype.NewMap()
136
137 err := m.Scan(pgtype.TextOID, pgx.TextFormatCode, []byte("foo"), nil)
138 assert.NoError(t, err)
139 }
140
141 func TestMapScanTextFormatInterfacePtr(t *testing.T) {
142 m := pgtype.NewMap()
143 var got any
144 err := m.Scan(pgtype.TextOID, pgx.TextFormatCode, []byte("foo"), &got)
145 require.NoError(t, err)
146 assert.Equal(t, "foo", got)
147 }
148
149 func TestMapScanTextFormatNonByteaIntoByteSlice(t *testing.T) {
150 m := pgtype.NewMap()
151 var got []byte
152 err := m.Scan(pgtype.JSONBOID, pgx.TextFormatCode, []byte("{}"), &got)
153 require.NoError(t, err)
154 assert.Equal(t, []byte("{}"), got)
155 }
156
157 func TestMapScanBinaryFormatInterfacePtr(t *testing.T) {
158 m := pgtype.NewMap()
159 var got any
160 err := m.Scan(pgtype.TextOID, pgx.BinaryFormatCode, []byte("foo"), &got)
161 require.NoError(t, err)
162 assert.Equal(t, "foo", got)
163 }
164
165 func TestMapScanUnknownOIDToStringsAndBytes(t *testing.T) {
166 unknownOID := uint32(999999)
167 srcBuf := []byte("foo")
168 m := pgtype.NewMap()
169
170 var s string
171 err := m.Scan(unknownOID, pgx.TextFormatCode, srcBuf, &s)
172 assert.NoError(t, err)
173 assert.Equal(t, "foo", s)
174
175 var rs _string
176 err = m.Scan(unknownOID, pgx.TextFormatCode, srcBuf, &rs)
177 assert.NoError(t, err)
178 assert.Equal(t, "foo", string(rs))
179
180 var b []byte
181 err = m.Scan(unknownOID, pgx.TextFormatCode, srcBuf, &b)
182 assert.NoError(t, err)
183 assert.Equal(t, []byte("foo"), b)
184
185 var rb _byteSlice
186 err = m.Scan(unknownOID, pgx.TextFormatCode, srcBuf, &rb)
187 assert.NoError(t, err)
188 assert.Equal(t, []byte("foo"), []byte(rb))
189 }
190
191 func TestMapScanPointerToNilStructDoesNotCrash(t *testing.T) {
192 m := pgtype.NewMap()
193
194 type myStruct struct{}
195 var p *myStruct
196 err := m.Scan(0, pgx.TextFormatCode, []byte("(foo,bar)"), &p)
197 require.NotNil(t, err)
198 }
199
200 func TestMapScanUnknownOIDTextFormat(t *testing.T) {
201 m := pgtype.NewMap()
202
203 var n int32
204 err := m.Scan(0, pgx.TextFormatCode, []byte("123"), &n)
205 assert.NoError(t, err)
206 assert.EqualValues(t, 123, n)
207 }
208
209 func TestMapScanUnknownOIDIntoSQLScanner(t *testing.T) {
210 m := pgtype.NewMap()
211
212 var s sql.NullString
213 err := m.Scan(0, pgx.TextFormatCode, []byte(nil), &s)
214 assert.NoError(t, err)
215 assert.Equal(t, "", s.String)
216 assert.False(t, s.Valid)
217 }
218
219 type scannerString string
220
221 func (ss *scannerString) Scan(v any) error {
222 *ss = scannerString("scanned")
223 return nil
224 }
225
226
227 func TestMapScanUnregisteredOIDIntoRenamedStringSQLScanner(t *testing.T) {
228 m := pgtype.NewMap()
229
230 var s scannerString
231 err := m.Scan(unregisteredOID, pgx.TextFormatCode, []byte(nil), &s)
232 assert.NoError(t, err)
233 assert.Equal(t, "scanned", string(s))
234 }
235
236 type pgCustomInt int64
237
238 func (ci *pgCustomInt) Scan(src interface{}) error {
239 *ci = pgCustomInt(src.(int64))
240 return nil
241 }
242
243 func TestScanPlanBinaryInt32ScanScanner(t *testing.T) {
244 m := pgtype.NewMap()
245 src := []byte{0, 42}
246 var v pgCustomInt
247
248 plan := m.PlanScan(pgtype.Int2OID, pgtype.BinaryFormatCode, &v)
249 err := plan.Scan(src, &v)
250 require.NoError(t, err)
251 require.EqualValues(t, 42, v)
252
253 ptr := new(pgCustomInt)
254 plan = m.PlanScan(pgtype.Int2OID, pgtype.BinaryFormatCode, &ptr)
255 err = plan.Scan(src, &ptr)
256 require.NoError(t, err)
257 require.EqualValues(t, 42, *ptr)
258
259 ptr = new(pgCustomInt)
260 err = plan.Scan(nil, &ptr)
261 require.NoError(t, err)
262 assert.Nil(t, ptr)
263
264 ptr = nil
265 plan = m.PlanScan(pgtype.Int2OID, pgtype.BinaryFormatCode, &ptr)
266 err = plan.Scan(src, &ptr)
267 require.NoError(t, err)
268 require.EqualValues(t, 42, *ptr)
269
270 ptr = nil
271 plan = m.PlanScan(pgtype.Int2OID, pgtype.BinaryFormatCode, &ptr)
272 err = plan.Scan(nil, &ptr)
273 require.NoError(t, err)
274 assert.Nil(t, ptr)
275 }
276
277
278 func TestScanPlanInterface(t *testing.T) {
279 m := pgtype.NewMap()
280 src := []byte{0, 42}
281 var v interface{}
282 plan := m.PlanScan(pgtype.Int2OID, pgtype.BinaryFormatCode, v)
283 err := plan.Scan(src, v)
284 assert.Error(t, err)
285 }
286
287 func TestPointerPointerStructScan(t *testing.T) {
288 m := pgtype.NewMap()
289 type composite struct {
290 ID int
291 }
292
293 int4Type, _ := m.TypeForOID(pgtype.Int4OID)
294 pgt := &pgtype.Type{
295 Codec: &pgtype.CompositeCodec{
296 Fields: []pgtype.CompositeCodecField{
297 {
298 Name: "id",
299 Type: int4Type,
300 },
301 },
302 },
303 Name: "composite",
304 OID: 215333,
305 }
306 m.RegisterType(pgt)
307
308 var c *composite
309 plan := m.PlanScan(pgt.OID, pgtype.TextFormatCode, &c)
310 err := plan.Scan([]byte("(1)"), &c)
311 require.NoError(t, err)
312 require.Equal(t, c.ID, 1)
313 }
314
315
316 func TestMapScanPtrToPtrToSlice(t *testing.T) {
317 m := pgtype.NewMap()
318 src := []byte("{foo,bar}")
319 var v *[]string
320 plan := m.PlanScan(pgtype.TextArrayOID, pgtype.TextFormatCode, &v)
321 err := plan.Scan(src, &v)
322 require.NoError(t, err)
323 require.Equal(t, []string{"foo", "bar"}, *v)
324 }
325
326 func TestMapScanPtrToPtrToSliceOfStruct(t *testing.T) {
327 type Team struct {
328 TeamID int
329 Name string
330 }
331
332
333 m := pgtype.NewMap()
334 src := []byte{0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0xc9, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1e, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x17, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x19, 0x0, 0x0, 0x0, 0x6, 0x74, 0x65, 0x61, 0x6d, 0x20, 0x31, 0x0, 0x0, 0x0, 0x1e, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x17, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x19, 0x0, 0x0, 0x0, 0x6, 0x74, 0x65, 0x61, 0x6d, 0x20, 0x32}
335 var v *[]Team
336 plan := m.PlanScan(pgtype.RecordArrayOID, pgtype.BinaryFormatCode, &v)
337 err := plan.Scan(src, &v)
338 require.NoError(t, err)
339 require.Equal(t, []Team{{1, "team 1"}, {2, "team 2"}}, *v)
340 }
341
342 type databaseValuerString string
343
344 func (s databaseValuerString) Value() (driver.Value, error) {
345 return fmt.Sprintf("%d", len(s)), nil
346 }
347
348
349 func TestMapEncodeTextFormatDatabaseValuerThatIsRenamedSimpleType(t *testing.T) {
350 m := pgtype.NewMap()
351 src := databaseValuerString("foo")
352 buf, err := m.Encode(pgtype.TextOID, pgtype.TextFormatCode, src, nil)
353 require.NoError(t, err)
354 require.Equal(t, "3", string(buf))
355 }
356
357 type databaseValuerFmtStringer string
358
359 func (s databaseValuerFmtStringer) Value() (driver.Value, error) {
360 return nil, nil
361 }
362
363 func (s databaseValuerFmtStringer) String() string {
364 return "foobar"
365 }
366
367
368 func TestMapEncodeTextFormatDatabaseValuerThatIsFmtStringer(t *testing.T) {
369 m := pgtype.NewMap()
370 src := databaseValuerFmtStringer("")
371 buf, err := m.Encode(pgtype.TextOID, pgtype.TextFormatCode, src, nil)
372 require.NoError(t, err)
373 require.Nil(t, buf)
374 }
375
376 type databaseValuerStringFormat struct {
377 n int32
378 }
379
380 func (v databaseValuerStringFormat) Value() (driver.Value, error) {
381 return fmt.Sprint(v.n), nil
382 }
383
384 func TestMapEncodeBinaryFormatDatabaseValuerThatReturnsString(t *testing.T) {
385 m := pgtype.NewMap()
386 src := databaseValuerStringFormat{n: 42}
387 buf, err := m.Encode(pgtype.Int4OID, pgtype.BinaryFormatCode, src, nil)
388 require.NoError(t, err)
389 require.Equal(t, []byte{0, 0, 0, 42}, buf)
390 }
391
392
393 func TestMapEncodeDatabaseValuerThatReturnsStringIntoUnregisteredTypeTextFormat(t *testing.T) {
394 m := pgtype.NewMap()
395 buf, err := m.Encode(unregisteredOID, pgtype.TextFormatCode, driverValuerFunc(func() (driver.Value, error) { return "foo", nil }), nil)
396 require.NoError(t, err)
397 require.Equal(t, []byte("foo"), buf)
398 }
399
400
401 func TestMapEncodeDatabaseValuerThatReturnsByteSliceIntoUnregisteredTypeTextFormat(t *testing.T) {
402 m := pgtype.NewMap()
403 buf, err := m.Encode(unregisteredOID, pgtype.TextFormatCode, driverValuerFunc(func() (driver.Value, error) { return []byte{0, 1, 2, 3}, nil }), nil)
404 require.NoError(t, err)
405 require.Equal(t, []byte(`\x00010203`), buf)
406 }
407
408 func TestMapEncodeStringIntoUnregisteredTypeTextFormat(t *testing.T) {
409 m := pgtype.NewMap()
410 buf, err := m.Encode(unregisteredOID, pgtype.TextFormatCode, "foo", nil)
411 require.NoError(t, err)
412 require.Equal(t, []byte("foo"), buf)
413 }
414
415 func TestMapEncodeByteSliceIntoUnregisteredTypeTextFormat(t *testing.T) {
416 m := pgtype.NewMap()
417 buf, err := m.Encode(unregisteredOID, pgtype.TextFormatCode, []byte{0, 1, 2, 3}, nil)
418 require.NoError(t, err)
419 require.Equal(t, []byte(`\x00010203`), buf)
420 }
421
422
423 func TestMapEncodeNamedTypeOfByteSliceIntoTextTextFormat(t *testing.T) {
424 m := pgtype.NewMap()
425 buf, err := m.Encode(pgtype.TextOID, pgtype.TextFormatCode, json.RawMessage(`{"foo": "bar"}`), nil)
426 require.NoError(t, err)
427 require.Equal(t, []byte(`{"foo": "bar"}`), buf)
428 }
429
430
431 func TestMapScanPointerToRenamedType(t *testing.T) {
432 srcBuf := []byte("foo")
433 m := pgtype.NewMap()
434
435 var rs *_string
436 err := m.Scan(pgtype.TextOID, pgx.TextFormatCode, srcBuf, &rs)
437 assert.NoError(t, err)
438 require.NotNil(t, rs)
439 assert.Equal(t, "foo", string(*rs))
440 }
441
442
443 func TestMapScanNullToWrongType(t *testing.T) {
444 m := pgtype.NewMap()
445
446 var n *int32
447 err := m.Scan(pgtype.TextOID, pgx.TextFormatCode, nil, &n)
448 assert.NoError(t, err)
449 assert.Nil(t, n)
450
451 var pn pgtype.Int4
452 err = m.Scan(pgtype.TextOID, pgx.TextFormatCode, nil, &pn)
453 assert.NoError(t, err)
454 assert.False(t, pn.Valid)
455 }
456
457 func TestScanToSliceOfRenamedUint8(t *testing.T) {
458 m := pgtype.NewMap()
459 var ruint8 []_uint8
460 err := m.Scan(pgtype.Int2ArrayOID, pgx.TextFormatCode, []byte("{2,4}"), &ruint8)
461 assert.NoError(t, err)
462 assert.Equal(t, []_uint8{2, 4}, ruint8)
463 }
464
465 func TestMapScanTextToBool(t *testing.T) {
466 tests := []struct {
467 name string
468 src []byte
469 want bool
470 }{
471 {"t", []byte("t"), true},
472 {"f", []byte("f"), false},
473 {"y", []byte("y"), true},
474 {"n", []byte("n"), false},
475 {"1", []byte("1"), true},
476 {"0", []byte("0"), false},
477 {"true", []byte("true"), true},
478 {"false", []byte("false"), false},
479 {"yes", []byte("yes"), true},
480 {"no", []byte("no"), false},
481 {"on", []byte("on"), true},
482 {"off", []byte("off"), false},
483 }
484
485 for _, tt := range tests {
486 t.Run(tt.name, func(t *testing.T) {
487 m := pgtype.NewMap()
488
489 var v bool
490 err := m.Scan(pgtype.BoolOID, pgx.TextFormatCode, tt.src, &v)
491 require.NoError(t, err)
492 assert.Equal(t, tt.want, v)
493 })
494 }
495 }
496
497 func TestMapScanTextToBoolError(t *testing.T) {
498 tests := []struct {
499 name string
500 src []byte
501 want string
502 }{
503 {"nil", nil, "cannot scan NULL into *bool"},
504 {"empty", []byte{}, "cannot scan empty string into *bool"},
505 {"foo", []byte("foo"), "unknown boolean string representation \"foo\""},
506 }
507
508 for _, tt := range tests {
509 t.Run(tt.name, func(t *testing.T) {
510 m := pgtype.NewMap()
511
512 var v bool
513 err := m.Scan(pgtype.BoolOID, pgx.TextFormatCode, tt.src, &v)
514 require.ErrorContains(t, err, tt.want)
515 })
516 }
517 }
518
519 type databaseValuerUUID [16]byte
520
521 func (v databaseValuerUUID) Value() (driver.Value, error) {
522 return fmt.Sprintf("%x", v), nil
523 }
524
525
526 func TestMapEncodePlanCacheUUIDTypeConfusion(t *testing.T) {
527 expected := []byte{
528 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0xb, 0x86, 0, 0, 0, 2, 0, 0, 0, 1,
529 0, 0, 0, 16,
530 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
531 0, 0, 0, 16,
532 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
533
534 m := pgtype.NewMap()
535 buf, err := m.Encode(pgtype.UUIDArrayOID, pgtype.BinaryFormatCode,
536 []databaseValuerUUID{{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, {15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1}},
537 nil)
538 require.NoError(t, err)
539 require.Equal(t, expected, buf)
540
541
542
543 _, err = m.Encode(pgtype.UUIDArrayOID, pgtype.BinaryFormatCode,
544 []string{"00010203-0405-0607-0809-0a0b0c0d0e0f", "0f0e0d0c-0b0a-0908-0706-0504-03020100"},
545 nil)
546 require.Error(t, err)
547 }
548
549
550 func TestMapEncodeRawJSONIntoUnknownOID(t *testing.T) {
551 m := pgtype.NewMap()
552 buf, err := m.Encode(0, pgtype.TextFormatCode, json.RawMessage(`{"foo": "bar"}`), nil)
553 require.NoError(t, err)
554 require.Equal(t, []byte(`{"foo": "bar"}`), buf)
555 }
556
557 func BenchmarkMapScanInt4IntoBinaryDecoder(b *testing.B) {
558 m := pgtype.NewMap()
559 src := []byte{0, 0, 0, 42}
560 var v pgtype.Int4
561
562 for i := 0; i < b.N; i++ {
563 v = pgtype.Int4{}
564 err := m.Scan(pgtype.Int4OID, pgtype.BinaryFormatCode, src, &v)
565 if err != nil {
566 b.Fatal(err)
567 }
568 if v != (pgtype.Int4{Int32: 42, Valid: true}) {
569 b.Fatal("scan failed due to bad value")
570 }
571 }
572 }
573
574 func BenchmarkMapScanInt4IntoGoInt32(b *testing.B) {
575 m := pgtype.NewMap()
576 src := []byte{0, 0, 0, 42}
577 var v int32
578
579 for i := 0; i < b.N; i++ {
580 v = 0
581 err := m.Scan(pgtype.Int4OID, pgtype.BinaryFormatCode, src, &v)
582 if err != nil {
583 b.Fatal(err)
584 }
585 if v != 42 {
586 b.Fatal("scan failed due to bad value")
587 }
588 }
589 }
590
591 func BenchmarkScanPlanScanInt4IntoBinaryDecoder(b *testing.B) {
592 m := pgtype.NewMap()
593 src := []byte{0, 0, 0, 42}
594 var v pgtype.Int4
595
596 plan := m.PlanScan(pgtype.Int4OID, pgtype.BinaryFormatCode, &v)
597
598 for i := 0; i < b.N; i++ {
599 v = pgtype.Int4{}
600 err := plan.Scan(src, &v)
601 if err != nil {
602 b.Fatal(err)
603 }
604 if v != (pgtype.Int4{Int32: 42, Valid: true}) {
605 b.Fatal("scan failed due to bad value")
606 }
607 }
608 }
609
610 func BenchmarkScanPlanScanInt4IntoGoInt32(b *testing.B) {
611 m := pgtype.NewMap()
612 src := []byte{0, 0, 0, 42}
613 var v int32
614
615 plan := m.PlanScan(pgtype.Int4OID, pgtype.BinaryFormatCode, &v)
616
617 for i := 0; i < b.N; i++ {
618 v = 0
619 err := plan.Scan(src, &v)
620 if err != nil {
621 b.Fatal(err)
622 }
623 if v != 42 {
624 b.Fatal("scan failed due to bad value")
625 }
626 }
627 }
628
629 func isExpectedEq(a any) func(any) bool {
630 return func(v any) bool {
631 return a == v
632 }
633 }
634
View as plain text