Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested transaction bug using a wrapper function #757

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions db.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"context"
"log"
"math/rand"
"os"
Expand Down Expand Up @@ -106,3 +107,11 @@ func RunMigrations() {
}
}
}

func Transaction(ctx context.Context, db *gorm.DB, apply func(ctx context.Context, db *gorm.DB) error) error {
return db.
WithContext(ctx).
Transaction(func(db *gorm.DB) error {
return apply(ctx, db)
})
}
57 changes: 52 additions & 5 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,67 @@
package main

import (
"context"
"fmt"
"testing"

"gorm.io/gorm"
)

// GORM_REPO: https://github.com/go-gorm/gorm.git
// GORM_BRANCH: master
// TEST_DRIVERS: sqlite, mysql, postgres, sqlserver

func TestGORM(t *testing.T) {
user := User{Name: "jinzhu"}
var user User
err := DB.First(&user, "name = ?", "jinzhu").Error
// ensure we start with no user
if err == nil {
t.Errorf("User %d already exists", user.ID)
}

// Create a wrapping transaction
err = Transaction(context.Background(), DB, func(ctx context.Context, tx *gorm.DB) error {
// Within this, create a transaction that retrieves a user, and then
// creates another transaction that retrieves the account, but errors,
// and bubble that error up.
err := Transaction(ctx, tx, func(ctx context.Context, tx *gorm.DB) error {
user := User{Name: "jinzhu"}
var account Account
err := tx.Create(&user).Error
if err != nil {
return err
}
fmt.Printf("User created: %d", user.ID)

// Since we propagate the error, we'll now also try to
// rollback this nested transaction, using the same
// savepoint ID, since the `Transaction` helper
// creates a single closure that always has the
// same address.
return Transaction(ctx, tx, func(ctx context.Context, tx *gorm.DB) error {
// We haven't created an account, so we return an error, which does
// a rollback of the inner transaction using a savepoint.
err := tx.First(&account, 1).Error
if err != nil {
return err
}

DB.Create(&user)
return nil
})
})
if err == nil {
t.Errorf("Expected an error, got none")
}
// We discard the inner transaction error, which allows us to commit this outer
// transaction (which does nothing), even if the inner fails.
return nil
})

var result User
if err := DB.First(&result, user.ID).Error; err != nil {
t.Errorf("Failed, got error: %v", err)
// Since we have rolled back the inner transaction, we expect that
// no user was created, since we should have rolled that back.
err = DB.First(&user, "name = ?", "jinzhu").Error
if err == nil {
t.Errorf("User %d was created, despite erroring inside the transaction", user.ID)
}
}
Loading