Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -825,14 +825,18 @@ func (cn *conn) CheckNamedValue(nv *driver.NamedValue) error {
return driver.ErrSkip
}

// Ignoring []byte / []uint8.
if _, ok := nv.Value.([]uint8); ok {
v := reflect.ValueOf(nv.Value)
if !v.IsValid() {
return driver.ErrSkip
}
t := v.Type()
for t.Kind() == reflect.Ptr {
t, v = t.Elem(), v.Elem()
}

v := reflect.ValueOf(nv.Value)
if v.Kind() == reflect.Ptr {
v = v.Elem()
// Ignore []byte and related types: *[]byte, json.RawMessage, etc.
if t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 {
return driver.ErrSkip
}

switch v.Kind() {
Expand Down
62 changes: 62 additions & 0 deletions conn_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package pq

import (
"bytes"
"context"
"database/sql"
"database/sql/driver"
"encoding/json"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -2166,6 +2168,66 @@ func TestUint64(t *testing.T) {
}
}

func TestBytea(t *testing.T) {
tests := []struct {
in any
want string
}{
{[]byte{0x00, 0x01, 0x02, 0xff},
`[]map[string][]uint8{map[string][]uint8{"b":[]uint8{0x0, 0x1, 0x2, 0xff}}}`},
{[]byte(nil),
`[]map[string][]uint8{map[string][]uint8{"b":[]uint8(nil)}}`},
{json.RawMessage(`{"key":"value"}`),
`[]map[string][]uint8{map[string][]uint8{"b":[]uint8{0x7b, 0x22, 0x6b, 0x65, 0x79, 0x22, 0x3a, 0x22, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x7d}}}`},
{pqtest.Ptr(pqtest.Ptr([]byte{0x00, 0x01, 0x02, 0xff})),
`[]map[string][]uint8{map[string][]uint8{"b":[]uint8{0x0, 0x1, 0x2, 0xff}}}`},
}

for _, tt := range tests {
tt := tt
t.Run("", func(t *testing.T) {
db := pqtest.MustDB(t)
pqtest.Exec(t, db, `create temp table tbl (b bytea)`)
pqtest.Exec(t, db, `insert into tbl values ($1)`, &tt.in)
rows := pqtest.Query[[]byte](t, db, `select b from tbl`)
if have := fmt.Sprintf("%#v", rows); have != tt.want {
t.Fatalf("\nhave: %s\nwant: %s", have, tt.want)
}
})
}
}

func TestJSONRawMessage(t *testing.T) {
db := pqtest.MustDB(t)

pqtest.Exec(t, db, `create temp table tbl (j json)`)

// Test json.RawMessage (a named []byte type) is correctly stored as JSON,
// not converted to a PostgreSQL array. This was a bug in CheckNamedValue
// where named byte slice types would hit the reflect.Slice case and get
// incorrectly converted to a PostgreSQL array.
data := json.RawMessage(`{"key":"value"}`)
pqtest.Exec(t, db, `insert into tbl values ($1)`, data)

rows, err := db.Query("select j from tbl")
if err != nil {
t.Fatal(err)
}
defer rows.Close()

if rows.Next() {
var j json.RawMessage
err := rows.Scan(&j)
if err != nil {
t.Fatal(err)
}

if !bytes.Equal(j, data) {
t.Fatalf("json mismatch\nhave: %s\nwant: %s", j, data)
}
}
}

func TestPreProtocolError(t *testing.T) {
tests := []struct {
name string
Expand Down
7 changes: 7 additions & 0 deletions internal/pqtest/pqtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ func InvalidCertificate(err error) bool {
return false
}

// Ptr gets a pointer to any value.
//
// TODO: replace with new(..) once pq requires Go 1.26.
func Ptr[T any](t T) *T {
return &t
}

var envOnce sync.Once

func DSN(conninfo string) string {
Expand Down
Loading