From 0135721378bac6a9a65c9d01925e903f71c145bf Mon Sep 17 00:00:00 2001 From: Eno Compton Date: Fri, 6 May 2022 14:15:03 -0600 Subject: [PATCH 1/9] Add support for Unix sockets on Windows Fixes #1199. --- config.go | 21 ++++++++++++++++- config_test.go | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++ pgconn.go | 2 +- 3 files changed, 82 insertions(+), 2 deletions(-) diff --git a/config.go b/config.go index 859672ea..e141a2f8 100644 --- a/config.go +++ b/config.go @@ -100,10 +100,29 @@ type FallbackConfig struct { TLSConfig *tls.Config // nil disables TLS } +// isAbsolutePath checks if the provided value is an absolute path either +// beginning with a forward slash (as on Linux-based systems) or with a capital +// letter A-Z followed by a colon and a backslash, e.g., "C:\", (as on Windows). +func isAbsolutePath(path string) bool { + isWindowsPath := func(p string) bool { + if len(p) < 3 { + return false + } + drive := p[0] + colon := p[1] + backslash := p[2] + if drive >= 'A' && drive <= 'Z' && colon == ':' && backslash == '\\' { + return true + } + return false + } + return strings.HasPrefix(path, "/") || isWindowsPath(path) +} + // NetworkAddress converts a PostgreSQL host and port into network and address suitable for use with // net.Dial. func NetworkAddress(host string, port uint16) (network, address string) { - if strings.HasPrefix(host, "/") { + if isAbsolutePath(host) { network = "unix" address = filepath.Join(host, ".s.PGSQL.") + strconv.FormatInt(int64(port), 10) } else { diff --git a/config_test.go b/config_test.go index da28782d..a28db3d6 100644 --- a/config_test.go +++ b/config_test.go @@ -231,6 +231,18 @@ func TestParseConfig(t *testing.T) { RuntimeParams: map[string]string{}, }, }, + { + name: "database url unix domain socket host on windows", + connString: "postgres:///foo?host=C:\\tmp", + config: &pgconn.Config{ + User: osUserName, + Host: "C:\\tmp", + Port: 5432, + Database: "foo", + TLSConfig: nil, + RuntimeParams: map[string]string{}, + }, + }, { name: "database url dbname", connString: "postgres://localhost/?dbname=foo&sslmode=disable", @@ -703,6 +715,55 @@ func TestConfigCopyCanBeUsedToConnect(t *testing.T) { assert.NoError(t, err) } +func TestNetworkAddress(t *testing.T) { + tests := []struct { + name string + host string + wantNet string + }{ + { + name: "Default Unix socket address", + host: "/var/run/postgresql", + wantNet: "unix", + }, + { + name: "Windows Unix socket address (standard drive name)", + host: "C:\\tmp", + wantNet: "unix", + }, + { + name: "Windows Unix socket address (first drive name)", + host: "A:\\tmp", + wantNet: "unix", + }, + { + name: "Windows Unix socket address (last drive name)", + host: "Z:\\tmp", + wantNet: "unix", + }, + { + name: "Assume TCP for unknown formats", + host: "a/tmp", + wantNet: "tcp", + }, + { + name: "loopback interface", + host: "localhost", + wantNet: "tcp", + }, + { + name: "IP address", + host: "127.0.0.1", + wantNet: "tcp", + }, + } + for i, tt := range tests { + gotNet, _ := pgconn.NetworkAddress(tt.host, 5432) + + assert.Equalf(t, tt.wantNet, gotNet, "Test %d (%s)", i, tt.name) + } +} + func assertConfigsEqual(t *testing.T, expected, actual *pgconn.Config, testName string) { if !assert.NotNil(t, expected) { return diff --git a/pgconn.go b/pgconn.go index f1304d08..ef5b76fd 100644 --- a/pgconn.go +++ b/pgconn.go @@ -187,7 +187,7 @@ func expandWithIPs(ctx context.Context, lookupFn LookupFunc, fallbacks []*Fallba for _, fb := range fallbacks { // skip resolve for unix sockets - if strings.HasPrefix(fb.Host, "/") { + if isAbsolutePath(fb.Host) { configs = append(configs, &FallbackConfig{ Host: fb.Host, Port: fb.Port, From 1d398317ca41b5aa3f8f83ecc99a932d25d8c870 Mon Sep 17 00:00:00 2001 From: Rafi Shamim Date: Fri, 6 May 2022 22:41:08 -0400 Subject: [PATCH 2/9] Stop ignoring ErrorResponse during SCRAM auth The server may send back an ErrorResponse during SCRAM auth, and these messages may contain useful information that described why authentication failed. For example, if the password was invalid. --- auth_scram.go | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/auth_scram.go b/auth_scram.go index 6a143fcd..d8d71116 100644 --- a/auth_scram.go +++ b/auth_scram.go @@ -78,12 +78,14 @@ func (c *PgConn) rxSASLContinue() (*pgproto3.AuthenticationSASLContinue, error) if err != nil { return nil, err } - saslContinue, ok := msg.(*pgproto3.AuthenticationSASLContinue) - if ok { - return saslContinue, nil + switch m := msg.(type) { + case *pgproto3.AuthenticationSASLContinue: + return m, nil + case *pgproto3.ErrorResponse: + return nil, ErrorResponseToPgError(m) } - return nil, errors.New("expected AuthenticationSASLContinue message but received unexpected message") + return nil, fmt.Errorf("expected AuthenticationSASLContinue message but received unexpected message %T", msg) } func (c *PgConn) rxSASLFinal() (*pgproto3.AuthenticationSASLFinal, error) { @@ -91,12 +93,14 @@ func (c *PgConn) rxSASLFinal() (*pgproto3.AuthenticationSASLFinal, error) { if err != nil { return nil, err } - saslFinal, ok := msg.(*pgproto3.AuthenticationSASLFinal) - if ok { - return saslFinal, nil + switch m := msg.(type) { + case *pgproto3.AuthenticationSASLFinal: + return m, nil + case *pgproto3.ErrorResponse: + return nil, ErrorResponseToPgError(m) } - return nil, errors.New("expected AuthenticationSASLFinal message but received unexpected message") + return nil, fmt.Errorf("expected AuthenticationSASLFinal message but received unexpected message %T", msg) } type scramClient struct { From 831fc211bc3b06eb7b461992c231fea7496dbf29 Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Sat, 7 May 2022 07:11:19 -0500 Subject: [PATCH 3/9] Release v1.12.1 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6df3ddcf..a3efb7f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 1.12.1 (May 7, 2022) + +* Fix: setting krbspn and krbsrvname in connection string (sireax) +* Add support for Unix sockets on Windows (Eno Compton) +* Stop ignoring ErrorResponse during SCRAM auth (Rafi Shamim) + # 1.12.0 (April 21, 2022) * Add pluggable GSSAPI support (Oliver Tan) From 7ddbd74d5e5a52cd24f750cacfc9be91d4f49f76 Mon Sep 17 00:00:00 2001 From: Oliver Tan Date: Tue, 24 May 2022 10:39:13 -0700 Subject: [PATCH 4/9] stop ignoring ErrorResponse during GSS auth --- krb5.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/krb5.go b/krb5.go index f2dbe45a..08427b8e 100644 --- a/krb5.go +++ b/krb5.go @@ -2,6 +2,8 @@ package pgconn import ( "errors" + "fmt" + "github.com/jackc/pgproto3/v2" ) @@ -85,10 +87,13 @@ func (c *PgConn) rxGSSContinue() (*pgproto3.AuthenticationGSSContinue, error) { if err != nil { return nil, err } - gssContinue, ok := msg.(*pgproto3.AuthenticationGSSContinue) - if ok { - return gssContinue, nil + + switch m := msg.(type) { + case *pgproto3.AuthenticationGSSContinue: + return m, nil + case *pgproto3.ErrorResponse: + return nil, ErrorResponseToPgError(m) } - return nil, errors.New("expected AuthenticationGSSContinue message but received unexpected message") + return nil, fmt.Errorf("expected AuthenticationGSSContinue message but received unexpected message %T", msg) } From 25935a39b6edcd7a885408fca5ffe20df753a82c Mon Sep 17 00:00:00 2001 From: "sergey.bashilov" Date: Fri, 17 Jun 2022 14:51:56 +0300 Subject: [PATCH 5/9] add prefer-standby target_session_attrs --- config.go | 22 +++++++++++++++++++++- config_test.go | 45 ++++++++++++++++++++++++--------------------- errors.go | 17 +++++++++++++++++ pgconn.go | 36 ++++++++++++++++++++++++++++-------- 4 files changed, 90 insertions(+), 30 deletions(-) diff --git a/config.go b/config.go index e141a2f8..fc5b3c0f 100644 --- a/config.go +++ b/config.go @@ -50,6 +50,8 @@ type Config struct { // fallback config is tried. This allows implementing high availability behavior such as libpq does with target_session_attrs. ValidateConnect ValidateConnectFunc + HasPreferStandbyTargetSessionAttr bool + // AfterConnect is called after ValidateConnect. It can be used to set up the connection (e.g. Set session variables // or prepare statements). If this returns an error the connection attempt fails. AfterConnect AfterConnectFunc @@ -367,7 +369,10 @@ func ParseConfig(connString string) (*Config, error) { config.ValidateConnect = ValidateConnectTargetSessionAttrsPrimary case "standby": config.ValidateConnect = ValidateConnectTargetSessionAttrsStandby - case "any", "prefer-standby": + case "prefer-standby": + config.ValidateConnect = ValidateConnectTargetSessionAttrsPrefferStandby + config.HasPreferStandbyTargetSessionAttr = true + case "any": // do nothing default: return nil, &parseConfigError{connString: connString, msg: fmt.Sprintf("unknown target_session_attrs value: %v", tsa)} @@ -810,3 +815,18 @@ func ValidateConnectTargetSessionAttrsPrimary(ctx context.Context, pgConn *PgCon return nil } + +// ValidateConnectTargetSessionAttrsPrimary is an ValidateConnectFunc that implements libpq compatible +// target_session_attrs=prefer-standby. +func ValidateConnectTargetSessionAttrsPrefferStandby(ctx context.Context, pgConn *PgConn) error { + result := pgConn.ExecParams(ctx, "select pg_is_in_recovery()", nil, nil, nil, nil).Read() + if result.Err != nil { + return result.Err + } + + if string(result.Rows[0][0]) != "t" { + return &preferStanbyNotFoundError{err: errors.New("server is not in hot standby mode")} + } + + return nil +} diff --git a/config_test.go b/config_test.go index a28db3d6..6311f1f1 100644 --- a/config_test.go +++ b/config_test.go @@ -584,13 +584,13 @@ func TestParseConfig(t *testing.T) { name: "target_session_attrs primary", connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable&target_session_attrs=primary", config: &pgconn.Config{ - User: "jack", - Password: "secret", - Host: "localhost", - Port: 5432, - Database: "mydb", - TLSConfig: nil, - RuntimeParams: map[string]string{}, + User: "jack", + Password: "secret", + Host: "localhost", + Port: 5432, + Database: "mydb", + TLSConfig: nil, + RuntimeParams: map[string]string{}, ValidateConnect: pgconn.ValidateConnectTargetSessionAttrsPrimary, }, }, @@ -598,13 +598,13 @@ func TestParseConfig(t *testing.T) { name: "target_session_attrs standby", connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable&target_session_attrs=standby", config: &pgconn.Config{ - User: "jack", - Password: "secret", - Host: "localhost", - Port: 5432, - Database: "mydb", - TLSConfig: nil, - RuntimeParams: map[string]string{}, + User: "jack", + Password: "secret", + Host: "localhost", + Port: 5432, + Database: "mydb", + TLSConfig: nil, + RuntimeParams: map[string]string{}, ValidateConnect: pgconn.ValidateConnectTargetSessionAttrsStandby, }, }, @@ -612,13 +612,15 @@ func TestParseConfig(t *testing.T) { name: "target_session_attrs prefer-standby", connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable&target_session_attrs=prefer-standby", config: &pgconn.Config{ - User: "jack", - Password: "secret", - Host: "localhost", - Port: 5432, - Database: "mydb", - TLSConfig: nil, - RuntimeParams: map[string]string{}, + User: "jack", + Password: "secret", + Host: "localhost", + Port: 5432, + Database: "mydb", + TLSConfig: nil, + RuntimeParams: map[string]string{}, + ValidateConnect: pgconn.ValidateConnectTargetSessionAttrsPrefferStandby, + HasPreferStandbyTargetSessionAttr: true, }, }, { @@ -783,6 +785,7 @@ func assertConfigsEqual(t *testing.T, expected, actual *pgconn.Config, testName // Can't test function equality, so just test that they are set or not. assert.Equalf(t, expected.ValidateConnect == nil, actual.ValidateConnect == nil, "%s - ValidateConnect", testName) assert.Equalf(t, expected.AfterConnect == nil, actual.AfterConnect == nil, "%s - AfterConnect", testName) + assert.Equalf(t, expected.HasPreferStandbyTargetSessionAttr, actual.HasPreferStandbyTargetSessionAttr, "%s - HasPreferStandbyTargetSessionAttr", testName) if assert.Equalf(t, expected.TLSConfig == nil, actual.TLSConfig == nil, "%s - TLSConfig", testName) { if expected.TLSConfig != nil { diff --git a/errors.go b/errors.go index a32b29c9..2bc74df7 100644 --- a/errors.go +++ b/errors.go @@ -219,3 +219,20 @@ func redactURL(u *url.URL) string { } return u.String() } + +type preferStanbyNotFoundError struct { + err error + safeToRetry bool +} + +func (e *preferStanbyNotFoundError) Error() string { + return fmt.Sprintf("standby server not found: %s", e.err.Error()) +} + +func (e *preferStanbyNotFoundError) SafeToRetry() bool { + return e.safeToRetry +} + +func (e *preferStanbyNotFoundError) Unwrap() error { + return e.err +} diff --git a/pgconn.go b/pgconn.go index ef5b76fd..8e7ac668 100644 --- a/pgconn.go +++ b/pgconn.go @@ -148,25 +148,34 @@ func ConnectConfig(ctx context.Context, config *Config) (pgConn *PgConn, err err return nil, &connectError{config: config, msg: "hostname resolving error", err: errors.New("ip addr wasn't found")} } + foundBestServer := false + var fallbackConfig *FallbackConfig for _, fc := range fallbackConfigs { pgConn, err = connect(ctx, config, fc) if err == nil { + foundBestServer = true break } else if pgerr, ok := err.(*PgError); ok { err = &connectError{config: config, msg: "server error", err: pgerr} - const ERRCODE_INVALID_PASSWORD = "28P01" // wrong password - const ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION = "28000" // wrong password or bad pg_hba.conf settings - const ERRCODE_INVALID_CATALOG_NAME = "3D000" // db does not exist - const ERRCODE_INSUFFICIENT_PRIVILEGE = "42501" // missing connect privilege - if pgerr.Code == ERRCODE_INVALID_PASSWORD || - pgerr.Code == ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION || - pgerr.Code == ERRCODE_INVALID_CATALOG_NAME || - pgerr.Code == ERRCODE_INSUFFICIENT_PRIVILEGE { + if checkPgError(pgerr) { break } + } else if cerr, ok := err.(*connectError); ok && config.HasPreferStandbyTargetSessionAttr { + if _, ok := cerr.err.(*preferStanbyNotFoundError); ok { + fallbackConfig = fc + } } } + if !foundBestServer && fallbackConfig != nil { + config.ValidateConnect = nil + pgConn, err = connect(ctx, config, fallbackConfig) + if pgerr, ok := err.(*PgError); ok { + err = &connectError{config: config, msg: "server error", err: pgerr} + } + config.ValidateConnect = ValidateConnectTargetSessionAttrsPrefferStandby + } + if err != nil { return nil, err // no need to wrap in connectError because it will already be wrapped in all cases except PgError } @@ -182,6 +191,17 @@ func ConnectConfig(ctx context.Context, config *Config) (pgConn *PgConn, err err return pgConn, nil } +func checkPgError(pgerr *PgError) bool { + const ERRCODE_INVALID_PASSWORD = "28P01" // wrong password + const ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION = "28000" // wrong password or bad pg_hba.conf settings + const ERRCODE_INVALID_CATALOG_NAME = "3D000" // db does not exist + const ERRCODE_INSUFFICIENT_PRIVILEGE = "42501" // missing connect privilege + return pgerr.Code == ERRCODE_INVALID_PASSWORD || + pgerr.Code == ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION || + pgerr.Code == ERRCODE_INVALID_CATALOG_NAME || + pgerr.Code == ERRCODE_INSUFFICIENT_PRIVILEGE +} + func expandWithIPs(ctx context.Context, lookupFn LookupFunc, fallbacks []*FallbackConfig) ([]*FallbackConfig, error) { var configs []*FallbackConfig From 1b6543f29c8c08ccfc51a8d426ea44d960ae4d3e Mon Sep 17 00:00:00 2001 From: "sergey.bashilov" Date: Mon, 20 Jun 2022 12:15:15 +0300 Subject: [PATCH 6/9] fix typos --- config.go | 8 ++++---- config_test.go | 2 +- errors.go | 8 ++++---- pgconn.go | 24 ++++++++++-------------- 4 files changed, 19 insertions(+), 23 deletions(-) diff --git a/config.go b/config.go index fc5b3c0f..dac7b95b 100644 --- a/config.go +++ b/config.go @@ -370,7 +370,7 @@ func ParseConfig(connString string) (*Config, error) { case "standby": config.ValidateConnect = ValidateConnectTargetSessionAttrsStandby case "prefer-standby": - config.ValidateConnect = ValidateConnectTargetSessionAttrsPrefferStandby + config.ValidateConnect = ValidateConnectTargetSessionAttrsPreferStandby config.HasPreferStandbyTargetSessionAttr = true case "any": // do nothing @@ -816,16 +816,16 @@ func ValidateConnectTargetSessionAttrsPrimary(ctx context.Context, pgConn *PgCon return nil } -// ValidateConnectTargetSessionAttrsPrimary is an ValidateConnectFunc that implements libpq compatible +// ValidateConnectTargetSessionAttrsPreferStandby is an ValidateConnectFunc that implements libpq compatible // target_session_attrs=prefer-standby. -func ValidateConnectTargetSessionAttrsPrefferStandby(ctx context.Context, pgConn *PgConn) error { +func ValidateConnectTargetSessionAttrsPreferStandby(ctx context.Context, pgConn *PgConn) error { result := pgConn.ExecParams(ctx, "select pg_is_in_recovery()", nil, nil, nil, nil).Read() if result.Err != nil { return result.Err } if string(result.Rows[0][0]) != "t" { - return &preferStanbyNotFoundError{err: errors.New("server is not in hot standby mode")} + return &preferStandbyNotFoundError{err: errors.New("server is not in hot standby mode")} } return nil diff --git a/config_test.go b/config_test.go index 6311f1f1..c8d8cee6 100644 --- a/config_test.go +++ b/config_test.go @@ -619,7 +619,7 @@ func TestParseConfig(t *testing.T) { Database: "mydb", TLSConfig: nil, RuntimeParams: map[string]string{}, - ValidateConnect: pgconn.ValidateConnectTargetSessionAttrsPrefferStandby, + ValidateConnect: pgconn.ValidateConnectTargetSessionAttrsPreferStandby, HasPreferStandbyTargetSessionAttr: true, }, }, diff --git a/errors.go b/errors.go index 2bc74df7..7ed8889c 100644 --- a/errors.go +++ b/errors.go @@ -220,19 +220,19 @@ func redactURL(u *url.URL) string { return u.String() } -type preferStanbyNotFoundError struct { +type preferStandbyNotFoundError struct { err error safeToRetry bool } -func (e *preferStanbyNotFoundError) Error() string { +func (e *preferStandbyNotFoundError) Error() string { return fmt.Sprintf("standby server not found: %s", e.err.Error()) } -func (e *preferStanbyNotFoundError) SafeToRetry() bool { +func (e *preferStandbyNotFoundError) SafeToRetry() bool { return e.safeToRetry } -func (e *preferStanbyNotFoundError) Unwrap() error { +func (e *preferStandbyNotFoundError) Unwrap() error { return e.err } diff --git a/pgconn.go b/pgconn.go index 8e7ac668..1a1d3505 100644 --- a/pgconn.go +++ b/pgconn.go @@ -157,11 +157,18 @@ func ConnectConfig(ctx context.Context, config *Config) (pgConn *PgConn, err err break } else if pgerr, ok := err.(*PgError); ok { err = &connectError{config: config, msg: "server error", err: pgerr} - if checkPgError(pgerr) { + const ERRCODE_INVALID_PASSWORD = "28P01" // wrong password + const ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION = "28000" // wrong password or bad pg_hba.conf settings + const ERRCODE_INVALID_CATALOG_NAME = "3D000" // db does not exist + const ERRCODE_INSUFFICIENT_PRIVILEGE = "42501" // missing connect privilege + if pgerr.Code == ERRCODE_INVALID_PASSWORD || + pgerr.Code == ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION || + pgerr.Code == ERRCODE_INVALID_CATALOG_NAME || + pgerr.Code == ERRCODE_INSUFFICIENT_PRIVILEGE { break } } else if cerr, ok := err.(*connectError); ok && config.HasPreferStandbyTargetSessionAttr { - if _, ok := cerr.err.(*preferStanbyNotFoundError); ok { + if _, ok := cerr.err.(*preferStandbyNotFoundError); ok { fallbackConfig = fc } } @@ -173,7 +180,7 @@ func ConnectConfig(ctx context.Context, config *Config) (pgConn *PgConn, err err if pgerr, ok := err.(*PgError); ok { err = &connectError{config: config, msg: "server error", err: pgerr} } - config.ValidateConnect = ValidateConnectTargetSessionAttrsPrefferStandby + config.ValidateConnect = ValidateConnectTargetSessionAttrsPreferStandby } if err != nil { @@ -191,17 +198,6 @@ func ConnectConfig(ctx context.Context, config *Config) (pgConn *PgConn, err err return pgConn, nil } -func checkPgError(pgerr *PgError) bool { - const ERRCODE_INVALID_PASSWORD = "28P01" // wrong password - const ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION = "28000" // wrong password or bad pg_hba.conf settings - const ERRCODE_INVALID_CATALOG_NAME = "3D000" // db does not exist - const ERRCODE_INSUFFICIENT_PRIVILEGE = "42501" // missing connect privilege - return pgerr.Code == ERRCODE_INVALID_PASSWORD || - pgerr.Code == ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION || - pgerr.Code == ERRCODE_INVALID_CATALOG_NAME || - pgerr.Code == ERRCODE_INSUFFICIENT_PRIVILEGE -} - func expandWithIPs(ctx context.Context, lookupFn LookupFunc, fallbacks []*FallbackConfig) ([]*FallbackConfig, error) { var configs []*FallbackConfig From 618a12a094636d97ec67a2950adc1dd7dac37241 Mon Sep 17 00:00:00 2001 From: "sergey.bashilov" Date: Fri, 24 Jun 2022 14:02:59 +0300 Subject: [PATCH 7/9] remove HasPreferStandbyTargetSessionAttr, rename error to indicate server is not standby --- config.go | 5 +---- config_test.go | 18 ++++++++---------- errors.go | 8 ++++---- pgconn.go | 4 ++-- 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/config.go b/config.go index dac7b95b..4ca09dda 100644 --- a/config.go +++ b/config.go @@ -50,8 +50,6 @@ type Config struct { // fallback config is tried. This allows implementing high availability behavior such as libpq does with target_session_attrs. ValidateConnect ValidateConnectFunc - HasPreferStandbyTargetSessionAttr bool - // AfterConnect is called after ValidateConnect. It can be used to set up the connection (e.g. Set session variables // or prepare statements). If this returns an error the connection attempt fails. AfterConnect AfterConnectFunc @@ -371,7 +369,6 @@ func ParseConfig(connString string) (*Config, error) { config.ValidateConnect = ValidateConnectTargetSessionAttrsStandby case "prefer-standby": config.ValidateConnect = ValidateConnectTargetSessionAttrsPreferStandby - config.HasPreferStandbyTargetSessionAttr = true case "any": // do nothing default: @@ -825,7 +822,7 @@ func ValidateConnectTargetSessionAttrsPreferStandby(ctx context.Context, pgConn } if string(result.Rows[0][0]) != "t" { - return &preferStandbyNotFoundError{err: errors.New("server is not in hot standby mode")} + return &NotStandbyError{err: errors.New("server is not in hot standby mode")} } return nil diff --git a/config_test.go b/config_test.go index c8d8cee6..6b48ea27 100644 --- a/config_test.go +++ b/config_test.go @@ -612,15 +612,14 @@ func TestParseConfig(t *testing.T) { name: "target_session_attrs prefer-standby", connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable&target_session_attrs=prefer-standby", config: &pgconn.Config{ - User: "jack", - Password: "secret", - Host: "localhost", - Port: 5432, - Database: "mydb", - TLSConfig: nil, - RuntimeParams: map[string]string{}, - ValidateConnect: pgconn.ValidateConnectTargetSessionAttrsPreferStandby, - HasPreferStandbyTargetSessionAttr: true, + User: "jack", + Password: "secret", + Host: "localhost", + Port: 5432, + Database: "mydb", + TLSConfig: nil, + RuntimeParams: map[string]string{}, + ValidateConnect: pgconn.ValidateConnectTargetSessionAttrsPreferStandby, }, }, { @@ -785,7 +784,6 @@ func assertConfigsEqual(t *testing.T, expected, actual *pgconn.Config, testName // Can't test function equality, so just test that they are set or not. assert.Equalf(t, expected.ValidateConnect == nil, actual.ValidateConnect == nil, "%s - ValidateConnect", testName) assert.Equalf(t, expected.AfterConnect == nil, actual.AfterConnect == nil, "%s - AfterConnect", testName) - assert.Equalf(t, expected.HasPreferStandbyTargetSessionAttr, actual.HasPreferStandbyTargetSessionAttr, "%s - HasPreferStandbyTargetSessionAttr", testName) if assert.Equalf(t, expected.TLSConfig == nil, actual.TLSConfig == nil, "%s - TLSConfig", testName) { if expected.TLSConfig != nil { diff --git a/errors.go b/errors.go index 7ed8889c..9f04476d 100644 --- a/errors.go +++ b/errors.go @@ -220,19 +220,19 @@ func redactURL(u *url.URL) string { return u.String() } -type preferStandbyNotFoundError struct { +type NotStandbyError struct { err error safeToRetry bool } -func (e *preferStandbyNotFoundError) Error() string { +func (e *NotStandbyError) Error() string { return fmt.Sprintf("standby server not found: %s", e.err.Error()) } -func (e *preferStandbyNotFoundError) SafeToRetry() bool { +func (e *NotStandbyError) SafeToRetry() bool { return e.safeToRetry } -func (e *preferStandbyNotFoundError) Unwrap() error { +func (e *NotStandbyError) Unwrap() error { return e.err } diff --git a/pgconn.go b/pgconn.go index 1a1d3505..5e436ffe 100644 --- a/pgconn.go +++ b/pgconn.go @@ -167,8 +167,8 @@ func ConnectConfig(ctx context.Context, config *Config) (pgConn *PgConn, err err pgerr.Code == ERRCODE_INSUFFICIENT_PRIVILEGE { break } - } else if cerr, ok := err.(*connectError); ok && config.HasPreferStandbyTargetSessionAttr { - if _, ok := cerr.err.(*preferStandbyNotFoundError); ok { + } else if cerr, ok := err.(*connectError); ok { + if _, ok := cerr.err.(*NotStandbyError); ok { fallbackConfig = fc } } From cdc240d920c29140eb8980a6d650b4a570c19a04 Mon Sep 17 00:00:00 2001 From: "sergey.bashilov" Date: Fri, 24 Jun 2022 14:20:36 +0300 Subject: [PATCH 8/9] rename error --- config.go | 2 +- errors.go | 8 ++++---- pgconn.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/config.go b/config.go index 4ca09dda..8fd7efbf 100644 --- a/config.go +++ b/config.go @@ -822,7 +822,7 @@ func ValidateConnectTargetSessionAttrsPreferStandby(ctx context.Context, pgConn } if string(result.Rows[0][0]) != "t" { - return &NotStandbyError{err: errors.New("server is not in hot standby mode")} + return &NotPreferredError{err: errors.New("server is not in hot standby mode")} } return nil diff --git a/errors.go b/errors.go index 9f04476d..66d35584 100644 --- a/errors.go +++ b/errors.go @@ -220,19 +220,19 @@ func redactURL(u *url.URL) string { return u.String() } -type NotStandbyError struct { +type NotPreferredError struct { err error safeToRetry bool } -func (e *NotStandbyError) Error() string { +func (e *NotPreferredError) Error() string { return fmt.Sprintf("standby server not found: %s", e.err.Error()) } -func (e *NotStandbyError) SafeToRetry() bool { +func (e *NotPreferredError) SafeToRetry() bool { return e.safeToRetry } -func (e *NotStandbyError) Unwrap() error { +func (e *NotPreferredError) Unwrap() error { return e.err } diff --git a/pgconn.go b/pgconn.go index 5e436ffe..6093d17b 100644 --- a/pgconn.go +++ b/pgconn.go @@ -168,7 +168,7 @@ func ConnectConfig(ctx context.Context, config *Config) (pgConn *PgConn, err err break } } else if cerr, ok := err.(*connectError); ok { - if _, ok := cerr.err.(*NotStandbyError); ok { + if _, ok := cerr.err.(*NotPreferredError); ok { fallbackConfig = fc } } From a18df2374a85fcdcf8ccf6ebc2cec5dcc1156c61 Mon Sep 17 00:00:00 2001 From: "sergey.bashilov" Date: Fri, 1 Jul 2022 17:50:25 +0300 Subject: [PATCH 9/9] add ignore not preferred err flag in connect func --- pgconn.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pgconn.go b/pgconn.go index 6093d17b..430f4367 100644 --- a/pgconn.go +++ b/pgconn.go @@ -151,7 +151,7 @@ func ConnectConfig(ctx context.Context, config *Config) (pgConn *PgConn, err err foundBestServer := false var fallbackConfig *FallbackConfig for _, fc := range fallbackConfigs { - pgConn, err = connect(ctx, config, fc) + pgConn, err = connect(ctx, config, fc, false) if err == nil { foundBestServer = true break @@ -175,12 +175,10 @@ func ConnectConfig(ctx context.Context, config *Config) (pgConn *PgConn, err err } if !foundBestServer && fallbackConfig != nil { - config.ValidateConnect = nil - pgConn, err = connect(ctx, config, fallbackConfig) + pgConn, err = connect(ctx, config, fallbackConfig, true) if pgerr, ok := err.(*PgError); ok { err = &connectError{config: config, msg: "server error", err: pgerr} } - config.ValidateConnect = ValidateConnectTargetSessionAttrsPreferStandby } if err != nil { @@ -243,7 +241,8 @@ func expandWithIPs(ctx context.Context, lookupFn LookupFunc, fallbacks []*Fallba return configs, nil } -func connect(ctx context.Context, config *Config, fallbackConfig *FallbackConfig) (*PgConn, error) { +func connect(ctx context.Context, config *Config, fallbackConfig *FallbackConfig, + ignoreNotPreferredErr bool) (*PgConn, error) { pgConn := new(PgConn) pgConn.config = config pgConn.wbuf = make([]byte, 0, wbufLen) @@ -356,6 +355,9 @@ func connect(ctx context.Context, config *Config, fallbackConfig *FallbackConfig err := config.ValidateConnect(ctx, pgConn) if err != nil { + if _, ok := err.(*NotPreferredError); ignoreNotPreferredErr && ok { + return pgConn, nil + } pgConn.conn.Close() return nil, &connectError{config: config, msg: "ValidateConnect failed", err: err} }