Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
unreleased
----------
This version of kq requires Go 1.18 or newer.
This version of pq requires Go 1.18 or newer.

pq now supports only maintained PostgreSQL releases, which is PostgreSQL 14 and
newer. Previously PostgreSQL 8.4 and newer were supported.
Expand Down Expand Up @@ -33,6 +33,8 @@ newer. Previously PostgreSQL 8.4 and newer were supported.
- Add `Config`, `NewConfig()`, and `NewConnectorConfig()` to supply connection
details in a more structured way ([#1240]).

- Support `hostaddr` and `$PGHOSTADDR` ([#1243]).

- Add `PQGO_DEBUG=1` print the communication with PostgreSQL to stderr, to aid
in debugging, testing, and bug reports ([#1223]).

Expand Down Expand Up @@ -104,6 +106,7 @@ newer. Previously PostgreSQL 8.4 and newer were supported.
[#1238]: https://github.com/lib/pq/pull/1238
[#1239]: https://github.com/lib/pq/pull/1239
[#1240]: https://github.com/lib/pq/pull/1240
[#1243]: https://github.com/lib/pq/pull/1243


v1.10.9 (2023-04-26)
Expand Down
2 changes: 1 addition & 1 deletion conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -1077,7 +1077,7 @@ func (cn *conn) ssl(o values) error {
// startup packet.
func isDriverSetting(key string) bool {
switch key {
case "host", "port", "password", "fallback_application_name",
case "host", "hostaddr", "port", "password", "fallback_application_name",
"sslmode", "sslcert", "sslkey", "sslrootcert", "sslinline", "sslsni",
"connect_timeout", "binary_parameters", "disable_prepared_binary_result",
"krbsrvname", "krbspn":
Expand Down
40 changes: 27 additions & 13 deletions conn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,20 +69,34 @@ func TestCommitInFailedTransaction(t *testing.T) {
}
}

func TestOpenURL(t *testing.T) {
t.Parallel()
testURL := func(url string) {
db := pqtest.MustDB(t, url)
// database/sql might not call our Open at all unless we do something with
// the connection
txn, err := db.Begin()
if err != nil {
t.Fatal(err)
}
txn.Rollback()
func TestOpen(t *testing.T) {
tests := []struct {
dsn, wantErr string
}{
{"postgres://", ""},
{"postgresql://", ""},
{"host=doesnotexist hostaddr=127.0.0.1", ""}, // Should ignore the host

{"hostaddr=255.255.255.255", "dial tcp 255.255.255.255"},
}

for _, tt := range tests {
tt := tt
t.Run(tt.dsn, func(t *testing.T) {
t.Parallel()

db, err := pqtest.DB(tt.dsn)
if err != nil {
t.Fatal(err)
}
defer db.Close()

err = db.Ping()
if !pqtest.ErrorContains(err, tt.wantErr) {
t.Errorf("wrong error:\nhave: %s\nwant: %s", err, tt.wantErr)
}
})
}
testURL("postgres://")
testURL("postgresql://")
}

func TestPgpass(t *testing.T) {
Expand Down
43 changes: 40 additions & 3 deletions connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"database/sql/driver"
"fmt"
"net"
"net/netip"
neturl "net/url"
"os"
"path/filepath"
Expand Down Expand Up @@ -104,6 +105,25 @@ type Config struct {
// for unix domain sockets. Defaults to localhost.
Host string `postgres:"host" env:"PGHOST"`

// IPv4 or IPv6 address to connect to. Using hostaddr allows the application
// to avoid a host name lookup, which might be important in applications
// with time constraints. A hostname is required for sslmode=verify-full and
// the GSSAPI or SSPI authentication methods.
//
// The following rules are used:
//
// - If host is given without hostaddr, a host name lookup occurs.
//
// - If hostaddr is given without host, the value for hostaddr gives the
// server network address. The connection attempt will fail if the
// authentication method requires a host name.
//
// - If both host and hostaddr are given, the value for hostaddr gives the
// server network address. The value for host is ignored unless the
// authentication method requires it, in which case it will be used as the
// host name.
Hostaddr netip.Addr `postgres:"hostaddr" env:"PGHOSTADDR"`

// The port to connect to. Defaults to 5432.
Port uint16 `postgres:"port" env:"PGPORT"`

Expand Down Expand Up @@ -302,6 +322,9 @@ func newConfig(dsn string, env []string) (Config, error) {
}

func (cfg Config) network() (string, string) {
if cfg.Hostaddr != (netip.Addr{}) {
return "tcp", net.JoinHostPort(cfg.Hostaddr.String(), strconv.Itoa(int(cfg.Port)))
}
// UNIX domain sockets are either represented by an (absolute) file system
// path or they live in the abstract name space (starting with an @).
if filepath.IsAbs(cfg.Host) || strings.HasPrefix(cfg.Host, "@") {
Expand All @@ -319,7 +342,7 @@ func (cfg *Config) fromEnv(env []string) error {
continue
}
switch k {
case "PGHOSTADDR", "PGREQUIREAUTH", "PGCHANNELBINDING", "PGSERVICE", "PGSERVICEFILE", "PGREALM",
case "PGREQUIREAUTH", "PGCHANNELBINDING", "PGSERVICE", "PGSERVICEFILE", "PGREALM",
"PGSSLCERTMODE", "PGSSLCOMPRESSION", "PGREQUIRESSL", "PGSSLCRL", "PGREQUIREPEER",
"PGSYSCONFDIR", "PGLOCALEDIR", "PGSSLCRLDIR", "PGSSLMINPROTOCOLVERSION", "PGSSLMAXPROTOCOLVERSION",
"PGGSSENCMODE", "PGGSSDELEGATION", "PGTARGETSESSIONATTRS", "PGLOADBALANCEHOSTS", "PGMINPROTOCOLVERSION",
Expand Down Expand Up @@ -468,7 +491,17 @@ func (cfg *Config) setFromTag(o map[string]string, tag string) error {
}
switch rt.Type.Kind() {
default:
return fmt.Errorf("don't know how to set %s: unknown type %s", rt.Name, rt.Type)
return fmt.Errorf("don't know how to set %s: unknown type %s", rt.Name, rt.Type.Kind())
case reflect.Struct:
if rt.Type == reflect.TypeOf(netip.Addr{}) {
ip, err := netip.ParseAddr(v)
if err != nil {
return fmt.Errorf(f+"%w", k, err)
}
rv.Set(reflect.ValueOf(ip))
} else {
return fmt.Errorf("don't know how to set %s: unknown type %s", rt.Name, rt.Type)
}
case reflect.String:
if ((tag == "postgres" && k == "sslmode") || (tag == "env" && k == "PGSSLMODE")) &&
!pqutil.Contains(sslModes, SSLMode(v)) &&
Expand Down Expand Up @@ -545,7 +578,11 @@ func (cfg Config) tomap() values {
if !rv.IsZero() || pqutil.Contains(cfg.set, k) {
switch rt.Type.Kind() {
default:
o[k] = rv.String()
if s, ok := rv.Interface().(fmt.Stringer); ok {
o[k] = s.String()
} else {
o[k] = rv.String()
}
case reflect.Uint16:
n := rv.Uint()
o[k] = strconv.FormatUint(n, 10)
Expand Down
5 changes: 5 additions & 0 deletions connector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,11 @@ func TestNewConfig(t *testing.T) {
{"port=5s", nil, "", `pq: wrong value for "port": strconv.ParseUint: parsing "5s": invalid syntax`},
{"", []string{"PGPORT=5s"}, "", `pq: wrong value for $PGPORT: strconv.ParseUint: parsing "5s": invalid syntax`},

// hostaddr
{"hostaddr=127.1.2.3", nil, "hostaddr=127.1.2.3", ""},
{"hostaddr=::1", nil, "hostaddr=::1", ""},
{"", []string{"PGHOSTADDR=2a01:4f9:3081:5413::2"}, "hostaddr=2a01:4f9:3081:5413::2", ""},

// Runtime
{"user=u search_path=abc", nil, "search_path=abc user=u", ""},
{"database=db", nil, "dbname=db", ``},
Expand Down
4 changes: 4 additions & 0 deletions deprecated.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ func ParseURL(url string) (string, error) { return convertURL(url) }
type values map[string]string

func (o values) network() (string, string) {
if ho := o["hostaddr"]; ho != "" {
return "tcp", net.JoinHostPort(ho, o["port"])
}

host := o["host"]
// UNIX domain sockets are either represented by an (absolute) file system
// path or they live in the abstract name space (starting with an @).
Expand Down
Loading