diff --git a/oracle/clause_builder.go b/oracle/clause_builder.go index b588d59..5a0b7a2 100644 --- a/oracle/clause_builder.go +++ b/oracle/clause_builder.go @@ -503,6 +503,8 @@ func shouldIncludeColumn(stmt *gorm.Statement, columnName string) bool { stmt.Schema.PrioritizedPrimaryField.AutoIncrement && stmt.Schema.PrioritizedPrimaryField.DBName == columnName { return false + } else if stmt.Schema.LookUpField(columnName).AutoIncrement { + return false } return true } diff --git a/oracle/create.go b/oracle/create.go index e7ba921..ab49831 100644 --- a/oracle/create.go +++ b/oracle/create.go @@ -398,6 +398,8 @@ func buildBulkMergePLSQL(db *gorm.DB, createValues clause.Values, onConflictClau schema.PrioritizedPrimaryField.AutoIncrement && strings.EqualFold(schema.PrioritizedPrimaryField.DBName, column.Name) { isAutoIncrement = true + } else if stmt.Schema.LookUpField(column.Name).AutoIncrement { + isAutoIncrement = true } if !isConflictColumn && !isAutoIncrement { @@ -520,7 +522,7 @@ func buildBulkMergePLSQL(db *gorm.DB, createValues clause.Values, onConflictClau " IF l_affected_records.COUNT > %d THEN :%d := l_affected_records(%d).", rowIdx, outParamIndex+1, rowIdx+1, )) - writeQuotedIdentifier(&plsqlBuilder, column) + db.QuoteTo(&plsqlBuilder, column) plsqlBuilder.WriteString("; END IF;\n") } else { // datatypes.JSON (text-based) -> serialize to CLOB @@ -529,13 +531,13 @@ func buildBulkMergePLSQL(db *gorm.DB, createValues clause.Values, onConflictClau " IF l_affected_records.COUNT > %d THEN :%d := JSON_SERIALIZE(l_affected_records(%d).", rowIdx, outParamIndex+1, rowIdx+1, )) - writeQuotedIdentifier(&plsqlBuilder, column) + db.QuoteTo(&plsqlBuilder, column) plsqlBuilder.WriteString(" RETURNING CLOB); END IF;\n") } } else { stmt.Vars = append(stmt.Vars, sql.Out{Dest: createTypedDestination(field)}) plsqlBuilder.WriteString(fmt.Sprintf(" IF l_affected_records.COUNT > %d THEN :%d := l_affected_records(%d).", rowIdx, outParamIndex+1, rowIdx+1)) - writeQuotedIdentifier(&plsqlBuilder, column) + db.QuoteTo(&plsqlBuilder, column) plsqlBuilder.WriteString("; END IF;\n") } outParamIndex++ @@ -696,6 +698,8 @@ func shouldIncludeColumnInInsert(stmt *gorm.Statement, columnName string) bool { stmt.Schema.PrioritizedPrimaryField.AutoIncrement && strings.EqualFold(stmt.Schema.PrioritizedPrimaryField.DBName, columnName) { return false + } else if stmt.Schema.LookUpField(columnName).AutoIncrement { + return false } return true } diff --git a/oracle/delete.go b/oracle/delete.go index b658971..56abcc9 100644 --- a/oracle/delete.go +++ b/oracle/delete.go @@ -299,14 +299,14 @@ func buildBulkDeletePLSQL(db *gorm.DB) { " IF l_deleted_records.COUNT > %d THEN :%d := l_deleted_records(%d).", rowIdx, outParamIndex+1, rowIdx+1, )) - writeQuotedIdentifier(&plsqlBuilder, column) + db.QuoteTo(&plsqlBuilder, column) plsqlBuilder.WriteString("; END IF;\n") } else { // JSON -> text bind stmt.Vars = append(stmt.Vars, sql.Out{Dest: new(string)}) plsqlBuilder.WriteString(fmt.Sprintf(" IF l_deleted_records.COUNT > %d THEN\n", rowIdx)) plsqlBuilder.WriteString(fmt.Sprintf(" :%d := JSON_SERIALIZE(l_deleted_records(%d).", outParamIndex+1, rowIdx+1)) - writeQuotedIdentifier(&plsqlBuilder, column) + db.QuoteTo(&plsqlBuilder, column) plsqlBuilder.WriteString(" RETURNING CLOB);\n") plsqlBuilder.WriteString(" END IF;\n") } @@ -316,7 +316,7 @@ func buildBulkDeletePLSQL(db *gorm.DB) { stmt.Vars = append(stmt.Vars, sql.Out{Dest: dest}) plsqlBuilder.WriteString(fmt.Sprintf(" IF l_deleted_records.COUNT > %d THEN\n", rowIdx)) plsqlBuilder.WriteString(fmt.Sprintf(" :%d := l_deleted_records(%d).", outParamIndex+1, rowIdx+1)) - writeQuotedIdentifier(&plsqlBuilder, column) + db.QuoteTo(&plsqlBuilder, column) plsqlBuilder.WriteString(";\n") plsqlBuilder.WriteString(" END IF;\n") } diff --git a/oracle/update.go b/oracle/update.go index a54eebb..9de9e47 100644 --- a/oracle/update.go +++ b/oracle/update.go @@ -572,17 +572,17 @@ func buildUpdatePLSQL(db *gorm.DB) { if isJSONField(field) { if isRawMessageField(field) { plsqlBuilder.WriteString(fmt.Sprintf("l_updated_records(%d).", rowIdx+1)) - writeQuotedIdentifier(&plsqlBuilder, column) + db.QuoteTo(&plsqlBuilder, column) } else { // serialize JSON so it binds as text plsqlBuilder.WriteString("JSON_SERIALIZE(") plsqlBuilder.WriteString(fmt.Sprintf("l_updated_records(%d).", rowIdx+1)) - writeQuotedIdentifier(&plsqlBuilder, column) + db.QuoteTo(&plsqlBuilder, column) plsqlBuilder.WriteString(" RETURNING CLOB)") } } else { plsqlBuilder.WriteString(fmt.Sprintf("l_updated_records(%d).", rowIdx+1)) - writeQuotedIdentifier(&plsqlBuilder, column) + db.QuoteTo(&plsqlBuilder, column) } plsqlBuilder.WriteString("; END IF;\n") diff --git a/tests/associations_test.go b/tests/associations_test.go index 59cd185..2cd2c47 100644 --- a/tests/associations_test.go +++ b/tests/associations_test.go @@ -39,9 +39,11 @@ package tests import ( + "errors" "strings" "testing" + "github.com/google/uuid" . "github.com/oracle-samples/gorm-oracle/tests/utils" "gorm.io/gorm" @@ -543,3 +545,66 @@ func TestBasicManyToManyAssociation(t *testing.T) { AssertAssociationCount(t, user, "Languages", 0, "after clear") } + +func TestSaveAssociationWithAutoIncrementField(t *testing.T) { + DB.Migrator().DropTable(&FolderData{}, &FolderProperty{}) + DB.Migrator().CreateTable(&FolderData{}, &FolderProperty{}) + + id := uuid.New().String() + folder := FolderData{ + ID: id, + Name: "My Folder", + Properties: []FolderProperty{ + { + ID: id, + Key: "foo1", + Value: "bar1", + }, + { + ID: id, + Key: "foo2", + Value: "bar2", + }, + }, + } + + if err := DB.Create(&folder).Error; err != nil { + t.Errorf("Failed to insert data, got %v", err) + } + + createdFolder := FolderData{} + if err := DB.Model(&FolderData{}).Preload("Properties").First(&createdFolder).Error; err != nil { + t.Errorf("Failed to query data, got %v", err) + } + + CheckFolderData(t, createdFolder, folder) + + createdFolder.Properties[1].Value = "baz1" + createdFolder.Properties = append(createdFolder.Properties, FolderProperty{ + ID: id, + Key: "foo3", + Value: "bar3", + }) + createdFolder.Properties = append(createdFolder.Properties, FolderProperty{ + ID: id, + Key: "foo4", + Value: "bar4", + }) + DB.Save(&createdFolder) + + updatedFolder := FolderData{} + if err := DB.Model(&FolderData{}).Preload("Properties").First(&updatedFolder).Error; err != nil { + t.Errorf("Failed to query data, got %v", err) + } + + CheckFolderData(t, updatedFolder, createdFolder) + + if err := DB.Select(clause.Associations).Delete(&createdFolder).Error; err != nil { + t.Errorf("Failed to delete data, got %v", err) + } + + result := FolderData{} + if err := DB.Where("\"folder_id\" = ?", createdFolder.ID).First(&result).Error; err == nil || !errors.Is(err, gorm.ErrRecordNotFound) { + t.Errorf("should returns record not found error, but got %v", err) + } +} diff --git a/tests/helper_test.go b/tests/helper_test.go index 535ef96..d07a5bd 100644 --- a/tests/helper_test.go +++ b/tests/helper_test.go @@ -357,3 +357,24 @@ func db(unscoped bool) *gorm.DB { return DB } } + +func CheckFolderData(t *testing.T, folderData FolderData, expect FolderData) { + tests.AssertObjEqual(t, folderData, expect, "ID", "Name") + t.Run("Properties", func(t *testing.T) { + if len(folderData.Properties) != len(expect.Properties) { + t.Fatalf("properties should equal, expect: %v, got %v", len(expect.Properties), len(folderData.Properties)) + } + + sort.Slice(folderData.Properties, func(i, j int) bool { + return folderData.Properties[i].ID > folderData.Properties[j].ID + }) + + sort.Slice(expect.Properties, func(i, j int) bool { + return expect.Properties[i].ID > expect.Properties[j].ID + }) + + for idx, property := range folderData.Properties { + tests.AssertObjEqual(t, property, expect.Properties[idx], "Seq", "ID", "Key", "Value") + } + }) +} diff --git a/tests/utils/models.go b/tests/utils/models.go index cca6fdc..2b033f6 100644 --- a/tests/utils/models.go +++ b/tests/utils/models.go @@ -102,3 +102,16 @@ type Child struct { ParentID *uint Parent *Parent } + +type FolderProperty struct { + Seq uint64 `gorm:"autoIncrement"` + ID string `gorm:"primaryKey;column:folder_id"` + Key string `gorm:"primaryKey;unique"` + Value string +} + +type FolderData struct { + ID string `gorm:"primaryKey;column:folder_id"` + Name string `gorm:"column:folder_nm"` + Properties []FolderProperty `gorm:"foreignKey:ID;PRELOAD:false"` +}