Skip to content

Commit

Permalink
pgtype array: Fix encoding of vtab \v
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
evanj authored and jackc committed Jun 17, 2023
1 parent 5b7cc8e commit e5db6a0
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 19 deletions.
7 changes: 4 additions & 3 deletions pgtype/array.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,12 +363,13 @@ func quoteArrayElement(src string) string {
}

func isSpace(ch byte) bool {
// see https://github.com/postgres/postgres/blob/REL_12_STABLE/src/backend/parser/scansup.c#L224
return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '\f'
// see array_isspace:
// 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 {
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 src
Expand Down
45 changes: 29 additions & 16 deletions pgtype/array_codec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package pgtype_test
import (
"context"
"encoding/hex"
"reflect"
"strings"
"testing"

Expand Down Expand Up @@ -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) {
for i, tt := range []struct {
expected any
}{
{pgtype.FlatArray[int32](nil)},
{pgtype.FlatArray[int32]{}},
{pgtype.FlatArray[int32]{1, 2, 3}},
} {
var actual pgtype.FlatArray[int32]
err := conn.QueryRow(
ctx,
"select $1::int[]",
tt.expected,
).Scan(&actual)
assert.NoErrorf(t, err, "%d", i)
assert.Equalf(t, tt.expected, actual, "%d", i)
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)
}
}
}
})
}
Expand Down

0 comments on commit e5db6a0

Please sign in to comment.