pgx is a pure Go driver and toolkit for PostgreSQL providing a native high-performance interface with PostgreSQL-specific features plus a database/sql compatibility adapter.
—
Working with PostgreSQL enums, composites, domains, and custom types.
import "github.com/jackc/pgx/v5"
import "github.com/jackc/pgx/v5/pgtype"func (c *Conn) LoadType(ctx context.Context, typeName string) (*pgtype.Type, error)
func (c *Conn) LoadTypes(ctx context.Context, typeNames []string) ([]*pgtype.Type, error)Supported types: array, composite, domain, enum, range, multirange types. Requires all dependent types to be already registered.
// Load and register a custom enum type
t, err := conn.LoadType(ctx, "my_status_enum")
if err != nil {
return err
}
conn.TypeMap().RegisterType(t)
// Load array of the enum
t2, err := conn.LoadType(ctx, "_my_status_enum") // underscore prefix = array type
if err != nil {
return err
}
conn.TypeMap().RegisterType(t2)
// Now you can use the type
var status string
err = conn.QueryRow(ctx, "SELECT status FROM orders WHERE id = $1", orderID).Scan(&status)// Load multiple types (handles dependencies automatically)
types, err := conn.LoadTypes(ctx, []string{"foo", "_foo", "bar", "_bar"})
if err != nil {
return err
}
for _, t := range types {
conn.TypeMap().RegisterType(t)
}For connection pools, register types in the AfterConnect hook:
config, err := pgxpool.ParseConfig(connString)
config.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error {
t, err := conn.LoadType(ctx, "my_status_enum")
if err != nil {
return err
}
conn.TypeMap().RegisterType(t)
// Load array type
arrType, err := conn.LoadType(ctx, "_my_status_enum")
if err != nil {
return err
}
conn.TypeMap().RegisterType(arrType)
return nil
}
pool, err := pgxpool.NewWithConfig(ctx, config)// Given PostgreSQL composite type:
// CREATE TYPE person AS (name TEXT, age INT);
type Person struct {
Name string
Age int32
}
// Load the type
t, err := conn.LoadType(ctx, "person")
conn.TypeMap().RegisterType(t)
// Query
var p Person
err = conn.QueryRow(ctx, "SELECT person_col FROM people WHERE id = $1", id).Scan(&p)person := Person{Name: "Alice", Age: 30}
_, err := conn.Exec(ctx, "INSERT INTO people (person_col) VALUES ($1)", person)For manual composite encoding/decoding:
func NewCompositeBinaryBuilder(m *Map, buf []byte) *CompositeBinaryBuilder
func (b *CompositeBinaryBuilder) AppendValue(oid uint32, field any)
func (b *CompositeBinaryBuilder) Finish() ([]byte, error)
func NewCompositeBinaryScanner(m *Map, src []byte) *CompositeBinaryScanner
func (cs *CompositeBinaryScanner) Next() bool
func (cs *CompositeBinaryScanner) OID() uint32
func (cs *CompositeBinaryScanner) Bytes() []byte
func (cs *CompositeBinaryScanner) IsNull() bool
func (cs *CompositeBinaryScanner) Err() error
func NewCompositeTextBuilder(m *Map, buf []byte) *CompositeTextBuilder
func (b *CompositeTextBuilder) AppendValue(oid uint32, field any)
func (b *CompositeTextBuilder) Finish() ([]byte, error)
func NewCompositeTextScanner(m *Map, src []byte) *CompositeTextScanner
func (cfs *CompositeTextScanner) Next() bool
func (cfs *CompositeTextScanner) Bytes() []byte
func (cfs *CompositeTextScanner) Err() errortype CompositeCodecField struct {
Name string
Type *Type
}
type CompositeIndexGetter interface {
IsNull() bool
Index(i int) any
}
type CompositeIndexScanner interface {
ScanNull() error
ScanIndex(i int) any
}Domain types are treated as their base type:
// Given: CREATE DOMAIN email AS TEXT CHECK (VALUE ~ '^.+@.+\..+$');
// Load domain type
t, err := conn.LoadType(ctx, "email")
conn.TypeMap().RegisterType(t)
// Use as string
var email string
err = conn.QueryRow(ctx, "SELECT email FROM users WHERE id = $1", id).Scan(&email)Implement Scanner/Valuer interfaces for custom Go types:
type Status string
const (
StatusPending Status = "pending"
StatusApproved Status = "approved"
StatusRejected Status = "rejected"
)
// Implement TextScanner
func (s *Status) ScanText(v pgtype.Text) error {
if !v.Valid {
*s = ""
return nil
}
*s = Status(v.String)
return nil
}
// Implement TextValuer
func (s Status) TextValue() (pgtype.Text, error) {
return pgtype.Text{String: string(s), Valid: s != ""}, nil
}
// Now you can scan directly
var status Status
err := conn.QueryRow(ctx, "SELECT status FROM orders WHERE id = $1", id).Scan(&status)For QueryExecModeExec or SimpleProtocol modes where OID is unknown:
conn.TypeMap().RegisterDefaultPgType(Status(""), "my_status_enum")type CompositeCodec struct {
Fields []CompositeCodecField
}Used for registering composite type codecs manually.
type EnumCodec struct{ /* unexported */ }Caches decoded strings to reduce allocations for types with small cardinality.
type RecordCodec struct{}Codec for generic PostgreSQL record type (binary decode only).
type SkipUnderlyingTypePlanner interface {
SkipUnderlyingTypePlan()
}Implement this empty method to prevent pgtype from treating a renamed type as its underlying type.
type MyCustomString string
func (MyCustomString) SkipUnderlyingTypePlan() {}type CompositeFields []any // for encoding composite values as slicesfunc GetAssignToDstType(dst any) (any, bool)
func NullAssignTo(dst any) errorCustom types loaded via LoadType are only loaded once per connection. Use AfterConnect hooks with pools to ensure all connections have the types registered.
Install with Tessl CLI
npx tessl i tessl/golang-github-com-jackc-pgx-v5