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

BeforeSave is executed twice in transaction #6285

Closed
ras0q opened this issue May 1, 2023 · 2 comments · Fixed by #6294
Closed

BeforeSave is executed twice in transaction #6285

ras0q opened this issue May 1, 2023 · 2 comments · Fixed by #6294
Assignees
Labels

Comments

@ras0q
Copy link

ras0q commented May 1, 2023

GORM Playground Link

go-gorm/playground#593

Description

When you save a model which BeforeSave is implemented in a transaction, BeforeSave will be executed twice.

package main_test

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

func Test_main(t *testing.T) {
	dsn := "root:pass@tcp(localhost:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn))
	assert.NoError(t, err)
	assert.NoError(t, db.AutoMigrate(&User{}, &Token{}))

	u := User{
		Name: "user",
		Token: Token{
			Content: "token",
		},
	}
	u1, err := saveUser(db, &u)
	assert.NoError(t, err)
	assert.Equal(t, "token_encrypted", u1.Token.Content)

	u = User{
		ID:   u.ID,
		Name: "user",
		Token: Token{
			Content: "token2",
		},
	}
	u2, err := saveUser(db, &u)
	assert.NoError(t, err)
	assert.Equal(t, "token2_encrypted", u2.Token.Content) // FAIL: actual is token2_encrypted_encrypted
}

func saveUser(db *gorm.DB, u *User) (*User, error) {
	var newUser User
	if err := db.Transaction(func(tx *gorm.DB) error {
		if err := tx.Debug().Session(&gorm.Session{FullSaveAssociations: true}).Save(u).Error; err != nil {
			return err
		}

		if err := tx.Preload("Token").First(&newUser, u.ID).Error; err != nil {
			return err
		}

		return nil
	}); err != nil {
		return nil, err
	}

	return &newUser, nil
}

// - Models

type User struct {
	ID    int    `gorm:"primary_key"`
	Name  string `gorm:"type:varchar(100)"`
	Token Token  `gorm:"foreignKey:UserID"`
}

type Token struct {
	UserID  int    `gorm:"primary_key"`
	Content string `gorm:"type:varchar(100)"`
}

// This method should be called only once
func (t *Token) BeforeSave(tx *gorm.DB) error {
	t.Content += "_encrypted"
	return nil
}
$ go test main_test.go

2023/05/01 11:00:35 /home/ras/misc/go/gorm/main_test.go:42
[2.085ms] [rows:1] INSERT INTO `tokens` (`content`,`user_id`) VALUES ('token_encrypted',7) ON DUPLICATE KEY UPDATE `content`=VALUES(`content`)

2023/05/01 11:00:35 /home/ras/misc/go/gorm/main_test.go:42
[5.084ms] [rows:1] INSERT INTO `users` (`name`) VALUES ('user')

2023/05/01 11:00:35 /home/ras/misc/go/gorm/main_test.go:42
[2.312ms] [rows:2] INSERT INTO `tokens` (`content`,`user_id`) VALUES ('token2_encrypted',7) ON DUPLICATE KEY UPDATE `content`=VALUES(`content`)

2023/05/01 11:00:35 /home/ras/misc/go/gorm/main_test.go:42
[4.827ms] [rows:0] UPDATE `users` SET `name`='user' WHERE `id` = 7

2023/05/01 11:00:35 /home/ras/misc/go/gorm/main_test.go:42
[2.382ms] [rows:2] INSERT INTO `tokens` (`content`,`user_id`) VALUES ('token2_encrypted_encrypted',7) ON DUPLICATE KEY UPDATE `content`=VALUES(`content`)

2023/05/01 11:00:35 /home/ras/misc/go/gorm/main_test.go:42
[5.107ms] [rows:0] INSERT INTO `users` (`name`,`id`) VALUES ('user',7) ON DUPLICATE KEY UPDATE `name`=VALUES(`name`)
--- FAIL: Test_main (0.12s)
    main_test.go:36: 
                Error Trace:    /home/ras/misc/go/gorm/main_test.go:36
                Error:          Not equal: 
                                expected: "token2_encrypted"
                                actual  : "token2_encrypted_encrypted"
                            
                                Diff:
                                --- Expected
                                +++ Actual
                                @@ -1 +1 @@
                                -token2_encrypted
                                +token2_encrypted_encrypted
                Test:           Test_main
FAIL
FAIL    command-line-arguments  0.125s
FAIL
@github-actions github-actions bot added the type:missing reproduction steps missing reproduction steps label May 1, 2023
@github-actions
Copy link

github-actions bot commented May 1, 2023

The issue has been automatically marked as stale as it missing playground pull request link, which is important to help others understand your issue effectively and make sure the issue hasn't been fixed on latest master, checkout https://github.com/go-gorm/playground for details. it will be closed in 30 days if no further activity occurs. if you are asking question, please use the Question template, most likely your question already answered https://github.com/go-gorm/gorm/issues or described in the document https://gorm.ioSearch Before Asking

@github-actions github-actions bot added status:stale type:with reproduction steps with reproduction steps and removed type:missing reproduction steps missing reproduction steps labels May 1, 2023
black-06 added a commit to black-06/gorm that referenced this issue May 5, 2023
@GuridMa
Copy link

GuridMa commented May 24, 2023

Have you solved this? I also encountered the same problem

jinzhu pushed a commit that referenced this issue May 26, 2023
alidevhere pushed a commit to alidevhere/gorm that referenced this issue May 30, 2023
jinzhu pushed a commit that referenced this issue May 30, 2023
* max identifier length changed to 63

* default maxIdentifierLength is 64

* renamed License to LICENSE (#6336)

* Added support of "Violates Foreign Key Constraint" (#6329)

* Added support of "Violates Foreign Key Constraint"

Updated the translator and added the support of "foreign key constraint violation". For this, this error type is needed here.

* changed the description of ErrForeignKeyViolated

* refactor: error translator test (#6350)

Co-authored-by: Saeid Saeidee <s.saeidee@sensysgatso.com>

* fix(nested transaction): SavePoint SQL Statement not support in Prepared Statements (#6220)

* test: add nested transaction and prepareStmt coexist test case

note: please test in the MySQL environment

Change-Id: I0db32adc5f74b0d443e98943d3b182236583b959
Signed-off-by: 王柳洋 <wangliuyang.520@bytedance.com>

* fix(nested transaction): SavePoint SQL Statement not support in Prepared Statements

1. SavetPoint SQL Statement not support in Prepared Statements
 e.g. see mysql8.0 doc: https://dev.mysql.com/doc/refman/8.0/en/sql-prepared-statements.html

Change-Id: I082012db9b140e8ec69764c633724665cc802692
Signed-off-by: 王柳洋 <wangliuyang.520@bytedance.com>

* revert(transaction_api): remove savepoint name pool,meaningless

Change-Id: I84aa9924fc54612005a81c83d66fdf8968ee56ad
Signed-off-by: 王柳洋 <wangliuyang.520@bytedance.com>

---------

Signed-off-by: 王柳洋 <wangliuyang.520@bytedance.com>
Co-authored-by: 王柳洋 <wangliuyang.520@bytedance.com>

* fix: save with hook (#6285) (#6294)

---------

Signed-off-by: 王柳洋 <wangliuyang.520@bytedance.com>
Co-authored-by: Avinaba Bhattacharjee <avinababhattacharjee2002@gmail.com>
Co-authored-by: Muhammad Amir Ejaz <37077032+codingamir@users.noreply.github.com>
Co-authored-by: Saeid <sk.saeidee@yahoo.com>
Co-authored-by: Saeid Saeidee <s.saeidee@sensysgatso.com>
Co-authored-by: wangliuyang <54885906+wangliuyang520@users.noreply.github.com>
Co-authored-by: 王柳洋 <wangliuyang.520@bytedance.com>
Co-authored-by: black-06 <hello.bug@foxmail.com>
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 a pull request may close this issue.

3 participants