gorm/tests/transaction_test.go

481 lines
13 KiB
Go
Raw Normal View History

2020-05-31 13:51:43 +03:00
package tests_test
import (
"context"
2020-05-31 13:51:43 +03:00
"errors"
"testing"
2020-06-02 04:16:07 +03:00
"gorm.io/gorm"
2020-06-02 05:34:50 +03:00
. "gorm.io/gorm/utils/tests"
2020-05-31 13:51:43 +03:00
)
func TestTransaction(t *testing.T) {
tx := DB.Begin()
2020-06-08 18:25:16 +03:00
user := *GetUser("transaction", Config{})
2020-05-31 13:51:43 +03:00
if err := tx.Save(&user).Error; err != nil {
2020-05-31 18:55:56 +03:00
t.Fatalf("No error should raise, but got %v", err)
2020-05-31 13:51:43 +03:00
}
2020-06-08 18:25:16 +03:00
if err := tx.First(&User{}, "name = ?", "transaction").Error; err != nil {
t.Fatalf("Should find saved record, but got %v", err)
}
user1 := *GetUser("transaction1-1", Config{})
if err := tx.Save(&user1).Error; err != nil {
t.Fatalf("No error should raise, but got %v", err)
}
if err := tx.First(&User{}, "name = ?", user1.Name).Error; err != nil {
2020-05-31 18:55:56 +03:00
t.Fatalf("Should find saved record, but got %v", err)
2020-05-31 13:51:43 +03:00
}
2020-06-05 05:08:22 +03:00
if sqlTx, ok := tx.Statement.ConnPool.(gorm.TxCommitter); !ok || sqlTx == nil {
2020-05-31 18:55:56 +03:00
t.Fatalf("Should return the underlying sql.Tx")
2020-05-31 13:51:43 +03:00
}
tx.Rollback()
2020-06-08 18:25:16 +03:00
if err := DB.First(&User{}, "name = ?", "transaction").Error; err == nil {
2020-05-31 18:55:56 +03:00
t.Fatalf("Should not find record after rollback, but got %v", err)
2020-05-31 13:51:43 +03:00
}
txDB := DB.Where("fake_name = ?", "fake_name")
tx2 := txDB.Session(&gorm.Session{NewDB: true}).Begin()
2020-06-08 18:25:16 +03:00
user2 := *GetUser("transaction-2", Config{})
2020-05-31 13:51:43 +03:00
if err := tx2.Save(&user2).Error; err != nil {
2020-05-31 18:55:56 +03:00
t.Fatalf("No error should raise, but got %v", err)
2020-05-31 13:51:43 +03:00
}
2020-06-08 18:25:16 +03:00
if err := tx2.First(&User{}, "name = ?", "transaction-2").Error; err != nil {
2020-05-31 18:55:56 +03:00
t.Fatalf("Should find saved record, but got %v", err)
2020-05-31 13:51:43 +03:00
}
tx2.Commit()
2020-06-08 18:25:16 +03:00
if err := DB.First(&User{}, "name = ?", "transaction-2").Error; err != nil {
2020-05-31 18:55:56 +03:00
t.Fatalf("Should be able to find committed record, but got %v", err)
2020-05-31 13:51:43 +03:00
}
t.Run("this is test nested transaction and prepareStmt coexist case", func(t *testing.T) {
// enable prepare statement
tx3 := DB.Session(&gorm.Session{PrepareStmt: true})
if err := tx3.Transaction(func(tx4 *gorm.DB) error {
// nested transaction
return tx4.Transaction(func(tx5 *gorm.DB) error {
return tx5.First(&User{}, "name = ?", "transaction-2").Error
})
}); err != nil {
t.Fatalf("prepare statement and nested transaction coexist" + err.Error())
}
})
2020-05-31 13:51:43 +03:00
}
func TestCancelTransaction(t *testing.T) {
ctx := context.Background()
ctx, cancelFunc := context.WithCancel(ctx)
cancelFunc()
user := *GetUser("cancel_transaction", Config{})
DB.Create(&user)
err := DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
var result User
tx.First(&result, user.ID)
return nil
})
if err == nil {
t.Fatalf("Transaction should get error when using cancelled context")
}
}
2020-05-31 13:51:43 +03:00
func TestTransactionWithBlock(t *testing.T) {
assertPanic := func(f func()) {
defer func() {
if r := recover(); r == nil {
2020-05-31 18:55:56 +03:00
t.Fatalf("The code did not panic")
2020-05-31 13:51:43 +03:00
}
}()
f()
}
// rollback
err := DB.Transaction(func(tx *gorm.DB) error {
2020-06-08 18:25:16 +03:00
user := *GetUser("transaction-block", Config{})
2020-05-31 13:51:43 +03:00
if err := tx.Save(&user).Error; err != nil {
2020-05-31 18:55:56 +03:00
t.Fatalf("No error should raise")
2020-05-31 13:51:43 +03:00
}
if err := tx.First(&User{}, "name = ?", user.Name).Error; err != nil {
2020-05-31 18:55:56 +03:00
t.Fatalf("Should find saved record")
2020-05-31 13:51:43 +03:00
}
return errors.New("the error message")
})
if err != nil && err.Error() != "the error message" {
2020-05-31 18:55:56 +03:00
t.Fatalf("Transaction return error will equal the block returns error")
2020-05-31 13:51:43 +03:00
}
2020-06-08 18:25:16 +03:00
if err := DB.First(&User{}, "name = ?", "transaction-block").Error; err == nil {
2020-05-31 18:55:56 +03:00
t.Fatalf("Should not find record after rollback")
2020-05-31 13:51:43 +03:00
}
// commit
DB.Transaction(func(tx *gorm.DB) error {
2020-06-08 18:25:16 +03:00
user := *GetUser("transaction-block-2", Config{})
2020-05-31 13:51:43 +03:00
if err := tx.Save(&user).Error; err != nil {
2020-05-31 18:55:56 +03:00
t.Fatalf("No error should raise")
2020-05-31 13:51:43 +03:00
}
if err := tx.First(&User{}, "name = ?", user.Name).Error; err != nil {
2020-05-31 18:55:56 +03:00
t.Fatalf("Should find saved record")
2020-05-31 13:51:43 +03:00
}
return nil
})
2020-06-08 18:25:16 +03:00
if err := DB.First(&User{}, "name = ?", "transaction-block-2").Error; err != nil {
2020-05-31 18:55:56 +03:00
t.Fatalf("Should be able to find committed record")
2020-05-31 13:51:43 +03:00
}
// panic will rollback
assertPanic(func() {
DB.Transaction(func(tx *gorm.DB) error {
2020-06-08 18:25:16 +03:00
user := *GetUser("transaction-block-3", Config{})
2020-05-31 13:51:43 +03:00
if err := tx.Save(&user).Error; err != nil {
2020-05-31 18:55:56 +03:00
t.Fatalf("No error should raise")
2020-05-31 13:51:43 +03:00
}
if err := tx.First(&User{}, "name = ?", user.Name).Error; err != nil {
2020-05-31 18:55:56 +03:00
t.Fatalf("Should find saved record")
2020-05-31 13:51:43 +03:00
}
panic("force panic")
})
})
2020-06-08 18:25:16 +03:00
if err := DB.First(&User{}, "name = ?", "transaction-block-3").Error; err == nil {
2020-05-31 18:55:56 +03:00
t.Fatalf("Should not find record after panic rollback")
2020-05-31 13:51:43 +03:00
}
}
func TestTransactionRaiseErrorOnRollbackAfterCommit(t *testing.T) {
tx := DB.Begin()
2020-06-08 18:25:16 +03:00
user := User{Name: "transaction"}
2020-05-31 13:51:43 +03:00
if err := tx.Save(&user).Error; err != nil {
2020-05-31 18:55:56 +03:00
t.Fatalf("No error should raise")
2020-05-31 13:51:43 +03:00
}
if err := tx.Commit().Error; err != nil {
2020-05-31 18:55:56 +03:00
t.Fatalf("Commit should not raise error")
2020-05-31 13:51:43 +03:00
}
if err := tx.Rollback().Error; err == nil {
2020-05-31 18:55:56 +03:00
t.Fatalf("Rollback after commit should raise error")
2020-05-31 13:51:43 +03:00
}
}
func TestTransactionWithSavePoint(t *testing.T) {
tx := DB.Begin()
user := *GetUser("transaction-save-point", Config{})
tx.Create(&user)
if err := tx.First(&User{}, "name = ?", user.Name).Error; err != nil {
t.Fatalf("Should find saved record")
}
if err := tx.SavePoint("save_point1").Error; err != nil {
t.Fatalf("Failed to save point, got error %v", err)
}
user1 := *GetUser("transaction-save-point-1", Config{})
tx.Create(&user1)
if err := tx.First(&User{}, "name = ?", user1.Name).Error; err != nil {
t.Fatalf("Should find saved record")
}
if err := tx.RollbackTo("save_point1").Error; err != nil {
t.Fatalf("Failed to save point, got error %v", err)
}
if err := tx.First(&User{}, "name = ?", user1.Name).Error; err == nil {
t.Fatalf("Should not find rollbacked record")
}
if err := tx.SavePoint("save_point2").Error; err != nil {
t.Fatalf("Failed to save point, got error %v", err)
}
user2 := *GetUser("transaction-save-point-2", Config{})
tx.Create(&user2)
if err := tx.First(&User{}, "name = ?", user2.Name).Error; err != nil {
t.Fatalf("Should find saved record")
}
if err := tx.Commit().Error; err != nil {
t.Fatalf("Failed to commit, got error %v", err)
}
if err := DB.First(&User{}, "name = ?", user.Name).Error; err != nil {
t.Fatalf("Should find saved record")
}
if err := DB.First(&User{}, "name = ?", user1.Name).Error; err == nil {
t.Fatalf("Should not find rollbacked record")
}
if err := DB.First(&User{}, "name = ?", user2.Name).Error; err != nil {
t.Fatalf("Should find saved record")
}
}
func TestNestedTransactionWithBlock(t *testing.T) {
var (
user = *GetUser("transaction-nested", Config{})
user1 = *GetUser("transaction-nested-1", Config{})
user2 = *GetUser("transaction-nested-2", Config{})
)
if err := DB.Transaction(func(tx *gorm.DB) error {
tx.Create(&user)
if err := tx.First(&User{}, "name = ?", user.Name).Error; err != nil {
t.Fatalf("Should find saved record")
}
if err := tx.Transaction(func(tx1 *gorm.DB) error {
tx1.Create(&user1)
if err := tx1.First(&User{}, "name = ?", user1.Name).Error; err != nil {
t.Fatalf("Should find saved record")
}
return errors.New("rollback")
}); err == nil {
t.Fatalf("nested transaction should returns error")
}
if err := tx.First(&User{}, "name = ?", user1.Name).Error; err == nil {
t.Fatalf("Should not find rollbacked record")
}
if err := tx.Transaction(func(tx2 *gorm.DB) error {
tx2.Create(&user2)
if err := tx2.First(&User{}, "name = ?", user2.Name).Error; err != nil {
t.Fatalf("Should find saved record")
}
return nil
}); err != nil {
t.Fatalf("nested transaction returns error: %v", err)
}
if err := tx.First(&User{}, "name = ?", user2.Name).Error; err != nil {
t.Fatalf("Should find saved record")
}
return nil
}); err != nil {
t.Fatalf("no error should return, but got %v", err)
}
if err := DB.First(&User{}, "name = ?", user.Name).Error; err != nil {
t.Fatalf("Should find saved record")
}
if err := DB.First(&User{}, "name = ?", user1.Name).Error; err == nil {
t.Fatalf("Should not find rollbacked record")
}
if err := DB.First(&User{}, "name = ?", user2.Name).Error; err != nil {
t.Fatalf("Should find saved record")
}
}
func TestDeeplyNestedTransactionWithBlockAndWrappedCallback(t *testing.T) {
transaction := func(ctx context.Context, db *gorm.DB, callback func(ctx context.Context, db *gorm.DB) error) error {
return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
return callback(ctx, tx)
})
}
var (
user = *GetUser("transaction-nested", Config{})
user1 = *GetUser("transaction-nested-1", Config{})
user2 = *GetUser("transaction-nested-2", Config{})
)
if err := transaction(context.Background(), DB, func(ctx context.Context, tx *gorm.DB) error {
tx.Create(&user)
if err := tx.First(&User{}, "name = ?", user.Name).Error; err != nil {
t.Fatalf("Should find saved record")
}
if err := transaction(ctx, tx, func(ctx context.Context, tx1 *gorm.DB) error {
tx1.Create(&user1)
if err := tx1.First(&User{}, "name = ?", user1.Name).Error; err != nil {
t.Fatalf("Should find saved record")
}
if err := transaction(ctx, tx1, func(ctx context.Context, tx2 *gorm.DB) error {
tx2.Create(&user2)
if err := tx2.First(&User{}, "name = ?", user2.Name).Error; err != nil {
t.Fatalf("Should find saved record")
}
return errors.New("inner rollback")
}); err == nil {
t.Fatalf("nested transaction has no error")
}
return errors.New("rollback")
}); err == nil {
t.Fatalf("nested transaction should returns error")
}
if err := tx.First(&User{}, "name = ?", user1.Name).Error; err == nil {
t.Fatalf("Should not find rollbacked record")
}
if err := tx.First(&User{}, "name = ?", user2.Name).Error; err != nil {
t.Fatalf("Should find saved record")
}
return nil
}); err != nil {
t.Fatalf("no error should return, but got %v", err)
}
if err := DB.First(&User{}, "name = ?", user.Name).Error; err != nil {
t.Fatalf("Should find saved record")
}
if err := DB.First(&User{}, "name = ?", user1.Name).Error; err == nil {
t.Fatalf("Should not find rollbacked parent record")
}
if err := DB.First(&User{}, "name = ?", user2.Name).Error; err != nil {
t.Fatalf("Should not find rollbacked nested record")
}
}
2020-12-16 14:33:35 +03:00
func TestDisabledNestedTransaction(t *testing.T) {
var (
user = *GetUser("transaction-nested", Config{})
user1 = *GetUser("transaction-nested-1", Config{})
user2 = *GetUser("transaction-nested-2", Config{})
)
if err := DB.Session(&gorm.Session{DisableNestedTransaction: true}).Transaction(func(tx *gorm.DB) error {
tx.Create(&user)
if err := tx.First(&User{}, "name = ?", user.Name).Error; err != nil {
t.Fatalf("Should find saved record")
}
if err := tx.Transaction(func(tx1 *gorm.DB) error {
tx1.Create(&user1)
if err := tx1.First(&User{}, "name = ?", user1.Name).Error; err != nil {
t.Fatalf("Should find saved record")
}
return errors.New("rollback")
}); err == nil {
t.Fatalf("nested transaction should returns error")
}
if err := tx.First(&User{}, "name = ?", user1.Name).Error; err != nil {
t.Fatalf("Should not rollback record if disabled nested transaction support")
}
if err := tx.Transaction(func(tx2 *gorm.DB) error {
tx2.Create(&user2)
if err := tx2.First(&User{}, "name = ?", user2.Name).Error; err != nil {
t.Fatalf("Should find saved record")
}
return nil
}); err != nil {
t.Fatalf("nested transaction returns error: %v", err)
}
if err := tx.First(&User{}, "name = ?", user2.Name).Error; err != nil {
t.Fatalf("Should find saved record")
}
return nil
}); err != nil {
t.Fatalf("no error should return, but got %v", err)
}
if err := DB.First(&User{}, "name = ?", user.Name).Error; err != nil {
t.Fatalf("Should find saved record")
}
if err := DB.First(&User{}, "name = ?", user1.Name).Error; err != nil {
t.Fatalf("Should not rollback record if disabled nested transaction support")
}
if err := DB.First(&User{}, "name = ?", user2.Name).Error; err != nil {
t.Fatalf("Should find saved record")
}
}
func TestTransactionOnClosedConn(t *testing.T) {
DB, err := OpenTestConnection(&gorm.Config{})
if err != nil {
t.Fatalf("failed to connect database, got error %v", err)
}
rawDB, _ := DB.DB()
rawDB.Close()
if err := DB.Transaction(func(tx *gorm.DB) error {
return nil
}); err == nil {
t.Errorf("should returns error when commit with closed conn, got error %v", err)
}
if err := DB.Session(&gorm.Session{PrepareStmt: true}).Transaction(func(tx *gorm.DB) error {
return nil
}); err == nil {
t.Errorf("should returns error when commit with closed conn, got error %v", err)
}
}
func TestTransactionWithHooks(t *testing.T) {
user := GetUser("tTestTransactionWithHooks", Config{Account: true})
DB.Create(&user)
var err error
err = DB.Transaction(func(tx *gorm.DB) error {
return tx.Model(&User{}).Limit(1).Transaction(func(tx2 *gorm.DB) error {
return tx2.Scan(&User{}).Error
})
})
if err != nil {
t.Error(err)
}
// method with hooks
err = DB.Transaction(func(tx1 *gorm.DB) error {
// callMethod do
tx2 := tx1.Find(&User{}).Session(&gorm.Session{NewDB: true})
// trx in hooks
return tx2.Transaction(func(tx3 *gorm.DB) error {
return tx3.Where("user_id", user.ID).Delete(&Account{}).Error
})
})
if err != nil {
t.Error(err)
}
}