taldir

Directory service to resolve wallet mailboxes by messenger addresses
Log | Files | Refs | Submodules | README | LICENSE

connector.go (37770B)


      1 package pq
      2 
      3 import (
      4 	"context"
      5 	"crypto/tls"
      6 	"database/sql/driver"
      7 	"fmt"
      8 	"math/rand"
      9 	"net"
     10 	"net/netip"
     11 	neturl "net/url"
     12 	"os"
     13 	"path/filepath"
     14 	"reflect"
     15 	"slices"
     16 	"sort"
     17 	"strconv"
     18 	"strings"
     19 	"time"
     20 	"unicode"
     21 
     22 	"github.com/lib/pq/internal/pgservice"
     23 	"github.com/lib/pq/internal/pqutil"
     24 	"github.com/lib/pq/internal/proto"
     25 )
     26 
     27 type (
     28 	// SSLMode is a sslmode setting.
     29 	SSLMode string
     30 
     31 	// SSLNegotiation is a sslnegotiation setting.
     32 	SSLNegotiation string
     33 
     34 	// TargetSessionAttrs is a target_session_attrs setting.
     35 	TargetSessionAttrs string
     36 
     37 	// LoadBalanceHosts is a load_balance_hosts setting.
     38 	LoadBalanceHosts string
     39 
     40 	// ProtocolVersion is a min_protocol_version or max_protocol_version
     41 	// setting.
     42 	ProtocolVersion string
     43 
     44 	// SSLProtocolVersion is a ssl_min_protocol_version or
     45 	// ssl_max_protocol_version setting.
     46 	SSLProtocolVersion string
     47 )
     48 
     49 // Values for [SSLMode] that pq supports.
     50 const (
     51 	// No SSL
     52 	SSLModeDisable = SSLMode("disable")
     53 
     54 	// First try a non-SSL connection and if that fails try an SSL connection.
     55 	SSLModeAllow = SSLMode("allow")
     56 
     57 	// First try an SSL connection and if that fails try a non-SSL connection.
     58 	SSLModePrefer = SSLMode("prefer")
     59 
     60 	// Require SSL, but skip verification. This is the default.
     61 	SSLModeRequire = SSLMode("require")
     62 
     63 	// Require SSL and verify that the certificate was signed by a trusted CA.
     64 	SSLModeVerifyCA = SSLMode("verify-ca")
     65 
     66 	// Require SSL and verify that the certificate was signed by a trusted CA
     67 	// and the server host name matches the one in the certificate.
     68 	SSLModeVerifyFull = SSLMode("verify-full")
     69 )
     70 
     71 var sslModes = []SSLMode{SSLModeDisable, SSLModeAllow, SSLModePrefer, SSLModeRequire,
     72 	SSLModeVerifyFull, SSLModeVerifyCA}
     73 
     74 func (s SSLMode) useSSL() bool {
     75 	switch s {
     76 	case SSLModePrefer, SSLModeRequire, SSLModeVerifyCA, SSLModeVerifyFull:
     77 		return true
     78 	}
     79 	return false
     80 }
     81 
     82 // Values for [SSLNegotiation] that pq supports.
     83 const (
     84 	// Negotiate whether SSL should be used. This is the default.
     85 	SSLNegotiationPostgres = SSLNegotiation("postgres")
     86 
     87 	// Always use SSL, don't try to negotiate.
     88 	SSLNegotiationDirect = SSLNegotiation("direct")
     89 )
     90 
     91 var sslNegotiations = []SSLNegotiation{SSLNegotiationPostgres, SSLNegotiationDirect}
     92 
     93 // Values for [TargetSessionAttrs] that pq supports.
     94 const (
     95 	// Any successful connection is acceptable. This is the default.
     96 	TargetSessionAttrsAny = TargetSessionAttrs("any")
     97 
     98 	// Session must accept read-write transactions by default: the server must
     99 	// not be in hot standby mode and default_transaction_read_only must be
    100 	// off.
    101 	TargetSessionAttrsReadWrite = TargetSessionAttrs("read-write")
    102 
    103 	// Session must not accept read-write transactions by default.
    104 	TargetSessionAttrsReadOnly = TargetSessionAttrs("read-only")
    105 
    106 	// Server must not be in hot standby mode.
    107 	TargetSessionAttrsPrimary = TargetSessionAttrs("primary")
    108 
    109 	// Server must be in hot standby mode.
    110 	TargetSessionAttrsStandby = TargetSessionAttrs("standby")
    111 
    112 	// First try to find a standby server, but if none of the listed hosts is a
    113 	// standby server, try again in any mode.
    114 	TargetSessionAttrsPreferStandby = TargetSessionAttrs("prefer-standby")
    115 )
    116 
    117 var targetSessionAttrs = []TargetSessionAttrs{TargetSessionAttrsAny,
    118 	TargetSessionAttrsReadWrite, TargetSessionAttrsReadOnly, TargetSessionAttrsPrimary,
    119 	TargetSessionAttrsStandby, TargetSessionAttrsPreferStandby}
    120 
    121 // Values for [LoadBalanceHosts] that pq supports.
    122 const (
    123 	// Don't load balance; try hosts in the order in which they're provided.
    124 	// This is the default.
    125 	LoadBalanceHostsDisable = LoadBalanceHosts("disable")
    126 
    127 	// Hosts are tried in random order to balance connections across multiple
    128 	// PostgreSQL servers.
    129 	//
    130 	// When using this value it's recommended to also configure a reasonable
    131 	// value for connect_timeout. Because then, if one of the nodes that are
    132 	// used for load balancing is not responding, a new node will be tried.
    133 	LoadBalanceHostsRandom = LoadBalanceHosts("random")
    134 )
    135 
    136 var loadBalanceHosts = []LoadBalanceHosts{LoadBalanceHostsDisable, LoadBalanceHostsRandom}
    137 
    138 // Values for [ProtocolVersion] that pq supports.
    139 const (
    140 	// ProtocolVersion30 is the default protocol version, supported in
    141 	// PostgreSQL 3.0 and newer.
    142 	ProtocolVersion30 = ProtocolVersion("3.0")
    143 
    144 	// ProtocolVersion32 uses a longer secret key length for query cancellation,
    145 	// supported in PostgreSQL 18 and newer.
    146 	ProtocolVersion32 = ProtocolVersion("3.2")
    147 
    148 	// ProtocolVersionLatest is the latest protocol version that pq supports
    149 	// (which may not be supported by the server).
    150 	ProtocolVersionLatest = ProtocolVersion("latest")
    151 )
    152 
    153 var protocolVersions = []ProtocolVersion{ProtocolVersion30, ProtocolVersion32, ProtocolVersionLatest}
    154 
    155 // Values for [SSLProtocolVersion] that pq supports.
    156 const (
    157 	SSLProtocolVersionTLS10 = SSLProtocolVersion("TLSv1.0")
    158 	SSLProtocolVersionTLS11 = SSLProtocolVersion("TLSv1.1")
    159 	SSLProtocolVersionTLS12 = SSLProtocolVersion("TLSv1.2")
    160 	SSLProtocolVersionTLS13 = SSLProtocolVersion("TLSv1.3")
    161 )
    162 
    163 var sslProtocolVersions = []SSLProtocolVersion{SSLProtocolVersionTLS10, SSLProtocolVersionTLS11,
    164 	SSLProtocolVersionTLS12, SSLProtocolVersionTLS13}
    165 
    166 func (s SSLProtocolVersion) tlsconf() uint16 {
    167 	switch s {
    168 	case SSLProtocolVersionTLS10:
    169 		return tls.VersionTLS10
    170 	case SSLProtocolVersionTLS11:
    171 		return tls.VersionTLS11
    172 	case SSLProtocolVersionTLS12:
    173 		return tls.VersionTLS12
    174 	case SSLProtocolVersionTLS13:
    175 		return tls.VersionTLS13
    176 	default:
    177 		return 0
    178 	}
    179 }
    180 
    181 // Connector represents a fixed configuration for the pq driver with a given
    182 // dsn. Connector satisfies the [database/sql/driver.Connector] interface and
    183 // can be used to create any number of DB Conn's via [sql.OpenDB].
    184 type Connector struct {
    185 	cfg    Config
    186 	dialer Dialer
    187 }
    188 
    189 // NewConnector returns a connector for the pq driver in a fixed configuration
    190 // with the given dsn. The returned connector can be used to create any number
    191 // of equivalent Conn's. The returned connector is intended to be used with
    192 // [sql.OpenDB].
    193 func NewConnector(dsn string) (*Connector, error) {
    194 	cfg, err := NewConfig(dsn)
    195 	if err != nil {
    196 		return nil, err
    197 	}
    198 	return NewConnectorConfig(cfg)
    199 }
    200 
    201 // NewConnectorConfig returns a connector for the pq driver in a fixed
    202 // configuration with the given [Config]. The returned connector can be used to
    203 // create any number of equivalent Conn's. The returned connector is intended to
    204 // be used with [sql.OpenDB].
    205 func NewConnectorConfig(cfg Config) (*Connector, error) {
    206 	return &Connector{cfg: cfg, dialer: defaultDialer{}}, nil
    207 }
    208 
    209 // Connect returns a connection to the database using the fixed configuration of
    210 // this Connector. Context is not used.
    211 func (c *Connector) Connect(ctx context.Context) (driver.Conn, error) { return c.open(ctx) }
    212 
    213 // Dialer allows change the dialer used to open connections.
    214 func (c *Connector) Dialer(dialer Dialer) { c.dialer = dialer }
    215 
    216 // Driver returns the underlying driver of this Connector.
    217 func (c *Connector) Driver() driver.Driver { return &Driver{} }
    218 
    219 func (p ProtocolVersion) proto() int {
    220 	switch p {
    221 	default:
    222 		return proto.ProtocolVersion30
    223 	case ProtocolVersion32, ProtocolVersionLatest:
    224 		return proto.ProtocolVersion32
    225 	}
    226 }
    227 
    228 // Config holds options pq supports when connecting to PostgreSQL.
    229 //
    230 // The postgres struct tag is used for the value from the DSN (e.g.
    231 // "dbname=abc"), and the env struct tag is used for the environment variable
    232 // (e.g. "PGDATABASE=abc")
    233 type Config struct {
    234 	// The host to connect to. Absolute paths and values that start with @ are
    235 	// for unix domain sockets. Defaults to localhost.
    236 	//
    237 	// A comma-separated list of host names is also accepted, in which case each
    238 	// host name in the list is tried in order or randomly if load_balance_hosts
    239 	// is set. An empty item selects the default of localhost. The
    240 	// target_session_attrs option controls properties the host must have to be
    241 	// considered acceptable.
    242 	Host string `postgres:"host" env:"PGHOST"`
    243 
    244 	// IPv4 or IPv6 address to connect to. Using hostaddr allows the application
    245 	// to avoid a host name lookup, which might be important in applications
    246 	// with time constraints. A hostname is required for sslmode=verify-full and
    247 	// the GSSAPI or SSPI authentication methods.
    248 	//
    249 	// The following rules are used:
    250 	//
    251 	// - If host is given without hostaddr, a host name lookup occurs.
    252 	//
    253 	// - If hostaddr is given without host, the value for hostaddr gives the
    254 	//   server network address. The connection attempt will fail if the
    255 	//   authentication method requires a host name.
    256 	//
    257 	// - If both host and hostaddr are given, the value for hostaddr gives the
    258 	//   server network address. The value for host is ignored unless the
    259 	//   authentication method requires it, in which case it will be used as the
    260 	//   host name.
    261 	//
    262 	// A comma-separated list of hostaddr values is also accepted, in which case
    263 	// each host in the list is tried in order or randonly if load_balance_hosts
    264 	// is set. An empty item causes the corresponding host name to be used, or
    265 	// the default host name if that is empty as well. The target_session_attrs
    266 	// option controls properties the host must have to be considered
    267 	// acceptable.
    268 	Hostaddr netip.Addr `postgres:"hostaddr" env:"PGHOSTADDR"`
    269 
    270 	// The port to connect to. Defaults to 5432.
    271 	//
    272 	// If multiple hosts were given in the host or hostaddr parameters, this
    273 	// parameter may specify a comma-separated list of ports of the same length
    274 	// as the host list, or it may specify a single port number to be used for
    275 	// all hosts. An empty string, or an empty item in a comma-separated list,
    276 	// specifies the default of 5432.
    277 	Port uint16 `postgres:"port" env:"PGPORT"`
    278 
    279 	// The name of the database to connect to.
    280 	Database string `postgres:"dbname" env:"PGDATABASE"`
    281 
    282 	// The user to sign in as. Defaults to the current user.
    283 	User string `postgres:"user" env:"PGUSER"`
    284 
    285 	// The user's password.
    286 	Password string `postgres:"password" env:"PGPASSWORD"`
    287 
    288 	// Path to [pgpass] file to store passwords; overrides Password.
    289 	//
    290 	// [pgpass]: http://www.postgresql.org/docs/current/static/libpq-pgpass.html
    291 	Passfile string `postgres:"passfile" env:"PGPASSFILE"`
    292 
    293 	// Commandline options to send to the server at connection start.
    294 	Options string `postgres:"options" env:"PGOPTIONS"`
    295 
    296 	// Application name, displayed in pg_stat_activity and log entries.
    297 	ApplicationName string `postgres:"application_name" env:"PGAPPNAME"`
    298 
    299 	// Used if application_name is not given. Specifying a fallback name is
    300 	// useful in generic utility programs that wish to set a default application
    301 	// name but allow it to be overridden by the user.
    302 	FallbackApplicationName string `postgres:"fallback_application_name" env:"-"`
    303 
    304 	// Whether to use SSL. Defaults to "require" (different from libpq's default
    305 	// of "prefer").
    306 	//
    307 	// [RegisterTLSConfig] can be used to registers a custom [tls.Config], which
    308 	// can be used by setting sslmode=pqgo-«key» in the connection string.
    309 	SSLMode SSLMode `postgres:"sslmode" env:"PGSSLMODE"`
    310 
    311 	// When set to "direct" it will use SSL without negotiation (PostgreSQL ≥17 only).
    312 	SSLNegotiation SSLNegotiation `postgres:"sslnegotiation" env:"PGSSLNEGOTIATION"`
    313 
    314 	// Path to client SSL certificate. The file must contain PEM encoded data.
    315 	//
    316 	// Defaults to ~/.postgresql/postgresql.crt
    317 	SSLCert string `postgres:"sslcert" env:"PGSSLCERT"`
    318 
    319 	// Path to secret key for sslcert. The file must contain PEM encoded data.
    320 	//
    321 	// Defaults to ~/.postgresql/postgresql.key
    322 	SSLKey string `postgres:"sslkey" env:"PGSSLKEY"`
    323 
    324 	// Path to root certificate. The file must contain PEM encoded data.
    325 	//
    326 	// The special value "system" can be used to load the system's root
    327 	// certificates ([x509.SystemCertPool]). This will change the default
    328 	// sslmode to verify-full and issue an error if a lower setting is used – as
    329 	// anyone can register a valid certificate hostname verification becomes
    330 	// essential.
    331 	//
    332 	// Defaults to ~/.postgresql/root.crt.
    333 	SSLRootCert string `postgres:"sslrootcert" env:"PGSSLROOTCERT"`
    334 
    335 	// By default SNI is on, any value which is not starting with "1" disables
    336 	// SNI.
    337 	SSLSNI bool `postgres:"sslsni" env:"PGSSLSNI"`
    338 
    339 	// Minimum SSL/TLS protocol version to allow for the connection.
    340 	//
    341 	// The default is determined by [tls.Config.MinVersion], which is TLSv1.2 at
    342 	// the time of writing.
    343 	SSLMinProtocolVersion SSLProtocolVersion `postgres:"ssl_min_protocol_version" env:"SSLPGMINPROTOCOLVERSION"`
    344 
    345 	// Maximum SSL/TLS protocol version to allow for the connection. If not set,
    346 	// this parameter is ignored and the connection will use the maximum bound
    347 	// defined by the backend, if set. Setting the maximum protocol version is
    348 	// mainly useful for testing or if some component has issues working with a
    349 	// newer protocol.
    350 	SSLMaxProtocolVersion SSLProtocolVersion `postgres:"ssl_max_protocol_version" env:"SSLPGMAXPROTOCOLVERSION"`
    351 
    352 	// Interpert sslcert and sslkey as PEM encoded data, rather than a path to a
    353 	// PEM file. This is a pq extension, not supported in libpq.
    354 	SSLInline bool `postgres:"sslinline" env:"-"`
    355 
    356 	// GSS (Kerberos) service name when constructing the SPN (default is
    357 	// postgres). This will be combined with the host to form the full SPN:
    358 	// krbsrvname/host.
    359 	KrbSrvname string `postgres:"krbsrvname" env:"PGKRBSRVNAME"`
    360 
    361 	// GSS (Kerberos) SPN. This takes priority over krbsrvname if present. This
    362 	// is a pq extension, not supported in libpq.
    363 	KrbSpn string `postgres:"krbspn" env:"-"`
    364 
    365 	// Maximum time to wait while connecting, in seconds. Zero, negative, or not
    366 	// specified means wait indefinitely
    367 	ConnectTimeout time.Duration `postgres:"connect_timeout" env:"PGCONNECT_TIMEOUT"`
    368 
    369 	// Whether to always send []byte parameters over as binary. Enables single
    370 	// round-trip mode for non-prepared Query calls. This is a pq extension, not
    371 	// supported in libpq.
    372 	BinaryParameters bool `postgres:"binary_parameters" env:"-"`
    373 
    374 	// This connection should never use the binary format when receiving query
    375 	// results from prepared statements. Only provided for debugging. This is a
    376 	// pq extension, not supported in libpq.
    377 	DisablePreparedBinaryResult bool `postgres:"disable_prepared_binary_result" env:"-"`
    378 
    379 	// Client encoding; pq only supports UTF8 and this must be blank or "UTF8".
    380 	ClientEncoding string `postgres:"client_encoding" env:"PGCLIENTENCODING"`
    381 
    382 	// Date/time representation to use; pq only supports "ISO, MDY" and this
    383 	// must be blank or "ISO, MDY".
    384 	Datestyle string `postgres:"datestyle" env:"PGDATESTYLE"`
    385 
    386 	// Default time zone.
    387 	TZ string `postgres:"tz" env:"PGTZ"`
    388 
    389 	// Default mode for the genetic query optimizer.
    390 	Geqo string `postgres:"geqo" env:"PGGEQO"`
    391 
    392 	// Determine whether the session must have certain properties to be
    393 	// acceptable. It's typically used in combination with multiple host names
    394 	// to select the first acceptable alternative among several hosts.
    395 	TargetSessionAttrs TargetSessionAttrs `postgres:"target_session_attrs" env:"PGTARGETSESSIONATTRS"`
    396 
    397 	// Controls the order in which the client tries to connect to the available
    398 	// hosts. Once a connection attempt is successful no other hosts will be
    399 	// tried. This parameter is typically used in combination with multiple host
    400 	// names.
    401 	//
    402 	// This parameter can be used in combination with target_session_attrs to,
    403 	// for example, load balance over standby servers only. Once successfully
    404 	// connected, subsequent queries on the returned connection will all be sent
    405 	// to the same server.
    406 	LoadBalanceHosts LoadBalanceHosts `postgres:"load_balance_hosts" env:"PGLOADBALANCEHOSTS"`
    407 
    408 	// Minimum acceptable PostgreSQL protocol version. If the server does not
    409 	// support at least this version, the connection will fail. Defaults to
    410 	// "3.0".
    411 	MinProtocolVersion ProtocolVersion `postgres:"min_protocol_version" env:"PGMINPROTOCOLVERSION"`
    412 
    413 	// Maximum PostgreSQL protocol version to request from the server. Defaults to "3.0".
    414 	MaxProtocolVersion ProtocolVersion `postgres:"max_protocol_version" env:"PGMAXPROTOCOLVERSION"`
    415 
    416 	// Load connection parameters from the service file at ~/.pg_service.conf
    417 	// (which can be configured with PGSERVICEFILE).
    418 	//
    419 	// The service file is a INI-like file to configure connection parameters:
    420 	//
    421 	//   [servicename]
    422 	//   # Comment
    423 	//   dbname=foo
    424 	//
    425 	// Unlike libpq, this does not look at the system-wide service file, as the
    426 	// location of this is a compile-time value that is not easy for pq to
    427 	// retrieve.
    428 	Service string `postgres:"service" env:"PGSERVICE"`
    429 
    430 	// Path to connection service file. Defaults to ~/.pg_service.conf.
    431 	ServiceFile string `postgres:"-" env:"PGSERVICEFILE"`
    432 
    433 	// Runtime parameters: any unrecognized parameter in the DSN will be added
    434 	// to this and sent to PostgreSQL during startup.
    435 	Runtime map[string]string `postgres:"-" env:"-"`
    436 
    437 	// Multi contains additional connection details. The first value is
    438 	// available in [Config.Host], [Config.Hostaddr], and [Config.Port], and
    439 	// additional ones (if any) are available here.
    440 	Multi []ConfigMultihost
    441 
    442 	// Record which parameters were given, so we can distinguish between an
    443 	// empty string "not given at all".
    444 	//
    445 	// The alternative is to use pointers or sql.Null[..], but that's more
    446 	// awkward to use.
    447 	set []string `env:"set"`
    448 
    449 	multiHost     []string
    450 	multiHostaddr []netip.Addr
    451 	multiPort     []uint16
    452 }
    453 
    454 // ConfigMultihost specifies an additional server to try to connect to.
    455 type ConfigMultihost struct {
    456 	Host     string
    457 	Hostaddr netip.Addr
    458 	Port     uint16
    459 }
    460 
    461 // NewConfig creates a new [Config] from the defaults, environment, service
    462 // file, and DSN, in that order. That is: a service overrides any value from the
    463 // environment, which in turn gets overridden by the same parameter in the
    464 // connection string.
    465 //
    466 // Most connection parameters supported by PostgreSQL are supported; see the
    467 // [Config] struct for supported parameters. pq also lets you specify any
    468 // [run-time parameter] such as search_path or work_mem in the connection
    469 // string. This is different from libpq, which uses the "options" parameter for
    470 // this (which also works in pq).
    471 //
    472 // # key=value connection strings
    473 //
    474 // For key=value strings, use single quotes for values that contain whitespace
    475 // or empty values. A backslash will escape the next character:
    476 //
    477 //	"user=pqgo password='with spaces'"
    478 //	"user=''"
    479 //	"user=space\ man password='it\'s valid'"
    480 //
    481 // # URL connection strings
    482 //
    483 // pq supports URL-style postgres:// or postgresql:// connection strings in the
    484 // form:
    485 //
    486 //	postgres[ql]://[user[:pwd]@][net-location][:port][/dbname][?param1=value1&...]
    487 //
    488 // Go's [net/url.Parse] is more strict than PostgreSQL's URL parser and will
    489 // (correctly) reject %2F in the host part. This means that unix-socket URLs:
    490 //
    491 //	postgres://[user[:pwd]@][unix-socket][:port[/dbname]][?param1=value1&...]
    492 //	postgres://%2Ftmp%2Fpostgres/db
    493 //
    494 // will not work. You will need to use "host=/tmp/postgres dbname=db".
    495 //
    496 // Similarly, multiple ports also won't work, but ?port= will:
    497 //
    498 //	postgres://host1,host2:5432,6543/dbname         Doesn't work
    499 //	postgres://host1,host2/dbname?port=5432,6543    Works
    500 //
    501 // # Environment
    502 //
    503 // Most [PostgreSQL environment variables] are supported by pq. Environment
    504 // variables have a lower precedence than explicitly provided connection
    505 // parameters. pq will return an error if environment variables it does not
    506 // support are set. Environment variables have a lower precedence than
    507 // explicitly provided connection parameters.
    508 //
    509 // [PostgreSQL environment variables]: http://www.postgresql.org/docs/current/static/libpq-envars.html
    510 // [run-time parameter]: http://www.postgresql.org/docs/current/static/runtime-config.html
    511 func NewConfig(dsn string) (Config, error) {
    512 	return newConfig(dsn, os.Environ())
    513 }
    514 
    515 // Clone returns a copy of the [Config].
    516 func (cfg Config) Clone() Config {
    517 	rt := make(map[string]string)
    518 	for k, v := range cfg.Runtime {
    519 		rt[k] = v
    520 	}
    521 	c := cfg
    522 	c.Runtime = rt
    523 	c.set = append([]string{}, cfg.set...)
    524 	return c
    525 }
    526 
    527 // hosts returns a slice of copies of this config, one for each host.
    528 func (cfg Config) hosts() []Config {
    529 	cfgs := make([]Config, 1, len(cfg.Multi)+1)
    530 	cfgs[0] = cfg.Clone()
    531 	for _, m := range cfg.Multi {
    532 		c := cfg.Clone()
    533 		c.Host, c.Hostaddr, c.Port = m.Host, m.Hostaddr, m.Port
    534 		cfgs = append(cfgs, c)
    535 	}
    536 
    537 	if cfg.LoadBalanceHosts == LoadBalanceHostsRandom {
    538 		rand.Shuffle(len(cfgs), func(i, j int) { cfgs[i], cfgs[j] = cfgs[j], cfgs[i] })
    539 	}
    540 
    541 	return cfgs
    542 }
    543 
    544 func newConfig(dsn string, env []string) (Config, error) {
    545 	cfg := Config{
    546 		Host:               "localhost",
    547 		Port:               5432,
    548 		SSLSNI:             true,
    549 		MinProtocolVersion: "3.0",
    550 		MaxProtocolVersion: "3.0",
    551 	}
    552 	if err := cfg.fromEnv(env); err != nil {
    553 		return Config{}, err
    554 	}
    555 	if err := cfg.fromDSN(dsn); err != nil {
    556 		return Config{}, err
    557 	}
    558 	if err := cfg.fromService(); err != nil {
    559 		return Config{}, err
    560 	}
    561 
    562 	// Need to have exactly the same number of host and hostaddr, or only specify one.
    563 	if cfg.isset("host") && cfg.Host != "" && cfg.Hostaddr != (netip.Addr{}) && len(cfg.multiHost) != len(cfg.multiHostaddr) {
    564 		return Config{}, fmt.Errorf("pq: could not match %d host names to %d hostaddr values",
    565 			len(cfg.multiHost)+1, len(cfg.multiHostaddr)+1)
    566 	}
    567 	// Need one port that applies to all or exactly the same number of ports as hosts.
    568 	l, ll := max(len(cfg.multiHost), len(cfg.multiHostaddr)), len(cfg.multiPort)
    569 	if l > 0 && ll > 0 && l != ll {
    570 		return Config{}, fmt.Errorf("pq: could not match %d port numbers to %d hosts", ll+1, l+1)
    571 	}
    572 
    573 	// Populate Multi
    574 	if len(cfg.multiHostaddr) > len(cfg.multiHost) {
    575 		cfg.multiHost = make([]string, len(cfg.multiHostaddr))
    576 	}
    577 	for i, h := range cfg.multiHost {
    578 		p := cfg.Port
    579 		if len(cfg.multiPort) > 0 {
    580 			p = cfg.multiPort[i]
    581 		}
    582 		var addr netip.Addr
    583 		if len(cfg.multiHostaddr) > 0 {
    584 			addr = cfg.multiHostaddr[i]
    585 		}
    586 		cfg.Multi = append(cfg.Multi, ConfigMultihost{
    587 			Host:     h,
    588 			Port:     p,
    589 			Hostaddr: addr,
    590 		})
    591 	}
    592 
    593 	// Use the "fallback" application name if necessary
    594 	if cfg.isset("fallback_application_name") && !cfg.isset("application_name") {
    595 		cfg.ApplicationName = cfg.FallbackApplicationName
    596 	}
    597 
    598 	// We can't work with any client_encoding other than UTF-8 currently.
    599 	// However, we have historically allowed the user to set it to UTF-8
    600 	// explicitly, and there's no reason to break such programs, so allow that.
    601 	// Note that the "options" setting could also set client_encoding, but
    602 	// parsing its value is not worth it.  Instead, we always explicitly send
    603 	// client_encoding as a separate run-time parameter, which should override
    604 	// anything set in options.
    605 	if cfg.isset("client_encoding") && !isUTF8(cfg.ClientEncoding) {
    606 		return Config{}, fmt.Errorf(`pq: unsupported client_encoding %q: must be absent or "UTF8"`, cfg.ClientEncoding)
    607 	}
    608 	// DateStyle needs a similar treatment.
    609 	if cfg.isset("datestyle") && cfg.Datestyle != "ISO, MDY" {
    610 		return Config{}, fmt.Errorf(`pq: unsupported datestyle %q: must be absent or "ISO, MDY"`, cfg.Datestyle)
    611 	}
    612 	cfg.ClientEncoding, cfg.Datestyle = "UTF8", "ISO, MDY"
    613 
    614 	// Set default user if not explicitly provided.
    615 	if !cfg.isset("user") {
    616 		u, err := pqutil.User()
    617 		if err != nil {
    618 			return Config{}, err
    619 		}
    620 		cfg.User = u
    621 	}
    622 
    623 	// SSL is not necessary or supported over UNIX domain sockets.
    624 	if nw, _ := cfg.network(); nw == "unix" {
    625 		cfg.SSLMode = SSLModeDisable
    626 	}
    627 
    628 	if cfg.MinProtocolVersion > cfg.MaxProtocolVersion {
    629 		return Config{}, fmt.Errorf("pq: min_protocol_version %q cannot be greater than max_protocol_version %q",
    630 			cfg.MinProtocolVersion, cfg.MaxProtocolVersion)
    631 	}
    632 	if cfg.SSLNegotiation == SSLNegotiationDirect {
    633 		switch cfg.SSLMode {
    634 		case SSLModeDisable, SSLModeAllow, SSLModePrefer:
    635 			return Config{}, fmt.Errorf(
    636 				`pq: weak sslmode %q may not be used with sslnegotiation=direct (use "require", "verify-ca", or "verify-full")`,
    637 				cfg.SSLMode)
    638 		}
    639 	}
    640 	if cfg.SSLRootCert == "system" {
    641 		if !cfg.isset("sslmode") {
    642 			cfg.SSLMode = SSLModeVerifyFull
    643 		}
    644 		if cfg.SSLMode != SSLModeVerifyFull {
    645 			return Config{}, fmt.Errorf(
    646 				`pq: weak sslmode %q may not be used with sslrootcert=system (use "verify-full")`,
    647 				cfg.SSLMode)
    648 		}
    649 	}
    650 
    651 	return cfg, nil
    652 }
    653 
    654 func (cfg Config) network() (string, string) {
    655 	if cfg.Hostaddr != (netip.Addr{}) {
    656 		return "tcp", net.JoinHostPort(cfg.Hostaddr.String(), strconv.Itoa(int(cfg.Port)))
    657 	}
    658 	// UNIX domain sockets are either represented by an (absolute) file system
    659 	// path or they live in the abstract name space (starting with an @).
    660 	if filepath.IsAbs(cfg.Host) || strings.HasPrefix(cfg.Host, "@") {
    661 		sockPath := filepath.Join(cfg.Host, ".s.PGSQL."+strconv.Itoa(int(cfg.Port)))
    662 		return "unix", sockPath
    663 	}
    664 	return "tcp", net.JoinHostPort(cfg.Host, strconv.Itoa(int(cfg.Port)))
    665 }
    666 
    667 func (cfg *Config) fromEnv(env []string) error {
    668 	e := make(map[string]string)
    669 	for _, v := range env {
    670 		k, v, ok := strings.Cut(v, "=")
    671 		if !ok {
    672 			continue
    673 		}
    674 		switch k {
    675 		case "PGREQUIRESSL", "PGSSLCOMPRESSION", // Deprecated.
    676 			"PGREALM", "PGGSSENCMODE", "PGGSSDELEGATION", "PGGSSLIB", // krb stuff
    677 			"PGREQUIREAUTH", "PGCHANNELBINDING",
    678 			"PGSSLCERTMODE", "PGSSLCRL", "PGSSLCRLDIR", "PGREQUIREPEER":
    679 			return fmt.Errorf("pq: environment variable $%s is not supported", k)
    680 		case "PGKRBSRVNAME":
    681 			if newGss == nil {
    682 				return fmt.Errorf("pq: environment variable $%s is not supported as Kerberos is not enabled", k)
    683 			}
    684 		}
    685 		e[k] = v
    686 	}
    687 	return cfg.setFromTag(e, "env", false)
    688 }
    689 
    690 // fromDSN parses the options from name and adds them to the values.
    691 //
    692 // The parsing code is based on conninfo_parse from libpq's fe-connect.c
    693 func (cfg *Config) fromDSN(dsn string) error {
    694 	if strings.HasPrefix(dsn, "postgres://") || strings.HasPrefix(dsn, "postgresql://") {
    695 		var err error
    696 		dsn, err = convertURL(dsn)
    697 		if err != nil {
    698 			return err
    699 		}
    700 	}
    701 
    702 	var (
    703 		opt  = make(map[string]string)
    704 		s    = []rune(dsn)
    705 		i    int
    706 		next = func() (rune, bool) {
    707 			if i >= len(s) {
    708 				return 0, false
    709 			}
    710 			r := s[i]
    711 			i++
    712 			return r, true
    713 		}
    714 		skipSpaces = func() (rune, bool) {
    715 			r, ok := next()
    716 			for unicode.IsSpace(r) && ok {
    717 				r, ok = next()
    718 			}
    719 			return r, ok
    720 		}
    721 	)
    722 
    723 	for {
    724 		var (
    725 			keyRunes, valRunes []rune
    726 			r                  rune
    727 			ok                 bool
    728 		)
    729 
    730 		if r, ok = skipSpaces(); !ok {
    731 			break
    732 		}
    733 
    734 		// Scan the key
    735 		for !unicode.IsSpace(r) && r != '=' {
    736 			keyRunes = append(keyRunes, r)
    737 			if r, ok = next(); !ok {
    738 				break
    739 			}
    740 		}
    741 
    742 		// Skip any whitespace if we're not at the = yet
    743 		if r != '=' {
    744 			r, ok = skipSpaces()
    745 		}
    746 
    747 		// The current character should be =
    748 		if r != '=' || !ok {
    749 			return fmt.Errorf(`missing "=" after %q in connection info string`, string(keyRunes))
    750 		}
    751 
    752 		// Skip any whitespace after the =
    753 		if r, ok = skipSpaces(); !ok {
    754 			// If we reach the end here, the last value is just an empty string as per libpq.
    755 			opt[string(keyRunes)] = ""
    756 			break
    757 		}
    758 
    759 		if r != '\'' {
    760 			for !unicode.IsSpace(r) {
    761 				if r == '\\' {
    762 					if r, ok = next(); !ok {
    763 						return fmt.Errorf(`missing character after backslash`)
    764 					}
    765 				}
    766 				valRunes = append(valRunes, r)
    767 
    768 				if r, ok = next(); !ok {
    769 					break
    770 				}
    771 			}
    772 		} else {
    773 		quote:
    774 			for {
    775 				if r, ok = next(); !ok {
    776 					return fmt.Errorf(`unterminated quoted string literal in connection string`)
    777 				}
    778 				switch r {
    779 				case '\'':
    780 					break quote
    781 				case '\\':
    782 					r, _ = next()
    783 					fallthrough
    784 				default:
    785 					valRunes = append(valRunes, r)
    786 				}
    787 			}
    788 		}
    789 
    790 		opt[string(keyRunes)] = string(valRunes)
    791 	}
    792 
    793 	return cfg.setFromTag(opt, "postgres", false)
    794 }
    795 
    796 func (cfg *Config) fromService() error {
    797 	if cfg.Service == "" {
    798 		return nil
    799 	}
    800 
    801 	if !cfg.isset("PGSERVICEFILE") {
    802 		if home := pqutil.Home(false); home != "" {
    803 			cfg.ServiceFile = filepath.Join(home, ".pg_service.conf")
    804 		}
    805 	}
    806 
    807 	opts, err := pgservice.FindService(cfg.ServiceFile, cfg.Service)
    808 	if err != nil {
    809 		return fmt.Errorf("pq: %w", err)
    810 	}
    811 	return cfg.setFromTag(opts, "postgres", true)
    812 }
    813 
    814 func (cfg *Config) setFromTag(o map[string]string, tag string, service bool) error {
    815 	f := "pq: wrong value for %q: "
    816 	if tag == "env" {
    817 		f = "pq: wrong value for $%s: "
    818 	}
    819 	var (
    820 		types  = reflect.TypeOf(cfg).Elem()
    821 		values = reflect.ValueOf(cfg).Elem()
    822 	)
    823 	for i := 0; i < types.NumField(); i++ {
    824 		var (
    825 			rt                    = types.Field(i)
    826 			rv                    = values.Field(i)
    827 			k                     = rt.Tag.Get(tag)
    828 			connectTimeout        = (tag == "postgres" && k == "connect_timeout") || (tag == "env" && k == "PGCONNECT_TIMEOUT")
    829 			host                  = (tag == "postgres" && k == "host") || (tag == "env" && k == "PGHOST")
    830 			hostaddr              = (tag == "postgres" && k == "hostaddr") || (tag == "env" && k == "PGHOSTADDR")
    831 			port                  = (tag == "postgres" && k == "port") || (tag == "env" && k == "PGPORT")
    832 			sslmode               = (tag == "postgres" && k == "sslmode") || (tag == "env" && k == "PGSSLMODE")
    833 			sslnegotiation        = (tag == "postgres" && k == "sslnegotiation") || (tag == "env" && k == "PGSSLNEGOTIATION")
    834 			targetsessionattrs    = (tag == "postgres" && k == "target_session_attrs") || (tag == "env" && k == "PGTARGETSESSIONATTRS")
    835 			loadbalancehosts      = (tag == "postgres" && k == "load_balance_hosts") || (tag == "env" && k == "PGLOADBALANCEHOSTS")
    836 			minprotocolversion    = (tag == "postgres" && k == "min_protocol_version") || (tag == "env" && k == "PGMINPROTOCOLVERSION")
    837 			maxprotocolversion    = (tag == "postgres" && k == "max_protocol_version") || (tag == "env" && k == "PGMAXPROTOCOLVERSION")
    838 			sslminprotocolversion = (tag == "postgres" && k == "ssl_min_protocol_version") || (tag == "env" && k == "SSLPGMINPROTOCOLVERSION")
    839 			sslmaxprotocolversion = (tag == "postgres" && k == "ssl_max_protocol_version") || (tag == "env" && k == "SSLPGMAXPROTOCOLVERSION")
    840 		)
    841 		if k == "" || k == "-" {
    842 			continue
    843 		}
    844 
    845 		v, ok := o[k]
    846 		delete(o, k)
    847 		if ok {
    848 			t, ok := rt.Tag.Lookup("postgres")
    849 			if !ok || t == "" || t == "-" { // For PGSERVICEFILE, which can only be from env
    850 				t, ok = rt.Tag.Lookup("env")
    851 			}
    852 			if ok && t != "" && t != "-" {
    853 				cfg.set = append(cfg.set, t)
    854 			}
    855 			switch rt.Type.Kind() {
    856 			default:
    857 				return fmt.Errorf("don't know how to set %s: unknown type %s", rt.Name, rt.Type.Kind())
    858 			case reflect.Struct:
    859 				if rt.Type == reflect.TypeOf(netip.Addr{}) {
    860 					if hostaddr {
    861 						vv := strings.Split(v, ",")
    862 						v = vv[0]
    863 						for _, vvv := range vv[1:] {
    864 							if vvv == "" {
    865 								cfg.multiHostaddr = append(cfg.multiHostaddr, netip.Addr{})
    866 							} else {
    867 								ip, err := netip.ParseAddr(vvv)
    868 								if err != nil {
    869 									return fmt.Errorf(f+"%w", k, err)
    870 								}
    871 								cfg.multiHostaddr = append(cfg.multiHostaddr, ip)
    872 							}
    873 						}
    874 					}
    875 					ip, err := netip.ParseAddr(v)
    876 					if err != nil {
    877 						return fmt.Errorf(f+"%w", k, err)
    878 					}
    879 					rv.Set(reflect.ValueOf(ip))
    880 				} else {
    881 					return fmt.Errorf("don't know how to set %s: unknown type %s", rt.Name, rt.Type)
    882 				}
    883 			case reflect.String:
    884 				if sslmode && !slices.Contains(sslModes, SSLMode(v)) && !(strings.HasPrefix(v, "pqgo-") && hasTLSConfig(v[5:])) {
    885 					return fmt.Errorf(f+`%q is not supported; supported values are %s`, k, v, pqutil.Join(sslModes))
    886 				}
    887 				if sslnegotiation && !slices.Contains(sslNegotiations, SSLNegotiation(v)) {
    888 					return fmt.Errorf(f+`%q is not supported; supported values are %s`, k, v, pqutil.Join(sslNegotiations))
    889 				}
    890 				if targetsessionattrs && !slices.Contains(targetSessionAttrs, TargetSessionAttrs(v)) {
    891 					return fmt.Errorf(f+`%q is not supported; supported values are %s`, k, v, pqutil.Join(targetSessionAttrs))
    892 				}
    893 				if loadbalancehosts && !slices.Contains(loadBalanceHosts, LoadBalanceHosts(v)) {
    894 					return fmt.Errorf(f+`%q is not supported; supported values are %s`, k, v, pqutil.Join(loadBalanceHosts))
    895 				}
    896 				if (minprotocolversion || maxprotocolversion) && !slices.Contains(protocolVersions, ProtocolVersion(v)) {
    897 					return fmt.Errorf(f+`%q is not supported; supported values are %s`, k, v, pqutil.Join(protocolVersions))
    898 				}
    899 				if (sslminprotocolversion || sslmaxprotocolversion) && !slices.Contains(sslProtocolVersions, SSLProtocolVersion(v)) {
    900 					return fmt.Errorf(f+`%q is not supported; supported values are %s`, k, v, pqutil.Join(sslProtocolVersions))
    901 				}
    902 				if host {
    903 					vv := strings.Split(v, ",")
    904 					v = vv[0]
    905 					for i, vvv := range vv[1:] {
    906 						if vvv == "" {
    907 							vv[i+1] = "localhost"
    908 						}
    909 					}
    910 					cfg.multiHost = append(cfg.multiHost, vv[1:]...)
    911 				}
    912 				rv.SetString(v)
    913 			case reflect.Int64:
    914 				n, err := strconv.ParseInt(v, 10, 64)
    915 				if err != nil {
    916 					return fmt.Errorf(f+"%w", k, err)
    917 				}
    918 				if connectTimeout {
    919 					n = int64(time.Duration(n) * time.Second)
    920 				}
    921 				rv.SetInt(n)
    922 			case reflect.Uint16:
    923 				if port {
    924 					vv := strings.Split(v, ",")
    925 					v = vv[0]
    926 					for _, vvv := range vv[1:] {
    927 						if vvv == "" {
    928 							vvv = "5432"
    929 						}
    930 						n, err := strconv.ParseUint(vvv, 10, 16)
    931 						if err != nil {
    932 							return fmt.Errorf(f+"%w", k, err)
    933 						}
    934 						cfg.multiPort = append(cfg.multiPort, uint16(n))
    935 					}
    936 				}
    937 				n, err := strconv.ParseUint(v, 10, 16)
    938 				if err != nil {
    939 					return fmt.Errorf(f+"%w", k, err)
    940 				}
    941 				rv.SetUint(n)
    942 			case reflect.Bool:
    943 				b, err := pqutil.ParseBool(v)
    944 				if err != nil {
    945 					return fmt.Errorf(f+"%w", k, err)
    946 				}
    947 				rv.SetBool(b)
    948 			}
    949 		}
    950 	}
    951 
    952 	if service && len(o) > 0 {
    953 		// TODO(go1.23): use maps.Keys once we require Go 1.23.
    954 		var key string
    955 		for k := range o {
    956 			key = k
    957 			break
    958 		}
    959 		return fmt.Errorf("pq: unknown setting %q in service file for service %q", key, cfg.Service)
    960 	}
    961 
    962 	// Set run-time; we delete map keys as they're set in the struct.
    963 	if !service && tag == "postgres" {
    964 		// Make sure database= sets dbname=, as that previously worked (kind of
    965 		// by accident).
    966 		// TODO(v2): remove
    967 		if d, ok := o["database"]; ok {
    968 			cfg.Database = d
    969 			delete(o, "database")
    970 		}
    971 		cfg.Runtime = o
    972 	}
    973 
    974 	return nil
    975 }
    976 
    977 // Should generally only be used from newConfig(), as it will never be set if
    978 // people go outside that.
    979 func (cfg Config) isset(name string) bool {
    980 	return slices.Contains(cfg.set, name)
    981 }
    982 
    983 // Convert to a map; used only in tests.
    984 func (cfg Config) tomap() map[string]string {
    985 	var (
    986 		o      = make(map[string]string)
    987 		values = reflect.ValueOf(cfg)
    988 		types  = reflect.TypeOf(cfg)
    989 	)
    990 	for i := 0; i < types.NumField(); i++ {
    991 		var (
    992 			rt = types.Field(i)
    993 			rv = values.Field(i)
    994 			k  = rt.Tag.Get("postgres")
    995 		)
    996 		if k == "" || k == "-" {
    997 			continue
    998 		}
    999 		if !rv.IsZero() || slices.Contains(cfg.set, k) {
   1000 			switch rt.Type.Kind() {
   1001 			default:
   1002 				if s, ok := rv.Interface().(fmt.Stringer); ok {
   1003 					o[k] = s.String()
   1004 				} else {
   1005 					o[k] = rv.String()
   1006 				}
   1007 			case reflect.Uint16:
   1008 				n := rv.Uint()
   1009 				o[k] = strconv.FormatUint(n, 10)
   1010 			case reflect.Int64:
   1011 				n := rv.Int()
   1012 				if k == "connect_timeout" {
   1013 					n = int64(time.Duration(n) / time.Second)
   1014 				}
   1015 				o[k] = strconv.FormatInt(n, 10)
   1016 			case reflect.Bool:
   1017 				if rv.Bool() {
   1018 					o[k] = "yes"
   1019 				} else {
   1020 					o[k] = "no"
   1021 				}
   1022 			}
   1023 		}
   1024 	}
   1025 	for k, v := range cfg.Runtime {
   1026 		o[k] = v
   1027 	}
   1028 	return o
   1029 }
   1030 
   1031 // Create DSN for this config; used only in tests.
   1032 func (cfg Config) string() string {
   1033 	var (
   1034 		m    = cfg.tomap()
   1035 		keys = make([]string, 0, len(m))
   1036 	)
   1037 	for k := range m {
   1038 		switch k {
   1039 		case "datestyle", "client_encoding":
   1040 			continue
   1041 		case "host", "port", "user", "sslsni", "min_protocol_version", "max_protocol_version":
   1042 			if !cfg.isset(k) {
   1043 				continue
   1044 			}
   1045 		}
   1046 		if k == "application_name" && m[k] == "pqgo" {
   1047 			continue
   1048 		}
   1049 		if k == "host" && len(cfg.multiHost) > 0 {
   1050 			m[k] += "," + strings.Join(cfg.multiHost, ",")
   1051 		}
   1052 		if k == "hostaddr" && len(cfg.multiHostaddr) > 0 {
   1053 			for _, ha := range cfg.multiHostaddr {
   1054 				m[k] += ","
   1055 				if ha != (netip.Addr{}) {
   1056 					m[k] += ha.String()
   1057 				}
   1058 			}
   1059 		}
   1060 		if k == "port" && len(cfg.multiPort) > 0 {
   1061 			for _, p := range cfg.multiPort {
   1062 				m[k] += "," + strconv.Itoa(int(p))
   1063 			}
   1064 		}
   1065 		keys = append(keys, k)
   1066 	}
   1067 	sort.Strings(keys)
   1068 
   1069 	var b strings.Builder
   1070 	for i, k := range keys {
   1071 		if i > 0 {
   1072 			b.WriteByte(' ')
   1073 		}
   1074 		b.WriteString(k)
   1075 		b.WriteByte('=')
   1076 		var (
   1077 			v     = m[k]
   1078 			nv    = make([]rune, 0, len(v)+2)
   1079 			quote = v == ""
   1080 		)
   1081 		for _, c := range v {
   1082 			if c == ' ' {
   1083 				quote = true
   1084 			}
   1085 			if c == '\'' {
   1086 				nv = append(nv, '\\')
   1087 			}
   1088 			nv = append(nv, c)
   1089 		}
   1090 		if quote {
   1091 			b.WriteByte('\'')
   1092 		}
   1093 		b.WriteString(string(nv))
   1094 		if quote {
   1095 			b.WriteByte('\'')
   1096 		}
   1097 	}
   1098 	return b.String()
   1099 }
   1100 
   1101 // Recognize all sorts of silly things as "UTF-8", like Postgres does
   1102 func isUTF8(name string) bool {
   1103 	s := strings.Map(func(c rune) rune {
   1104 		if 'A' <= c && c <= 'Z' {
   1105 			return c + ('a' - 'A')
   1106 		}
   1107 		if 'a' <= c && c <= 'z' || '0' <= c && c <= '9' {
   1108 			return c
   1109 		}
   1110 		return -1 // discard
   1111 	}, name)
   1112 	return s == "utf8" || s == "unicode"
   1113 }
   1114 
   1115 func convertURL(url string) (string, error) {
   1116 	u, err := neturl.Parse(url)
   1117 	if err != nil {
   1118 		return "", err
   1119 	}
   1120 
   1121 	if u.Scheme != "postgres" && u.Scheme != "postgresql" {
   1122 		return "", fmt.Errorf("invalid connection protocol: %s", u.Scheme)
   1123 	}
   1124 
   1125 	var kvs []string
   1126 	escaper := strings.NewReplacer(`'`, `\'`, `\`, `\\`)
   1127 	accrue := func(k, v string) {
   1128 		if v != "" {
   1129 			kvs = append(kvs, k+"='"+escaper.Replace(v)+"'")
   1130 		}
   1131 	}
   1132 
   1133 	if u.User != nil {
   1134 		pw, _ := u.User.Password()
   1135 		accrue("user", u.User.Username())
   1136 		accrue("password", pw)
   1137 	}
   1138 
   1139 	if host, port, err := net.SplitHostPort(u.Host); err != nil {
   1140 		accrue("host", u.Host)
   1141 	} else {
   1142 		accrue("host", host)
   1143 		accrue("port", port)
   1144 	}
   1145 
   1146 	if u.Path != "" {
   1147 		accrue("dbname", u.Path[1:])
   1148 	}
   1149 
   1150 	q := u.Query()
   1151 	for k := range q {
   1152 		accrue(k, q.Get(k))
   1153 	}
   1154 
   1155 	sort.Strings(kvs) // Makes testing easier (not a performance concern)
   1156 	return strings.Join(kvs, " "), nil
   1157 }