1
0
mirror of https://github.com/jackc/pgx.git synced 2025-04-27 21:25:53 +00:00

pgtype array: Fix encoding of vtab \v

Arrays with values that start or end with vtab ("\v") must be quoted.
Postgres's array parser skips leading and trailing whitespace with
the array_isspace() function, which is slightly different from the
scanner_isspace() function that was previously linked. Add a test
that reproduces this failure, and fix the definition of isSpace.

This also includes a change to use strings.EqualFold which should
really not matter, but does not require copying the string.
This commit is contained in:
Evan Jones 2023-06-17 11:25:41 -04:00 committed by Jack Christensen
parent 5b7cc8e215
commit e5db6a0467
2 changed files with 33 additions and 19 deletions

@ -363,12 +363,13 @@ func quoteArrayElement(src string) string {
} }
func isSpace(ch byte) bool { func isSpace(ch byte) bool {
// see https://github.com/postgres/postgres/blob/REL_12_STABLE/src/backend/parser/scansup.c#L224 // see array_isspace:
return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '\f' // https://github.com/postgres/postgres/blob/master/src/backend/utils/adt/arrayfuncs.c
return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '\v' || ch == '\f'
} }
func quoteArrayElementIfNeeded(src string) string { func quoteArrayElementIfNeeded(src string) string {
if src == "" || (len(src) == 4 && strings.ToLower(src) == "null") || isSpace(src[0]) || isSpace(src[len(src)-1]) || strings.ContainsAny(src, `{},"\`) { if src == "" || (len(src) == 4 && strings.EqualFold(src, "null")) || isSpace(src[0]) || isSpace(src[len(src)-1]) || strings.ContainsAny(src, `{},"\`) {
return quoteArrayElement(src) return quoteArrayElement(src)
} }
return src return src

@ -3,6 +3,7 @@ package pgtype_test
import ( import (
"context" "context"
"encoding/hex" "encoding/hex"
"reflect"
"strings" "strings"
"testing" "testing"
@ -51,23 +52,35 @@ func TestArrayCodec(t *testing.T) {
}) })
} }
func TestArrayCodecFlatArray(t *testing.T) { func TestArrayCodecFlatArrayString(t *testing.T) {
testCases := []struct {
input []string
}{
{nil},
{[]string{}},
{[]string{"a"}},
{[]string{"a", "b"}},
// previously had a bug with whitespace handling
{[]string{"\v", "\t", "\n", "\r", "\f", " "}},
{[]string{"a\vb", "a\tb", "a\nb", "a\rb", "a\fb", "a b"}},
}
queryModes := []pgx.QueryExecMode{pgx.QueryExecModeSimpleProtocol, pgx.QueryExecModeDescribeExec}
defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
for i, tt := range []struct { for i, testCase := range testCases {
expected any for _, queryMode := range queryModes {
}{ var out []string
{pgtype.FlatArray[int32](nil)}, err := conn.QueryRow(ctx, "select $1::text[]", queryMode, testCase.input).Scan(&out)
{pgtype.FlatArray[int32]{}}, if err != nil {
{pgtype.FlatArray[int32]{1, 2, 3}}, t.Fatalf("i=%d input=%#v queryMode=%s: Scan failed: %s",
} { i, testCase.input, queryMode, err)
var actual pgtype.FlatArray[int32] }
err := conn.QueryRow( if !reflect.DeepEqual(out, testCase.input) {
ctx, t.Errorf("i=%d input=%#v queryMode=%s: not equal output=%#v",
"select $1::int[]", i, testCase.input, queryMode, out)
tt.expected, }
).Scan(&actual) }
assert.NoErrorf(t, err, "%d", i)
assert.Equalf(t, tt.expected, actual, "%d", i)
} }
}) })
} }