From 563d9b588ac212e2795248125ba87af97aab10b3 Mon Sep 17 00:00:00 2001 From: mohammad ali ashraf Date: Sun, 21 May 2023 01:46:27 +0500 Subject: [PATCH 1/7] max identifier length changed to 63 --- gorm.go | 2 +- schema/naming.go | 17 +++++++++++------ schema/naming_test.go | 6 +++--- schema/relationship_test.go | 4 ++-- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/gorm.go b/gorm.go index 07a913fc2..32c7cef40 100644 --- a/gorm.go +++ b/gorm.go @@ -146,7 +146,7 @@ func Open(dialector Dialector, opts ...Option) (db *DB, err error) { } if config.NamingStrategy == nil { - config.NamingStrategy = schema.NamingStrategy{} + config.NamingStrategy = schema.NamingStrategy{IdentifierMaxLength: 63} } if config.Logger == nil { diff --git a/schema/naming.go b/schema/naming.go index a258beed3..37b949802 100644 --- a/schema/naming.go +++ b/schema/naming.go @@ -28,10 +28,11 @@ type Replacer interface { // NamingStrategy tables, columns naming strategy type NamingStrategy struct { - TablePrefix string - SingularTable bool - NameReplacer Replacer - NoLowerCase bool + TablePrefix string + SingularTable bool + NameReplacer Replacer + NoLowerCase bool + IdentifierMaxLength int } // TableName convert string to table name @@ -89,12 +90,16 @@ func (ns NamingStrategy) formatName(prefix, table, name string) string { prefix, table, name, }, "_"), ".", "_") - if utf8.RuneCountInString(formattedName) > 64 { + if ns.IdentifierMaxLength == 0 { + ns.IdentifierMaxLength = 63 + } + + if utf8.RuneCountInString(formattedName) > ns.IdentifierMaxLength { h := sha1.New() h.Write([]byte(formattedName)) bs := h.Sum(nil) - formattedName = formattedName[0:56] + hex.EncodeToString(bs)[:8] + formattedName = formattedName[0:ns.IdentifierMaxLength-8] + hex.EncodeToString(bs)[:8] } return formattedName } diff --git a/schema/naming_test.go b/schema/naming_test.go index 3f598c33e..9ce8f0994 100644 --- a/schema/naming_test.go +++ b/schema/naming_test.go @@ -189,11 +189,11 @@ func TestCustomReplacerWithNoLowerCase(t *testing.T) { } } -func TestFormatNameWithStringLongerThan64Characters(t *testing.T) { - ns := NamingStrategy{} +func TestFormatNameWithStringLongerThan63Characters(t *testing.T) { + ns := NamingStrategy{IdentifierMaxLength: 63} formattedName := ns.formatName("prefix", "table", "thisIsAVeryVeryVeryVeryVeryVeryVeryVeryVeryLongString") - if formattedName != "prefix_table_thisIsAVeryVeryVeryVeryVeryVeryVeryVeryVery180f2c67" { + if formattedName != "prefix_table_thisIsAVeryVeryVeryVeryVeryVeryVeryVeryVer180f2c67" { t.Errorf("invalid formatted name generated, got %v", formattedName) } } diff --git a/schema/relationship_test.go b/schema/relationship_test.go index 732f6f75f..de5fa0441 100644 --- a/schema/relationship_test.go +++ b/schema/relationship_test.go @@ -768,13 +768,13 @@ func TestParseConstraintNameWithSchemaQualifiedLongTableName(t *testing.T) { s, err := schema.Parse( &Book{}, &sync.Map{}, - schema.NamingStrategy{}, + schema.NamingStrategy{IdentifierMaxLength: 63}, ) if err != nil { t.Fatalf("Failed to parse schema") } - expectedConstraintName := "fk_my_schema_a_very_very_very_very_very_very_very_very_l4db13eec" + expectedConstraintName := "fk_my_schema_a_very_very_very_very_very_very_very_very_4db13eec" constraint := s.Relationships.Relations["Author"].ParseConstraint() if constraint.Name != expectedConstraintName { From b8d39da106b17408b8fd7f1a4f7861a72aace069 Mon Sep 17 00:00:00 2001 From: mohammad ali ashraf Date: Tue, 30 May 2023 06:34:29 +0500 Subject: [PATCH 2/7] default maxIdentifierLength is 64 --- gorm.go | 2 +- schema/naming.go | 2 +- schema/naming_test.go | 9 +++++++++ schema/relationship_test.go | 4 ++-- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/gorm.go b/gorm.go index 32c7cef40..84d4b4333 100644 --- a/gorm.go +++ b/gorm.go @@ -146,7 +146,7 @@ func Open(dialector Dialector, opts ...Option) (db *DB, err error) { } if config.NamingStrategy == nil { - config.NamingStrategy = schema.NamingStrategy{IdentifierMaxLength: 63} + config.NamingStrategy = schema.NamingStrategy{IdentifierMaxLength: 64} // Default Identifier length is 64 } if config.Logger == nil { diff --git a/schema/naming.go b/schema/naming.go index 37b949802..a2a0150a3 100644 --- a/schema/naming.go +++ b/schema/naming.go @@ -91,7 +91,7 @@ func (ns NamingStrategy) formatName(prefix, table, name string) string { }, "_"), ".", "_") if ns.IdentifierMaxLength == 0 { - ns.IdentifierMaxLength = 63 + ns.IdentifierMaxLength = 64 } if utf8.RuneCountInString(formattedName) > ns.IdentifierMaxLength { diff --git a/schema/naming_test.go b/schema/naming_test.go index 9ce8f0994..ab7a5e317 100644 --- a/schema/naming_test.go +++ b/schema/naming_test.go @@ -198,6 +198,15 @@ func TestFormatNameWithStringLongerThan63Characters(t *testing.T) { } } +func TestFormatNameWithStringLongerThan64Characters(t *testing.T) { + ns := NamingStrategy{IdentifierMaxLength: 64} + + formattedName := ns.formatName("prefix", "table", "thisIsAVeryVeryVeryVeryVeryVeryVeryVeryVeryLongString") + if formattedName != "prefix_table_thisIsAVeryVeryVeryVeryVeryVeryVeryVeryVery180f2c67" { + t.Errorf("invalid formatted name generated, got %v", formattedName) + } +} + func TestReplaceEmptyTableName(t *testing.T) { ns := NamingStrategy{ SingularTable: true, diff --git a/schema/relationship_test.go b/schema/relationship_test.go index de5fa0441..1eb66bb4c 100644 --- a/schema/relationship_test.go +++ b/schema/relationship_test.go @@ -768,13 +768,13 @@ func TestParseConstraintNameWithSchemaQualifiedLongTableName(t *testing.T) { s, err := schema.Parse( &Book{}, &sync.Map{}, - schema.NamingStrategy{IdentifierMaxLength: 63}, + schema.NamingStrategy{IdentifierMaxLength: 64}, ) if err != nil { t.Fatalf("Failed to parse schema") } - expectedConstraintName := "fk_my_schema_a_very_very_very_very_very_very_very_very_4db13eec" + expectedConstraintName := "fk_my_schema_a_very_very_very_very_very_very_very_very_l4db13eec" constraint := s.Relationships.Relations["Author"].ParseConstraint() if constraint.Name != expectedConstraintName { From 28b3de032facf153c2c3786d0033f757e282f614 Mon Sep 17 00:00:00 2001 From: Avinaba Bhattacharjee Date: Sun, 21 May 2023 18:54:00 +0530 Subject: [PATCH 3/7] renamed License to LICENSE (#6336) --- License => LICENSE | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename License => LICENSE (100%) diff --git a/License b/LICENSE similarity index 100% rename from License rename to LICENSE From 2733c1c6753112054f4892fdfcd0268d5b1154e4 Mon Sep 17 00:00:00 2001 From: Muhammad Amir Ejaz <37077032+codingamir@users.noreply.github.com> Date: Sun, 21 May 2023 18:27:22 +0500 Subject: [PATCH 4/7] 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 --- errors.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/errors.go b/errors.go index 57e3fc5eb..cd76f1f52 100644 --- a/errors.go +++ b/errors.go @@ -47,4 +47,6 @@ var ( ErrPreloadNotAllowed = errors.New("preload is not allowed when count is used") // ErrDuplicatedKey occurs when there is a unique key constraint violation ErrDuplicatedKey = errors.New("duplicated key not allowed") + // ErrForeignKeyViolated occurs when there is a foreign key constraint violation + ErrForeignKeyViolated = errors.New("violates foreign key constraint") ) From 42d6d46416efac76c3ab4567db84b6d8cbb2b0be Mon Sep 17 00:00:00 2001 From: Saeid Date: Thu, 25 May 2023 05:10:00 +0200 Subject: [PATCH 5/7] refactor: error translator test (#6350) Co-authored-by: Saeid Saeidee --- tests/error_translator_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/error_translator_test.go b/tests/error_translator_test.go index ead26fce8..ca985a096 100644 --- a/tests/error_translator_test.go +++ b/tests/error_translator_test.go @@ -15,8 +15,8 @@ func TestDialectorWithErrorTranslatorSupport(t *testing.T) { db, _ := gorm.Open(tests.DummyDialector{TranslatedErr: translatedErr}) err := db.AddError(untranslatedErr) - if errors.Is(err, translatedErr) { - t.Fatalf("expected err: %v got err: %v", translatedErr, err) + if !errors.Is(err, untranslatedErr) { + t.Fatalf("expected err: %v got err: %v", untranslatedErr, err) } // it should translate error when the TranslateError flag is true From ccceee14e8cb06bdbea35700fa35c66607c154e2 Mon Sep 17 00:00:00 2001 From: wangliuyang <54885906+wangliuyang520@users.noreply.github.com> Date: Fri, 26 May 2023 10:24:28 +0800 Subject: [PATCH 6/7] fix(nested transaction): SavePoint SQL Statement not support in Prepared Statements (#6220) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: add nested transaction and prepareStmt coexist test case note: please test in the MySQL environment Change-Id: I0db32adc5f74b0d443e98943d3b182236583b959 Signed-off-by: 王柳洋 * 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: 王柳洋 * revert(transaction_api): remove savepoint name pool,meaningless Change-Id: I84aa9924fc54612005a81c83d66fdf8968ee56ad Signed-off-by: 王柳洋 --------- Signed-off-by: 王柳洋 Co-authored-by: 王柳洋 --- finisher_api.go | 46 +++++++++++++++++++++++++-------------- tests/transaction_test.go | 13 +++++++++++ 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/finisher_api.go b/finisher_api.go index ad14e2986..6d0b4cd21 100644 --- a/finisher_api.go +++ b/finisher_api.go @@ -6,8 +6,6 @@ import ( "fmt" "reflect" "strings" - "sync" - "sync/atomic" "gorm.io/gorm/clause" "gorm.io/gorm/logger" @@ -612,15 +610,6 @@ func (db *DB) Connection(fc func(tx *DB) error) (err error) { return fc(tx) } -var ( - savepointIdx int64 - savepointNamePool = &sync.Pool{ - New: func() interface{} { - return fmt.Sprintf("gorm_%d", atomic.AddInt64(&savepointIdx, 1)) - }, - } -) - // Transaction start a transaction as a block, return error will rollback, otherwise to commit. Transaction executes an // arbitrary number of commands in fc within a transaction. On success the changes are committed; if an error occurs // they are rolled back. @@ -630,17 +619,14 @@ func (db *DB) Transaction(fc func(tx *DB) error, opts ...*sql.TxOptions) (err er if committer, ok := db.Statement.ConnPool.(TxCommitter); ok && committer != nil { // nested transaction if !db.DisableNestedTransaction { - poolName := savepointNamePool.Get() - defer savepointNamePool.Put(poolName) - err = db.SavePoint(poolName.(string)).Error + err = db.SavePoint(fmt.Sprintf("sp%p", fc)).Error if err != nil { return } - defer func() { // Make sure to rollback when panic, Block error or Commit error if panicked || err != nil { - db.RollbackTo(poolName.(string)) + db.RollbackTo(fmt.Sprintf("sp%p", fc)) } }() } @@ -721,7 +707,21 @@ func (db *DB) Rollback() *DB { func (db *DB) SavePoint(name string) *DB { if savePointer, ok := db.Dialector.(SavePointerDialectorInterface); ok { + // close prepared statement, because SavePoint not support prepared statement. + // e.g. mysql8.0 doc: https://dev.mysql.com/doc/refman/8.0/en/sql-prepared-statements.html + var ( + preparedStmtTx *PreparedStmtTX + isPreparedStmtTx bool + ) + // close prepared statement, because SavePoint not support prepared statement. + if preparedStmtTx, isPreparedStmtTx = db.Statement.ConnPool.(*PreparedStmtTX); isPreparedStmtTx { + db.Statement.ConnPool = preparedStmtTx.Tx + } db.AddError(savePointer.SavePoint(db, name)) + // restore prepared statement + if isPreparedStmtTx { + db.Statement.ConnPool = preparedStmtTx + } } else { db.AddError(ErrUnsupportedDriver) } @@ -730,7 +730,21 @@ func (db *DB) SavePoint(name string) *DB { func (db *DB) RollbackTo(name string) *DB { if savePointer, ok := db.Dialector.(SavePointerDialectorInterface); ok { + // close prepared statement, because RollbackTo not support prepared statement. + // e.g. mysql8.0 doc: https://dev.mysql.com/doc/refman/8.0/en/sql-prepared-statements.html + var ( + preparedStmtTx *PreparedStmtTX + isPreparedStmtTx bool + ) + // close prepared statement, because SavePoint not support prepared statement. + if preparedStmtTx, isPreparedStmtTx = db.Statement.ConnPool.(*PreparedStmtTX); isPreparedStmtTx { + db.Statement.ConnPool = preparedStmtTx.Tx + } db.AddError(savePointer.RollbackTo(db, name)) + // restore prepared statement + if isPreparedStmtTx { + db.Statement.ConnPool = preparedStmtTx + } } else { db.AddError(ErrUnsupportedDriver) } diff --git a/tests/transaction_test.go b/tests/transaction_test.go index 5872da94a..bfbd86997 100644 --- a/tests/transaction_test.go +++ b/tests/transaction_test.go @@ -57,6 +57,19 @@ func TestTransaction(t *testing.T) { if err := DB.First(&User{}, "name = ?", "transaction-2").Error; err != nil { t.Fatalf("Should be able to find committed record, but got %v", err) } + + 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()) + } + }) } func TestCancelTransaction(t *testing.T) { From 8eb7a5260944fcdf07ca470519b98cf282819398 Mon Sep 17 00:00:00 2001 From: black-06 Date: Fri, 26 May 2023 10:28:02 +0800 Subject: [PATCH 7/7] fix: save with hook (#6285) (#6294) --- finisher_api.go | 2 +- tests/update_test.go | 73 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/finisher_api.go b/finisher_api.go index 6d0b4cd21..f80aa6c04 100644 --- a/finisher_api.go +++ b/finisher_api.go @@ -105,7 +105,7 @@ func (db *DB) Save(value interface{}) (tx *DB) { updateTx := tx.callbacks.Update().Execute(tx.Session(&Session{Initialized: true})) if updateTx.Error == nil && updateTx.RowsAffected == 0 && !updateTx.DryRun && !selectedUpdate { - return tx.Clauses(clause.OnConflict{UpdateAll: true}).Create(value) + return tx.Session(&Session{SkipHooks: true}).Clauses(clause.OnConflict{UpdateAll: true}).Create(value) } return updateTx diff --git a/tests/update_test.go b/tests/update_test.go index f7c36d74b..c03d2d470 100644 --- a/tests/update_test.go +++ b/tests/update_test.go @@ -809,3 +809,76 @@ func TestUpdateWithDiffSchema(t *testing.T) { AssertEqual(t, err, nil) AssertEqual(t, "update-diff-schema-2", user.Name) } + +type TokenOwner struct { + ID int + Name string + Token Token `gorm:"foreignKey:UserID"` +} + +func (t *TokenOwner) BeforeSave(tx *gorm.DB) error { + t.Name += "_name" + return nil +} + +type Token struct { + UserID int `gorm:"primary_key"` + Content string `gorm:"type:varchar(100)"` +} + +func (t *Token) BeforeSave(tx *gorm.DB) error { + t.Content += "_encrypted" + return nil +} + +func TestSaveWithHooks(t *testing.T) { + DB.Migrator().DropTable(&Token{}, &TokenOwner{}) + DB.AutoMigrate(&Token{}, &TokenOwner{}) + + saveTokenOwner := func(owner *TokenOwner) (*TokenOwner, error) { + var newOwner TokenOwner + if err := DB.Transaction(func(tx *gorm.DB) error { + if err := tx.Debug().Session(&gorm.Session{FullSaveAssociations: true}).Save(owner).Error; err != nil { + return err + } + if err := tx.Preload("Token").First(&newOwner, owner.ID).Error; err != nil { + return err + } + return nil + }); err != nil { + return nil, err + } + return &newOwner, nil + } + + owner := TokenOwner{ + Name: "user", + Token: Token{Content: "token"}, + } + o1, err := saveTokenOwner(&owner) + if err != nil { + t.Errorf("failed to save token owner, got error: %v", err) + } + if o1.Name != "user_name" { + t.Errorf(`owner name should be "user_name", but got: "%s"`, o1.Name) + } + if o1.Token.Content != "token_encrypted" { + t.Errorf(`token content should be "token_encrypted", but got: "%s"`, o1.Token.Content) + } + + owner = TokenOwner{ + ID: owner.ID, + Name: "user", + Token: Token{Content: "token2"}, + } + o2, err := saveTokenOwner(&owner) + if err != nil { + t.Errorf("failed to save token owner, got error: %v", err) + } + if o2.Name != "user_name" { + t.Errorf(`owner name should be "user_name", but got: "%s"`, o2.Name) + } + if o2.Token.Content != "token2_encrypted" { + t.Errorf(`token content should be "token2_encrypted", but got: "%s"`, o2.Token.Content) + } +}