Fallback to other format when encoding query arguments

The preferred format may not be possible for certain arguments. For
example, the preferred format for numeric is binary. But if
shopspring/decimal is being used without jackc/pgx-shopspring-decimal
then it will use the database/sql/driver.Valuer interface. This will
return a string. That string should be sent in the text format.

A similar case occurs when encoding a []string into a non-text
PostgreSQL array such as uuid[].
v5-dev
Jack Christensen 2022-08-22 20:06:42 -05:00
parent ae65a8007b
commit 0d5d8e0137
3 changed files with 75 additions and 3 deletions

View File

@ -51,14 +51,33 @@ func (eqb *ExtendedQueryBuilder) Build(m *pgtype.Map, sd *pgconn.StatementDescri
// must be an untyped nil.
func (eqb *ExtendedQueryBuilder) appendParam(m *pgtype.Map, oid uint32, format int16, arg any) error {
if format == -1 {
format = eqb.chooseParameterFormatCode(m, oid, arg)
preferredFormat := eqb.chooseParameterFormatCode(m, oid, arg)
preferredErr := eqb.appendParam(m, oid, preferredFormat, arg)
if preferredErr == nil {
return nil
}
var otherFormat int16
if preferredFormat == TextFormatCode {
otherFormat = BinaryFormatCode
} else {
otherFormat = TextFormatCode
}
otherErr := eqb.appendParam(m, oid, otherFormat, arg)
if otherErr == nil {
return nil
}
return preferredErr // return the error from the preferred format
}
eqb.ParamFormats = append(eqb.ParamFormats, format)
v, err := eqb.encodeExtendedParamValue(m, oid, format, arg)
if err != nil {
return err
}
eqb.ParamFormats = append(eqb.ParamFormats, format)
eqb.ParamValues = append(eqb.ParamValues, v)
return nil

View File

@ -2,6 +2,8 @@ package pgtype_test
import (
"context"
"encoding/hex"
"strings"
"testing"
pgx "github.com/jackc/pgx/v5"
@ -124,6 +126,37 @@ func TestArrayCodecAnySlice(t *testing.T) {
})
}
// https://github.com/jackc/pgx/issues/1273#issuecomment-1218262703
func TestArrayCodecSliceArgConversion(t *testing.T) {
defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
arg := []string{
"3ad95bfd-ecea-4032-83c3-0c823cafb372",
"951baf11-c0cc-4afc-a779-abff0611dbf1",
"8327f244-7e2f-45e7-a10b-fbdc9d6f3378",
}
var expected []pgtype.UUID
for _, s := range arg {
buf, err := hex.DecodeString(strings.ReplaceAll(s, "-", ""))
require.NoError(t, err)
var u pgtype.UUID
copy(u.Bytes[:], buf)
u.Valid = true
expected = append(expected, u)
}
var actual []pgtype.UUID
err := conn.QueryRow(
ctx,
"select $1::uuid[]",
arg,
).Scan(&actual)
require.NoError(t, err)
require.Equal(t, expected, actual)
})
}
func TestArrayCodecDecodeValue(t *testing.T) {
defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, _ testing.TB, conn *pgx.Conn) {
for _, tt := range []struct {

View File

@ -1165,7 +1165,7 @@ func TestConnQueryDatabaseSQLDriverValuerWithAutoGeneratedPointerReceiver(t *tes
ensureConnValid(t, conn)
}
func TestConnQueryDatabaseSQLDriverValuerWithBinaryPgTypeThatAcceptsSameType(t *testing.T) {
func TestConnQueryDatabaseSQLDriverScannerWithBinaryPgTypeThatAcceptsSameType(t *testing.T) {
t.Parallel()
conn := mustConnectString(t, os.Getenv("PGX_TEST_DATABASE"))
@ -1181,6 +1181,26 @@ func TestConnQueryDatabaseSQLDriverValuerWithBinaryPgTypeThatAcceptsSameType(t *
ensureConnValid(t, conn)
}
// https://github.com/jackc/pgx/issues/1273#issuecomment-1221672175
func TestConnQueryDatabaseSQLDriverValuerTextWhenBinaryIsPreferred(t *testing.T) {
t.Parallel()
conn := mustConnectString(t, os.Getenv("PGX_TEST_DATABASE"))
defer closeConn(t, conn)
arg := sql.NullString{String: "1.234", Valid: true}
var result pgtype.Numeric
err := conn.QueryRow(context.Background(), "select $1::numeric", arg).Scan(&result)
require.NoError(t, err)
require.True(t, result.Valid)
f64, err := result.Float64Value()
require.NoError(t, err)
require.Equal(t, pgtype.Float8{Float64: 1.234, Valid: true}, f64)
ensureConnValid(t, conn)
}
func TestConnQueryDatabaseSQLNullX(t *testing.T) {
t.Parallel()