From 34da2fed9570ec3ff22dfa181323ef9cf702b2ca Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Sat, 3 Feb 2024 09:49:56 -0600 Subject: [PATCH] Improve CopyFrom auto-conversion of text-ish values CopyFrom requires that all values are encoded in the binary format. It already tried to parse strings to values that can then be encoded into the binary format. But it didn't handle types that can be encoded as text and then parsed and converted to binary. It now does. --- copy_from_test.go | 34 ++++++++++++++++++++++++++++++++++ values.go | 6 +++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/copy_from_test.go b/copy_from_test.go index 9da23c04f..423337e45 100644 --- a/copy_from_test.go +++ b/copy_from_test.go @@ -803,6 +803,40 @@ func TestConnCopyFromAutomaticStringConversion(t *testing.T) { ensureConnValid(t, conn) } +// https://github.com/jackc/pgx/discussions/1891 +func TestConnCopyFromAutomaticStringConversionArray(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) + defer cancel() + + conn := mustConnectString(t, os.Getenv("PGX_TEST_DATABASE")) + defer closeConn(t, conn) + + mustExec(t, conn, `create temporary table foo( + a numeric[] + )`) + + inputRows := [][]interface{}{ + {[]string{"42"}}, + {[]string{"7"}}, + {[]string{"8", "9"}}, + {[][]string{{"10", "11"}, {"12", "13"}}}, + } + + copyCount, err := conn.CopyFrom(ctx, pgx.Identifier{"foo"}, []string{"a"}, pgx.CopyFromRows(inputRows)) + require.NoError(t, err) + require.EqualValues(t, len(inputRows), copyCount) + + // Test reads as int64 and flattened array for simplicity. + rows, _ := conn.Query(ctx, "select * from foo") + nums, err := pgx.CollectRows(rows, pgx.RowTo[[]int64]) + require.NoError(t, err) + require.Equal(t, [][]int64{{42}, {7}, {8, 9}, {10, 11, 12, 13}}, nums) + + ensureConnValid(t, conn) +} + func TestCopyFromFunc(t *testing.T) { t.Parallel() diff --git a/values.go b/values.go index 19c642fa9..cab717d0a 100644 --- a/values.go +++ b/values.go @@ -55,7 +55,11 @@ func encodeCopyValue(m *pgtype.Map, buf []byte, oid uint32, arg any) ([]byte, er func tryScanStringCopyValueThenEncode(m *pgtype.Map, buf []byte, oid uint32, arg any) ([]byte, error) { s, ok := arg.(string) if !ok { - return nil, errors.New("not a string") + textBuf, err := m.Encode(oid, TextFormatCode, arg, nil) + if err != nil { + return nil, errors.New("not a string and cannot be encoded as text") + } + s = string(textBuf) } var v any