mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: preserve rollback errors in runTx (#24598)
Previously, `runTx` could lose a deferred rollback failure when returning an existing transaction error, because the rollback path could not update the final return value. https://go.dev/play/p/AhBK31lO0Gd
This commit is contained in:
@@ -182,7 +182,7 @@ func (q *sqlQuerier) InTx(function func(Store) error, txOpts *TxOptions) error {
|
||||
}
|
||||
|
||||
// InTx performs database operations inside a transaction.
|
||||
func (q *sqlQuerier) runTx(function func(Store) error, txOpts *sql.TxOptions) error {
|
||||
func (q *sqlQuerier) runTx(function func(Store) error, txOpts *sql.TxOptions) (err error) {
|
||||
if _, ok := q.db.(*sqlx.Tx); ok {
|
||||
// If the current inner "db" is already a transaction, we just reuse it.
|
||||
// We do not need to handle commit/rollback as the outer tx will handle
|
||||
|
||||
@@ -5,9 +5,11 @@ import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/google/uuid"
|
||||
"github.com/lib/pq"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
||||
@@ -82,6 +84,33 @@ func TestNestedInTx(t *testing.T) {
|
||||
require.Equal(t, uid, user.ID, "user id expected")
|
||||
}
|
||||
|
||||
func TestInTx_CapturesRollbackError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
sqlDB, mock, err := sqlmock.New()
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { _ = sqlDB.Close() })
|
||||
|
||||
db := database.New(sqlDB)
|
||||
|
||||
callbackErr := xerrors.New("callback failed")
|
||||
rollbackErr := xerrors.New("rollback failed")
|
||||
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectRollback().WillReturnError(rollbackErr)
|
||||
|
||||
err = db.InTx(func(_ database.Store) error {
|
||||
return callbackErr
|
||||
}, nil)
|
||||
require.EqualError(t, err, "defer (rollback failed): execute transaction: callback failed")
|
||||
require.ErrorIs(t, err, callbackErr,
|
||||
"returned error should still match the callback error when rollback fails")
|
||||
require.NotErrorIs(t, err, rollbackErr,
|
||||
"rollback failure should be reported in the message, not wrapped in the error chain")
|
||||
|
||||
require.NoError(t, mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
func testSQLDB(t testing.TB) *sql.DB {
|
||||
t.Helper()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user