- update vendor

master
李光春 1 year ago
parent 60ce1d8dd1
commit bdd705368c

@ -49,7 +49,7 @@ require (
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.2.0 // indirect github.com/jackc/pgx/v5 v5.3.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect

@ -234,8 +234,9 @@ github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6
github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg= github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
github.com/jackc/pgx/v4 v4.11.0/go.mod h1:i62xJgdrtVDsnL3U8ekyrQXEwGNTRoG7/8r+CIdYfcc= github.com/jackc/pgx/v4 v4.11.0/go.mod h1:i62xJgdrtVDsnL3U8ekyrQXEwGNTRoG7/8r+CIdYfcc=
github.com/jackc/pgx/v4 v4.12.0/go.mod h1:fE547h6VulLPA3kySjfnSG/e2D861g/50JlVUa/ub60= github.com/jackc/pgx/v4 v4.12.0/go.mod h1:fE547h6VulLPA3kySjfnSG/e2D861g/50JlVUa/ub60=
github.com/jackc/pgx/v5 v5.2.0 h1:NdPpngX0Y6z6XDFKqmFQaE+bCtkqzvQIOt1wvBlAqs8=
github.com/jackc/pgx/v5 v5.2.0/go.mod h1:Ptn7zmohNsWEsdxRawMzk3gaKma2obW+NWTnKa0S4nk= github.com/jackc/pgx/v5 v5.2.0/go.mod h1:Ptn7zmohNsWEsdxRawMzk3gaKma2obW+NWTnKa0S4nk=
github.com/jackc/pgx/v5 v5.3.0 h1:/NQi8KHMpKWHInxXesC8yD4DhkXPrVhmnwYkjp9AmBA=
github.com/jackc/pgx/v5 v5.3.0/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=

@ -23,3 +23,5 @@ _testmain.go
.envrc .envrc
/.testdb /.testdb
.DS_Store

@ -1,3 +1,27 @@
# 5.3.0 (February 11, 2023)
* Fix: json values work with sql.Scanner
* Fixed / improved error messages (Mark Chambers and Yevgeny Pats)
* Fix: support scan into single dimensional arrays
* Fix: MaxConnLifetimeJitter setting actually jitter (Ben Weintraub)
* Fix: driver.Value representation of bytea should be []byte not string
* Fix: better handling of unregistered OIDs
* CopyFrom can use query cache to avoid extra round trip to get OIDs (Alejandro Do Nascimento Mora)
* Fix: encode to json ignoring driver.Valuer
* Support sql.Scanner on renamed base type
* Fix: pgtype.Numeric text encoding of negative numbers (Mark Chambers)
* Fix: connect with multiple hostnames when one can't be resolved
* Upgrade puddle to remove dependency on uber/atomic and fix alignment issue on 32-bit platform
* Fix: scanning json column into **string
* Multiple reductions in memory allocations
* Fake non-blocking read adapts its max wait time
* Improve CopyFrom performance and reduce memory usage
* Fix: encode []any to array
* Fix: LoadType for composite with dropped attributes (Felix Röhrich)
* Support v4 and v5 stdlib in same program
* Fix: text format array decoding with string of "NULL"
* Prefer binary format for arrays
# 5.2.0 (December 5, 2022) # 5.2.0 (December 5, 2022)
* `tracelog.TraceLog` implements the pgx.PrepareTracer interface. (Vitalii Solodilov) * `tracelog.TraceLog` implements the pgx.PrepareTracer interface. (Vitalii Solodilov)

@ -88,7 +88,7 @@ See CONTRIBUTING.md for setup instructions.
## Supported Go and PostgreSQL Versions ## Supported Go and PostgreSQL Versions
pgx supports the same versions of Go and PostgreSQL that are supported by their respective teams. For [Go](https://golang.org/doc/devel/release.html#policy) that is the two most recent major releases and for [PostgreSQL](https://www.postgresql.org/support/versioning/) the major releases in the last 5 years. This means pgx supports Go 1.18 and higher and PostgreSQL 11 and higher. pgx also is tested against the latest version of [CockroachDB](https://www.cockroachlabs.com/product/). pgx supports the same versions of Go and PostgreSQL that are supported by their respective teams. For [Go](https://golang.org/doc/devel/release.html#policy) that is the two most recent major releases and for [PostgreSQL](https://www.postgresql.org/support/versioning/) the major releases in the last 5 years. This means pgx supports Go 1.19 and higher and PostgreSQL 11 and higher. pgx also is tested against the latest version of [CockroachDB](https://www.cockroachlabs.com/product/).
## Version Policy ## Version Policy
@ -142,3 +142,12 @@ Library for scanning data from a database into Go structs and more.
### [https://github.com/otan/gopgkrb5](https://github.com/otan/gopgkrb5) ### [https://github.com/otan/gopgkrb5](https://github.com/otan/gopgkrb5)
Adds GSSAPI / Kerberos authentication support. Adds GSSAPI / Kerberos authentication support.
### [github.com/wcamarao/pmx](https://github.com/wcamarao/pmx)
Explicit data mapping and scanning library for Go structs and slices.
### [github.com/stephenafamo/scan](https://github.com/stephenafamo/scan)
Type safe and flexible package for scanning database data into Go types.
Supports, structs, maps, slices and custom mapping functions.

@ -197,17 +197,17 @@ func ParseConfigWithOptions(connString string, options ParseConfigOptions) (*Con
// ParseConfig creates a ConnConfig from a connection string. ParseConfig handles all options that pgconn.ParseConfig // ParseConfig creates a ConnConfig from a connection string. ParseConfig handles all options that pgconn.ParseConfig
// does. In addition, it accepts the following options: // does. In addition, it accepts the following options:
// //
// default_query_exec_mode // - default_query_exec_mode.
// Possible values: "cache_statement", "cache_describe", "describe_exec", "exec", and "simple_protocol". See // Possible values: "cache_statement", "cache_describe", "describe_exec", "exec", and "simple_protocol". See
// QueryExecMode constant documentation for the meaning of these values. Default: "cache_statement". // QueryExecMode constant documentation for the meaning of these values. Default: "cache_statement".
// //
// statement_cache_capacity // - statement_cache_capacity.
// The maximum size of the statement cache used when executing a query with "cache_statement" query exec mode. // The maximum size of the statement cache used when executing a query with "cache_statement" query exec mode.
// Default: 512. // Default: 512.
// //
// description_cache_capacity // - description_cache_capacity.
// The maximum size of the description cache used when executing a query with "cache_describe" query exec mode. // The maximum size of the description cache used when executing a query with "cache_describe" query exec mode.
// Default: 512. // Default: 512.
func ParseConfig(connString string) (*ConnConfig, error) { func ParseConfig(connString string) (*ConnConfig, error) {
return ParseConfigWithOptions(connString, ParseConfigOptions{}) return ParseConfigWithOptions(connString, ParseConfigOptions{})
} }
@ -721,43 +721,10 @@ optionLoop:
sd, explicitPreparedStatement := c.preparedStatements[sql] sd, explicitPreparedStatement := c.preparedStatements[sql]
if sd != nil || mode == QueryExecModeCacheStatement || mode == QueryExecModeCacheDescribe || mode == QueryExecModeDescribeExec { if sd != nil || mode == QueryExecModeCacheStatement || mode == QueryExecModeCacheDescribe || mode == QueryExecModeDescribeExec {
if sd == nil { if sd == nil {
switch mode { sd, err = c.getStatementDescription(ctx, mode, sql)
case QueryExecModeCacheStatement: if err != nil {
if c.statementCache == nil { rows.fatal(err)
err = errDisabledStatementCache return rows, err
rows.fatal(err)
return rows, err
}
sd = c.statementCache.Get(sql)
if sd == nil {
sd, err = c.Prepare(ctx, stmtcache.NextStatementName(), sql)
if err != nil {
rows.fatal(err)
return rows, err
}
c.statementCache.Put(sd)
}
case QueryExecModeCacheDescribe:
if c.descriptionCache == nil {
err = errDisabledDescriptionCache
rows.fatal(err)
return rows, err
}
sd = c.descriptionCache.Get(sql)
if sd == nil {
sd, err = c.Prepare(ctx, "", sql)
if err != nil {
rows.fatal(err)
return rows, err
}
c.descriptionCache.Put(sd)
}
case QueryExecModeDescribeExec:
sd, err = c.Prepare(ctx, "", sql)
if err != nil {
rows.fatal(err)
return rows, err
}
} }
} }
@ -827,6 +794,48 @@ optionLoop:
return rows, rows.err return rows, rows.err
} }
// getStatementDescription returns the statement description of the sql query
// according to the given mode.
//
// If the mode is one that doesn't require to know the param and result OIDs
// then nil is returned without error.
func (c *Conn) getStatementDescription(
ctx context.Context,
mode QueryExecMode,
sql string,
) (sd *pgconn.StatementDescription, err error) {
switch mode {
case QueryExecModeCacheStatement:
if c.statementCache == nil {
return nil, errDisabledStatementCache
}
sd = c.statementCache.Get(sql)
if sd == nil {
sd, err = c.Prepare(ctx, stmtcache.NextStatementName(), sql)
if err != nil {
return nil, err
}
c.statementCache.Put(sd)
}
case QueryExecModeCacheDescribe:
if c.descriptionCache == nil {
return nil, errDisabledDescriptionCache
}
sd = c.descriptionCache.Get(sql)
if sd == nil {
sd, err = c.Prepare(ctx, "", sql)
if err != nil {
return nil, err
}
c.descriptionCache.Put(sd)
}
case QueryExecModeDescribeExec:
return c.Prepare(ctx, "", sql)
}
return sd, err
}
// QueryRow is a convenience wrapper over Query. Any error that occurs while // QueryRow is a convenience wrapper over Query. Any error that occurs while
// querying is deferred until calling Scan on the returned Row. That Row will // querying is deferred until calling Scan on the returned Row. That Row will
// error with ErrNoRows if no rows are returned. // error with ErrNoRows if no rows are returned.
@ -1106,6 +1115,8 @@ func (c *Conn) sendBatchExtendedWithDescription(ctx context.Context, b *Batch, d
for _, bi := range b.queuedQueries { for _, bi := range b.queuedQueries {
err := c.eqb.Build(c.typeMap, bi.sd, bi.arguments) err := c.eqb.Build(c.typeMap, bi.sd, bi.arguments)
if err != nil { if err != nil {
// we wrap the error so we the user can understand which query failed inside the batch
err = fmt.Errorf("error building query %s: %w", bi.query, err)
return &pipelineBatchResults{ctx: ctx, conn: c, err: err} return &pipelineBatchResults{ctx: ctx, conn: c, err: err}
} }
@ -1271,7 +1282,7 @@ func (c *Conn) getCompositeFields(ctx context.Context, oid uint32) ([]pgtype.Com
var fieldOID uint32 var fieldOID uint32
rows, _ := c.Query(ctx, `select attname, atttypid rows, _ := c.Query(ctx, `select attname, atttypid
from pg_attribute from pg_attribute
where attrelid=$1 where attrelid=$1 and not attisdropped
order by attnum`, order by attnum`,
typrelid, typrelid,
) )

@ -85,6 +85,7 @@ type copyFrom struct {
columnNames []string columnNames []string
rowSrc CopyFromSource rowSrc CopyFromSource
readerErrChan chan error readerErrChan chan error
mode QueryExecMode
} }
func (ct *copyFrom) run(ctx context.Context) (int64, error) { func (ct *copyFrom) run(ctx context.Context) (int64, error) {
@ -105,9 +106,29 @@ func (ct *copyFrom) run(ctx context.Context) (int64, error) {
} }
quotedColumnNames := cbuf.String() quotedColumnNames := cbuf.String()
sd, err := ct.conn.Prepare(ctx, "", fmt.Sprintf("select %s from %s", quotedColumnNames, quotedTableName)) var sd *pgconn.StatementDescription
if err != nil { switch ct.mode {
return 0, err case QueryExecModeExec, QueryExecModeSimpleProtocol:
// These modes don't support the binary format. Before the inclusion of the
// QueryExecModes, Conn.Prepare was called on every COPY operation to get
// the OIDs. These prepared statements were not cached.
//
// Since that's the same behavior provided by QueryExecModeDescribeExec,
// we'll default to that mode.
ct.mode = QueryExecModeDescribeExec
fallthrough
case QueryExecModeCacheStatement, QueryExecModeCacheDescribe, QueryExecModeDescribeExec:
var err error
sd, err = ct.conn.getStatementDescription(
ctx,
ct.mode,
fmt.Sprintf("select %s from %s", quotedColumnNames, quotedTableName),
)
if err != nil {
return 0, fmt.Errorf("statement description failed: %w", err)
}
default:
return 0, fmt.Errorf("unknown QueryExecMode: %v", ct.mode)
} }
r, w := io.Pipe() r, w := io.Pipe()
@ -167,8 +188,13 @@ func (ct *copyFrom) run(ctx context.Context) (int64, error) {
} }
func (ct *copyFrom) buildCopyBuf(buf []byte, sd *pgconn.StatementDescription) (bool, []byte, error) { func (ct *copyFrom) buildCopyBuf(buf []byte, sd *pgconn.StatementDescription) (bool, []byte, error) {
const sendBufSize = 65536 - 5 // The packet has a 5-byte header
lastBufLen := 0
largestRowLen := 0
for ct.rowSrc.Next() { for ct.rowSrc.Next() {
lastBufLen = len(buf)
values, err := ct.rowSrc.Values() values, err := ct.rowSrc.Values()
if err != nil { if err != nil {
return false, nil, err return false, nil, err
@ -185,7 +211,15 @@ func (ct *copyFrom) buildCopyBuf(buf []byte, sd *pgconn.StatementDescription) (b
} }
} }
if len(buf) > 65536 { rowLen := len(buf) - lastBufLen
if rowLen > largestRowLen {
largestRowLen = rowLen
}
// Try not to overflow size of the buffer PgConn.CopyFrom will be reading into. If that happens then the nature of
// io.Pipe means that the next Read will be short. This can lead to pathological send sizes such as 65531, 13, 65531
// 13, 65531, 13, 65531, 13.
if len(buf) > sendBufSize-largestRowLen {
return true, buf, nil return true, buf, nil
} }
} }
@ -208,6 +242,7 @@ func (c *Conn) CopyFrom(ctx context.Context, tableName Identifier, columnNames [
columnNames: columnNames, columnNames: columnNames,
rowSrc: rowSrc, rowSrc: rowSrc,
readerErrChan: make(chan error), readerErrChan: make(chan error),
mode: c.config.DefaultQueryExecMode,
} }
return ct.run(ctx) return ct.run(ctx)

@ -69,8 +69,9 @@ Use Exec to execute a query that does not return a result set.
PostgreSQL Data Types PostgreSQL Data Types
The package pgtype provides extensive and customizable support for converting Go values to and from PostgreSQL values pgx uses the pgtype package to converting Go values to and from PostgreSQL values. It supports many PostgreSQL types
including array and composite types. See that package's documentation for details. directly and is customizable and extendable. User defined data types such as enums, domains, and composite types may
require type registration. See that package's documentation for details.
Transactions Transactions

@ -1,6 +1,7 @@
package pgx package pgx
import ( import (
"database/sql/driver"
"fmt" "fmt"
"github.com/jackc/pgx/v5/internal/anynil" "github.com/jackc/pgx/v5/internal/anynil"
@ -181,6 +182,19 @@ func (eqb *ExtendedQueryBuilder) appendParamsForQueryExecModeExec(m *pgtype.Map,
} }
} }
} }
if !ok {
var dv driver.Valuer
if dv, ok = arg.(driver.Valuer); ok {
v, err := dv.Value()
if err != nil {
return err
}
dt, ok = m.TypeForValue(v)
if ok {
arg = v
}
}
}
if !ok { if !ok {
var str fmt.Stringer var str fmt.Stringer
if str, ok = arg.(fmt.Stringer); ok { if str, ok = arg.(fmt.Stringer); ok {

@ -1,4 +1,7 @@
// Package iobufpool implements a global segregated-fit pool of buffers for IO. // Package iobufpool implements a global segregated-fit pool of buffers for IO.
//
// It uses *[]byte instead of []byte to avoid the sync.Pool allocation with Put. Unfortunately, using a pointer to avoid
// an allocation is purposely not documented. https://github.com/golang/go/issues/16323
package iobufpool package iobufpool
import "sync" import "sync"
@ -10,17 +13,27 @@ var pools [18]*sync.Pool
func init() { func init() {
for i := range pools { for i := range pools {
bufLen := 1 << (minPoolExpOf2 + i) bufLen := 1 << (minPoolExpOf2 + i)
pools[i] = &sync.Pool{New: func() any { return make([]byte, bufLen) }} pools[i] = &sync.Pool{
New: func() any {
buf := make([]byte, bufLen)
return &buf
},
}
} }
} }
// Get gets a []byte of len size with cap <= size*2. // Get gets a []byte of len size with cap <= size*2.
func Get(size int) []byte { func Get(size int) *[]byte {
i := getPoolIdx(size) i := getPoolIdx(size)
if i >= len(pools) { if i >= len(pools) {
return make([]byte, size) buf := make([]byte, size)
return &buf
} }
return pools[i].Get().([]byte)[:size]
ptrBuf := (pools[i].Get().(*[]byte))
*ptrBuf = (*ptrBuf)[:size]
return ptrBuf
} }
func getPoolIdx(size int) int { func getPoolIdx(size int) int {
@ -36,8 +49,8 @@ func getPoolIdx(size int) int {
} }
// Put returns buf to the pool. // Put returns buf to the pool.
func Put(buf []byte) { func Put(buf *[]byte) {
i := putPoolIdx(cap(buf)) i := putPoolIdx(cap(*buf))
if i < 0 { if i < 0 {
return return
} }

@ -8,11 +8,11 @@ const minBufferQueueLen = 8
type bufferQueue struct { type bufferQueue struct {
lock sync.Mutex lock sync.Mutex
queue [][]byte queue []*[]byte
r, w int r, w int
} }
func (bq *bufferQueue) pushBack(buf []byte) { func (bq *bufferQueue) pushBack(buf *[]byte) {
bq.lock.Lock() bq.lock.Lock()
defer bq.lock.Unlock() defer bq.lock.Unlock()
@ -23,7 +23,7 @@ func (bq *bufferQueue) pushBack(buf []byte) {
bq.w++ bq.w++
} }
func (bq *bufferQueue) pushFront(buf []byte) { func (bq *bufferQueue) pushFront(buf *[]byte) {
bq.lock.Lock() bq.lock.Lock()
defer bq.lock.Unlock() defer bq.lock.Unlock()
@ -35,7 +35,7 @@ func (bq *bufferQueue) pushFront(buf []byte) {
bq.w++ bq.w++
} }
func (bq *bufferQueue) popFront() []byte { func (bq *bufferQueue) popFront() *[]byte {
bq.lock.Lock() bq.lock.Lock()
defer bq.lock.Unlock() defer bq.lock.Unlock()
@ -51,7 +51,7 @@ func (bq *bufferQueue) popFront() []byte {
bq.r = 0 bq.r = 0
bq.w = 0 bq.w = 0
if len(bq.queue) > minBufferQueueLen { if len(bq.queue) > minBufferQueueLen {
bq.queue = make([][]byte, minBufferQueueLen) bq.queue = make([]*[]byte, minBufferQueueLen)
} }
} }
@ -64,7 +64,7 @@ func (bq *bufferQueue) growQueue() {
desiredLen = minBufferQueueLen desiredLen = minBufferQueueLen
} }
newQueue := make([][]byte, desiredLen) newQueue := make([]*[]byte, desiredLen)
copy(newQueue, bq.queue) copy(newQueue, bq.queue)
bq.queue = newQueue bq.queue = newQueue
} }

@ -26,7 +26,9 @@ import (
var errClosed = errors.New("closed") var errClosed = errors.New("closed")
var ErrWouldBlock = new(wouldBlockError) var ErrWouldBlock = new(wouldBlockError)
const fakeNonblockingWaitDuration = 100 * time.Millisecond const fakeNonblockingWriteWaitDuration = 100 * time.Millisecond
const minNonblockingReadWaitDuration = time.Microsecond
const maxNonblockingReadWaitDuration = 100 * time.Millisecond
// NonBlockingDeadline is a magic value that when passed to Set[Read]Deadline places the connection in non-blocking read // NonBlockingDeadline is a magic value that when passed to Set[Read]Deadline places the connection in non-blocking read
// mode. // mode.
@ -54,7 +56,7 @@ type Conn interface {
// Flush flushes any buffered writes. // Flush flushes any buffered writes.
Flush() error Flush() error
// BufferReadUntilBlock reads and buffers any sucessfully read bytes until the read would block. // BufferReadUntilBlock reads and buffers any successfully read bytes until the read would block.
BufferReadUntilBlock() error BufferReadUntilBlock() error
} }
@ -73,14 +75,24 @@ type NetConn struct {
readFlushLock sync.Mutex readFlushLock sync.Mutex
// non-blocking writes with syscall.RawConn are done with a callback function. By using these fields instead of the // non-blocking writes with syscall.RawConn are done with a callback function. By using these fields instead of the
// callback functions closure to pass the buf argument and receive the n and err results we avoid some allocations. // callback functions closure to pass the buf argument and receive the n and err results we avoid some allocations.
nonblockWriteBuf []byte nonblockWriteFunc func(fd uintptr) (done bool)
nonblockWriteErr error nonblockWriteBuf []byte
nonblockWriteN int nonblockWriteErr error
nonblockWriteN int
readDeadlineLock sync.Mutex
readDeadline time.Time // non-blocking reads with syscall.RawConn are done with a callback function. By using these fields instead of the
readNonblocking bool // callback functions closure to pass the buf argument and receive the n and err results we avoid some allocations.
nonblockReadFunc func(fd uintptr) (done bool)
nonblockReadBuf []byte
nonblockReadErr error
nonblockReadN int
readDeadlineLock sync.Mutex
readDeadline time.Time
readNonblocking bool
fakeNonBlockingShortReadCount int
fakeNonblockingReadWaitDuration time.Duration
writeDeadlineLock sync.Mutex writeDeadlineLock sync.Mutex
writeDeadline time.Time writeDeadline time.Time
@ -88,7 +100,8 @@ type NetConn struct {
func NewNetConn(conn net.Conn, fakeNonBlockingIO bool) *NetConn { func NewNetConn(conn net.Conn, fakeNonBlockingIO bool) *NetConn {
nc := &NetConn{ nc := &NetConn{
conn: conn, conn: conn,
fakeNonblockingReadWaitDuration: maxNonblockingReadWaitDuration,
} }
if !fakeNonBlockingIO { if !fakeNonBlockingIO {
@ -121,9 +134,9 @@ func (c *NetConn) Read(b []byte) (n int, err error) {
if buf == nil { if buf == nil {
break break
} }
copiedN := copy(b[n:], buf) copiedN := copy(b[n:], *buf)
if copiedN < len(buf) { if copiedN < len(*buf) {
buf = buf[copiedN:] *buf = (*buf)[copiedN:]
c.readQueue.pushFront(buf) c.readQueue.pushFront(buf)
} else { } else {
iobufpool.Put(buf) iobufpool.Put(buf)
@ -160,7 +173,7 @@ func (c *NetConn) Write(b []byte) (n int, err error) {
} }
buf := iobufpool.Get(len(b)) buf := iobufpool.Get(len(b))
copy(buf, b) copy(*buf, b)
c.writeQueue.pushBack(buf) c.writeQueue.pushBack(buf)
return len(b), nil return len(b), nil
} }
@ -278,14 +291,14 @@ func (c *NetConn) flush() error {
}() }()
for buf := c.writeQueue.popFront(); buf != nil; buf = c.writeQueue.popFront() { for buf := c.writeQueue.popFront(); buf != nil; buf = c.writeQueue.popFront() {
remainingBuf := buf remainingBuf := *buf
for len(remainingBuf) > 0 { for len(remainingBuf) > 0 {
n, err := c.nonblockingWrite(remainingBuf) n, err := c.nonblockingWrite(remainingBuf)
remainingBuf = remainingBuf[n:] remainingBuf = remainingBuf[n:]
if err != nil { if err != nil {
if !errors.Is(err, ErrWouldBlock) { if !errors.Is(err, ErrWouldBlock) {
buf = buf[:len(remainingBuf)] *buf = (*buf)[:len(remainingBuf)]
copy(buf, remainingBuf) copy(*buf, remainingBuf)
c.writeQueue.pushFront(buf) c.writeQueue.pushFront(buf)
return err return err
} }
@ -313,10 +326,12 @@ func (c *NetConn) flush() error {
func (c *NetConn) BufferReadUntilBlock() error { func (c *NetConn) BufferReadUntilBlock() error {
for { for {
buf := iobufpool.Get(8 * 1024) buf := iobufpool.Get(8 * 1024)
n, err := c.nonblockingRead(buf) n, err := c.nonblockingRead(*buf)
if n > 0 { if n > 0 {
buf = buf[:n] *buf = (*buf)[:n]
c.readQueue.pushBack(buf) c.readQueue.pushBack(buf)
} else if n == 0 {
iobufpool.Put(buf)
} }
if err != nil { if err != nil {
@ -369,7 +384,7 @@ func (c *NetConn) fakeNonblockingWrite(b []byte) (n int, err error) {
c.writeDeadlineLock.Lock() c.writeDeadlineLock.Lock()
defer c.writeDeadlineLock.Unlock() defer c.writeDeadlineLock.Unlock()
deadline := time.Now().Add(fakeNonblockingWaitDuration) deadline := time.Now().Add(fakeNonblockingWriteWaitDuration)
if c.writeDeadline.IsZero() || deadline.Before(c.writeDeadline) { if c.writeDeadline.IsZero() || deadline.Before(c.writeDeadline) {
err = c.conn.SetWriteDeadline(deadline) err = c.conn.SetWriteDeadline(deadline)
if err != nil { if err != nil {
@ -402,13 +417,40 @@ func (c *NetConn) fakeNonblockingRead(b []byte) (n int, err error) {
c.readDeadlineLock.Lock() c.readDeadlineLock.Lock()
defer c.readDeadlineLock.Unlock() defer c.readDeadlineLock.Unlock()
deadline := time.Now().Add(fakeNonblockingWaitDuration) // The first 5 reads only read 1 byte at a time. This should give us 4 chances to read when we are sure the bytes are
// already in Go or the OS's receive buffer.
if c.fakeNonBlockingShortReadCount < 5 && len(b) > 0 {
b = b[:1]
}
startTime := time.Now()
deadline := startTime.Add(c.fakeNonblockingReadWaitDuration)
if c.readDeadline.IsZero() || deadline.Before(c.readDeadline) { if c.readDeadline.IsZero() || deadline.Before(c.readDeadline) {
err = c.conn.SetReadDeadline(deadline) err = c.conn.SetReadDeadline(deadline)
if err != nil { if err != nil {
return 0, err return 0, err
} }
defer func() { defer func() {
// If the read was successful and the wait duration is not already the minimum
if err == nil && c.fakeNonblockingReadWaitDuration > minNonblockingReadWaitDuration {
endTime := time.Now()
if n > 0 && c.fakeNonBlockingShortReadCount < 5 {
c.fakeNonBlockingShortReadCount++
}
// The wait duration should be 2x the fastest read that has occurred. This should give reasonable assurance that
// a Read deadline will not block a read before it has a chance to read data already in Go or the OS's receive
// buffer.
proposedWait := endTime.Sub(startTime) * 2
if proposedWait < minNonblockingReadWaitDuration {
proposedWait = minNonblockingReadWaitDuration
}
if proposedWait < c.fakeNonblockingReadWaitDuration {
c.fakeNonblockingReadWaitDuration = proposedWait
}
}
// Ignoring error resetting deadline as there is nothing that can reasonably be done if it fails. // Ignoring error resetting deadline as there is nothing that can reasonably be done if it fails.
c.conn.SetReadDeadline(c.readDeadline) c.conn.SetReadDeadline(c.readDeadline)

@ -1,9 +1,7 @@
//go:build !(aix || android || darwin || dragonfly || freebsd || hurd || illumos || ios || linux || netbsd || openbsd || solaris) //go:build !unix
package nbconn package nbconn
// Not using unix build tag for support on Go 1.18.
func (c *NetConn) realNonblockingWrite(b []byte) (n int, err error) { func (c *NetConn) realNonblockingWrite(b []byte) (n int, err error) {
return c.fakeNonblockingWrite(b) return c.fakeNonblockingWrite(b)
} }

@ -1,9 +1,7 @@
//go:build aix || android || darwin || dragonfly || freebsd || hurd || illumos || ios || linux || netbsd || openbsd || solaris //go:build unix
package nbconn package nbconn
// Not using unix build tag for support on Go 1.18.
import ( import (
"errors" "errors"
"io" "io"
@ -12,14 +10,19 @@ import (
// realNonblockingWrite does a non-blocking write. readFlushLock must already be held. // realNonblockingWrite does a non-blocking write. readFlushLock must already be held.
func (c *NetConn) realNonblockingWrite(b []byte) (n int, err error) { func (c *NetConn) realNonblockingWrite(b []byte) (n int, err error) {
if c.nonblockWriteFunc == nil {
c.nonblockWriteFunc = func(fd uintptr) (done bool) {
c.nonblockWriteN, c.nonblockWriteErr = syscall.Write(int(fd), c.nonblockWriteBuf)
return true
}
}
c.nonblockWriteBuf = b c.nonblockWriteBuf = b
c.nonblockWriteN = 0 c.nonblockWriteN = 0
c.nonblockWriteErr = nil c.nonblockWriteErr = nil
err = c.rawConn.Write(func(fd uintptr) (done bool) {
c.nonblockWriteN, c.nonblockWriteErr = syscall.Write(int(fd), c.nonblockWriteBuf) err = c.rawConn.Write(c.nonblockWriteFunc)
return true
})
n = c.nonblockWriteN n = c.nonblockWriteN
c.nonblockWriteBuf = nil // ensure that no reference to b is kept.
if err == nil && c.nonblockWriteErr != nil { if err == nil && c.nonblockWriteErr != nil {
if errors.Is(c.nonblockWriteErr, syscall.EWOULDBLOCK) { if errors.Is(c.nonblockWriteErr, syscall.EWOULDBLOCK) {
err = ErrWouldBlock err = ErrWouldBlock
@ -40,16 +43,24 @@ func (c *NetConn) realNonblockingWrite(b []byte) (n int, err error) {
} }
func (c *NetConn) realNonblockingRead(b []byte) (n int, err error) { func (c *NetConn) realNonblockingRead(b []byte) (n int, err error) {
var funcErr error if c.nonblockReadFunc == nil {
err = c.rawConn.Read(func(fd uintptr) (done bool) { c.nonblockReadFunc = func(fd uintptr) (done bool) {
n, funcErr = syscall.Read(int(fd), b) c.nonblockReadN, c.nonblockReadErr = syscall.Read(int(fd), c.nonblockReadBuf)
return true return true
}) }
if err == nil && funcErr != nil { }
if errors.Is(funcErr, syscall.EWOULDBLOCK) { c.nonblockReadBuf = b
c.nonblockReadN = 0
c.nonblockReadErr = nil
err = c.rawConn.Read(c.nonblockReadFunc)
n = c.nonblockReadN
c.nonblockReadBuf = nil // ensure that no reference to b is kept.
if err == nil && c.nonblockReadErr != nil {
if errors.Is(c.nonblockReadErr, syscall.EWOULDBLOCK) {
err = ErrWouldBlock err = ErrWouldBlock
} else { } else {
err = funcErr err = c.nonblockReadErr
} }
} }
if err != nil { if err != nil {

@ -8,7 +8,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"math" "math"
"net" "net"
"net/url" "net/url"
@ -211,9 +210,9 @@ func NetworkAddress(host string, port uint16) (network, address string) {
// //
// In addition, ParseConfig accepts the following options: // In addition, ParseConfig accepts the following options:
// //
// servicefile // - servicefile.
// libpq only reads servicefile from the PGSERVICEFILE environment variable. ParseConfig accepts servicefile as a // libpq only reads servicefile from the PGSERVICEFILE environment variable. ParseConfig accepts servicefile as a
// part of the connection string. // part of the connection string.
func ParseConfig(connString string) (*Config, error) { func ParseConfig(connString string) (*Config, error) {
var parseConfigOptions ParseConfigOptions var parseConfigOptions ParseConfigOptions
return ParseConfigWithOptions(connString, parseConfigOptions) return ParseConfigWithOptions(connString, parseConfigOptions)
@ -687,7 +686,7 @@ func configTLS(settings map[string]string, thisHost string, parseConfigOptions P
caCertPool := x509.NewCertPool() caCertPool := x509.NewCertPool()
caPath := sslrootcert caPath := sslrootcert
caCert, err := ioutil.ReadFile(caPath) caCert, err := os.ReadFile(caPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to read CA file: %w", err) return nil, fmt.Errorf("unable to read CA file: %w", err)
} }
@ -705,7 +704,7 @@ func configTLS(settings map[string]string, thisHost string, parseConfigOptions P
} }
if sslcert != "" && sslkey != "" { if sslcert != "" && sslkey != "" {
buf, err := ioutil.ReadFile(sslkey) buf, err := os.ReadFile(sslkey)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to read sslkey: %w", err) return nil, fmt.Errorf("unable to read sslkey: %w", err)
} }
@ -744,7 +743,7 @@ func configTLS(settings map[string]string, thisHost string, parseConfigOptions P
} else { } else {
pemKey = pem.EncodeToMemory(block) pemKey = pem.EncodeToMemory(block)
} }
certfile, err := ioutil.ReadFile(sslcert) certfile, err := os.ReadFile(sslcert)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to read cert: %w", err) return nil, fmt.Errorf("unable to read cert: %w", err)
} }

@ -203,6 +203,8 @@ func ConnectConfig(octx context.Context, config *Config) (pgConn *PgConn, err er
func expandWithIPs(ctx context.Context, lookupFn LookupFunc, fallbacks []*FallbackConfig) ([]*FallbackConfig, error) { func expandWithIPs(ctx context.Context, lookupFn LookupFunc, fallbacks []*FallbackConfig) ([]*FallbackConfig, error) {
var configs []*FallbackConfig var configs []*FallbackConfig
var lookupErrors []error
for _, fb := range fallbacks { for _, fb := range fallbacks {
// skip resolve for unix sockets // skip resolve for unix sockets
if isAbsolutePath(fb.Host) { if isAbsolutePath(fb.Host) {
@ -217,7 +219,8 @@ func expandWithIPs(ctx context.Context, lookupFn LookupFunc, fallbacks []*Fallba
ips, err := lookupFn(ctx, fb.Host) ips, err := lookupFn(ctx, fb.Host)
if err != nil { if err != nil {
return nil, err lookupErrors = append(lookupErrors, err)
continue
} }
for _, ip := range ips { for _, ip := range ips {
@ -242,6 +245,12 @@ func expandWithIPs(ctx context.Context, lookupFn LookupFunc, fallbacks []*Fallba
} }
} }
// See https://github.com/jackc/pgx/issues/1464. When Go 1.20 can be used in pgx consider using errors.Join so all
// errors are reported.
if len(configs) == 0 && len(lookupErrors) > 0 {
return nil, lookupErrors[0]
}
return configs, nil return configs, nil
} }
@ -1166,20 +1175,20 @@ func (pgConn *PgConn) CopyFrom(ctx context.Context, r io.Reader, sql string) (Co
buf := iobufpool.Get(65536) buf := iobufpool.Get(65536)
defer iobufpool.Put(buf) defer iobufpool.Put(buf)
buf[0] = 'd' (*buf)[0] = 'd'
var readErr, pgErr error var readErr, pgErr error
for pgErr == nil { for pgErr == nil {
// Read chunk from r. // Read chunk from r.
var n int var n int
n, readErr = r.Read(buf[5:cap(buf)]) n, readErr = r.Read((*buf)[5:cap(*buf)])
// Send chunk to PostgreSQL. // Send chunk to PostgreSQL.
if n > 0 { if n > 0 {
buf = buf[0 : n+5] *buf = (*buf)[0 : n+5]
pgio.SetInt32(buf[1:], int32(n+4)) pgio.SetInt32((*buf)[1:], int32(n+4))
writeErr := pgConn.frontend.SendUnbufferedEncodedCopyData(buf) writeErr := pgConn.frontend.SendUnbufferedEncodedCopyData(*buf)
if writeErr != nil { if writeErr != nil {
pgConn.asyncClose() pgConn.asyncClose()
return CommandTag{}, err return CommandTag{}, err

@ -196,7 +196,7 @@ func (b *Backend) Receive() (FrontendMessage, error) {
case AuthTypeCleartextPassword, AuthTypeMD5Password: case AuthTypeCleartextPassword, AuthTypeMD5Password:
fallthrough fallthrough
default: default:
// to maintain backwards compatability // to maintain backwards compatibility
msg = &PasswordMessage{} msg = &PasswordMessage{}
} }
case 'Q': case 'Q':
@ -233,11 +233,11 @@ func (b *Backend) Receive() (FrontendMessage, error) {
// contextual identification of FrontendMessages. For example, in the // contextual identification of FrontendMessages. For example, in the
// PG message flow documentation for PasswordMessage: // PG message flow documentation for PasswordMessage:
// //
// Byte1('p') // Byte1('p')
// //
// Identifies the message as a password response. Note that this is also used for // Identifies the message as a password response. Note that this is also used for
// GSSAPI, SSPI and SASL response messages. The exact message type can be deduced from // GSSAPI, SSPI and SASL response messages. The exact message type can be deduced from
// the context. // the context.
// //
// Since the Frontend does not know about the state of a backend, it is important // Since the Frontend does not know about the state of a backend, it is important
// to call SetAuthType() after an authentication request is received by the Frontend. // to call SetAuthType() after an authentication request is received by the Frontend.

@ -14,7 +14,7 @@ import (
type chunkReader struct { type chunkReader struct {
r io.Reader r io.Reader
buf []byte buf *[]byte
rp, wp int // buf read position and write position rp, wp int // buf read position and write position
minBufSize int minBufSize int
@ -45,7 +45,7 @@ func newChunkReader(r io.Reader, minBufSize int) *chunkReader {
func (r *chunkReader) Next(n int) (buf []byte, err error) { func (r *chunkReader) Next(n int) (buf []byte, err error) {
// Reset the buffer if it is empty // Reset the buffer if it is empty
if r.rp == r.wp { if r.rp == r.wp {
if len(r.buf) != r.minBufSize { if len(*r.buf) != r.minBufSize {
iobufpool.Put(r.buf) iobufpool.Put(r.buf)
r.buf = iobufpool.Get(r.minBufSize) r.buf = iobufpool.Get(r.minBufSize)
} }
@ -55,15 +55,15 @@ func (r *chunkReader) Next(n int) (buf []byte, err error) {
// n bytes already in buf // n bytes already in buf
if (r.wp - r.rp) >= n { if (r.wp - r.rp) >= n {
buf = r.buf[r.rp : r.rp+n : r.rp+n] buf = (*r.buf)[r.rp : r.rp+n : r.rp+n]
r.rp += n r.rp += n
return buf, err return buf, err
} }
// buf is smaller than requested number of bytes // buf is smaller than requested number of bytes
if len(r.buf) < n { if len(*r.buf) < n {
bigBuf := iobufpool.Get(n) bigBuf := iobufpool.Get(n)
r.wp = copy(bigBuf, r.buf[r.rp:r.wp]) r.wp = copy((*bigBuf), (*r.buf)[r.rp:r.wp])
r.rp = 0 r.rp = 0
iobufpool.Put(r.buf) iobufpool.Put(r.buf)
r.buf = bigBuf r.buf = bigBuf
@ -71,20 +71,20 @@ func (r *chunkReader) Next(n int) (buf []byte, err error) {
// buf is large enough, but need to shift filled area to start to make enough contiguous space // buf is large enough, but need to shift filled area to start to make enough contiguous space
minReadCount := n - (r.wp - r.rp) minReadCount := n - (r.wp - r.rp)
if (len(r.buf) - r.wp) < minReadCount { if (len(*r.buf) - r.wp) < minReadCount {
r.wp = copy(r.buf, r.buf[r.rp:r.wp]) r.wp = copy((*r.buf), (*r.buf)[r.rp:r.wp])
r.rp = 0 r.rp = 0
} }
// Read at least the required number of bytes from the underlying io.Reader // Read at least the required number of bytes from the underlying io.Reader
readBytesCount, err := io.ReadAtLeast(r.r, r.buf[r.wp:], minReadCount) readBytesCount, err := io.ReadAtLeast(r.r, (*r.buf)[r.wp:], minReadCount)
r.wp += readBytesCount r.wp += readBytesCount
// fmt.Println("read", n) // fmt.Println("read", n)
if err != nil { if err != nil {
return nil, err return nil, err
} }
buf = r.buf[r.rp : r.rp+n : r.rp+n] buf = (*r.buf)[r.rp : r.rp+n : r.rp+n]
r.rp += n r.rp += n
return buf, nil return buf, nil
} }

@ -47,7 +47,16 @@ func (c *ArrayCodec) FormatSupported(format int16) bool {
} }
func (c *ArrayCodec) PreferredFormat() int16 { func (c *ArrayCodec) PreferredFormat() int16 {
return c.ElementType.Codec.PreferredFormat() // The binary format should always be preferred for arrays if it is supported. Usually, this will happen automatically
// because most types that support binary prefer it. However, text, json, and jsonb support binary but prefer the text
// format. This is because it is simpler for jsonb and PostgreSQL can be significantly faster using the text format
// for text-like data types than binary. However, arrays appear to always be faster in binary.
//
// https://www.postgresql.org/message-id/CAMovtNoHFod2jMAKQjjxv209PCTJx5Kc66anwWvX0mEiaXwgmA%40mail.gmail.com
if c.ElementType.Codec.FormatSupported(BinaryFormatCode) {
return BinaryFormatCode
}
return TextFormatCode
} }
func (c *ArrayCodec) PlanEncode(m *Map, oid uint32, format int16, value any) EncodePlan { func (c *ArrayCodec) PlanEncode(m *Map, oid uint32, format int16, value any) EncodePlan {
@ -60,7 +69,9 @@ func (c *ArrayCodec) PlanEncode(m *Map, oid uint32, format int16, value any) Enc
elementEncodePlan := m.PlanEncode(c.ElementType.OID, format, elementType) elementEncodePlan := m.PlanEncode(c.ElementType.OID, format, elementType)
if elementEncodePlan == nil { if elementEncodePlan == nil {
return nil if reflect.TypeOf(elementType) != nil {
return nil
}
} }
switch format { switch format {
@ -301,7 +312,7 @@ func (c *ArrayCodec) decodeText(m *Map, arrayOID uint32, src []byte, array Array
for i, s := range uta.Elements { for i, s := range uta.Elements {
elem := array.ScanIndex(i) elem := array.ScanIndex(i)
var elemSrc []byte var elemSrc []byte
if s != "NULL" { if s != "NULL" || uta.Quoted[i] {
elemSrc = []byte(s) elemSrc = []byte(s)
} }

@ -910,3 +910,43 @@ func (a *anyMultiDimSliceArray) ScanIndexType() any {
} }
return reflect.New(lowestSliceType.Elem()).Interface() return reflect.New(lowestSliceType.Elem()).Interface()
} }
type anyArrayArrayReflect struct {
array reflect.Value
}
func (a anyArrayArrayReflect) Dimensions() []ArrayDimension {
return []ArrayDimension{{Length: int32(a.array.Len()), LowerBound: 1}}
}
func (a anyArrayArrayReflect) Index(i int) any {
return a.array.Index(i).Interface()
}
func (a anyArrayArrayReflect) IndexType() any {
return reflect.New(a.array.Type().Elem()).Elem().Interface()
}
func (a *anyArrayArrayReflect) SetDimensions(dimensions []ArrayDimension) error {
if dimensions == nil {
return fmt.Errorf("anyArrayArrayReflect: cannot scan NULL into %v", a.array.Type().String())
}
if len(dimensions) != 1 {
return fmt.Errorf("anyArrayArrayReflect: cannot scan multi-dimensional array into %v", a.array.Type().String())
}
if int(dimensions[0].Length) != a.array.Len() {
return fmt.Errorf("anyArrayArrayReflect: cannot scan array with length %v into %v", dimensions[0].Length, a.array.Type().String())
}
return nil
}
func (a *anyArrayArrayReflect) ScanIndex(i int) any {
return a.array.Index(i).Addr().Interface()
}
func (a *anyArrayArrayReflect) ScanIndexType() any {
return reflect.New(a.array.Type().Elem()).Interface()
}

@ -238,7 +238,7 @@ func decodeHexBytea(src []byte) ([]byte, error) {
} }
func (c ByteaCodec) DecodeDatabaseSQLValue(m *Map, oid uint32, format int16, src []byte) (driver.Value, error) { func (c ByteaCodec) DecodeDatabaseSQLValue(m *Map, oid uint32, format int16, src []byte) (driver.Value, error) {
return codecDecodeToTextFormat(c, m, oid, format, src) return c.DecodeValue(m, oid, format, src)
} }
func (c ByteaCodec) DecodeValue(m *Map, oid uint32, format int16, src []byte) (any, error) { func (c ByteaCodec) DecodeValue(m *Map, oid uint32, format int16, src []byte) (any, error) {

@ -57,27 +57,7 @@ JSON Support
pgtype automatically marshals and unmarshals data from json and jsonb PostgreSQL types. pgtype automatically marshals and unmarshals data from json and jsonb PostgreSQL types.
Array Support Extending Existing PostgreSQL Type Support
ArrayCodec implements support for arrays. If pgtype supports type T then it can easily support []T by registering an
ArrayCodec for the appropriate PostgreSQL OID. In addition, Array[T] type can support multi-dimensional arrays.
Composite Support
CompositeCodec implements support for PostgreSQL composite types. Go structs can be scanned into if the public fields of
the struct are in the exact order and type of the PostgreSQL type or by implementing CompositeIndexScanner and
CompositeIndexGetter.
Enum Support
PostgreSQL enums can usually be treated as text. However, EnumCodec implements support for interning strings which can
reduce memory usage.
Array, Composite, and Enum Type Registration
Array, composite, and enum types can be easily registered from a pgx.Conn with the LoadType method.
Extending Existing Type Support
Generally, all Codecs will support interfaces that can be implemented to enable scanning and encoding. For example, Generally, all Codecs will support interfaces that can be implemented to enable scanning and encoding. For example,
PointCodec can use any Go type that implements the PointScanner and PointValuer interfaces. So rather than use PointCodec can use any Go type that implements the PointScanner and PointValuer interfaces. So rather than use
@ -90,11 +70,58 @@ pgx support such as github.com/shopspring/decimal. These types can be registered
logic. See https://github.com/jackc/pgx-shopspring-decimal and https://github.com/jackc/pgx-gofrs-uuid for a example logic. See https://github.com/jackc/pgx-shopspring-decimal and https://github.com/jackc/pgx-gofrs-uuid for a example
integrations. integrations.
Entirely New Type Support New PostgreSQL Type Support
pgtype uses the PostgreSQL OID to determine how to encode or decode a value. pgtype supports array, composite, domain,
and enum types. However, any type created in PostgreSQL with CREATE TYPE will receive a new OID. This means that the OID
of each new PostgreSQL type must be registered for pgtype to handle values of that type with the correct Codec.
The pgx.Conn LoadType method can return a *Type for array, composite, domain, and enum types by inspecting the database
metadata. This *Type can then be registered with Map.RegisterType.
For example, the following function could be called after a connection is established:
func RegisterDataTypes(ctx context.Context, conn *pgx.Conn) error {
dataTypeNames := []string{
"foo",
"_foo",
"bar",
"_bar",
}
for _, typeName := range dataTypeNames {
dataType, err := conn.LoadType(ctx, typeName)
if err != nil {
return err
}
conn.TypeMap().RegisterType(dataType)
}
return nil
}
A type cannot be registered unless all types it depends on are already registered. e.g. An array type cannot be
registered until its element type is registered.
ArrayCodec implements support for arrays. If pgtype supports type T then it can easily support []T by registering an
ArrayCodec for the appropriate PostgreSQL OID. In addition, Array[T] type can support multi-dimensional arrays.
CompositeCodec implements support for PostgreSQL composite types. Go structs can be scanned into if the public fields of
the struct are in the exact order and type of the PostgreSQL type or by implementing CompositeIndexScanner and
CompositeIndexGetter.
Domain types are treated as their underlying type if the underlying type and the domain type are registered.
PostgreSQL enums can usually be treated as text. However, EnumCodec implements support for interning strings which can
reduce memory usage.
While pgtype will often still work with unregistered types it is highly recommended that all types be registered due to
an improvement in performance and the elimination of certain edge cases.
If the PostgreSQL type is not already supported then an OID / Codec mapping can be registered with Map.RegisterType. If an entirely new PostgreSQL type (e.g. PostGIS types) is used then the application or a library can create a new
There is no difference between a Codec defined and registered by the application and a Codec built in to pgtype. See any Codec. Then the OID / Codec mapping can be registered with Map.RegisterType. There is no difference between a Codec
of the Codecs in pgtype for Codec examples and for examples of type registration. defined and registered by the application and a Codec built in to pgtype. See any of the Codecs in pgtype for Codec
examples and for examples of type registration.
Encoding Unknown Types Encoding Unknown Types

@ -33,7 +33,7 @@ func (dst *Int2) ScanInt64(n Int8) error {
} }
if n.Int64 < math.MinInt16 { if n.Int64 < math.MinInt16 {
return fmt.Errorf("%d is greater than maximum value for Int2", n.Int64) return fmt.Errorf("%d is less than minimum value for Int2", n.Int64)
} }
if n.Int64 > math.MaxInt16 { if n.Int64 > math.MaxInt16 {
return fmt.Errorf("%d is greater than maximum value for Int2", n.Int64) return fmt.Errorf("%d is greater than maximum value for Int2", n.Int64)
@ -593,7 +593,7 @@ func (dst *Int4) ScanInt64(n Int8) error {
} }
if n.Int64 < math.MinInt32 { if n.Int64 < math.MinInt32 {
return fmt.Errorf("%d is greater than maximum value for Int4", n.Int64) return fmt.Errorf("%d is less than minimum value for Int4", n.Int64)
} }
if n.Int64 > math.MaxInt32 { if n.Int64 > math.MaxInt32 {
return fmt.Errorf("%d is greater than maximum value for Int4", n.Int64) return fmt.Errorf("%d is greater than maximum value for Int4", n.Int64)
@ -1164,7 +1164,7 @@ func (dst *Int8) ScanInt64(n Int8) error {
} }
if n.Int64 < math.MinInt64 { if n.Int64 < math.MinInt64 {
return fmt.Errorf("%d is greater than maximum value for Int8", n.Int64) return fmt.Errorf("%d is less than minimum value for Int8", n.Int64)
} }
if n.Int64 > math.MaxInt64 { if n.Int64 > math.MaxInt64 {
return fmt.Errorf("%d is greater than maximum value for Int8", n.Int64) return fmt.Errorf("%d is greater than maximum value for Int8", n.Int64)

@ -3,6 +3,7 @@ package pgtype
import ( import (
"database/sql/driver" "database/sql/driver"
"encoding/binary" "encoding/binary"
"encoding/json"
"fmt" "fmt"
"math" "math"
"strconv" "strconv"
@ -34,7 +35,7 @@ func (dst *Int<%= pg_byte_size %>) ScanInt64(n Int8) error {
} }
if n.Int64 < math.MinInt<%= pg_bit_size %> { if n.Int64 < math.MinInt<%= pg_bit_size %> {
return fmt.Errorf("%d is greater than maximum value for Int<%= pg_byte_size %>", n.Int64) return fmt.Errorf("%d is less than minimum value for Int<%= pg_byte_size %>", n.Int64)
} }
if n.Int64 > math.MaxInt<%= pg_bit_size %> { if n.Int64 > math.MaxInt<%= pg_bit_size %> {
return fmt.Errorf("%d is greater than maximum value for Int<%= pg_byte_size %>", n.Int64) return fmt.Errorf("%d is greater than maximum value for Int<%= pg_byte_size %>", n.Int64)

@ -1,6 +1,7 @@
package pgtype package pgtype
import ( import (
"database/sql"
"database/sql/driver" "database/sql/driver"
"encoding/json" "encoding/json"
"fmt" "fmt"
@ -23,6 +24,12 @@ func (c JSONCodec) PlanEncode(m *Map, oid uint32, format int16, value any) Encod
return encodePlanJSONCodecEitherFormatString{} return encodePlanJSONCodecEitherFormatString{}
case []byte: case []byte:
return encodePlanJSONCodecEitherFormatByteSlice{} return encodePlanJSONCodecEitherFormatByteSlice{}
// Cannot rely on driver.Valuer being handled later because anything can be marshalled.
//
// https://github.com/jackc/pgx/issues/1430
case driver.Valuer:
return &encodePlanDriverValuer{m: m, oid: oid, formatCode: format}
} }
// Because anything can be marshalled the normal wrapping in Map.PlanScan doesn't get a chance to run. So try the // Because anything can be marshalled the normal wrapping in Map.PlanScan doesn't get a chance to run. So try the
@ -82,10 +89,28 @@ func (JSONCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan
return scanPlanJSONToByteSlice{} return scanPlanJSONToByteSlice{}
case BytesScanner: case BytesScanner:
return scanPlanBinaryBytesToBytesScanner{} return scanPlanBinaryBytesToBytesScanner{}
default:
return scanPlanJSONToJSONUnmarshal{} // Cannot rely on sql.Scanner being handled later because scanPlanJSONToJSONUnmarshal will take precedence.
//
// https://github.com/jackc/pgx/issues/1418
case sql.Scanner:
return &scanPlanSQLScanner{formatCode: format}
}
// This is to fix **string scanning. It seems wrong to special case sql.Scanner and pointer to pointer, but it's not
// clear what a better solution would be.
//
// https://github.com/jackc/pgx/issues/1470
if wrapperPlan, nextDst, ok := TryPointerPointerScanPlan(target); ok {
if nextPlan := m.planScan(oid, format, nextDst); nextPlan != nil {
if _, failed := nextPlan.(*scanPlanFail); !failed {
wrapperPlan.SetNext(nextPlan)
return wrapperPlan
}
}
} }
return scanPlanJSONToJSONUnmarshal{}
} }
type scanPlanAnyToString struct{} type scanPlanAnyToString struct{}

@ -240,10 +240,29 @@ func (n Numeric) MarshalJSON() ([]byte, error) {
return n.numberTextBytes(), nil return n.numberTextBytes(), nil
} }
func (n *Numeric) UnmarshalJSON(src []byte) error {
if bytes.Compare(src, []byte(`null`)) == 0 {
*n = Numeric{}
return nil
}
if bytes.Compare(src, []byte(`"NaN"`)) == 0 {
*n = Numeric{NaN: true, Valid: true}
return nil
}
return scanPlanTextAnyToNumericScanner{}.Scan(src, n)
}
// numberString returns a string of the number. undefined if NaN, infinite, or NULL // numberString returns a string of the number. undefined if NaN, infinite, or NULL
func (n Numeric) numberTextBytes() []byte { func (n Numeric) numberTextBytes() []byte {
intStr := n.Int.String() intStr := n.Int.String()
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
if len(intStr) > 0 && intStr[:1] == "-" {
intStr = intStr[1:]
buf.WriteByte('-')
}
exp := int(n.Exp) exp := int(n.Exp)
if exp > 0 { if exp > 0 {
buf.WriteString(intStr) buf.WriteString(intStr)

@ -192,7 +192,8 @@ type Map struct {
reflectTypeToType map[reflect.Type]*Type reflectTypeToType map[reflect.Type]*Type
memoizedScanPlans map[uint32]map[reflect.Type][2]ScanPlan memoizedScanPlans map[uint32]map[reflect.Type][2]ScanPlan
memoizedEncodePlans map[uint32]map[reflect.Type][2]EncodePlan
// TryWrapEncodePlanFuncs is a slice of functions that will wrap a value that cannot be encoded by the Codec. Every // TryWrapEncodePlanFuncs is a slice of functions that will wrap a value that cannot be encoded by the Codec. Every
// time a wrapper is found the PlanEncode method will be recursively called with the new value. This allows several layers of wrappers // time a wrapper is found the PlanEncode method will be recursively called with the new value. This allows several layers of wrappers
@ -214,7 +215,8 @@ func NewMap() *Map {
reflectTypeToName: make(map[reflect.Type]string), reflectTypeToName: make(map[reflect.Type]string),
oidToFormatCode: make(map[uint32]int16), oidToFormatCode: make(map[uint32]int16),
memoizedScanPlans: make(map[uint32]map[reflect.Type][2]ScanPlan), memoizedScanPlans: make(map[uint32]map[reflect.Type][2]ScanPlan),
memoizedEncodePlans: make(map[uint32]map[reflect.Type][2]EncodePlan),
TryWrapEncodePlanFuncs: []TryWrapEncodePlanFunc{ TryWrapEncodePlanFuncs: []TryWrapEncodePlanFunc{
TryWrapDerefPointerEncodePlan, TryWrapDerefPointerEncodePlan,
@ -223,6 +225,7 @@ func NewMap() *Map {
TryWrapStructEncodePlan, TryWrapStructEncodePlan,
TryWrapSliceEncodePlan, TryWrapSliceEncodePlan,
TryWrapMultiDimSliceEncodePlan, TryWrapMultiDimSliceEncodePlan,
TryWrapArrayEncodePlan,
}, },
TryWrapScanPlanFuncs: []TryWrapScanPlanFunc{ TryWrapScanPlanFuncs: []TryWrapScanPlanFunc{
@ -232,6 +235,7 @@ func NewMap() *Map {
TryWrapStructScanPlan, TryWrapStructScanPlan,
TryWrapPtrSliceScanPlan, TryWrapPtrSliceScanPlan,
TryWrapPtrMultiDimSliceScanPlan, TryWrapPtrMultiDimSliceScanPlan,
TryWrapPtrArrayScanPlan,
}, },
} }
@ -420,6 +424,9 @@ func (m *Map) RegisterType(t *Type) {
for k := range m.memoizedScanPlans { for k := range m.memoizedScanPlans {
delete(m.memoizedScanPlans, k) delete(m.memoizedScanPlans, k)
} }
for k := range m.memoizedEncodePlans {
delete(m.memoizedEncodePlans, k)
}
} }
// RegisterDefaultPgType registers a mapping of a Go type to a PostgreSQL type name. Typically the data type to be // RegisterDefaultPgType registers a mapping of a Go type to a PostgreSQL type name. Typically the data type to be
@ -433,6 +440,9 @@ func (m *Map) RegisterDefaultPgType(value any, name string) {
for k := range m.memoizedScanPlans { for k := range m.memoizedScanPlans {
delete(m.memoizedScanPlans, k) delete(m.memoizedScanPlans, k)
} }
for k := range m.memoizedEncodePlans {
delete(m.memoizedEncodePlans, k)
}
} }
func (m *Map) TypeForOID(oid uint32) (*Type, bool) { func (m *Map) TypeForOID(oid uint32) (*Type, bool) {
@ -1018,7 +1028,7 @@ func TryWrapStructScanPlan(target any) (plan WrappedScanPlanNextSetter, nextValu
var targetElemValue reflect.Value var targetElemValue reflect.Value
if targetValue.IsNil() { if targetValue.IsNil() {
targetElemValue = reflect.New(targetValue.Type().Elem()) targetElemValue = reflect.Zero(targetValue.Type().Elem())
} else { } else {
targetElemValue = targetValue.Elem() targetElemValue = targetValue.Elem()
} }
@ -1139,6 +1149,31 @@ func (plan *wrapPtrMultiDimSliceScanPlan) Scan(src []byte, target any) error {
return plan.next.Scan(src, &anyMultiDimSliceArray{slice: reflect.ValueOf(target).Elem()}) return plan.next.Scan(src, &anyMultiDimSliceArray{slice: reflect.ValueOf(target).Elem()})
} }
// TryWrapPtrArrayScanPlan tries to wrap a pointer to a single dimension array.
func TryWrapPtrArrayScanPlan(target any) (plan WrappedScanPlanNextSetter, nextValue any, ok bool) {
targetValue := reflect.ValueOf(target)
if targetValue.Kind() != reflect.Ptr {
return nil, nil, false
}
targetElemValue := targetValue.Elem()
if targetElemValue.Kind() == reflect.Array {
return &wrapPtrArrayReflectScanPlan{}, &anyArrayArrayReflect{array: targetElemValue}, true
}
return nil, nil, false
}
type wrapPtrArrayReflectScanPlan struct {
next ScanPlan
}
func (plan *wrapPtrArrayReflectScanPlan) SetNext(next ScanPlan) { plan.next = next }
func (plan *wrapPtrArrayReflectScanPlan) Scan(src []byte, target any) error {
return plan.next.Scan(src, &anyArrayArrayReflect{array: reflect.ValueOf(target).Elem()})
}
// PlanScan prepares a plan to scan a value into target. // PlanScan prepares a plan to scan a value into target.
func (m *Map) PlanScan(oid uint32, formatCode int16, target any) ScanPlan { func (m *Map) PlanScan(oid uint32, formatCode int16, target any) ScanPlan {
oidMemo := m.memoizedScanPlans[oid] oidMemo := m.memoizedScanPlans[oid]
@ -1200,6 +1235,16 @@ func (m *Map) planScan(oid uint32, formatCode int16, target any) ScanPlan {
} }
} }
// This needs to happen before trying m.TryWrapScanPlanFuncs. Otherwise, a sql.Scanner would not get called if it was
// defined on a type that could be unwrapped such as `type myString string`.
//
// https://github.com/jackc/pgtype/issues/197
if dt == nil {
if _, ok := target.(sql.Scanner); ok {
return &scanPlanSQLScanner{formatCode: formatCode}
}
}
for _, f := range m.TryWrapScanPlanFuncs { for _, f := range m.TryWrapScanPlanFuncs {
if wrapperPlan, nextDst, ok := f(target); ok { if wrapperPlan, nextDst, ok := f(target); ok {
if nextPlan := m.planScan(oid, formatCode, nextDst); nextPlan != nil { if nextPlan := m.planScan(oid, formatCode, nextDst); nextPlan != nil {
@ -1221,10 +1266,6 @@ func (m *Map) planScan(oid uint32, formatCode int16, target any) ScanPlan {
} }
} }
if _, ok := target.(sql.Scanner); ok {
return &scanPlanSQLScanner{formatCode: formatCode}
}
return &scanPlanFail{m: m, oid: oid, formatCode: formatCode} return &scanPlanFail{m: m, oid: oid, formatCode: formatCode}
} }
@ -1289,6 +1330,24 @@ func codecDecodeToTextFormat(codec Codec, m *Map, oid uint32, format int16, src
// PlanEncode returns an Encode plan for encoding value into PostgreSQL format for oid and format. If no plan can be // PlanEncode returns an Encode plan for encoding value into PostgreSQL format for oid and format. If no plan can be
// found then nil is returned. // found then nil is returned.
func (m *Map) PlanEncode(oid uint32, format int16, value any) EncodePlan { func (m *Map) PlanEncode(oid uint32, format int16, value any) EncodePlan {
oidMemo := m.memoizedEncodePlans[oid]
if oidMemo == nil {
oidMemo = make(map[reflect.Type][2]EncodePlan)
m.memoizedEncodePlans[oid] = oidMemo
}
targetReflectType := reflect.TypeOf(value)
typeMemo := oidMemo[targetReflectType]
plan := typeMemo[format]
if plan == nil {
plan = m.planEncode(oid, format, value)
typeMemo[format] = plan
oidMemo[targetReflectType] = typeMemo
}
return plan
}
func (m *Map) planEncode(oid uint32, format int16, value any) EncodePlan {
if format == TextFormatCode { if format == TextFormatCode {
switch value.(type) { switch value.(type) {
case string: case string:
@ -1299,16 +1358,16 @@ func (m *Map) PlanEncode(oid uint32, format int16, value any) EncodePlan {
} }
var dt *Type var dt *Type
if dataType, ok := m.TypeForOID(oid); ok {
if oid == 0 { dt = dataType
} else {
// If no type for the OID was found, then either it is unknowable (e.g. the simple protocol) or it is an
// unregistered type. In either case try to find the type and OID that matches the value (e.g. a []byte would be
// registered to PostgreSQL bytea).
if dataType, ok := m.TypeForValue(value); ok { if dataType, ok := m.TypeForValue(value); ok {
dt = dataType dt = dataType
oid = dt.OID // Preserve assumed OID in case we are recursively called below. oid = dt.OID // Preserve assumed OID in case we are recursively called below.
} }
} else {
if dataType, ok := m.TypeForOID(oid); ok {
dt = dataType
}
} }
if dt != nil { if dt != nil {
@ -1941,6 +2000,35 @@ func (plan *wrapMultiDimSliceEncodePlan) Encode(value any, buf []byte) (newBuf [
return plan.next.Encode(&w, buf) return plan.next.Encode(&w, buf)
} }
func TryWrapArrayEncodePlan(value any) (plan WrappedEncodePlanNextSetter, nextValue any, ok bool) {
if _, ok := value.(driver.Valuer); ok {
return nil, nil, false
}
if valueType := reflect.TypeOf(value); valueType != nil && valueType.Kind() == reflect.Array {
w := anyArrayArrayReflect{
array: reflect.ValueOf(value),
}
return &wrapArrayEncodeReflectPlan{}, w, true
}
return nil, nil, false
}
type wrapArrayEncodeReflectPlan struct {
next EncodePlan
}
func (plan *wrapArrayEncodeReflectPlan) SetNext(next EncodePlan) { plan.next = next }
func (plan *wrapArrayEncodeReflectPlan) Encode(value any, buf []byte) (newBuf []byte, err error) {
w := anyArrayArrayReflect{
array: reflect.ValueOf(value),
}
return plan.next.Encode(w, buf)
}
func newEncodeError(value any, m *Map, oid uint32, formatCode int16, err error) error { func newEncodeError(value any, m *Map, oid uint32, formatCode int16, err error) error {
var format string var format string
switch formatCode { switch formatCode {

@ -22,7 +22,7 @@ type TIDValuer interface {
// //
// When one does // When one does
// //
// select ctid, * from some_table; // select ctid, * from some_table;
// //
// it is the data type of the ctid hidden system column. // it is the data type of the ctid hidden system column.
// //

@ -2,58 +2,58 @@
// //
// A database/sql connection can be established through sql.Open. // A database/sql connection can be established through sql.Open.
// //
// db, err := sql.Open("pgx", "postgres://pgx_md5:secret@localhost:5432/pgx_test?sslmode=disable") // db, err := sql.Open("pgx", "postgres://pgx_md5:secret@localhost:5432/pgx_test?sslmode=disable")
// if err != nil { // if err != nil {
// return err // return err
// } // }
// //
// Or from a DSN string. // Or from a DSN string.
// //
// db, err := sql.Open("pgx", "user=postgres password=secret host=localhost port=5432 database=pgx_test sslmode=disable") // db, err := sql.Open("pgx", "user=postgres password=secret host=localhost port=5432 database=pgx_test sslmode=disable")
// if err != nil { // if err != nil {
// return err // return err
// } // }
// //
// Or a pgx.ConnConfig can be used to set configuration not accessible via connection string. In this case the // Or a pgx.ConnConfig can be used to set configuration not accessible via connection string. In this case the
// pgx.ConnConfig must first be registered with the driver. This registration returns a connection string which is used // pgx.ConnConfig must first be registered with the driver. This registration returns a connection string which is used
// with sql.Open. // with sql.Open.
// //
// connConfig, _ := pgx.ParseConfig(os.Getenv("DATABASE_URL")) // connConfig, _ := pgx.ParseConfig(os.Getenv("DATABASE_URL"))
// connConfig.Logger = myLogger // connConfig.Logger = myLogger
// connStr := stdlib.RegisterConnConfig(connConfig) // connStr := stdlib.RegisterConnConfig(connConfig)
// db, _ := sql.Open("pgx", connStr) // db, _ := sql.Open("pgx", connStr)
// //
// pgx uses standard PostgreSQL positional parameters in queries. e.g. $1, $2. It does not support named parameters. // pgx uses standard PostgreSQL positional parameters in queries. e.g. $1, $2. It does not support named parameters.
// //
// db.QueryRow("select * from users where id=$1", userID) // db.QueryRow("select * from users where id=$1", userID)
// //
// (*sql.Conn) Raw() can be used to get a *pgx.Conn from the standard database/sql.DB connection pool. This allows // (*sql.Conn) Raw() can be used to get a *pgx.Conn from the standard database/sql.DB connection pool. This allows
// operations that use pgx specific functionality. // operations that use pgx specific functionality.
// //
// // Given db is a *sql.DB // // Given db is a *sql.DB
// conn, err := db.Conn(context.Background()) // conn, err := db.Conn(context.Background())
// if err != nil { // if err != nil {
// // handle error from acquiring connection from DB pool // // handle error from acquiring connection from DB pool
// } // }
// //
// err = conn.Raw(func(driverConn any) error { // err = conn.Raw(func(driverConn any) error {
// conn := driverConn.(*stdlib.Conn).Conn() // conn is a *pgx.Conn // conn := driverConn.(*stdlib.Conn).Conn() // conn is a *pgx.Conn
// // Do pgx specific stuff with conn // // Do pgx specific stuff with conn
// conn.CopyFrom(...) // conn.CopyFrom(...)
// return nil // return nil
// }) // })
// if err != nil { // if err != nil {
// // handle error that occurred while using *pgx.Conn // // handle error that occurred while using *pgx.Conn
// } // }
// //
// PostgreSQL Specific Data Types // # PostgreSQL Specific Data Types
// //
// The pgtype package provides support for PostgreSQL specific types. *pgtype.Map.SQLScanner is an adapter that makes // The pgtype package provides support for PostgreSQL specific types. *pgtype.Map.SQLScanner is an adapter that makes
// these types usable as a sql.Scanner. // these types usable as a sql.Scanner.
// //
// m := pgtype.NewMap() // m := pgtype.NewMap()
// var a []int64 // var a []int64
// err := db.QueryRow("select '{1,2,3}'::bigint[]").Scan(m.SQLScanner(&a)) // err := db.QueryRow("select '{1,2,3}'::bigint[]").Scan(m.SQLScanner(&a))
package stdlib package stdlib
import ( import (
@ -66,6 +66,7 @@ import (
"math" "math"
"math/rand" "math/rand"
"reflect" "reflect"
"sort"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -85,7 +86,13 @@ func init() {
pgxDriver = &Driver{ pgxDriver = &Driver{
configs: make(map[string]*pgx.ConnConfig), configs: make(map[string]*pgx.ConnConfig),
} }
sql.Register("pgx", pgxDriver)
drivers := sql.Drivers()
// if pgx driver was already registered by different pgx major version then we skip registration under the default name.
if i := sort.SearchStrings(sql.Drivers(), "pgx"); len(drivers) >= i || drivers[i] != "pgx" {
sql.Register("pgx", pgxDriver)
}
sql.Register("pgx/v5", pgxDriver)
databaseSQLResultFormats = pgx.QueryResultFormatsByOID{ databaseSQLResultFormats = pgx.QueryResultFormatsByOID{
pgtype.BoolOID: 1, pgtype.BoolOID: 1,
@ -140,7 +147,7 @@ func RandomizeHostOrderFunc(ctx context.Context, connConfig *pgx.ConnConfig) err
return nil return nil
} }
newFallbacks := append([]*pgconn.FallbackConfig{&pgconn.FallbackConfig{ newFallbacks := append([]*pgconn.FallbackConfig{{
Host: connConfig.Host, Host: connConfig.Host,
Port: connConfig.Port, Port: connConfig.Port,
TLSConfig: connConfig.TLSConfig, TLSConfig: connConfig.TLSConfig,

@ -84,8 +84,8 @@ github.com/jackc/pgpassfile
# github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a # github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a
## explicit; go 1.14 ## explicit; go 1.14
github.com/jackc/pgservicefile github.com/jackc/pgservicefile
# github.com/jackc/pgx/v5 v5.2.0 # github.com/jackc/pgx/v5 v5.3.0
## explicit; go 1.18 ## explicit; go 1.19
github.com/jackc/pgx/v5 github.com/jackc/pgx/v5
github.com/jackc/pgx/v5/internal/anynil github.com/jackc/pgx/v5/internal/anynil
github.com/jackc/pgx/v5/internal/iobufpool github.com/jackc/pgx/v5/internal/iobufpool

Loading…
Cancel
Save