From d45a78cf2780f13ccfa14b561a3c4e55e41d5928 Mon Sep 17 00:00:00 2001 From: Ting-Lan Wang Date: Tue, 30 Sep 2025 14:43:37 -0400 Subject: [PATCH] Fix Issue #78 --- oracle/common.go | 10 +++++- oracle/create.go | 13 ++++++-- tests/create_test.go | 75 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 3 deletions(-) diff --git a/oracle/common.go b/oracle/common.go index c266137..70db86a 100644 --- a/oracle/common.go +++ b/oracle/common.go @@ -53,7 +53,7 @@ import ( ) // Helper function to get Oracle array type for a field -func getOracleArrayType(field *schema.Field) string { +func getOracleArrayType(field *schema.Field, values []any) string { switch field.DataType { case schema.Bool: return "TABLE OF NUMBER(1)" @@ -64,6 +64,14 @@ func getOracleArrayType(field *schema.Field) string { case schema.String: if field.Size > 0 && field.Size <= 4000 { return fmt.Sprintf("TABLE OF VARCHAR2(%d)", field.Size) + } else { + for _, value := range values { + if strValue, ok := value.(string); ok { + if len(strValue) > 4000 { + return "TABLE OF CLOB" + } + } + } } return "TABLE OF VARCHAR2(4000)" case schema.Time: diff --git a/oracle/create.go b/oracle/create.go index ab49831..0dcac30 100644 --- a/oracle/create.go +++ b/oracle/create.go @@ -296,7 +296,7 @@ func buildBulkMergePLSQL(db *gorm.DB, createValues clause.Values, onConflictClau for i, column := range createValues.Columns { var arrayType string if field := findFieldByDBName(schema, column.Name); field != nil { - arrayType = getOracleArrayType(field) + arrayType = getOracleArrayType(field, pluck(createValues.Values, i)) } else { arrayType = "TABLE OF VARCHAR2(4000)" } @@ -579,7 +579,7 @@ func buildBulkInsertOnlyPLSQL(db *gorm.DB, createValues clause.Values) { for i, column := range createValues.Columns { var arrayType string if field := findFieldByDBName(schema, column.Name); field != nil { - arrayType = getOracleArrayType(field) + arrayType = getOracleArrayType(field, pluck(createValues.Values, i)) } else { arrayType = "TABLE OF VARCHAR2(4000)" } @@ -1007,3 +1007,12 @@ func sanitizeCreateValuesForBulkArrays(stmt *gorm.Statement, cv *clause.Values) } } } + +// pluck extracts the values at index col from a slice of arrays []T. +func pluck[T any, N int](data [][]T, col int) []T { + out := make([]T, len(data)) + for i := range data { + out[i] = data[i][col] + } + return out +} diff --git a/tests/create_test.go b/tests/create_test.go index 35a8040..d964075 100644 --- a/tests/create_test.go +++ b/tests/create_test.go @@ -45,6 +45,7 @@ import ( "strings" "testing" + "github.com/google/uuid" . "github.com/oracle-samples/gorm-oracle/tests/utils" "time" @@ -846,3 +847,77 @@ func TestCreateFromMapWithTable(t *testing.T) { t.Errorf("failed to create data from map with table, @id != id") } } + +// Issue #78: PL/SQL failure when upserting CLOBs +func TestCreateAndUpdateLargeString(t *testing.T) { + DB.Migrator().DropTable(FolderData{}, FolderProperty{}) + + if err := DB.Exec(` + CREATE TABLE "folder_data" ( + "folder_id" VARCHAR2(4000), + "folder_nm" VARCHAR2(4000), + PRIMARY KEY ("folder_id")) + `).Error; err != nil { + t.Errorf("Failed to create table: %v", err) + } + + if err := DB.Exec(` + CREATE TABLE "folder_properties" ( + "seq" NUMBER(20) GENERATED BY DEFAULT AS IDENTITY, + "folder_id" VARCHAR2(4000), + "key" VARCHAR2(4000), + "value" CLOB, + PRIMARY KEY ("folder_id","key"), + CONSTRAINT "fk_folder_data_properties" FOREIGN KEY ("folder_id") REFERENCES "folder_data"("folder_id"), + CONSTRAINT "uni_folder_properties_key" UNIQUE ("key")) + `).Error; err != nil { + t.Errorf("Failed to create table. Got: %v", err) + } + + id := uuid.New().String() + + folder := &FolderData{ + ID: id, + Name: "My Folder", + Properties: []FolderProperty{ + { + ID: id, + Key: "prop1", + Value: strings.Repeat("A", 5000), + }, + { + ID: id, + Key: "prop2", + Value: strings.Repeat("B", 5000), + }, + }, + } + + if err := DB.Create(&folder).Error; err != nil { + t.Errorf("Failed to insert record. Got: %v", err) + } + + createdFolder := &FolderData{} + if err := DB.Model(&FolderData{}).Preload("Properties").First(&createdFolder).Error; err != nil { + t.Errorf("Failed to load record. Got: %v", err) + } + + for i, p := range folder.Properties { + tests.AssertObjEqual(t, p, createdFolder.Properties[i], "Seq", "ID", "Key", "Value") + } + + clobContent := strings.Repeat("C", 4020) + folder.Properties[1].Value = clobContent + if err := DB.Save(&folder).Error; err != nil { + t.Errorf("Failed to update record. Got: %v", err) + } + + updatedFolder := &FolderData{} + if err := DB.Model(&FolderData{}).Preload("Properties").First(&updatedFolder).Error; err != nil { + t.Errorf("Failed to load record. Got: %v", err) + } + + for i, p := range folder.Properties { + tests.AssertObjEqual(t, p, updatedFolder.Properties[i], "Seq", "ID", "Key", "Value") + } +}