From 1bbf1797f79b1208da4db197c0f75d3708216105 Mon Sep 17 00:00:00 2001 From: Matvey Gubanikhin Date: Fri, 30 Oct 2026 15:34:25 +0300 Subject: [PATCH] Add comparable errors for target_session_attrs. --- fbconn/config.go | 25 +++++++++++++----- fbconn/errors_internal_test.go | 47 ++++++++++++++++++++++++++++++++++ fbconn/fbconn_test.go | 20 +++++++++++++++ 3 files changed, 86 insertions(+), 6 deletions(-) diff --git a/fbconn/config.go b/fbconn/config.go index b08e25c..cd3a845 100644 --- a/fbconn/config.go +++ b/fbconn/config.go @@ -675,6 +675,19 @@ func makeConnectTimeoutDialFunc(timeout time.Duration) DialFunc { return d.DialContext } +var ( + // ErrReadOnlyConnection is returned when a read-write connection is required but the connection is read-only. + ErrReadOnlyConnection = errors.New("read only connection") + // ErrReadWriteConnection is returned when a read-only connection is required but the connection is read-write. + ErrReadWriteConnection = errors.New("connection is not read only") + // ErrPrimary is returned when a replica connection is required but the connection is primary. + ErrPrimary = errors.New("not replica") + // ErrReplica is returned when a primary connection is required but the connection is replica. + ErrReplica = errors.New("not primary") + // ErrReplicationNotConfigured is returned when replication not configured on host. + ErrReplicationNotConfigured = errors.New("replication is not configured") +) + // ValidateConnectTargetSessionAttrsReadWrite is a ValidateConnectFunc that implements libpq compatible // target_session_attrs=read-write. func ValidateConnectTargetSessionAttrsReadWrite(ctx context.Context, fbConn *FbConn) error { @@ -684,7 +697,7 @@ func ValidateConnectTargetSessionAttrsReadWrite(ctx context.Context, fbConn *FbC } if res.Rows[0][0][3] == 1 { - return errors.New("read only connection") + return ErrReadOnlyConnection } return nil @@ -699,7 +712,7 @@ func ValidateConnectTargetSessionAttrsReadOnly(ctx context.Context, fbConn *FbCo } if res.Rows[0][0][3] != 1 { - return errors.New("connection is not read only") + return ErrReadWriteConnection } return nil @@ -717,7 +730,7 @@ func checkSessionAttrIsPrimary(ctx context.Context, fbConn *FbConn) (bool, error return false, nil } - return false, errors.New("replication is not configured") + return false, ErrReplicationNotConfigured } // ValidateConnectTargetSessionAttrsReplica is a [ValidateConnectFunc] that implements @@ -730,7 +743,7 @@ func ValidateConnectTargetSessionAttrsReplica(ctx context.Context, fbConn *FbCon } if isPrimary { - return errors.New("not replica") + return ErrPrimary } return nil @@ -746,7 +759,7 @@ func ValidateConnectTargetSessionAttrsPrimary(ctx context.Context, fbConn *FbCon } if !isPrimary { - return errors.New("not primary") + return ErrReplica } return nil @@ -762,7 +775,7 @@ func ValidateConnectTargetSessionAttrsPreferReplica(ctx context.Context, fbConn } if isPrimary { - return &NotPreferredError{err: errors.New("not replica")} + return &NotPreferredError{err: ErrPrimary} } return nil diff --git a/fbconn/errors_internal_test.go b/fbconn/errors_internal_test.go index c210238..9682fb5 100644 --- a/fbconn/errors_internal_test.go +++ b/fbconn/errors_internal_test.go @@ -3,8 +3,11 @@ package fbconn import ( "context" "errors" + "os" "testing" + "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -21,3 +24,47 @@ func TestSyncConnClosedConnErrorIsErrConnClosed(t *testing.T) { require.Error(t, err) require.ErrorIs(t, err, ErrConnClosed) } + +func TestConnectWithValidateConnectSessionAttributeErrors(t *testing.T) { + tests := []struct { + name string + err error + msg string + }{ + {"read-only connection", ErrReadOnlyConnection, "read only connection"}, + {"read-write connection", ErrReadWriteConnection, "connection is not read only"}, + {"primary", ErrPrimary, "not replica"}, + {"replica", ErrReplica, "not primary"}, + {"replication not configured", ErrReplicationNotConfigured, "replication is not configured"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) + defer cancel() + + config, err := ParseConfig(os.Getenv("FBX_TEST_DATABASE")) + require.NoError(t, err) + config.ValidateConnect = func(context.Context, *FbConn) error { + return tt.err + } + + conn, err := ConnectConfig(ctx, config) + if !assert.NotNil(t, err) { + _ = conn.Close(ctx) + } + require.ErrorIs(t, err, tt.err) + var connectErr *ConnectError + require.ErrorAs(t, err, &connectErr) + require.ErrorContains(t, err, "ValidateConnect failed: "+tt.msg) + require.EqualError(t, tt.err, tt.msg) + }) + } + + t.Run("prefer-replica", func(t *testing.T) { + err := &NotPreferredError{err: ErrPrimary} + require.ErrorIs(t, err, ErrPrimary) + require.NotErrorIs(t, err, ErrReplica) + require.EqualError(t, err, "standby server not found: not replica") + }) +} diff --git a/fbconn/fbconn_test.go b/fbconn/fbconn_test.go index f64d529..69753f5 100644 --- a/fbconn/fbconn_test.go +++ b/fbconn/fbconn_test.go @@ -420,6 +420,26 @@ func TestConnectWithValidateConnectTargetSessionAttrsReadWrite(t *testing.T) { if !assert.NotNil(t, err) { _ = conn.Close(ctx) } + require.ErrorIs(t, err, fbconn.ErrReadOnlyConnection) +} + +func TestConnectWithValidateConnectTargetSessionAttrsReadOnly(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) + defer cancel() + + config, err := fbconn.ParseConfig(os.Getenv("FBX_TEST_DATABASE")) + require.NoError(t, err) + + config.ValidateConnect = fbconn.ValidateConnectTargetSessionAttrsReadOnly + config.DefaultTransactionReadOnly = false + + conn, err := fbconn.ConnectConfig(ctx, config) + if !assert.NotNil(t, err) { + _ = conn.Close(ctx) + } + require.ErrorIs(t, err, fbconn.ErrReadWriteConnection) } func TestConnectWithAfterConnect(t *testing.T) {