Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion oracle/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Expand All @@ -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:
Expand Down
13 changes: 11 additions & 2 deletions oracle/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
}
Expand Down Expand Up @@ -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)"
}
Expand Down Expand Up @@ -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
}
75 changes: 75 additions & 0 deletions tests/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import (
"strings"
"testing"

"github.com/google/uuid"
. "github.com/oracle-samples/gorm-oracle/tests/utils"

"time"
Expand Down Expand Up @@ -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")
}
}
Loading