Skip to content

Commit 8714ac7

Browse files
committed
Simplify custom type autoloading with pgxpool
Provide some backwards-compatible configuration options for pgxpool which streamlines the use of the bulk loading of custom types: - AutoLoadTypes: a list of type (or class) names to automatically load for each connection, automatically also loading any other types these depend on. - ReuseTypeMaps: if enabled, pgxpool will cache the typemap information, avoiding the need to perform any further queries as new connections are created. ReuseTypeMaps is disabled by default as in some situations, a connection string might resolve to a pool of servers which do not share the same type name -> OID mapping.
1 parent ca18af4 commit 8714ac7

File tree

2 files changed

+78
-9
lines changed

2 files changed

+78
-9
lines changed

pgxpool/pool.go

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@ import (
1515
"github.com/jackc/puddle/v2"
1616
)
1717

18-
var defaultMaxConns = int32(4)
19-
var defaultMinConns = int32(0)
20-
var defaultMaxConnLifetime = time.Hour
21-
var defaultMaxConnIdleTime = time.Minute * 30
22-
var defaultHealthCheckPeriod = time.Minute
18+
var (
19+
defaultMaxConns = int32(4)
20+
defaultMinConns = int32(0)
21+
defaultMaxConnLifetime = time.Hour
22+
defaultMaxConnIdleTime = time.Minute * 30
23+
defaultHealthCheckPeriod = time.Minute
24+
)
2325

2426
type connResource struct {
2527
conn *pgx.Conn
@@ -100,6 +102,11 @@ type Pool struct {
100102

101103
closeOnce sync.Once
102104
closeChan chan struct{}
105+
106+
autoLoadTypes []string
107+
reuseTypeMap bool
108+
autoLoadMutex *sync.Mutex
109+
autoLoadTypeInfos []*pgx.TypeInfo
103110
}
104111

105112
// Config is the configuration struct for creating a pool. It must be created by [ParseConfig] and then it can be
@@ -147,6 +154,15 @@ type Config struct {
147154
// HealthCheckPeriod is the duration between checks of the health of idle connections.
148155
HealthCheckPeriod time.Duration
149156

157+
// AutoLoadTypes is a list of user-defined types which should automatically be loaded
158+
// as each new connection is created. This will also load any related types, directly
159+
// or indirectly required to handle these types.
160+
AutoLoadTypes []string
161+
162+
// ReuseTypeMaps, if enabled, will reuse the typemap information being used by AutoLoadTypes.
163+
// This removes the need to query the database each time a new connection is created.
164+
ReuseTypeMaps bool
165+
150166
createdByParseConfig bool // Used to enforce created by ParseConfig rule.
151167
}
152168

@@ -185,6 +201,8 @@ func NewWithConfig(ctx context.Context, config *Config) (*Pool, error) {
185201
config: config,
186202
beforeConnect: config.BeforeConnect,
187203
afterConnect: config.AfterConnect,
204+
autoLoadTypes: config.AutoLoadTypes,
205+
reuseTypeMap: config.ReuseTypeMaps,
188206
beforeAcquire: config.BeforeAcquire,
189207
afterRelease: config.AfterRelease,
190208
beforeClose: config.BeforeClose,
@@ -196,6 +214,7 @@ func NewWithConfig(ctx context.Context, config *Config) (*Pool, error) {
196214
healthCheckPeriod: config.HealthCheckPeriod,
197215
healthCheckChan: make(chan struct{}, 1),
198216
closeChan: make(chan struct{}),
217+
autoLoadMutex: new(sync.Mutex),
199218
}
200219

201220
if t, ok := config.ConnConfig.Tracer.(AcquireTracer); ok {
@@ -237,6 +256,19 @@ func NewWithConfig(ctx context.Context, config *Config) (*Pool, error) {
237256
}
238257
}
239258

259+
if len(p.autoLoadTypes) > 0 {
260+
types, err := p.LoadTypes(ctx, conn, p.autoLoadTypes)
261+
if err != nil {
262+
conn.Close(ctx)
263+
panic(err)
264+
}
265+
if err = conn.RegisterTypes(types, conn.TypeMap()); err != nil {
266+
conn.Close(ctx)
267+
268+
panic(err)
269+
}
270+
}
271+
240272
jitterSecs := rand.Float64() * config.MaxConnLifetimeJitter.Seconds()
241273
maxAgeTime := time.Now().Add(config.MaxConnLifetime).Add(time.Duration(jitterSecs) * time.Second)
242274

@@ -388,6 +420,22 @@ func (p *Pool) Close() {
388420
})
389421
}
390422

423+
func (p *Pool) LoadTypes(ctx context.Context, conn *pgx.Conn, typeNames []string) ([]*pgx.TypeInfo, error) {
424+
p.autoLoadMutex.Lock()
425+
defer p.autoLoadMutex.Unlock()
426+
if p.autoLoadTypeInfos != nil {
427+
return p.autoLoadTypeInfos, nil
428+
}
429+
types, err := conn.LoadTypes(ctx, typeNames)
430+
if err != nil {
431+
return nil, err
432+
}
433+
if p.reuseTypeMap {
434+
p.autoLoadTypeInfos = types
435+
}
436+
return types, err
437+
}
438+
391439
func (p *Pool) isExpired(res *puddle.Resource[*connResource]) bool {
392440
return time.Now().After(res.Value().maxAgeTime)
393441
}
@@ -482,7 +530,6 @@ func (p *Pool) checkMinConns() error {
482530
func (p *Pool) createIdleResources(parentCtx context.Context, targetResources int) error {
483531
ctx, cancel := context.WithCancel(parentCtx)
484532
defer cancel()
485-
486533
errs := make(chan error, targetResources)
487534

488535
for i := 0; i < targetResources; i++ {
@@ -495,7 +542,6 @@ func (p *Pool) createIdleResources(parentCtx context.Context, targetResources in
495542
errs <- err
496543
}()
497544
}
498-
499545
var firstError error
500546
for i := 0; i < targetResources; i++ {
501547
err := <-errs

pgxpool/pool_test.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,31 @@ func TestPoolBeforeConnect(t *testing.T) {
261261
assert.EqualValues(t, "pgx", str)
262262
}
263263

264+
func TestAutoLoadTypes(t *testing.T) {
265+
t.Parallel()
266+
267+
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
268+
defer cancel()
269+
270+
config, err := pgxpool.ParseConfig(os.Getenv("PGX_TEST_DATABASE"))
271+
require.NoError(t, err)
272+
273+
db1, err := pgxpool.NewWithConfig(ctx, config)
274+
require.NoError(t, err)
275+
defer db1.Close()
276+
db1.Exec(ctx, "DROP DOMAIN IF EXISTS autoload_uint64; CREATE DOMAIN autoload_uint64 as numeric(20,0)")
277+
defer db1.Exec(ctx, "DROP DOMAIN autoload_uint64")
278+
279+
config.AutoLoadTypes = []string{"autoload_uint64"}
280+
db2, err := pgxpool.NewWithConfig(ctx, config)
281+
require.NoError(t, err)
282+
283+
var n uint64
284+
err = db2.QueryRow(ctx, "select 12::autoload_uint64").Scan(&n)
285+
require.NoError(t, err)
286+
assert.EqualValues(t, uint64(12), n)
287+
}
288+
264289
func TestPoolAfterConnect(t *testing.T) {
265290
t.Parallel()
266291

@@ -676,7 +701,6 @@ func TestPoolQuery(t *testing.T) {
676701
stats = pool.Stat()
677702
assert.EqualValues(t, 0, stats.AcquiredConns())
678703
assert.EqualValues(t, 1, stats.TotalConns())
679-
680704
}
681705

682706
func TestPoolQueryRow(t *testing.T) {
@@ -1104,7 +1128,6 @@ func TestConnectEagerlyReachesMinPoolSize(t *testing.T) {
11041128
}
11051129

11061130
t.Fatal("did not reach min pool size")
1107-
11081131
}
11091132

11101133
func TestPoolSendBatchBatchCloseTwice(t *testing.T) {

0 commit comments

Comments
 (0)