diff --git a/composite_test.go b/composite_test.go index b38cdd45..3e63151c 100644 --- a/composite_test.go +++ b/composite_test.go @@ -7,63 +7,11 @@ import ( "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() { +//ExampleComposite demonstrates use of Row() function to pass and receive +// back composite types without creating boilderplate custom types. +func Example_composite() { conn, err := pgx.Connect(context.Background(), os.Getenv("PGX_TEST_DATABASE")) E(err) @@ -77,43 +25,34 @@ create type mytype as ( 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")). + err = conn.QueryRow(context.Background(), "select $1::mytype", + pgtype.Row(2, "bar")). Scan(pgtype.Row(&isNull, &a, &b)) E(err) - fmt.Printf("Adhoc: isNull=%v a=%d b=%s", isNull, a, *b) + fmt.Printf("First: isNull=%v a=%d b=%s\n", isNull, a, *b) + + err = conn.QueryRow(context.Background(), "select (1, NULL)::mytype").Scan(pgtype.Row(&isNull, &a, &b)) + E(err) + + fmt.Printf("Second: isNull=%v a=%d b=%v\n", isNull, a, b) + + err = conn.QueryRow(context.Background(), "select NULL::mytype").Scan(pgtype.Row(&isNull, &a, &b)) + E(err) + + fmt.Printf("Third: isNull=%v\n", isNull) // Output: - // First row: a=1 b=foo - // Second row: - // Adhoc: isNull=false a=2 b=bar + // First: isNull=false a=2 b=bar + // Second: isNull=false a=1 b= + // Third: isNull=true } diff --git a/custom_composite_test.go b/custom_composite_test.go new file mode 100644 index 00000000..61ea91c5 --- /dev/null +++ b/custom_composite_test.go @@ -0,0 +1,101 @@ +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) + } +} + +// ExampleCustomCompositeTypes demonstrates how support for custom types mappable to SQL +// composites can be added. +func Example_customCompositeTypes() { + 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) + + // Output: + // First row: a=1 b=foo + // Second row: +}