diff --git a/pgxpool/rows.go b/pgxpool/rows.go index 59279bc2..a9765d08 100644 --- a/pgxpool/rows.go +++ b/pgxpool/rows.go @@ -17,6 +17,7 @@ func (errRows) FieldDescriptions() []pgproto3.FieldDescription { return nil } func (errRows) Next() bool { return false } func (e errRows) Scan(dest ...interface{}) error { return e.err } func (e errRows) Values() ([]interface{}, error) { return nil, e.err } +func (e errRows) RawValues() [][]byte { return nil } type errRow struct { err error @@ -81,6 +82,10 @@ func (rows *poolRows) Values() ([]interface{}, error) { return values, err } +func (rows *poolRows) RawValues() [][]byte { + return rows.r.RawValues() +} + type poolRow struct { r pgx.Row c *Conn diff --git a/query_test.go b/query_test.go index 8093d017..431135b5 100644 --- a/query_test.go +++ b/query_test.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "reflect" + "strconv" "strings" "testing" "time" @@ -300,6 +301,39 @@ func TestRowsScanDoesNotAllowScanningBinaryFormatValuesIntoString(t *testing.T) ensureConnValid(t, conn) } +func TestConnQueryRawValues(t *testing.T) { + t.Parallel() + + conn := mustConnectString(t, os.Getenv("PGX_TEST_DATABASE")) + defer closeConn(t, conn) + + var rowCount int32 + + rows, err := conn.Query( + context.Background(), + "select 'foo'::text, 'bar'::varchar, n, null, n from generate_series(1,$1) n", + pgx.QuerySimpleProtocol(true), + 10, + ) + require.NoError(t, err) + defer rows.Close() + + for rows.Next() { + rowCount++ + + rawValues := rows.RawValues() + assert.Len(t, rawValues, 5) + assert.Equal(t, "foo", string(rawValues[0])) + assert.Equal(t, "bar", string(rawValues[1])) + assert.Equal(t, strconv.FormatInt(int64(rowCount), 10), string(rawValues[2])) + assert.Nil(t, rawValues[3]) + assert.Equal(t, strconv.FormatInt(int64(rowCount), 10), string(rawValues[4])) + } + + require.NoError(t, rows.Err()) + assert.EqualValues(t, 10, rowCount) +} + // Test that a connection stays valid when query results are closed early func TestConnQueryCloseEarly(t *testing.T) { t.Parallel() diff --git a/rows.go b/rows.go index 7b3a5f17..379bdc7a 100644 --- a/rows.go +++ b/rows.go @@ -40,8 +40,12 @@ type Rows interface { // copy the raw bytes received from PostgreSQL. nil will skip the value entirely. Scan(dest ...interface{}) error - // Values returns an array of the row values + // Values returns the decoded row values. Values() ([]interface{}, error) + + // RawValues returns the unparsed bytes of the row values. The returned [][]byte is only valid until the next Next + // call or the Rows is closed. However, the underlying byte data is safe to retain a reference to and mutate. + RawValues() [][]byte } // Row is a convenience wrapper over Rows that is returned by QueryRow. @@ -249,6 +253,10 @@ func (rows *connRows) Values() ([]interface{}, error) { return values, rows.Err() } +func (rows *connRows) RawValues() [][]byte { + return rows.resultReader.Values() +} + type scanArgError struct { col int err error