55 "database/sql/driver"
66 "fmt"
77 "net"
8+ "net/netip"
89 neturl "net/url"
910 "os"
1011 "path/filepath"
@@ -104,6 +105,25 @@ type Config struct {
104105 // for unix domain sockets. Defaults to localhost.
105106 Host string `postgres:"host" env:"PGHOST"`
106107
108+ // IPv4 or IPv6 address to connect to. Using hostaddr allows the application
109+ // to avoid a host name lookup, which might be important in applications
110+ // with time constraints. A hostname is required for sslmode=verify-full and
111+ // the GSSAPI or SSPI authentication methods.
112+ //
113+ // The following rules are used:
114+ //
115+ // - If host is given without hostaddr, a host name lookup occurs.
116+ //
117+ // - If hostaddr is given without host, the value for hostaddr gives the
118+ // server network address. The connection attempt will fail if the
119+ // authentication method requires a host name.
120+ //
121+ // - If both host and hostaddr are given, the value for hostaddr gives the
122+ // server network address. The value for host is ignored unless the
123+ // authentication method requires it, in which case it will be used as the
124+ // host name.
125+ Hostaddr netip.Addr `postgres:"hostaddr" env:"PGHOSTADDR"`
126+
107127 // The port to connect to. Defaults to 5432.
108128 Port uint16 `postgres:"port" env:"PGPORT"`
109129
@@ -302,6 +322,9 @@ func newConfig(dsn string, env []string) (Config, error) {
302322}
303323
304324func (cfg Config ) network () (string , string ) {
325+ if cfg .Hostaddr != (netip.Addr {}) {
326+ return "tcp" , net .JoinHostPort (cfg .Hostaddr .String (), strconv .Itoa (int (cfg .Port )))
327+ }
305328 // UNIX domain sockets are either represented by an (absolute) file system
306329 // path or they live in the abstract name space (starting with an @).
307330 if filepath .IsAbs (cfg .Host ) || strings .HasPrefix (cfg .Host , "@" ) {
@@ -319,7 +342,7 @@ func (cfg *Config) fromEnv(env []string) error {
319342 continue
320343 }
321344 switch k {
322- case "PGHOSTADDR" , " PGREQUIREAUTH" , "PGCHANNELBINDING" , "PGSERVICE" , "PGSERVICEFILE" , "PGREALM" ,
345+ case "PGREQUIREAUTH" , "PGCHANNELBINDING" , "PGSERVICE" , "PGSERVICEFILE" , "PGREALM" ,
323346 "PGSSLCERTMODE" , "PGSSLCOMPRESSION" , "PGREQUIRESSL" , "PGSSLCRL" , "PGREQUIREPEER" ,
324347 "PGSYSCONFDIR" , "PGLOCALEDIR" , "PGSSLCRLDIR" , "PGSSLMINPROTOCOLVERSION" , "PGSSLMAXPROTOCOLVERSION" ,
325348 "PGGSSENCMODE" , "PGGSSDELEGATION" , "PGTARGETSESSIONATTRS" , "PGLOADBALANCEHOSTS" , "PGMINPROTOCOLVERSION" ,
@@ -468,7 +491,17 @@ func (cfg *Config) setFromTag(o map[string]string, tag string) error {
468491 }
469492 switch rt .Type .Kind () {
470493 default :
471- return fmt .Errorf ("don't know how to set %s: unknown type %s" , rt .Name , rt .Type )
494+ return fmt .Errorf ("don't know how to set %s: unknown type %s" , rt .Name , rt .Type .Kind ())
495+ case reflect .Struct :
496+ if rt .Type == reflect .TypeOf (netip.Addr {}) {
497+ ip , err := netip .ParseAddr (v )
498+ if err != nil {
499+ return fmt .Errorf (f + "%w" , k , err )
500+ }
501+ rv .Set (reflect .ValueOf (ip ))
502+ } else {
503+ return fmt .Errorf ("don't know how to set %s: unknown type %s" , rt .Name , rt .Type )
504+ }
472505 case reflect .String :
473506 if ((tag == "postgres" && k == "sslmode" ) || (tag == "env" && k == "PGSSLMODE" )) &&
474507 ! pqutil .Contains (sslModes , SSLMode (v )) &&
@@ -545,7 +578,11 @@ func (cfg Config) tomap() values {
545578 if ! rv .IsZero () || pqutil .Contains (cfg .set , k ) {
546579 switch rt .Type .Kind () {
547580 default :
548- o [k ] = rv .String ()
581+ if s , ok := rv .Interface ().(fmt.Stringer ); ok {
582+ o [k ] = s .String ()
583+ } else {
584+ o [k ] = rv .String ()
585+ }
549586 case reflect .Uint16 :
550587 n := rv .Uint ()
551588 o [k ] = strconv .FormatUint (n , 10 )
0 commit comments