fix transaction rollbacks in DoTx (#55)
* fix transaction rollbacks in DoTx * better assertion * Update internal/db/read_writer_test.go Co-authored-by: Michael Gaffney <mgaffney@users.noreply.github.com> Co-authored-by: Jim Lambert <jimlambrt@Jims-MBP-3.home> Co-authored-by: Michael Gaffney <mgaffney@users.noreply.github.com>
This commit is contained in:
2 files changed
+82
-18
No files matched your search
+16
-18
@@ -273,7 +273,7 @@ func (rw *Db) Update(ctx context.Context, i interface{}, fieldMaskPaths []string
|
||||
return NoRowsAffected, fmt.Errorf("error updating: %w", underlying.Error)
|
||||
}
|
||||
rowsUpdated := int(underlying.RowsAffected)
|
||||
if withOplog {
|
||||
if withOplog && rowsUpdated > 0 {
|
||||
if err := rw.addOplog(ctx, UpdateOp, opts, i); err != nil {
|
||||
return rowsUpdated, err
|
||||
}
|
||||
@@ -315,7 +315,7 @@ func (rw *Db) Delete(ctx context.Context, i interface{}, opt ...Option) (int, er
|
||||
return NoRowsAffected, fmt.Errorf("error deleting: %w", underlying.Error)
|
||||
}
|
||||
rowsDeleted := int(underlying.RowsAffected)
|
||||
if withOplog {
|
||||
if withOplog && rowsDeleted > 0 {
|
||||
if err := rw.addOplog(ctx, DeleteOp, opts, i); err != nil {
|
||||
return rowsDeleted, err
|
||||
}
|
||||
@@ -401,34 +401,32 @@ func (w *Db) DoTx(ctx context.Context, retries uint, backOff Backoff, Handler Tx
|
||||
if attempts > retries+1 {
|
||||
return info, fmt.Errorf("Too many retries: %d of %d", attempts-1, retries+1)
|
||||
}
|
||||
|
||||
// step one of this, start a transaction...
|
||||
newTx := w.underlying.BeginTx(ctx, nil)
|
||||
|
||||
if err := Handler(&Db{newTx}); err != nil {
|
||||
if err := newTx.Rollback().Error; err != nil {
|
||||
return info, err
|
||||
}
|
||||
if errors.Is(err, oplog.ErrTicketAlreadyRedeemed) {
|
||||
newTx.Rollback() // need to still handle rollback errors
|
||||
d := backOff.Duration(attempts)
|
||||
info.Retries++
|
||||
info.Backoff = info.Backoff + d
|
||||
time.Sleep(d)
|
||||
continue
|
||||
} else {
|
||||
}
|
||||
return info, err
|
||||
}
|
||||
|
||||
if err := newTx.Commit().Error; err != nil {
|
||||
if err := newTx.Rollback().Error; err != nil {
|
||||
return info, err
|
||||
}
|
||||
} else {
|
||||
if err := newTx.Commit().Error; err != nil {
|
||||
if errors.Is(err, oplog.ErrTicketAlreadyRedeemed) {
|
||||
d := backOff.Duration(attempts)
|
||||
info.Retries++
|
||||
info.Backoff = info.Backoff + d
|
||||
time.Sleep(d)
|
||||
continue
|
||||
} else {
|
||||
return info, err
|
||||
}
|
||||
}
|
||||
break // it all worked!!!
|
||||
return info, err
|
||||
}
|
||||
return info, nil // it all worked!!!
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// LookupByName will lookup resource my its friendly name which must be unique
|
||||
|
||||
@@ -3,6 +3,7 @@ package db
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/go-uuid"
|
||||
@@ -604,6 +605,71 @@ func TestDb_DoTx(t *testing.T) {
|
||||
assert.Equal(3, got.Retries)
|
||||
assert.Equal("Too many retries: 3 of 3", err.Error())
|
||||
})
|
||||
t.Run("updating-good-bad-good", func(t *testing.T) {
|
||||
w := Db{underlying: db}
|
||||
id, err := uuid.GenerateUUID()
|
||||
assert.NoError(err)
|
||||
user, err := db_test.NewTestUser()
|
||||
assert.NoError(err)
|
||||
user.Name = "foo-" + id
|
||||
err = w.Create(context.Background(), user)
|
||||
assert.NoError(err)
|
||||
assert.NotZero(user.Id)
|
||||
|
||||
_, err = w.DoTx(context.Background(), 10, ExpBackoff{}, func(w Writer) error {
|
||||
user.Name = "friendly-" + id
|
||||
rowsUpdated, err := w.Update(context.Background(), user, []string{"Name"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rowsUpdated != 1 {
|
||||
return fmt.Errorf("error in number of rows updated %d", rowsUpdated)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
assert.NoError(err)
|
||||
|
||||
foundUser, err := db_test.NewTestUser()
|
||||
assert.NoError(err)
|
||||
foundUser.PublicId = user.PublicId
|
||||
err = w.LookupByPublicId(context.Background(), foundUser)
|
||||
assert.NoError(err)
|
||||
assert.Equal(foundUser.Name, user.Name)
|
||||
|
||||
user2, err := db_test.NewTestUser()
|
||||
assert.NoError(err)
|
||||
_, err = w.DoTx(context.Background(), 10, ExpBackoff{}, func(w Writer) error {
|
||||
user2.Name = "friendly2-" + id
|
||||
rowsUpdated, err := w.Update(context.Background(), user2, []string{"Name"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rowsUpdated != 1 {
|
||||
return fmt.Errorf("error in number of rows updated %d", rowsUpdated)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
assert.Error(err)
|
||||
err = w.LookupByPublicId(context.Background(), foundUser)
|
||||
assert.NoError(err)
|
||||
assert.NotEqual(foundUser.Name, user2.Name)
|
||||
|
||||
_, err = w.DoTx(context.Background(), 10, ExpBackoff{}, func(w Writer) error {
|
||||
user.Name = "friendly2-" + id
|
||||
rowsUpdated, err := w.Update(context.Background(), user, []string{"Name"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rowsUpdated != 1 {
|
||||
return fmt.Errorf("error in number of rows updated %d", rowsUpdated)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
assert.NoError(err)
|
||||
err = w.LookupByPublicId(context.Background(), foundUser)
|
||||
assert.NoError(err)
|
||||
assert.Equal(foundUser.Name, user.Name)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDb_Delete(t *testing.T) {
|
||||
|
||||
Reference in new issue
Block a user