package postgres import ( "context" "fmt" "net/url" "strconv" "strings" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" ) type Module struct { pool Pool Store *Store } func (module *Module) Close() { if module != nil && module.pool != nil { module.pool.Close() } } func Open(ctx context.Context, config Config) (*Module, error) { if config.Backend == BackendLocal { return &Module{Store: NewDatabase(config, nil)}, nil } if config.Backend != BackendPostgres { return nil, fmt.Errorf("unsupported data backend %q", config.Backend) } databaseURL, err := forcePlaintextDatabaseURL(config.DatabaseURL) if err != nil { return nil, fmt.Errorf("parse DATABASE_URL: %w", err) } poolConfig, err := pgxpool.ParseConfig(databaseURL) if err != nil { return nil, fmt.Errorf("parse DATABASE_URL: %w", err) } poolConfig.MaxConns = config.PoolMax poolConfig.MaxConnIdleTime = config.IdleTimeout poolConfig.ConnConfig.ConnectTimeout = config.ConnectionTimeout poolConfig.ConnConfig.RuntimeParams["statement_timeout"] = strconv.FormatInt(config.StatementTimeout.Milliseconds(), 10) poolConfig.ConnConfig.RuntimeParams["application_name"] = config.ApplicationName poolConfig.ConnConfig.TLSConfig = nil poolConfig.ConnConfig.Fallbacks = nil pool, err := pgxpool.NewWithConfig(ctx, poolConfig) if err != nil { return nil, fmt.Errorf("open PostgreSQL pool: %w", err) } adapter := &pgxPoolAdapter{pool: pool} return &Module{pool: adapter, Store: NewDatabase(config, adapter)}, nil } func forcePlaintextDatabaseURL(databaseURL string) (string, error) { parsed, err := url.Parse(databaseURL) if err != nil { return "", err } query := parsed.Query() for key := range query { switch strings.ToLower(key) { case "sslmode", "sslrootcert": query.Del(key) } } query.Set("sslmode", string(SSLDisable)) parsed.RawQuery = query.Encode() return parsed.String(), nil } type pgxPoolAdapter struct { pool *pgxpool.Pool } func (p *pgxPoolAdapter) Query(ctx context.Context, sql string, args ...any) (Rows, error) { return p.pool.Query(ctx, sql, args...) } func (p *pgxPoolAdapter) Begin(ctx context.Context) (Transaction, error) { tx, err := p.pool.Begin(ctx) if err != nil { return nil, err } return &pgxTransactionAdapter{tx: tx}, nil } func (p *pgxPoolAdapter) Close() { p.pool.Close() } var _ Pool = (*pgxPoolAdapter)(nil) var _ TransactionBeginner = (*pgxPoolAdapter)(nil) type pgxTransactionAdapter struct { tx pgx.Tx } func (t *pgxTransactionAdapter) Query(ctx context.Context, sql string, args ...any) (Rows, error) { return t.tx.Query(ctx, sql, args...) } func (t *pgxTransactionAdapter) Exec(ctx context.Context, sql string, args ...any) error { _, err := t.tx.Exec(ctx, sql, args...) return err } func (t *pgxTransactionAdapter) Commit(ctx context.Context) error { return t.tx.Commit(ctx) } func (t *pgxTransactionAdapter) Rollback(ctx context.Context) error { return t.tx.Rollback(ctx) } var _ Transaction = (*pgxTransactionAdapter)(nil)