Skip to content

Commit 1928a1d

Browse files
lieut-dataarp242
andauthored
Fix []byte types incorrectly converted to PostgreSQL array (#1252)
Fix []byte types incorrectly converted to PostgreSQL array CheckNamedValue was incorrectly converting byte slice types to PostgreSQL arrays. This affected: - *[]byte pointers (dereference would result in []byte hitting Slice case) - Named byte slice types like json.RawMessage The fix checks if the value's element type is uint8 after pointer dereferencing, which correctly handles both []byte and named types whose underlying type is []byte. Co-authored-by: Martin Tournoij <martin@arp242.net>
1 parent 9e2aa8e commit 1928a1d

File tree

3 files changed

+78
-5
lines changed

3 files changed

+78
-5
lines changed

conn.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -825,14 +825,18 @@ func (cn *conn) CheckNamedValue(nv *driver.NamedValue) error {
825825
return driver.ErrSkip
826826
}
827827

828-
// Ignoring []byte / []uint8.
829-
if _, ok := nv.Value.([]uint8); ok {
828+
v := reflect.ValueOf(nv.Value)
829+
if !v.IsValid() {
830830
return driver.ErrSkip
831831
}
832+
t := v.Type()
833+
for t.Kind() == reflect.Ptr {
834+
t, v = t.Elem(), v.Elem()
835+
}
832836

833-
v := reflect.ValueOf(nv.Value)
834-
if v.Kind() == reflect.Ptr {
835-
v = v.Elem()
837+
// Ignore []byte and related types: *[]byte, json.RawMessage, etc.
838+
if t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 {
839+
return driver.ErrSkip
836840
}
837841

838842
switch v.Kind() {

conn_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package pq
22

33
import (
4+
"bytes"
45
"context"
56
"database/sql"
67
"database/sql/driver"
8+
"encoding/json"
79
"errors"
810
"fmt"
911
"io"
@@ -2166,6 +2168,66 @@ func TestUint64(t *testing.T) {
21662168
}
21672169
}
21682170

2171+
func TestBytea(t *testing.T) {
2172+
tests := []struct {
2173+
in any
2174+
want string
2175+
}{
2176+
{[]byte{0x00, 0x01, 0x02, 0xff},
2177+
`[]map[string][]uint8{map[string][]uint8{"b":[]uint8{0x0, 0x1, 0x2, 0xff}}}`},
2178+
{[]byte(nil),
2179+
`[]map[string][]uint8{map[string][]uint8{"b":[]uint8(nil)}}`},
2180+
{json.RawMessage(`{"key":"value"}`),
2181+
`[]map[string][]uint8{map[string][]uint8{"b":[]uint8{0x7b, 0x22, 0x6b, 0x65, 0x79, 0x22, 0x3a, 0x22, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x7d}}}`},
2182+
{pqtest.Ptr(pqtest.Ptr([]byte{0x00, 0x01, 0x02, 0xff})),
2183+
`[]map[string][]uint8{map[string][]uint8{"b":[]uint8{0x0, 0x1, 0x2, 0xff}}}`},
2184+
}
2185+
2186+
for _, tt := range tests {
2187+
tt := tt
2188+
t.Run("", func(t *testing.T) {
2189+
db := pqtest.MustDB(t)
2190+
pqtest.Exec(t, db, `create temp table tbl (b bytea)`)
2191+
pqtest.Exec(t, db, `insert into tbl values ($1)`, &tt.in)
2192+
rows := pqtest.Query[[]byte](t, db, `select b from tbl`)
2193+
if have := fmt.Sprintf("%#v", rows); have != tt.want {
2194+
t.Fatalf("\nhave: %s\nwant: %s", have, tt.want)
2195+
}
2196+
})
2197+
}
2198+
}
2199+
2200+
func TestJSONRawMessage(t *testing.T) {
2201+
db := pqtest.MustDB(t)
2202+
2203+
pqtest.Exec(t, db, `create temp table tbl (j json)`)
2204+
2205+
// Test json.RawMessage (a named []byte type) is correctly stored as JSON,
2206+
// not converted to a PostgreSQL array. This was a bug in CheckNamedValue
2207+
// where named byte slice types would hit the reflect.Slice case and get
2208+
// incorrectly converted to a PostgreSQL array.
2209+
data := json.RawMessage(`{"key":"value"}`)
2210+
pqtest.Exec(t, db, `insert into tbl values ($1)`, data)
2211+
2212+
rows, err := db.Query("select j from tbl")
2213+
if err != nil {
2214+
t.Fatal(err)
2215+
}
2216+
defer rows.Close()
2217+
2218+
if rows.Next() {
2219+
var j json.RawMessage
2220+
err := rows.Scan(&j)
2221+
if err != nil {
2222+
t.Fatal(err)
2223+
}
2224+
2225+
if !bytes.Equal(j, data) {
2226+
t.Fatalf("json mismatch\nhave: %s\nwant: %s", j, data)
2227+
}
2228+
}
2229+
}
2230+
21692231
func TestPreProtocolError(t *testing.T) {
21702232
tests := []struct {
21712233
name string

internal/pqtest/pqtest.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ func InvalidCertificate(err error) bool {
5151
return false
5252
}
5353

54+
// Ptr gets a pointer to any value.
55+
//
56+
// TODO: replace with new(..) once pq requires Go 1.26.
57+
func Ptr[T any](t T) *T {
58+
return &t
59+
}
60+
5461
var envOnce sync.Once
5562

5663
func DSN(conninfo string) string {

0 commit comments

Comments
 (0)