Skip to content

feat: support isolated nested transactions#271

Merged
hsluoyz merged 2 commits intoapache:masterfrom
yxrxy:master
Jul 28, 2025
Merged

feat: support isolated nested transactions#271
hsluoyz merged 2 commits intoapache:masterfrom
yxrxy:master

Conversation

@yxrxy
Copy link
Contributor

@yxrxy yxrxy commented Jul 21, 2025

Fix issue in PR: #269

savepointName := fmt.Sprintf("casbin_nested_%d", time.Now().UnixNano())

// create savepoint
if err := adapter.db.SavePoint(savepointName).Error; err != nil {
Copy link
Contributor

@ankitrgadiya ankitrgadiya Jul 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: The gorm's Transaction function already does this. Consider just removing the isTxAdapter check.

Also some bulk methods like RemovePoliciesCtx are using the gorm's Transaction directly. This can lead to some operations happening in a separate transaction loosing the ability to rollback in case of further errors. Consider updating those to use this Transaction method as well.

cc: @YaswanthKumar-eng

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe Need Help: Bulk methods such as RemovePoliciesCtx and UpdatePoliciesCtx require the enforcer in order to use Adapter.Transaction. But existing code does not provide it in the context. How can I switch those bulk methods to use Adapter.Transaction?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: The gorm's Transaction function already does this. Consider just removing the isTxAdapter check.

Also some bulk methods like RemovePoliciesCtx are using the gorm's Transaction directly. This can lead to some operations happening in a separate transaction loosing the ability to rollback in case of further errors. Consider updating those to use this Transaction method as well.

cc: @YaswanthKumar-eng

If we remove the isTxAdapter check and only use GORM’s Transaction, it can cause deadlocks in concurrent scenarios(some tests will fail). The lock is necessary to prevent race conditions on the adapter’s state. i think this logic is needed for thread safety.

@hsluoyz
Copy link
Member

hsluoyz commented Jul 23, 2025

@tangyang9464 plz review

@hsluoyz hsluoyz requested review from Copilot and tangyang9464 July 23, 2025 12:28
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements support for isolated nested transactions in the gorm-adapter, addressing an issue identified in PR #269. The implementation uses database savepoints to handle nested transaction scenarios with proper rollback capabilities.

  • Adds savepoint-based nested transaction support to allow inner transactions to fail without affecting outer transactions
  • Enhances test coverage with comprehensive nested transaction scenarios including success/failure combinations and deeply nested cases
  • Standardizes test database setup by switching from in-memory SQLite to MySQL using the common initAdapter pattern

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
adapter.go Implements savepoint-based nested transaction logic with model state preservation
adapter_test.go Adds comprehensive nested transaction test cases and standardizes database setup
Comments suppressed due to low confidence (1)

adapter_test.go:847

  • The test expects the outer transaction to continue after inner transaction failure, but the code returns the error immediately, preventing the outer transaction from continuing. This doesn't match the test's intent or the comment on line 850.
				return err

}
// restore model state to undo inner transaction changes
e.SetModel(originalModel)
fmt.Printf("Warning: Inner transaction failed and was rolled back: %v\n", err)
Copy link

Copilot AI Jul 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using fmt.Printf for error logging is not ideal for a library. Consider using a proper logging framework or removing this debug output for production code.

Suggested change
fmt.Printf("Warning: Inner transaction failed and was rolled back: %v\n", err)
adapter.db.Logger.Warn(context.Background(), "Inner transaction failed and was rolled back: %v", err)

Copilot uses AI. Check for mistakes.
Comment on lines +736 to +737
// Don't return error to allow outer transaction to continue
return nil
Copy link

Copilot AI Jul 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silently swallowing the inner transaction error by returning nil changes the expected behavior. Consider returning the error or providing a way for callers to handle nested transaction failures explicitly.

Suggested change
// Don't return error to allow outer transaction to continue
return nil
// Return the error to allow callers to handle the inner transaction failure explicitly
return errors.Wrap(err, "inner transaction failed and was rolled back")

Copilot uses AI. Check for mistakes.
Comment on lines +922 to +940
_, err := e.AddPolicy("frank", "data13", "read")
if err != nil {
return err
}

// level 2
err = adapter.Transaction(e, func(e2 casbin.IEnforcer) error {
_, err := e2.AddPolicy("frank", "data14", "write")
if err != nil {
return err
}

// level 3
return adapter.Transaction(e2, func(e3 casbin.IEnforcer) error {
_, err := e3.AddPolicy("frank", "data15", "delete")
if err != nil {
return err
}
_, err = e3.AddPolicy("frank", "data16", "execute")
Copy link

Copilot AI Jul 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The policy data 'data13' conflicts with the policy created in test 5 (line 896). This could cause test interdependencies if tests run in different orders.

Suggested change
_, err := e.AddPolicy("frank", "data13", "read")
if err != nil {
return err
}
// level 2
err = adapter.Transaction(e, func(e2 casbin.IEnforcer) error {
_, err := e2.AddPolicy("frank", "data14", "write")
if err != nil {
return err
}
// level 3
return adapter.Transaction(e2, func(e3 casbin.IEnforcer) error {
_, err := e3.AddPolicy("frank", "data15", "delete")
if err != nil {
return err
}
_, err = e3.AddPolicy("frank", "data16", "execute")
_, err := e.AddPolicy("frank", "data17", "read")
if err != nil {
return err
}
// level 2
err = adapter.Transaction(e, func(e2 casbin.IEnforcer) error {
_, err := e2.AddPolicy("frank", "data18", "write")
if err != nil {
return err
}
// level 3
return adapter.Transaction(e2, func(e3 casbin.IEnforcer) error {
_, err := e3.AddPolicy("frank", "data19", "delete")
if err != nil {
return err
}
_, err = e3.AddPolicy("frank", "data20", "execute")

Copilot uses AI. Check for mistakes.
@hsluoyz hsluoyz merged commit 8dd25ef into apache:master Jul 28, 2025
6 checks passed
@github-actions
Copy link

🎉 This PR is included in version 3.35.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants