pgx/composite_test.go
Maxim Ivanov 3ce29f9e05 Add Composite type for inplace row() values handling
Composite() function returns a private type, which should
be registered with ConnInfo.RegisterDataType for the composite
type's OID.

All subsequent interaction with Composite types is to be done
via Row(...) function. Function return value can be either
passed as a query argument to build SQL composite value out of
individual fields or passed to Scan to read SQL composite value
back.

When passed to Scan, Row() should have first argument of type
*bool to flag NULL values returned from query.
2020-04-13 17:41:44 +01:00

120 lines
2.8 KiB
Go

package pgtype_test
import (
"context"
"fmt"
"os"
"github.com/jackc/pgtype"
pgx "github.com/jackc/pgx/v4"
errors "golang.org/x/xerrors"
)
type MyType struct {
a int32 // NULL will cause decoding error
b *string // there can be NULL in this position in SQL
}
func (dst *MyType) DecodeBinary(ci *pgtype.ConnInfo, src []byte) error {
if src == nil {
return errors.New("NULL values can't be decoded. Scan into a &*MyType to handle NULLs")
}
a := pgtype.Int4{}
b := pgtype.Text{}
if err := pgtype.ScanRowValue(ci, src, &a, &b); err != nil {
return err
}
// type compatibility is checked by AssignTo
// only lossless assignments will succeed
if err := a.AssignTo(&dst.a); err != nil {
return err
}
// AssignTo also deals with null value handling
if err := b.AssignTo(&dst.b); err != nil {
return err
}
return nil
}
func (src MyType) EncodeBinary(ci *pgtype.ConnInfo, buf []byte) (newBuf []byte, err error) {
a := pgtype.Int4{src.a, pgtype.Present}
var b pgtype.Text
if src.b != nil {
b = pgtype.Text{*src.b, pgtype.Present}
} else {
b = pgtype.Text{Status: pgtype.Null}
}
return pgtype.EncodeRow(ci, buf, &a, &b)
}
func ptrS(s string) *string {
return &s
}
func E(err error) {
if err != nil {
panic(err)
}
}
func Example_compositeTypes() {
conn, err := pgx.Connect(context.Background(), os.Getenv("PGX_TEST_DATABASE"))
E(err)
defer conn.Close(context.Background())
_, err = conn.Exec(context.Background(), `drop type if exists mytype;
create type mytype as (
a int4,
b text
);`)
E(err)
defer conn.Exec(context.Background(), "drop type mytype")
var result *MyType
// Demonstrates both passing and reading back composite values
err = conn.QueryRow(context.Background(), "select $1::mytype",
pgx.QueryResultFormats{pgx.BinaryFormatCode}, MyType{1, ptrS("foo")}).
Scan(&result)
E(err)
fmt.Printf("First row: a=%d b=%s\n", result.a, *result.b)
// Because we scan into &*MyType, NULLs are handled generically by assigning nil to result
err = conn.QueryRow(context.Background(), "select NULL::mytype", pgx.QueryResultFormats{pgx.BinaryFormatCode}).Scan(&result)
E(err)
fmt.Printf("Second row: %v\n", result)
//WIP
q, err := conn.Prepare(context.Background(), "z", "select $1::mytype")
E(err)
conn.ConnInfo().RegisterDataType(pgtype.DataType{pgtype.Composite(&pgtype.Int4{}, &pgtype.Text{}), "mytype", q.ParamOIDs[0]})
// Adhoc rows can be decoded inplace without boilerplate
// Composite types can be encoded/decoded inplace
var isNull bool
var a int
var b *string
err = conn.QueryRow(context.Background(), "select row(($1::mytype).a, ($1).b)",
pgx.QueryResultFormats{pgx.BinaryFormatCode}, pgtype.Row(2, "bar")).
Scan(pgtype.Row(&isNull, &a, &b))
E(err)
fmt.Printf("Adhoc: isNull=%v a=%d b=%s", isNull, a, *b)
// Output:
// First row: a=1 b=foo
// Second row: <nil>
// Adhoc: isNull=false a=2 b=bar
}