2020-05-31 13:51:43 +03:00
|
|
|
package tests_test
|
|
|
|
|
|
|
|
import (
|
2020-08-17 07:16:42 +03:00
|
|
|
"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 {
|
2020-06-19 07:38:03 +03:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2021-02-26 11:43: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
|
|
|
}
|
2023-05-26 05:24:28 +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 transcation coexist" + err.Error())
|
|
|
|
}
|
|
|
|
})
|
2020-05-31 13:51:43 +03:00
|
|
|
}
|
|
|
|
|
2020-08-17 07:16:42 +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")
|
|
|
|
})
|
|
|
|
|
2022-09-30 06:14:34 +03:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
2020-06-19 13:30:04 +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")
|
|
|
|
}
|
|
|
|
}
|
2020-09-03 14:16:55 +03:00
|
|
|
|
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")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-03 14:16:55 +03:00
|
|
|
func TestTransactionOnClosedConn(t *testing.T) {
|
|
|
|
DB, err := OpenTestConnection()
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
2022-05-17 09:13:41 +03:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|