44 "context"
55 "database/sql/driver"
66 "fmt"
7+ "math/rand"
78 "net"
89 "net/netip"
910 neturl "net/url"
2930
3031 // TargetSessionAttrs is a target_session_attrs setting.
3132 TargetSessionAttrs string
33+
34+ // LoadBalanceHosts is a load_balance_hosts setting.
35+ LoadBalanceHosts string
3236)
3337
3438// Values for [SSLMode] that pq supports.
@@ -89,6 +93,23 @@ var targetSessionAttrs = []TargetSessionAttrs{TargetSessionAttrsAny,
8993 TargetSessionAttrsReadWrite , TargetSessionAttrsReadOnly , TargetSessionAttrsPrimary ,
9094 TargetSessionAttrsStandby , TargetSessionAttrsPreferStandby }
9195
96+ // Values for [LoadBalanceHosts] that pq supports.
97+ const (
98+ // Don't load balance; try hosts in the order in which they're provided.
99+ // This is the default.
100+ LoadBalanceHostsDisable = LoadBalanceHosts ("disable" )
101+
102+ // Hosts are tried in random order to balance connections across multiple
103+ // PostgreSQL servers.
104+ //
105+ // When using this value it's recommended to also configure a reasonable
106+ // value for connect_timeout. Because then, if one of the nodes that are
107+ // used for load balancing is not responding, a new node will be tried.
108+ LoadBalanceHostsRandom = LoadBalanceHosts ("random" )
109+ )
110+
111+ var loadBalanceHosts = []LoadBalanceHosts {LoadBalanceHostsDisable , LoadBalanceHostsRandom }
112+
92113// Connector represents a fixed configuration for the pq driver with a given
93114// dsn. Connector satisfies the [database/sql/driver.Connector] interface and
94115// can be used to create any number of DB Conn's via [sql.OpenDB].
@@ -137,9 +158,10 @@ type Config struct {
137158 // for unix domain sockets. Defaults to localhost.
138159 //
139160 // A comma-separated list of host names is also accepted, in which case each
140- // host name in the list is tried in order; an empty item selects the
141- // default of localhost. The target_session_attrs option controls properties
142- // the host must have to be considered acceptable.
161+ // host name in the list is tried in order or randomly if load_balance_hosts
162+ // is set. An empty item selects the default of localhost. The
163+ // target_session_attrs option controls properties the host must have to be
164+ // considered acceptable.
143165 Host string `postgres:"host" env:"PGHOST"`
144166
145167 // IPv4 or IPv6 address to connect to. Using hostaddr allows the application
@@ -161,10 +183,11 @@ type Config struct {
161183 // host name.
162184 //
163185 // A comma-separated list of hostaddr values is also accepted, in which case
164- // each host in the list is tried in order. An empty item causes the
165- // corresponding host name to be used, or the default host name if that is
166- // empty as well. The target_session_attrs option controls properties the
167- // host must have to be considered acceptable.
186+ // each host in the list is tried in order or randonly if load_balance_hosts
187+ // is set. An empty item causes the corresponding host name to be used, or
188+ // the default host name if that is empty as well. The target_session_attrs
189+ // option controls properties the host must have to be considered
190+ // acceptable.
168191 Hostaddr netip.Addr `postgres:"hostaddr" env:"PGHOSTADDR"`
169192
170193 // The port to connect to. Defaults to 5432.
@@ -264,6 +287,22 @@ type Config struct {
264287 // Default mode for the genetic query optimizer.
265288 Geqo string `postgres:"geqo" env:"PGGEQO"`
266289
290+ // Determine whether the session must have certain properties to be
291+ // acceptable. It's typically used in combination with multiple host names
292+ // to select the first acceptable alternative among several hosts.
293+ TargetSessionAttrs TargetSessionAttrs `postgres:"target_session_attrs" env:"PGTARGETSESSIONATTRS"`
294+
295+ // Controls the order in which the client tries to connect to the available
296+ // hosts. Once a connection attempt is successful no other hosts will be
297+ // tried. This parameter is typically used in combination with multiple host
298+ // names.
299+ //
300+ // This parameter can be used in combination with target_session_attrs to,
301+ // for example, load balance over standby servers only. Once successfully
302+ // connected, subsequent queries on the returned connection will all be sent
303+ // to the same server.
304+ LoadBalanceHosts LoadBalanceHosts `postgres:"load_balance_hosts" env:"PGLOADBALANCEHOSTS"`
305+
267306 // Runtime parameters: any unrecognized parameter in the DSN will be added
268307 // to this and sent to PostgreSQL during startup.
269308 Runtime map [string ]string `postgres:"-" env:"-"`
@@ -273,11 +312,6 @@ type Config struct {
273312 // additional ones (if any) are available here.
274313 Multi []ConfigMultihost
275314
276- // Determine whether the session must have certain properties to be
277- // acceptable. It's typically used in combination with multiple host names
278- // to select the first acceptable alternative among several hosts.
279- TargetSessionAttrs TargetSessionAttrs `postgres:"target_session_attrs" env:"PGTARGETSESSIONATTRS"`
280-
281315 // Record which parameters were given, so we can distinguish between an
282316 // empty string "not given at all".
283317 //
@@ -370,6 +404,11 @@ func (cfg Config) hosts() []Config {
370404 c .Host , c .Hostaddr , c .Port = m .Host , m .Hostaddr , m .Port
371405 cfgs = append (cfgs , c )
372406 }
407+
408+ if cfg .LoadBalanceHosts == LoadBalanceHostsRandom {
409+ rand .Shuffle (len (cfgs ), func (i , j int ) { cfgs [i ], cfgs [j ] = cfgs [j ], cfgs [i ] })
410+ }
411+
373412 return cfgs
374413}
375414
@@ -475,8 +514,7 @@ func (cfg *Config) fromEnv(env []string) error {
475514 case "PGREQUIREAUTH" , "PGCHANNELBINDING" , "PGSERVICE" , "PGSERVICEFILE" , "PGREALM" ,
476515 "PGSSLCERTMODE" , "PGSSLCOMPRESSION" , "PGREQUIRESSL" , "PGSSLCRL" , "PGREQUIREPEER" ,
477516 "PGSYSCONFDIR" , "PGLOCALEDIR" , "PGSSLCRLDIR" , "PGSSLMINPROTOCOLVERSION" , "PGSSLMAXPROTOCOLVERSION" ,
478- "PGGSSENCMODE" , "PGGSSDELEGATION" , "PGLOADBALANCEHOSTS" , "PGMINPROTOCOLVERSION" ,
479- "PGMAXPROTOCOLVERSION" , "PGGSSLIB" :
517+ "PGGSSENCMODE" , "PGGSSDELEGATION" , "PGMINPROTOCOLVERSION" , "PGMAXPROTOCOLVERSION" , "PGGSSLIB" :
480518 return fmt .Errorf ("pq: environment variable $%s is not supported" , k )
481519 case "PGKRBSRVNAME" :
482520 if newGss == nil {
@@ -615,6 +653,7 @@ func (cfg *Config) setFromTag(o map[string]string, tag string) error {
615653 sslmode = (tag == "postgres" && k == "sslmode" ) || (tag == "env" && k == "PGSSLMODE" )
616654 sslnegotiation = (tag == "postgres" && k == "sslnegotiation" ) || (tag == "env" && k == "PGSSLNEGOTIATION" )
617655 targetsessionattrs = (tag == "postgres" && k == "target_session_attrs" ) || (tag == "env" && k == "PGTARGETSESSIONATTRS" )
656+ loadbalancehosts = (tag == "postgres" && k == "load_balance_hosts" ) || (tag == "env" && k == "PGLOADBALANCEHOSTS" )
618657 )
619658 if k == "" || k == "-" {
620659 continue
@@ -664,6 +703,9 @@ func (cfg *Config) setFromTag(o map[string]string, tag string) error {
664703 if targetsessionattrs && ! slices .Contains (targetSessionAttrs , TargetSessionAttrs (v )) {
665704 return fmt .Errorf (f + `%q is not supported; supported values are %s` , k , v , pqutil .Join (targetSessionAttrs ))
666705 }
706+ if loadbalancehosts && ! slices .Contains (loadBalanceHosts , LoadBalanceHosts (v )) {
707+ return fmt .Errorf (f + `%q is not supported; supported values are %s` , k , v , pqutil .Join (loadBalanceHosts ))
708+ }
667709 if host {
668710 vv := strings .Split (v , "," )
669711 v = vv [0 ]
0 commit comments