mirror of
https://github.com/jackc/pgx.git
synced 2025-05-03 14:09:42 +00:00
ScanRowValue is useful when reading ROW() values with known field types as well as composite types. It accepts pgtype.Value arguments, where ROW() fields are written to on successfull scan.
229 lines
5.6 KiB
Go
229 lines
5.6 KiB
Go
package pgtype_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/jackc/pgtype"
|
|
"github.com/jackc/pgtype/testutil"
|
|
"github.com/jackc/pgx/v4"
|
|
)
|
|
|
|
var recordTests = []struct {
|
|
sql string
|
|
expected pgtype.Record
|
|
}{
|
|
{
|
|
sql: `select row()`,
|
|
expected: pgtype.Record{
|
|
Fields: []pgtype.Value{},
|
|
Status: pgtype.Present,
|
|
},
|
|
},
|
|
{
|
|
sql: `select row('foo'::text, 42::int4)`,
|
|
expected: pgtype.Record{
|
|
Fields: []pgtype.Value{
|
|
&pgtype.Text{String: "foo", Status: pgtype.Present},
|
|
&pgtype.Int4{Int: 42, Status: pgtype.Present},
|
|
},
|
|
Status: pgtype.Present,
|
|
},
|
|
},
|
|
{
|
|
sql: `select row(100.0::float4, 1.09::float4)`,
|
|
expected: pgtype.Record{
|
|
Fields: []pgtype.Value{
|
|
&pgtype.Float4{Float: 100, Status: pgtype.Present},
|
|
&pgtype.Float4{Float: 1.09, Status: pgtype.Present},
|
|
},
|
|
Status: pgtype.Present,
|
|
},
|
|
},
|
|
{
|
|
sql: `select row('foo'::text, array[1, 2, null, 4]::int4[], 42::int4)`,
|
|
expected: pgtype.Record{
|
|
Fields: []pgtype.Value{
|
|
&pgtype.Text{String: "foo", Status: pgtype.Present},
|
|
&pgtype.Int4Array{
|
|
Elements: []pgtype.Int4{
|
|
{Int: 1, Status: pgtype.Present},
|
|
{Int: 2, Status: pgtype.Present},
|
|
{Status: pgtype.Null},
|
|
{Int: 4, Status: pgtype.Present},
|
|
},
|
|
Dimensions: []pgtype.ArrayDimension{{Length: 4, LowerBound: 1}},
|
|
Status: pgtype.Present,
|
|
},
|
|
&pgtype.Int4{Int: 42, Status: pgtype.Present},
|
|
},
|
|
Status: pgtype.Present,
|
|
},
|
|
},
|
|
{
|
|
sql: `select row(null)`,
|
|
expected: pgtype.Record{
|
|
Fields: []pgtype.Value{
|
|
&pgtype.Unknown{Status: pgtype.Null},
|
|
},
|
|
Status: pgtype.Present,
|
|
},
|
|
},
|
|
{
|
|
sql: `select null::record`,
|
|
expected: pgtype.Record{
|
|
Status: pgtype.Null,
|
|
},
|
|
},
|
|
}
|
|
|
|
// row values are binary compatible with records, so we test our helper
|
|
// routines here
|
|
func TestScanRowValue(t *testing.T) {
|
|
conn := testutil.MustConnectPgx(t)
|
|
defer testutil.MustCloseContext(t, conn)
|
|
|
|
for i := 0; i < len(recordTests); i++ {
|
|
tt := recordTests[i]
|
|
psName := fmt.Sprintf("test%d", i)
|
|
_, err := conn.Prepare(context.Background(), psName, tt.sql)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Run(tt.sql, func(t *testing.T) {
|
|
desc := append([]pgtype.Value(nil), tt.expected.Fields...)
|
|
|
|
var raw pgtype.GenericBinary
|
|
|
|
if err := conn.QueryRow(context.Background(), psName, pgx.QueryResultFormats{pgx.BinaryFormatCode}).Scan(&raw); err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
if raw.Status == pgtype.Null {
|
|
// ScanRowValue deals with complete rows only, NULL values (but NOT null fields)
|
|
// should be handled by the calling code
|
|
return
|
|
}
|
|
|
|
if err := pgtype.ScanRowValue(conn.ConnInfo(), raw.Bytes, desc...); err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
// borrow fields from a neighbor test, this makes scan always fail
|
|
desc = append([]pgtype.Value(nil), recordTests[(i+1)%len(recordTests)].expected.Fields...)
|
|
if err := pgtype.ScanRowValue(conn.ConnInfo(), raw.Bytes, desc...); err == nil {
|
|
t.Error("Matching scan didn't fail, despite fields not mathching query result")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRecordTranscode(t *testing.T) {
|
|
conn := testutil.MustConnectPgx(t)
|
|
defer testutil.MustCloseContext(t, conn)
|
|
|
|
for i, tt := range recordTests {
|
|
psName := fmt.Sprintf("test%d", i)
|
|
_, err := conn.Prepare(context.Background(), psName, tt.sql)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Run(tt.sql, func(t *testing.T) {
|
|
var result pgtype.Record
|
|
if err := conn.QueryRow(context.Background(), psName, pgx.QueryResultFormats{pgx.BinaryFormatCode}).Scan(&result); err != nil {
|
|
t.Errorf("%v", err)
|
|
return
|
|
}
|
|
|
|
if !reflect.DeepEqual(tt.expected, result) {
|
|
t.Errorf("expected %#v, got %#v", tt.expected, result)
|
|
}
|
|
})
|
|
|
|
}
|
|
}
|
|
|
|
func TestRecordWithUnknownOID(t *testing.T) {
|
|
conn := testutil.MustConnectPgx(t)
|
|
defer testutil.MustCloseContext(t, conn)
|
|
|
|
_, err := conn.Exec(context.Background(), `drop type if exists floatrange;
|
|
|
|
create type floatrange as range (
|
|
subtype = float8,
|
|
subtype_diff = float8mi
|
|
);`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer conn.Exec(context.Background(), "drop type floatrange")
|
|
|
|
var result pgtype.Record
|
|
err = conn.QueryRow(context.Background(), "select row('foo'::text, floatrange(1, 10), 'bar'::text)").Scan(&result)
|
|
if err == nil {
|
|
t.Errorf("expected error but none")
|
|
}
|
|
}
|
|
|
|
func TestRecordAssignTo(t *testing.T) {
|
|
var valueSlice []pgtype.Value
|
|
var interfaceSlice []interface{}
|
|
|
|
simpleTests := []struct {
|
|
src pgtype.Record
|
|
dst interface{}
|
|
expected interface{}
|
|
}{
|
|
{
|
|
src: pgtype.Record{
|
|
Fields: []pgtype.Value{
|
|
&pgtype.Text{String: "foo", Status: pgtype.Present},
|
|
&pgtype.Int4{Int: 42, Status: pgtype.Present},
|
|
},
|
|
Status: pgtype.Present,
|
|
},
|
|
dst: &valueSlice,
|
|
expected: []pgtype.Value{
|
|
&pgtype.Text{String: "foo", Status: pgtype.Present},
|
|
&pgtype.Int4{Int: 42, Status: pgtype.Present},
|
|
},
|
|
},
|
|
{
|
|
src: pgtype.Record{
|
|
Fields: []pgtype.Value{
|
|
&pgtype.Text{String: "foo", Status: pgtype.Present},
|
|
&pgtype.Int4{Int: 42, Status: pgtype.Present},
|
|
},
|
|
Status: pgtype.Present,
|
|
},
|
|
dst: &interfaceSlice,
|
|
expected: []interface{}{"foo", int32(42)},
|
|
},
|
|
{
|
|
src: pgtype.Record{Status: pgtype.Null},
|
|
dst: &valueSlice,
|
|
expected: (([]pgtype.Value)(nil)),
|
|
},
|
|
{
|
|
src: pgtype.Record{Status: pgtype.Null},
|
|
dst: &interfaceSlice,
|
|
expected: (([]interface{})(nil)),
|
|
},
|
|
}
|
|
|
|
for i, tt := range simpleTests {
|
|
err := tt.src.AssignTo(tt.dst)
|
|
if err != nil {
|
|
t.Errorf("%d: %v", i, err)
|
|
}
|
|
|
|
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); !reflect.DeepEqual(dst, tt.expected) {
|
|
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
|
}
|
|
}
|
|
}
|