Skip to content

Commit 1b3c470

Browse files
committed
Merge remote-tracking branch 'origin' into blobTest
2 parents c9c3a6e + 8748963 commit 1b3c470

File tree

14 files changed

+343
-20
lines changed

14 files changed

+343
-20
lines changed

.github/workflows/run-tests.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
- name: Set up Go
2626
uses: actions/setup-go@v4
2727
with:
28-
go-version: '1.24.4'
28+
go-version: '1.25.1'
2929

3030
- name: Install Oracle Instant Client
3131
run: |

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/oracle-samples/gorm-oracle
22

3-
go 1.24.4
3+
go 1.25.1
44

55
require (
66
github.com/godror/godror v0.49.3

oracle/clause_builder.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,8 @@ func shouldIncludeColumn(stmt *gorm.Statement, columnName string) bool {
503503
stmt.Schema.PrioritizedPrimaryField.AutoIncrement &&
504504
stmt.Schema.PrioritizedPrimaryField.DBName == columnName {
505505
return false
506+
} else if stmt.Schema.LookUpField(columnName).AutoIncrement {
507+
return false
506508
}
507509
return true
508510
}

oracle/create.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,8 @@ func buildBulkMergePLSQL(db *gorm.DB, createValues clause.Values, onConflictClau
398398
schema.PrioritizedPrimaryField.AutoIncrement &&
399399
strings.EqualFold(schema.PrioritizedPrimaryField.DBName, column.Name) {
400400
isAutoIncrement = true
401+
} else if stmt.Schema.LookUpField(column.Name).AutoIncrement {
402+
isAutoIncrement = true
401403
}
402404

403405
if !isConflictColumn && !isAutoIncrement {
@@ -520,7 +522,7 @@ func buildBulkMergePLSQL(db *gorm.DB, createValues clause.Values, onConflictClau
520522
" IF l_affected_records.COUNT > %d THEN :%d := l_affected_records(%d).",
521523
rowIdx, outParamIndex+1, rowIdx+1,
522524
))
523-
writeQuotedIdentifier(&plsqlBuilder, column)
525+
db.QuoteTo(&plsqlBuilder, column)
524526
plsqlBuilder.WriteString("; END IF;\n")
525527
} else {
526528
// datatypes.JSON (text-based) -> serialize to CLOB
@@ -529,13 +531,13 @@ func buildBulkMergePLSQL(db *gorm.DB, createValues clause.Values, onConflictClau
529531
" IF l_affected_records.COUNT > %d THEN :%d := JSON_SERIALIZE(l_affected_records(%d).",
530532
rowIdx, outParamIndex+1, rowIdx+1,
531533
))
532-
writeQuotedIdentifier(&plsqlBuilder, column)
534+
db.QuoteTo(&plsqlBuilder, column)
533535
plsqlBuilder.WriteString(" RETURNING CLOB); END IF;\n")
534536
}
535537
} else {
536538
stmt.Vars = append(stmt.Vars, sql.Out{Dest: createTypedDestination(field)})
537539
plsqlBuilder.WriteString(fmt.Sprintf(" IF l_affected_records.COUNT > %d THEN :%d := l_affected_records(%d).", rowIdx, outParamIndex+1, rowIdx+1))
538-
writeQuotedIdentifier(&plsqlBuilder, column)
540+
db.QuoteTo(&plsqlBuilder, column)
539541
plsqlBuilder.WriteString("; END IF;\n")
540542
}
541543
outParamIndex++
@@ -696,6 +698,8 @@ func shouldIncludeColumnInInsert(stmt *gorm.Statement, columnName string) bool {
696698
stmt.Schema.PrioritizedPrimaryField.AutoIncrement &&
697699
strings.EqualFold(stmt.Schema.PrioritizedPrimaryField.DBName, columnName) {
698700
return false
701+
} else if stmt.Schema.LookUpField(columnName).AutoIncrement {
702+
return false
699703
}
700704
return true
701705
}

oracle/delete.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -299,14 +299,14 @@ func buildBulkDeletePLSQL(db *gorm.DB) {
299299
" IF l_deleted_records.COUNT > %d THEN :%d := l_deleted_records(%d).",
300300
rowIdx, outParamIndex+1, rowIdx+1,
301301
))
302-
writeQuotedIdentifier(&plsqlBuilder, column)
302+
db.QuoteTo(&plsqlBuilder, column)
303303
plsqlBuilder.WriteString("; END IF;\n")
304304
} else {
305305
// JSON -> text bind
306306
stmt.Vars = append(stmt.Vars, sql.Out{Dest: new(string)})
307307
plsqlBuilder.WriteString(fmt.Sprintf(" IF l_deleted_records.COUNT > %d THEN\n", rowIdx))
308308
plsqlBuilder.WriteString(fmt.Sprintf(" :%d := JSON_SERIALIZE(l_deleted_records(%d).", outParamIndex+1, rowIdx+1))
309-
writeQuotedIdentifier(&plsqlBuilder, column)
309+
db.QuoteTo(&plsqlBuilder, column)
310310
plsqlBuilder.WriteString(" RETURNING CLOB);\n")
311311
plsqlBuilder.WriteString(" END IF;\n")
312312
}
@@ -316,7 +316,7 @@ func buildBulkDeletePLSQL(db *gorm.DB) {
316316
stmt.Vars = append(stmt.Vars, sql.Out{Dest: dest})
317317
plsqlBuilder.WriteString(fmt.Sprintf(" IF l_deleted_records.COUNT > %d THEN\n", rowIdx))
318318
plsqlBuilder.WriteString(fmt.Sprintf(" :%d := l_deleted_records(%d).", outParamIndex+1, rowIdx+1))
319-
writeQuotedIdentifier(&plsqlBuilder, column)
319+
db.QuoteTo(&plsqlBuilder, column)
320320
plsqlBuilder.WriteString(";\n")
321321
plsqlBuilder.WriteString(" END IF;\n")
322322
}

oracle/oracle.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,18 @@ func (d Dialector) Initialize(db *gorm.DB) (err error) {
109109
callback.Update().Replace("gorm:update", Update)
110110
callback.Query().Before("gorm:query").Register("oracle:before_query", BeforeQuery)
111111

112+
if d.SkipQuoteIdentifiers {
113+
// When identifiers are not quoted, columns are returned by Oracle in uppercase.
114+
// Fields in the models may be lower case for compatibility with other databases.
115+
// Match them up with the fields using the column mapping.
116+
oracleCaseHandler := "oracle:case_handler"
117+
if callback.Query().Get(oracleCaseHandler) == nil {
118+
if err := callback.Query().Before("gorm:query").Register(oracleCaseHandler, MismatchedCaseHandler); err != nil {
119+
return err
120+
}
121+
}
122+
}
123+
112124
maps.Copy(db.ClauseBuilders, OracleClauseBuilders())
113125

114126
if d.Conn == nil {

oracle/query.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,19 @@ func BeforeQuery(db *gorm.DB) {
6565
}
6666
}
6767
}
68+
69+
// MismatchedCaseHandler handles Oracle Case Insensitivity.
70+
// When identifiers are not quoted, columns are returned by Oracle in uppercase.
71+
// Fields in the models may be lower case for compatibility with other databases.
72+
// Match them up with the fields using the column mapping.
73+
func MismatchedCaseHandler(gormDB *gorm.DB) {
74+
if gormDB.Statement == nil || gormDB.Statement.Schema == nil {
75+
return
76+
}
77+
if len(gormDB.Statement.Schema.Fields) > 0 && gormDB.Statement.ColumnMapping == nil {
78+
gormDB.Statement.ColumnMapping = map[string]string{}
79+
}
80+
for _, field := range gormDB.Statement.Schema.Fields {
81+
gormDB.Statement.ColumnMapping[strings.ToUpper(field.DBName)] = field.Name
82+
}
83+
}

oracle/update.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -572,17 +572,17 @@ func buildUpdatePLSQL(db *gorm.DB) {
572572
if isJSONField(field) {
573573
if isRawMessageField(field) {
574574
plsqlBuilder.WriteString(fmt.Sprintf("l_updated_records(%d).", rowIdx+1))
575-
writeQuotedIdentifier(&plsqlBuilder, column)
575+
db.QuoteTo(&plsqlBuilder, column)
576576
} else {
577577
// serialize JSON so it binds as text
578578
plsqlBuilder.WriteString("JSON_SERIALIZE(")
579579
plsqlBuilder.WriteString(fmt.Sprintf("l_updated_records(%d).", rowIdx+1))
580-
writeQuotedIdentifier(&plsqlBuilder, column)
580+
db.QuoteTo(&plsqlBuilder, column)
581581
plsqlBuilder.WriteString(" RETURNING CLOB)")
582582
}
583583
} else {
584584
plsqlBuilder.WriteString(fmt.Sprintf("l_updated_records(%d).", rowIdx+1))
585-
writeQuotedIdentifier(&plsqlBuilder, column)
585+
db.QuoteTo(&plsqlBuilder, column)
586586
}
587587

588588
plsqlBuilder.WriteString("; END IF;\n")

tests/associations_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,11 @@
3939
package tests
4040

4141
import (
42+
"errors"
4243
"strings"
4344
"testing"
4445

46+
"github.com/google/uuid"
4547
. "github.com/oracle-samples/gorm-oracle/tests/utils"
4648

4749
"gorm.io/gorm"
@@ -543,3 +545,66 @@ func TestBasicManyToManyAssociation(t *testing.T) {
543545

544546
AssertAssociationCount(t, user, "Languages", 0, "after clear")
545547
}
548+
549+
func TestSaveAssociationWithAutoIncrementField(t *testing.T) {
550+
DB.Migrator().DropTable(&FolderData{}, &FolderProperty{})
551+
DB.Migrator().CreateTable(&FolderData{}, &FolderProperty{})
552+
553+
id := uuid.New().String()
554+
folder := FolderData{
555+
ID: id,
556+
Name: "My Folder",
557+
Properties: []FolderProperty{
558+
{
559+
ID: id,
560+
Key: "foo1",
561+
Value: "bar1",
562+
},
563+
{
564+
ID: id,
565+
Key: "foo2",
566+
Value: "bar2",
567+
},
568+
},
569+
}
570+
571+
if err := DB.Create(&folder).Error; err != nil {
572+
t.Errorf("Failed to insert data, got %v", err)
573+
}
574+
575+
createdFolder := FolderData{}
576+
if err := DB.Model(&FolderData{}).Preload("Properties").First(&createdFolder).Error; err != nil {
577+
t.Errorf("Failed to query data, got %v", err)
578+
}
579+
580+
CheckFolderData(t, createdFolder, folder)
581+
582+
createdFolder.Properties[1].Value = "baz1"
583+
createdFolder.Properties = append(createdFolder.Properties, FolderProperty{
584+
ID: id,
585+
Key: "foo3",
586+
Value: "bar3",
587+
})
588+
createdFolder.Properties = append(createdFolder.Properties, FolderProperty{
589+
ID: id,
590+
Key: "foo4",
591+
Value: "bar4",
592+
})
593+
DB.Save(&createdFolder)
594+
595+
updatedFolder := FolderData{}
596+
if err := DB.Model(&FolderData{}).Preload("Properties").First(&updatedFolder).Error; err != nil {
597+
t.Errorf("Failed to query data, got %v", err)
598+
}
599+
600+
CheckFolderData(t, updatedFolder, createdFolder)
601+
602+
if err := DB.Select(clause.Associations).Delete(&createdFolder).Error; err != nil {
603+
t.Errorf("Failed to delete data, got %v", err)
604+
}
605+
606+
result := FolderData{}
607+
if err := DB.Where("\"folder_id\" = ?", createdFolder.ID).First(&result).Error; err == nil || !errors.Is(err, gorm.ErrRecordNotFound) {
608+
t.Errorf("should returns record not found error, but got %v", err)
609+
}
610+
}

tests/config_test.go

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,23 +78,57 @@ func TestSkipQuoteIdentifiers(t *testing.T) {
7878
t.Errorf("Failed to get column: name")
7979
}
8080

81+
student := Student{ID: 1, Name: "John"}
82+
if err := db.Model(&Student{}).Create(&student).Error; err != nil {
83+
t.Errorf("Failed to insert student, got %v", err)
84+
}
85+
86+
var result Student
87+
if err := db.First(&result).Error; err != nil {
88+
t.Errorf("Failed to query first student, got %v", err)
89+
}
90+
91+
if result.ID != student.ID {
92+
t.Errorf("id should be %v, but got %v", student.ID, result.ID)
93+
}
94+
95+
if result.Name != student.Name {
96+
t.Errorf("name should be %v, but got %v", student.Name, result.Name)
97+
}
98+
}
99+
100+
func TestSkipQuoteIdentifiersSQL(t *testing.T) {
101+
db, err := openTestDBWithOptions(
102+
&oracle.Config{SkipQuoteIdentifiers: true},
103+
&gorm.Config{Logger: newLogger})
104+
if err != nil {
105+
t.Fatalf("failed to connect database, got error %v", err)
106+
}
81107
dryrunDB := db.Session(&gorm.Session{DryRun: true})
82108

83-
result := dryrunDB.Model(&Student{}).Create(&Student{ID: 1, Name: "John"})
109+
insertedStudent := Student{ID: 1, Name: "John"}
110+
result := dryrunDB.Model(&Student{}).Create(&insertedStudent)
111+
84112
if !regexp.MustCompile(`^INSERT INTO STUDENTS \(name,id\) VALUES \(:1,:2\)$`).MatchString(result.Statement.SQL.String()) {
85113
t.Errorf("invalid insert SQL, got %v", result.Statement.SQL.String())
86114
}
87115

88-
result = dryrunDB.First(&Student{})
116+
// Test First
117+
var firstStudent Student
118+
result = dryrunDB.First(&firstStudent)
119+
89120
if !regexp.MustCompile(`^SELECT \* FROM STUDENTS ORDER BY STUDENTS\.id FETCH NEXT 1 ROW ONLY$`).MatchString(result.Statement.SQL.String()) {
90121
t.Fatalf("SQL should include selected names, but got %v", result.Statement.SQL.String())
91122
}
92123

93-
result = dryrunDB.Find(&Student{ID: 1, Name: "John"})
94-
if !regexp.MustCompile(`^SELECT \* FROM STUDENTS WHERE STUDENTS\.id = :1$`).MatchString(result.Statement.SQL.String()) {
124+
// Test Find
125+
var foundStudent Student
126+
result = dryrunDB.Find(foundStudent, "id = ?", insertedStudent.ID)
127+
if !regexp.MustCompile(`^SELECT \* FROM STUDENTS WHERE id = :1$`).MatchString(result.Statement.SQL.String()) {
95128
t.Fatalf("SQL should include selected names, but got %v", result.Statement.SQL.String())
96129
}
97130

131+
// Test Save
98132
result = dryrunDB.Save(&Student{ID: 2, Name: "Mary"})
99133
if !regexp.MustCompile(`^UPDATE STUDENTS SET name=:1 WHERE id = :2$`).MatchString(result.Statement.SQL.String()) {
100134
t.Fatalf("SQL should include selected names, but got %v", result.Statement.SQL.String())

0 commit comments

Comments
 (0)