From fd96b2d9573aacecbf90db72aa3e24c01112fa96 Mon Sep 17 00:00:00 2001 From: Ayoub Aarrasse Date: Tue, 19 Aug 2025 15:32:31 +0100 Subject: [PATCH 1/7] Fixing merge issue --- oracle/create.go | 49 +++++++++++++++++++++++++++++++++++++ tests/preload_suits_test.go | 5 ++-- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/oracle/create.go b/oracle/create.go index 4aeddcd..baf9352 100644 --- a/oracle/create.go +++ b/oracle/create.go @@ -176,6 +176,8 @@ func validateCreateData(stmt *gorm.Statement) error { // Build PL/SQL block for bulk INSERT/MERGE with RETURNING func buildBulkInsertPLSQL(db *gorm.DB, createValues clause.Values) { + sanitizeCreateValuesForBulkArrays(db.Statement, &createValues) + stmt := db.Statement schema := stmt.Schema @@ -238,6 +240,8 @@ func buildBulkInsertPLSQL(db *gorm.DB, createValues clause.Values) { // Build PL/SQL block for bulk MERGE with RETURNING (OnConflict case) func buildBulkMergePLSQL(db *gorm.DB, createValues clause.Values, onConflictClause clause.Clause) { + sanitizeCreateValuesForBulkArrays(db.Statement, &createValues) + stmt := db.Statement schema := stmt.Schema @@ -409,6 +413,25 @@ func buildBulkMergePLSQL(db *gorm.DB, createValues clause.Values, onConflictClau } } plsqlBuilder.WriteString("\n") + } else { + onCols := map[string]struct{}{} + for _, c := range conflictColumns { + onCols[strings.ToUpper(c.Name)] = struct{}{} + } + + // Picking the first non-ON column from the INSERT/MERGE columns + var noopCol string + for _, c := range createValues.Columns { + if _, inOn := onCols[strings.ToUpper(c.Name)]; !inOn { + noopCol = c.Name + break + } + } + plsqlBuilder.WriteString(" WHEN MATCHED THEN UPDATE SET t.") + writeQuotedIdentifier(&plsqlBuilder, noopCol) + plsqlBuilder.WriteString(" = t.") + writeQuotedIdentifier(&plsqlBuilder, noopCol) + plsqlBuilder.WriteString("\n") } // WHEN NOT MATCHED THEN INSERT (unless DoNothing for inserts) @@ -923,3 +946,29 @@ func handleLastInsertId(db *gorm.DB, result sql.Result) { } } } +func sanitizeCreateValuesForBulkArrays(stmt *gorm.Statement, cv *clause.Values) { + for r := range cv.Values { + for c, col := range cv.Columns { + v := cv.Values[r][c] + switch v.(type) { + case clause.Expr: + if f := findFieldByDBName(stmt.Schema, col.Name); f != nil { + switch f.DataType { + case schema.Int, schema.Uint: + cv.Values[r][c] = sql.NullInt64{} // NULL + case schema.Float: + cv.Values[r][c] = sql.NullFloat64{} + case schema.String: + cv.Values[r][c] = sql.NullString{} + case schema.Time: + cv.Values[r][c] = sql.NullTime{} + default: + cv.Values[r][c] = nil + } + } else { + cv.Values[r][c] = nil + } + } + } + } +} diff --git a/tests/preload_suits_test.go b/tests/preload_suits_test.go index 9b65c48..4ccfd75 100644 --- a/tests/preload_suits_test.go +++ b/tests/preload_suits_test.go @@ -1037,7 +1037,6 @@ func TestNestedManyToManyPreload2(t *testing.T) { } func TestNestedManyToManyPreload3(t *testing.T) { - t.Skip() type ( Level1 struct { ID uint @@ -1099,7 +1098,7 @@ func TestNestedManyToManyPreload3(t *testing.T) { var gots []*Level3 if err := DB.Preload("Level2.Level1s", func(db *gorm.DB) *gorm.DB { - return db.Order("level1.id ASC") + return db.Order("\"level1\".\"id\" ASC") }).Find(&gots).Error; err != nil { t.Error(err) } @@ -1173,7 +1172,7 @@ func TestNestedManyToManyPreload3ForStruct(t *testing.T) { var gots []*Level3 if err := DB.Preload("Level2.Level1s", func(db *gorm.DB) *gorm.DB { - return db.Order("level1.id ASC") + return db.Order("\"level1\".\"id\" ASC") }).Find(&gots).Error; err != nil { t.Error(err) } From 446f01dfbdd4f3e3f58889fab8c70b6000d4a8d7 Mon Sep 17 00:00:00 2001 From: Ayoub Aarrasse Date: Tue, 19 Aug 2025 15:33:11 +0100 Subject: [PATCH 2/7] Fixing associations_has_many_test tests --- tests/associations_has_many_test.go | 51 +++++++++++++---------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/tests/associations_has_many_test.go b/tests/associations_has_many_test.go index 245fcb3..6756cac 100644 --- a/tests/associations_has_many_test.go +++ b/tests/associations_has_many_test.go @@ -47,23 +47,22 @@ import ( ) func TestHasManyAssociation(t *testing.T) { - t.Skip() user := *GetUser("hasmany", Config{Pets: 2}) if err := DB.Create(&user).Error; err != nil { t.Fatalf("errors happened when create: %v", err) } - CheckUser(t, user, user) + CheckUserSkipUpdatedAt(t, user, user) // Find var user2 User - DB.Find(&user2, "id = ?", user.ID) + DB.Find(&user2, "\"id\" = ?", user.ID) DB.Model(&user2).Association("Pets").Find(&user2.Pets) - CheckUser(t, user2, user) + CheckUserSkipUpdatedAt(t, user2, user) var pets []Pet - DB.Model(&user).Where("name = ?", user.Pets[0].Name).Association("Pets").Find(&pets) + DB.Model(&user).Where("\"name\" = ?", user.Pets[0].Name).Association("Pets").Find(&pets) if len(pets) != 1 { t.Fatalf("should only find one pets, but got %v", len(pets)) @@ -71,11 +70,11 @@ func TestHasManyAssociation(t *testing.T) { CheckPet(t, pets[0], *user.Pets[0]) - if count := DB.Model(&user).Where("name = ?", user.Pets[1].Name).Association("Pets").Count(); count != 1 { + if count := DB.Model(&user).Where("\"name\" = ?", user.Pets[1].Name).Association("Pets").Count(); count != 1 { t.Fatalf("should only find one pets, but got %v", count) } - if count := DB.Model(&user).Where("name = ?", "not found").Association("Pets").Count(); count != 0 { + if count := DB.Model(&user).Where("\"name\" = ?", "not found").Association("Pets").Count(); count != 0 { t.Fatalf("should only find no pet with invalid conditions, but got %v", count) } @@ -94,7 +93,7 @@ func TestHasManyAssociation(t *testing.T) { } user.Pets = append(user.Pets, &pet) - CheckUser(t, user2, user) + CheckUserSkipUpdatedAt(t, user2, user) AssertAssociationCount(t, user, "Pets", 3, "AfterAppend") @@ -113,7 +112,7 @@ func TestHasManyAssociation(t *testing.T) { user.Pets = append(user.Pets, &pet) } - CheckUser(t, user2, user) + CheckUserSkipUpdatedAt(t, user2, user) AssertAssociationCount(t, user, "Pets", 5, "AfterAppendSlice") @@ -129,7 +128,7 @@ func TestHasManyAssociation(t *testing.T) { } user.Pets = []*Pet{&pet2} - CheckUser(t, user2, user) + CheckUserSkipUpdatedAt(t, user2, user) AssertAssociationCount(t, user2, "Pets", 1, "AfterReplace") @@ -160,20 +159,19 @@ func TestHasManyAssociation(t *testing.T) { } func TestSingleTableHasManyAssociation(t *testing.T) { - t.Skip() user := *GetUser("hasmany", Config{Team: 2}) if err := DB.Create(&user).Error; err != nil { t.Fatalf("errors happened when create: %v", err) } - CheckUser(t, user, user) + CheckUserSkipUpdatedAt(t, user, user) // Find var user2 User - DB.Find(&user2, "id = ?", user.ID) + DB.Find(&user2, "\"id\" = ?", user.ID) DB.Model(&user2).Association("Team").Find(&user2.Team) - CheckUser(t, user2, user) + CheckUserSkipUpdatedAt(t, user2, user) // Count AssertAssociationCount(t, user, "Team", 2, "") @@ -190,7 +188,7 @@ func TestSingleTableHasManyAssociation(t *testing.T) { } user.Team = append(user.Team, team) - CheckUser(t, user2, user) + CheckUserSkipUpdatedAt(t, user2, user) AssertAssociationCount(t, user, "Team", 3, "AfterAppend") @@ -209,7 +207,7 @@ func TestSingleTableHasManyAssociation(t *testing.T) { user.Team = append(user.Team, team) } - CheckUser(t, user2, user) + CheckUserSkipUpdatedAt(t, user2, user) AssertAssociationCount(t, user, "Team", 5, "AfterAppendSlice") @@ -225,7 +223,7 @@ func TestSingleTableHasManyAssociation(t *testing.T) { } user.Team = []User{team2} - CheckUser(t, user2, user) + CheckUserSkipUpdatedAt(t, user2, user) AssertAssociationCount(t, user2, "Team", 1, "AfterReplace") @@ -256,7 +254,6 @@ func TestSingleTableHasManyAssociation(t *testing.T) { } func TestHasManyAssociationForSlice(t *testing.T) { - t.Skip() users := []User{ *GetUser("slice-hasmany-1", Config{Pets: 2}), *GetUser("slice-hasmany-2", Config{Pets: 0}), @@ -311,7 +308,6 @@ func TestHasManyAssociationForSlice(t *testing.T) { } func TestSingleTableHasManyAssociationForSlice(t *testing.T) { - t.Skip() users := []User{ *GetUser("slice-hasmany-1", Config{Team: 2}), *GetUser("slice-hasmany-2", Config{Team: 0}), @@ -368,20 +364,19 @@ func TestSingleTableHasManyAssociationForSlice(t *testing.T) { } func TestPolymorphicHasManyAssociation(t *testing.T) { - t.Skip() user := *GetUser("hasmany", Config{Toys: 2}) if err := DB.Create(&user).Error; err != nil { t.Fatalf("errors happened when create: %v", err) } - CheckUser(t, user, user) + CheckUserSkipUpdatedAt(t, user, user) // Find var user2 User - DB.Find(&user2, "id = ?", user.ID) + DB.Find(&user2, "\"id\" = ?", user.ID) DB.Model(&user2).Association("Toys").Find(&user2.Toys) - CheckUser(t, user2, user) + CheckUserSkipUpdatedAt(t, user2, user) // Count AssertAssociationCount(t, user, "Toys", 2, "") @@ -398,7 +393,7 @@ func TestPolymorphicHasManyAssociation(t *testing.T) { } user.Toys = append(user.Toys, toy) - CheckUser(t, user2, user) + CheckUserSkipUpdatedAt(t, user2, user) AssertAssociationCount(t, user, "Toys", 3, "AfterAppend") @@ -417,7 +412,7 @@ func TestPolymorphicHasManyAssociation(t *testing.T) { user.Toys = append(user.Toys, toy) } - CheckUser(t, user2, user) + CheckUserSkipUpdatedAt(t, user2, user) AssertAssociationCount(t, user, "Toys", 5, "AfterAppendSlice") @@ -433,7 +428,7 @@ func TestPolymorphicHasManyAssociation(t *testing.T) { } user.Toys = []Toy{toy2} - CheckUser(t, user2, user) + CheckUserSkipUpdatedAt(t, user2, user) AssertAssociationCount(t, user2, "Toys", 1, "AfterReplace") @@ -464,7 +459,6 @@ func TestPolymorphicHasManyAssociation(t *testing.T) { } func TestPolymorphicHasManyAssociationForSlice(t *testing.T) { - t.Skip() users := []User{ *GetUser("slice-hasmany-1", Config{Toys: 2}), *GetUser("slice-hasmany-2", Config{Toys: 0, Tools: 2}), @@ -601,8 +595,7 @@ func TestHasManyAssociationUnscoped(t *testing.T) { } func TestHasManyAssociationReplaceWithNonValidValue(t *testing.T) { - t.Skip() - user := User{Name: "jinzhu", Languages: []Language{{Name: "EN"}}} + user := User{Name: "jinzhu", Languages: []Language{{Code: "EN", Name: "EN"}}} if err := DB.Create(&user).Error; err != nil { t.Fatalf("errors happened when create: %v", err) From 52c7e006e0d7fd695ace724aa9db9720d2836ac9 Mon Sep 17 00:00:00 2001 From: Ayoub Aarrasse Date: Tue, 19 Aug 2025 15:37:48 +0100 Subject: [PATCH 3/7] Fixing more tests related to associations --- tests/associations_many2many_test.go | 12 +++++------- tests/multi_primary_keys_test.go | 3 +-- tests/update_many2many_test.go | 1 - 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/tests/associations_many2many_test.go b/tests/associations_many2many_test.go index 86dcd5e..ec31e44 100644 --- a/tests/associations_many2many_test.go +++ b/tests/associations_many2many_test.go @@ -235,21 +235,20 @@ func TestMany2ManyAssociationForSlice(t *testing.T) { } func TestSingleTableMany2ManyAssociation(t *testing.T) { - t.Skip() user := *GetUser("many2many", Config{Friends: 2}) if err := DB.Create(&user).Error; err != nil { t.Fatalf("errors happened when create: %v", err) } - CheckUser(t, user, user) + CheckUserSkipUpdatedAt(t, user, user) // Find var user2 User DB.Find(&user2, "\"id\" = ?", user.ID) DB.Model(&user2).Association("Friends").Find(&user2.Friends) - CheckUser(t, user2, user) + CheckUserSkipUpdatedAt(t, user2, user) // Count AssertAssociationCount(t, user, "Friends", 2, "") @@ -262,7 +261,7 @@ func TestSingleTableMany2ManyAssociation(t *testing.T) { } user.Friends = append(user.Friends, &friend) - CheckUser(t, user2, user) + CheckUserSkipUpdatedAt(t, user2, user) AssertAssociationCount(t, user, "Friends", 3, "AfterAppend") @@ -274,7 +273,7 @@ func TestSingleTableMany2ManyAssociation(t *testing.T) { user.Friends = append(user.Friends, friends...) - CheckUser(t, user2, user) + CheckUserSkipUpdatedAt(t, user2, user) AssertAssociationCount(t, user, "Friends", 5, "AfterAppendSlice") @@ -286,7 +285,7 @@ func TestSingleTableMany2ManyAssociation(t *testing.T) { } user.Friends = []*User{&friend2} - CheckUser(t, user2, user) + CheckUserSkipUpdatedAt(t, user2, user) AssertAssociationCount(t, user2, "Friends", 1, "AfterReplace") @@ -317,7 +316,6 @@ func TestSingleTableMany2ManyAssociation(t *testing.T) { } func TestSingleTableMany2ManyAssociationForSlice(t *testing.T) { - t.Skip() users := []User{ *GetUser("slice-many2many-1", Config{Team: 2}), *GetUser("slice-many2many-2", Config{Team: 0}), diff --git a/tests/multi_primary_keys_test.go b/tests/multi_primary_keys_test.go index 7a8d4ac..b288861 100644 --- a/tests/multi_primary_keys_test.go +++ b/tests/multi_primary_keys_test.go @@ -75,7 +75,6 @@ func compareTags(tags []Tag, contents []string) bool { } func TestManyToManyWithMultiPrimaryKeys(t *testing.T) { - t.Skip() if name := DB.Dialector.Name(); name == "sqlite" || name == "sqlserver" { t.Skip("skip sqlite, sqlserver due to it doesn't support multiple primary keys with auto increment") } @@ -455,7 +454,7 @@ func TestManyToManyWithCustomizedForeignKeys2(t *testing.T) { func TestCompositePrimaryKeysAssociations(t *testing.T) { t.Skip() - + type Label struct { BookID *uint `gorm:"primarykey"` Name string `gorm:"primarykey"` diff --git a/tests/update_many2many_test.go b/tests/update_many2many_test.go index 06c4420..670da85 100644 --- a/tests/update_many2many_test.go +++ b/tests/update_many2many_test.go @@ -47,7 +47,6 @@ import ( ) func TestUpdateMany2ManyAssociations(t *testing.T) { - t.Skip() user := *GetUser("update-many2many", Config{}) if err := DB.Create(&user).Error; err != nil { From 2b831af6e59520065a621ce75ae00a0d202bd7bb Mon Sep 17 00:00:00 2001 From: Ayoub Aarrasse Date: Tue, 19 Aug 2025 15:47:19 +0100 Subject: [PATCH 4/7] wrong addition --- tests/multi_primary_keys_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/multi_primary_keys_test.go b/tests/multi_primary_keys_test.go index b288861..aa3939a 100644 --- a/tests/multi_primary_keys_test.go +++ b/tests/multi_primary_keys_test.go @@ -75,6 +75,7 @@ func compareTags(tags []Tag, contents []string) bool { } func TestManyToManyWithMultiPrimaryKeys(t *testing.T) { + t.Skip() if name := DB.Dialector.Name(); name == "sqlite" || name == "sqlserver" { t.Skip("skip sqlite, sqlserver due to it doesn't support multiple primary keys with auto increment") } From d8c749e42fbbebd6dd07225722b009d5d065b50c Mon Sep 17 00:00:00 2001 From: Ayoub Aarrasse Date: Tue, 19 Aug 2025 16:31:51 +0100 Subject: [PATCH 5/7] updating passed tests --- tests/passed-tests.txt | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/passed-tests.txt b/tests/passed-tests.txt index f74b330..472b158 100644 --- a/tests/passed-tests.txt +++ b/tests/passed-tests.txt @@ -2,14 +2,14 @@ TestBelongsToAssociation TestBelongsToAssociationForSlice TestBelongsToDefaultValue TestBelongsToAssociationUnscoped -#TestHasManyAssociation -#TestSingleTableHasManyAssociation -#TestHasManyAssociationForSlice -#TestSingleTableHasManyAssociationForSlice -#TestPolymorphicHasManyAssociation -#TestPolymorphicHasManyAssociationForSlice +TestHasManyAssociation +TestSingleTableHasManyAssociation +TestHasManyAssociationForSlice +TestSingleTableHasManyAssociationForSlice +TestPolymorphicHasManyAssociation +TestPolymorphicHasManyAssociationForSlice TestHasManyAssociationUnscoped -#TestHasManyAssociationReplaceWithNonValidValue +TestHasManyAssociationReplaceWithNonValidValue TestHasOneAssociation TestHasOneAssociationWithSelect TestHasOneAssociationForSlice @@ -19,8 +19,8 @@ TestHasOneAssociationReplaceWithNonValidValue TestMany2ManyAssociation TestMany2ManyOmitAssociations TestMany2ManyAssociationForSlice -#TestSingleTableMany2ManyAssociation -#TestSingleTableMany2ManyAssociationForSlice +TestSingleTableMany2ManyAssociation +TestSingleTableMany2ManyAssociationForSlice TestDuplicateMany2ManyAssociation TestConcurrentMany2ManyAssociation TestMany2ManyDuplicateBelongsToAssociation @@ -211,8 +211,8 @@ TestManyToManyPreloadWithMultiPrimaryKeys TestManyToManyPreloadForNestedPointer TestNestedManyToManyPreload TestNestedManyToManyPreload2 -#TestNestedManyToManyPreload3 -#TestNestedManyToManyPreload3ForStruct +TestNestedManyToManyPreload3 +TestNestedManyToManyPreload3ForStruct TestNestedManyToManyPreload4 TestManyToManyPreloadForPointer TestNilPointerSlice @@ -338,7 +338,7 @@ TestTransactionWithRawSQL TestUpdateBelongsTo #TestUpdateHasManyAssociations TestUpdateHasOne -#TestUpdateMany2ManyAssociations +TestUpdateMany2ManyAssociations TestUpdate TestUpdates TestUpdateColumn From 7c567117826eb8e3097328b4d0c1bdf5182a217f Mon Sep 17 00:00:00 2001 From: Ayoub Aarrasse Date: Tue, 19 Aug 2025 16:38:33 +0100 Subject: [PATCH 6/7] forgotten passed test --- tests/preload_suits_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/preload_suits_test.go b/tests/preload_suits_test.go index 4ccfd75..025f566 100644 --- a/tests/preload_suits_test.go +++ b/tests/preload_suits_test.go @@ -1109,7 +1109,6 @@ func TestNestedManyToManyPreload3(t *testing.T) { } func TestNestedManyToManyPreload3ForStruct(t *testing.T) { - t.Skip() type ( Level1 struct { ID uint From e6f224e628b69fc67c3ac6970d61feb839e502eb Mon Sep 17 00:00:00 2001 From: Ayoub Aarrasse Date: Wed, 20 Aug 2025 00:47:38 +0100 Subject: [PATCH 7/7] Adding comment and removing unused function --- oracle/create.go | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/oracle/create.go b/oracle/create.go index 6c243c7..8a02fc0 100644 --- a/oracle/create.go +++ b/oracle/create.go @@ -814,19 +814,6 @@ func handleSingleRowReturning(db *gorm.DB) { } } -// Simplified RETURNING clause addition for single row operations -func addReturningClause(db *gorm.DB, fields []*schema.Field) { - if len(fields) == 0 { - return - } - - columns := make([]clause.Column, len(fields)) - for idx, field := range fields { - columns[idx] = clause.Column{Name: field.DBName} - } - db.Statement.AddClauseIfNotExists(clause.Returning{Columns: columns}) -} - // Handle bulk RETURNING results for PL/SQL operations func getBulkReturningValues(db *gorm.DB, rowCount int) { if db.Statement.Schema == nil { @@ -946,6 +933,11 @@ func handleLastInsertId(db *gorm.DB, result sql.Result) { } } } + +// This replaces expressions (clause.Expr) in bulk insert values +// with appropriate NULL placeholders based on the column's data type. This ensures that +// PL/SQL array binding remains consistent and avoids unsupported expressions during +// FORALL bulk operations. func sanitizeCreateValuesForBulkArrays(stmt *gorm.Statement, cv *clause.Values) { for r := range cv.Values { for c, col := range cv.Columns { @@ -955,7 +947,7 @@ func sanitizeCreateValuesForBulkArrays(stmt *gorm.Statement, cv *clause.Values) if f := findFieldByDBName(stmt.Schema, col.Name); f != nil { switch f.DataType { case schema.Int, schema.Uint: - cv.Values[r][c] = sql.NullInt64{} // NULL + cv.Values[r][c] = sql.NullInt64{} case schema.Float: cv.Values[r][c] = sql.NullFloat64{} case schema.String: