@@ -176,6 +176,8 @@ func validateCreateData(stmt *gorm.Statement) error {
176176
177177// Build PL/SQL block for bulk INSERT/MERGE with RETURNING
178178func buildBulkInsertPLSQL (db * gorm.DB , createValues clause.Values ) {
179+ sanitizeCreateValuesForBulkArrays (db .Statement , & createValues )
180+
179181 stmt := db .Statement
180182 schema := stmt .Schema
181183
@@ -217,7 +219,6 @@ func buildBulkInsertPLSQL(db *gorm.DB, createValues clause.Values) {
217219 conflictColumns := onConflict .Columns
218220 if len (conflictColumns ) == 0 {
219221 if len (schema .PrimaryFields ) == 0 {
220- db .AddError (fmt .Errorf ("OnConflict requires either explicit columns or primary key fields" ))
221222 return
222223 }
223224 for _ , primaryField := range schema .PrimaryFields {
@@ -238,6 +239,8 @@ func buildBulkInsertPLSQL(db *gorm.DB, createValues clause.Values) {
238239
239240// Build PL/SQL block for bulk MERGE with RETURNING (OnConflict case)
240241func buildBulkMergePLSQL (db * gorm.DB , createValues clause.Values , onConflictClause clause.Clause ) {
242+ sanitizeCreateValuesForBulkArrays (db .Statement , & createValues )
243+
241244 stmt := db .Statement
242245 schema := stmt .Schema
243246
@@ -251,7 +254,6 @@ func buildBulkMergePLSQL(db *gorm.DB, createValues clause.Values, onConflictClau
251254 conflictColumns := onConflict .Columns
252255 if len (conflictColumns ) == 0 {
253256 if schema == nil || len (schema .PrimaryFields ) == 0 {
254- db .AddError (fmt .Errorf ("OnConflict requires either explicit columns or primary key fields" ))
255257 return
256258 }
257259 for _ , primaryField := range schema .PrimaryFields {
@@ -265,9 +267,11 @@ func buildBulkMergePLSQL(db *gorm.DB, createValues clause.Values, onConflictClau
265267 valuesColumnMap [strings .ToUpper (column .Name )] = true
266268 }
267269
270+ // Filter conflict columns to remove non unique columns
268271 var filteredConflictColumns []clause.Column
269272 for _ , conflictCol := range conflictColumns {
270- if valuesColumnMap [strings .ToUpper (conflictCol .Name )] {
273+ field := stmt .Schema .LookUpField (conflictCol .Name )
274+ if valuesColumnMap [strings .ToUpper (conflictCol .Name )] && (field .Unique || field .AutoIncrement ) {
271275 filteredConflictColumns = append (filteredConflictColumns , conflictCol )
272276 }
273277 }
@@ -285,9 +289,7 @@ func buildBulkMergePLSQL(db *gorm.DB, createValues clause.Values, onConflictClau
285289
286290 // Start PL/SQL block
287291 plsqlBuilder .WriteString ("DECLARE\n " )
288- plsqlBuilder .WriteString (" TYPE t_records IS TABLE OF " )
289- writeQuotedIdentifier (& plsqlBuilder , stmt .Table )
290- plsqlBuilder .WriteString ("%ROWTYPE;\n " )
292+ writeTableRecordCollectionDecl (& plsqlBuilder , stmt .Schema .DBNames , stmt .Table )
291293 plsqlBuilder .WriteString (" l_affected_records t_records;\n " )
292294
293295 // Create array types and variables for each column
@@ -336,6 +338,7 @@ func buildBulkMergePLSQL(db *gorm.DB, createValues clause.Values, onConflictClau
336338
337339 // Build ON clause using conflict columns
338340 plsqlBuilder .WriteString (" ON (" )
341+
339342 for idx , conflictCol := range conflictColumns {
340343 if idx > 0 {
341344 plsqlBuilder .WriteString (" AND " )
@@ -409,6 +412,25 @@ func buildBulkMergePLSQL(db *gorm.DB, createValues clause.Values, onConflictClau
409412 }
410413 }
411414 plsqlBuilder .WriteString ("\n " )
415+ } else {
416+ onCols := map [string ]struct {}{}
417+ for _ , c := range conflictColumns {
418+ onCols [strings .ToUpper (c .Name )] = struct {}{}
419+ }
420+
421+ // Picking the first non-ON column from the INSERT/MERGE columns
422+ var noopCol string
423+ for _ , c := range createValues .Columns {
424+ if _ , inOn := onCols [strings .ToUpper (c .Name )]; ! inOn {
425+ noopCol = c .Name
426+ break
427+ }
428+ }
429+ plsqlBuilder .WriteString (" WHEN MATCHED THEN UPDATE SET t." )
430+ writeQuotedIdentifier (& plsqlBuilder , noopCol )
431+ plsqlBuilder .WriteString (" = s." )
432+ writeQuotedIdentifier (& plsqlBuilder , noopCol )
433+ plsqlBuilder .WriteString ("\n " )
412434 }
413435
414436 // WHEN NOT MATCHED THEN INSERT (unless DoNothing for inserts)
@@ -526,9 +548,7 @@ func buildBulkInsertOnlyPLSQL(db *gorm.DB, createValues clause.Values) {
526548
527549 // Start PL/SQL block
528550 plsqlBuilder .WriteString ("DECLARE\n " )
529- plsqlBuilder .WriteString (" TYPE t_records IS TABLE OF " )
530- writeQuotedIdentifier (& plsqlBuilder , stmt .Table )
531- plsqlBuilder .WriteString ("%ROWTYPE;\n " )
551+ writeTableRecordCollectionDecl (& plsqlBuilder , stmt .Schema .DBNames , stmt .Table )
532552 plsqlBuilder .WriteString (" l_inserted_records t_records;\n " )
533553
534554 // Create array types and variables for each column
@@ -791,19 +811,6 @@ func handleSingleRowReturning(db *gorm.DB) {
791811 }
792812}
793813
794- // Simplified RETURNING clause addition for single row operations
795- func addReturningClause (db * gorm.DB , fields []* schema.Field ) {
796- if len (fields ) == 0 {
797- return
798- }
799-
800- columns := make ([]clause.Column , len (fields ))
801- for idx , field := range fields {
802- columns [idx ] = clause.Column {Name : field .DBName }
803- }
804- db .Statement .AddClauseIfNotExists (clause.Returning {Columns : columns })
805- }
806-
807814// Handle bulk RETURNING results for PL/SQL operations
808815func getBulkReturningValues (db * gorm.DB , rowCount int ) {
809816 if db .Statement .Schema == nil {
@@ -923,3 +930,34 @@ func handleLastInsertId(db *gorm.DB, result sql.Result) {
923930 }
924931 }
925932}
933+
934+ // This replaces expressions (clause.Expr) in bulk insert values
935+ // with appropriate NULL placeholders based on the column's data type. This ensures that
936+ // PL/SQL array binding remains consistent and avoids unsupported expressions during
937+ // FORALL bulk operations.
938+ func sanitizeCreateValuesForBulkArrays (stmt * gorm.Statement , cv * clause.Values ) {
939+ for r := range cv .Values {
940+ for c , col := range cv .Columns {
941+ v := cv.Values [r ][c ]
942+ switch v .(type ) {
943+ case clause.Expr :
944+ if f := findFieldByDBName (stmt .Schema , col .Name ); f != nil {
945+ switch f .DataType {
946+ case schema .Int , schema .Uint :
947+ cv.Values [r ][c ] = sql.NullInt64 {}
948+ case schema .Float :
949+ cv.Values [r ][c ] = sql.NullFloat64 {}
950+ case schema .String :
951+ cv.Values [r ][c ] = sql.NullString {}
952+ case schema .Time :
953+ cv.Values [r ][c ] = sql.NullTime {}
954+ default :
955+ cv.Values [r ][c ] = nil
956+ }
957+ } else {
958+ cv.Values [r ][c ] = nil
959+ }
960+ }
961+ }
962+ }
963+ }
0 commit comments