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.
pull/1651/head
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

View File

@ -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

View File

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