Skip to content

Commit aed583c

Browse files
committed
Merge remote-tracking branch 'origin' into addConnectionTests
2 parents b2d720b + 97118aa commit aed583c

12 files changed

+241
-92
lines changed

oracle/create.go

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ func validateCreateData(stmt *gorm.Statement) error {
176176

177177
// Build PL/SQL block for bulk INSERT/MERGE with RETURNING
178178
func buildBulkInsertPLSQL(db *gorm.DB, createValues clause.Values) {
179+
sanitizeCreateValuesForBulkArrays(db.Statement, &createValues)
180+
179181
stmt := db.Statement
180182
schema := stmt.Schema
181183

@@ -238,6 +240,8 @@ func buildBulkInsertPLSQL(db *gorm.DB, createValues clause.Values) {
238240

239241
// Build PL/SQL block for bulk MERGE with RETURNING (OnConflict case)
240242
func buildBulkMergePLSQL(db *gorm.DB, createValues clause.Values, onConflictClause clause.Clause) {
243+
sanitizeCreateValuesForBulkArrays(db.Statement, &createValues)
244+
241245
stmt := db.Statement
242246
schema := stmt.Schema
243247

@@ -407,6 +411,25 @@ func buildBulkMergePLSQL(db *gorm.DB, createValues clause.Values, onConflictClau
407411
}
408412
}
409413
plsqlBuilder.WriteString("\n")
414+
} else {
415+
onCols := map[string]struct{}{}
416+
for _, c := range conflictColumns {
417+
onCols[strings.ToUpper(c.Name)] = struct{}{}
418+
}
419+
420+
// Picking the first non-ON column from the INSERT/MERGE columns
421+
var noopCol string
422+
for _, c := range createValues.Columns {
423+
if _, inOn := onCols[strings.ToUpper(c.Name)]; !inOn {
424+
noopCol = c.Name
425+
break
426+
}
427+
}
428+
plsqlBuilder.WriteString(" WHEN MATCHED THEN UPDATE SET t.")
429+
writeQuotedIdentifier(&plsqlBuilder, noopCol)
430+
plsqlBuilder.WriteString(" = t.")
431+
writeQuotedIdentifier(&plsqlBuilder, noopCol)
432+
plsqlBuilder.WriteString("\n")
410433
}
411434

412435
// WHEN NOT MATCHED THEN INSERT (unless DoNothing for inserts)
@@ -787,19 +810,6 @@ func handleSingleRowReturning(db *gorm.DB) {
787810
}
788811
}
789812

790-
// Simplified RETURNING clause addition for single row operations
791-
func addReturningClause(db *gorm.DB, fields []*schema.Field) {
792-
if len(fields) == 0 {
793-
return
794-
}
795-
796-
columns := make([]clause.Column, len(fields))
797-
for idx, field := range fields {
798-
columns[idx] = clause.Column{Name: field.DBName}
799-
}
800-
db.Statement.AddClauseIfNotExists(clause.Returning{Columns: columns})
801-
}
802-
803813
// Handle bulk RETURNING results for PL/SQL operations
804814
func getBulkReturningValues(db *gorm.DB, rowCount int) {
805815
if db.Statement.Schema == nil {
@@ -919,3 +929,34 @@ func handleLastInsertId(db *gorm.DB, result sql.Result) {
919929
}
920930
}
921931
}
932+
933+
// This replaces expressions (clause.Expr) in bulk insert values
934+
// with appropriate NULL placeholders based on the column's data type. This ensures that
935+
// PL/SQL array binding remains consistent and avoids unsupported expressions during
936+
// FORALL bulk operations.
937+
func sanitizeCreateValuesForBulkArrays(stmt *gorm.Statement, cv *clause.Values) {
938+
for r := range cv.Values {
939+
for c, col := range cv.Columns {
940+
v := cv.Values[r][c]
941+
switch v.(type) {
942+
case clause.Expr:
943+
if f := findFieldByDBName(stmt.Schema, col.Name); f != nil {
944+
switch f.DataType {
945+
case schema.Int, schema.Uint:
946+
cv.Values[r][c] = sql.NullInt64{}
947+
case schema.Float:
948+
cv.Values[r][c] = sql.NullFloat64{}
949+
case schema.String:
950+
cv.Values[r][c] = sql.NullString{}
951+
case schema.Time:
952+
cv.Values[r][c] = sql.NullTime{}
953+
default:
954+
cv.Values[r][c] = nil
955+
}
956+
} else {
957+
cv.Values[r][c] = nil
958+
}
959+
}
960+
}
961+
}
962+
}

oracle/delete.go

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,22 @@ func Delete(db *gorm.DB) {
9393
addPrimaryKeyWhereClause(db)
9494
}
9595

96+
// redirect soft-delete to update clause with bulk returning
97+
if stmt.Schema != nil {
98+
if deletedAtField := stmt.Schema.LookUpField("deleted_at"); deletedAtField != nil && !stmt.Unscoped {
99+
for _, c := range stmt.Schema.DeleteClauses {
100+
stmt.AddClause(c)
101+
}
102+
delete(stmt.Clauses, "DELETE")
103+
delete(stmt.Clauses, "FROM")
104+
stmt.SQL.Reset()
105+
stmt.Vars = stmt.Vars[:0]
106+
stmt.AddClauseIfNotExists(clause.Update{})
107+
Update(db)
108+
return
109+
}
110+
}
111+
96112
// This prevents soft deletes from bypassing the safety check
97113
checkMissingWhereConditions(db)
98114
if db.Error != nil {
@@ -432,25 +448,7 @@ func executeDelete(db *gorm.DB) {
432448
_, hasReturning := stmt.Clauses["RETURNING"]
433449

434450
if hasReturning {
435-
// For RETURNING, we need to check if it's a soft delete or hard delete
436-
if stmt.Schema != nil {
437-
if deletedAtField := stmt.Schema.LookUpField("deleted_at"); deletedAtField != nil && !stmt.Unscoped {
438-
// Soft delete with RETURNING - use QueryContext
439-
if rows, err := stmt.ConnPool.QueryContext(stmt.Context, stmt.SQL.String(), stmt.Vars...); err == nil {
440-
defer rows.Close()
441-
gorm.Scan(rows, db, gorm.ScanInitialized)
442-
443-
if stmt.Result != nil {
444-
stmt.Result.RowsAffected = db.RowsAffected
445-
}
446-
} else {
447-
db.AddError(err)
448-
}
449-
return
450-
}
451-
}
452-
453-
// Hard delete with RETURNING - use ExecContext (for PL/SQL blocks)
451+
// Hard delete & soft delete with RETURNING - use ExecContext (for PL/SQL blocks)
454452
result, err := stmt.ConnPool.ExecContext(stmt.Context, stmt.SQL.String(), stmt.Vars...)
455453
if err == nil {
456454
db.RowsAffected, _ = result.RowsAffected()

tests/associations_has_many_test.go

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -47,35 +47,34 @@ import (
4747
)
4848

4949
func TestHasManyAssociation(t *testing.T) {
50-
t.Skip()
5150
user := *GetUser("hasmany", Config{Pets: 2})
5251

5352
if err := DB.Create(&user).Error; err != nil {
5453
t.Fatalf("errors happened when create: %v", err)
5554
}
5655

57-
CheckUser(t, user, user)
56+
CheckUserSkipUpdatedAt(t, user, user)
5857

5958
// Find
6059
var user2 User
61-
DB.Find(&user2, "id = ?", user.ID)
60+
DB.Find(&user2, "\"id\" = ?", user.ID)
6261
DB.Model(&user2).Association("Pets").Find(&user2.Pets)
63-
CheckUser(t, user2, user)
62+
CheckUserSkipUpdatedAt(t, user2, user)
6463

6564
var pets []Pet
66-
DB.Model(&user).Where("name = ?", user.Pets[0].Name).Association("Pets").Find(&pets)
65+
DB.Model(&user).Where("\"name\" = ?", user.Pets[0].Name).Association("Pets").Find(&pets)
6766

6867
if len(pets) != 1 {
6968
t.Fatalf("should only find one pets, but got %v", len(pets))
7069
}
7170

7271
CheckPet(t, pets[0], *user.Pets[0])
7372

74-
if count := DB.Model(&user).Where("name = ?", user.Pets[1].Name).Association("Pets").Count(); count != 1 {
73+
if count := DB.Model(&user).Where("\"name\" = ?", user.Pets[1].Name).Association("Pets").Count(); count != 1 {
7574
t.Fatalf("should only find one pets, but got %v", count)
7675
}
7776

78-
if count := DB.Model(&user).Where("name = ?", "not found").Association("Pets").Count(); count != 0 {
77+
if count := DB.Model(&user).Where("\"name\" = ?", "not found").Association("Pets").Count(); count != 0 {
7978
t.Fatalf("should only find no pet with invalid conditions, but got %v", count)
8079
}
8180

@@ -94,7 +93,7 @@ func TestHasManyAssociation(t *testing.T) {
9493
}
9594

9695
user.Pets = append(user.Pets, &pet)
97-
CheckUser(t, user2, user)
96+
CheckUserSkipUpdatedAt(t, user2, user)
9897

9998
AssertAssociationCount(t, user, "Pets", 3, "AfterAppend")
10099

@@ -113,7 +112,7 @@ func TestHasManyAssociation(t *testing.T) {
113112
user.Pets = append(user.Pets, &pet)
114113
}
115114

116-
CheckUser(t, user2, user)
115+
CheckUserSkipUpdatedAt(t, user2, user)
117116

118117
AssertAssociationCount(t, user, "Pets", 5, "AfterAppendSlice")
119118

@@ -129,7 +128,7 @@ func TestHasManyAssociation(t *testing.T) {
129128
}
130129

131130
user.Pets = []*Pet{&pet2}
132-
CheckUser(t, user2, user)
131+
CheckUserSkipUpdatedAt(t, user2, user)
133132

134133
AssertAssociationCount(t, user2, "Pets", 1, "AfterReplace")
135134

@@ -160,20 +159,19 @@ func TestHasManyAssociation(t *testing.T) {
160159
}
161160

162161
func TestSingleTableHasManyAssociation(t *testing.T) {
163-
t.Skip()
164162
user := *GetUser("hasmany", Config{Team: 2})
165163

166164
if err := DB.Create(&user).Error; err != nil {
167165
t.Fatalf("errors happened when create: %v", err)
168166
}
169167

170-
CheckUser(t, user, user)
168+
CheckUserSkipUpdatedAt(t, user, user)
171169

172170
// Find
173171
var user2 User
174-
DB.Find(&user2, "id = ?", user.ID)
172+
DB.Find(&user2, "\"id\" = ?", user.ID)
175173
DB.Model(&user2).Association("Team").Find(&user2.Team)
176-
CheckUser(t, user2, user)
174+
CheckUserSkipUpdatedAt(t, user2, user)
177175

178176
// Count
179177
AssertAssociationCount(t, user, "Team", 2, "")
@@ -190,7 +188,7 @@ func TestSingleTableHasManyAssociation(t *testing.T) {
190188
}
191189

192190
user.Team = append(user.Team, team)
193-
CheckUser(t, user2, user)
191+
CheckUserSkipUpdatedAt(t, user2, user)
194192

195193
AssertAssociationCount(t, user, "Team", 3, "AfterAppend")
196194

@@ -209,7 +207,7 @@ func TestSingleTableHasManyAssociation(t *testing.T) {
209207
user.Team = append(user.Team, team)
210208
}
211209

212-
CheckUser(t, user2, user)
210+
CheckUserSkipUpdatedAt(t, user2, user)
213211

214212
AssertAssociationCount(t, user, "Team", 5, "AfterAppendSlice")
215213

@@ -225,7 +223,7 @@ func TestSingleTableHasManyAssociation(t *testing.T) {
225223
}
226224

227225
user.Team = []User{team2}
228-
CheckUser(t, user2, user)
226+
CheckUserSkipUpdatedAt(t, user2, user)
229227

230228
AssertAssociationCount(t, user2, "Team", 1, "AfterReplace")
231229

@@ -256,7 +254,6 @@ func TestSingleTableHasManyAssociation(t *testing.T) {
256254
}
257255

258256
func TestHasManyAssociationForSlice(t *testing.T) {
259-
t.Skip()
260257
users := []User{
261258
*GetUser("slice-hasmany-1", Config{Pets: 2}),
262259
*GetUser("slice-hasmany-2", Config{Pets: 0}),
@@ -311,7 +308,6 @@ func TestHasManyAssociationForSlice(t *testing.T) {
311308
}
312309

313310
func TestSingleTableHasManyAssociationForSlice(t *testing.T) {
314-
t.Skip()
315311
users := []User{
316312
*GetUser("slice-hasmany-1", Config{Team: 2}),
317313
*GetUser("slice-hasmany-2", Config{Team: 0}),
@@ -368,20 +364,19 @@ func TestSingleTableHasManyAssociationForSlice(t *testing.T) {
368364
}
369365

370366
func TestPolymorphicHasManyAssociation(t *testing.T) {
371-
t.Skip()
372367
user := *GetUser("hasmany", Config{Toys: 2})
373368

374369
if err := DB.Create(&user).Error; err != nil {
375370
t.Fatalf("errors happened when create: %v", err)
376371
}
377372

378-
CheckUser(t, user, user)
373+
CheckUserSkipUpdatedAt(t, user, user)
379374

380375
// Find
381376
var user2 User
382-
DB.Find(&user2, "id = ?", user.ID)
377+
DB.Find(&user2, "\"id\" = ?", user.ID)
383378
DB.Model(&user2).Association("Toys").Find(&user2.Toys)
384-
CheckUser(t, user2, user)
379+
CheckUserSkipUpdatedAt(t, user2, user)
385380

386381
// Count
387382
AssertAssociationCount(t, user, "Toys", 2, "")
@@ -398,7 +393,7 @@ func TestPolymorphicHasManyAssociation(t *testing.T) {
398393
}
399394

400395
user.Toys = append(user.Toys, toy)
401-
CheckUser(t, user2, user)
396+
CheckUserSkipUpdatedAt(t, user2, user)
402397

403398
AssertAssociationCount(t, user, "Toys", 3, "AfterAppend")
404399

@@ -417,7 +412,7 @@ func TestPolymorphicHasManyAssociation(t *testing.T) {
417412
user.Toys = append(user.Toys, toy)
418413
}
419414

420-
CheckUser(t, user2, user)
415+
CheckUserSkipUpdatedAt(t, user2, user)
421416

422417
AssertAssociationCount(t, user, "Toys", 5, "AfterAppendSlice")
423418

@@ -433,7 +428,7 @@ func TestPolymorphicHasManyAssociation(t *testing.T) {
433428
}
434429

435430
user.Toys = []Toy{toy2}
436-
CheckUser(t, user2, user)
431+
CheckUserSkipUpdatedAt(t, user2, user)
437432

438433
AssertAssociationCount(t, user2, "Toys", 1, "AfterReplace")
439434

@@ -464,7 +459,6 @@ func TestPolymorphicHasManyAssociation(t *testing.T) {
464459
}
465460

466461
func TestPolymorphicHasManyAssociationForSlice(t *testing.T) {
467-
t.Skip()
468462
users := []User{
469463
*GetUser("slice-hasmany-1", Config{Toys: 2}),
470464
*GetUser("slice-hasmany-2", Config{Toys: 0, Tools: 2}),
@@ -601,8 +595,7 @@ func TestHasManyAssociationUnscoped(t *testing.T) {
601595
}
602596

603597
func TestHasManyAssociationReplaceWithNonValidValue(t *testing.T) {
604-
t.Skip()
605-
user := User{Name: "jinzhu", Languages: []Language{{Name: "EN"}}}
598+
user := User{Name: "jinzhu", Languages: []Language{{Code: "EN", Name: "EN"}}}
606599

607600
if err := DB.Create(&user).Error; err != nil {
608601
t.Fatalf("errors happened when create: %v", err)

0 commit comments

Comments
 (0)