Skip to content

Commit c67cb43

Browse files
committed
Merge branch 'main' into migrate_nullable_fix
# Conflicts: # tests/passed-tests.txt
2 parents a59654f + ede94ac commit c67cb43

15 files changed

+1427
-829
lines changed

oracle/clause_builder.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,16 @@ func OnConflictClauseBuilder(c clause.Clause, builder clause.Builder) {
325325
missingColumns = append(missingColumns, conflictCol.Name)
326326
}
327327
}
328+
328329
if len(missingColumns) > 0 {
330+
// primary keys with auto increment will always be missing from create values columns
331+
for _, missingCol := range missingColumns {
332+
field := stmt.Schema.LookUpField(missingCol)
333+
if field != nil && field.PrimaryKey && field.AutoIncrement {
334+
return
335+
}
336+
}
337+
329338
var selectedColumns []string
330339
for col := range selectedColumnSet {
331340
selectedColumns = append(selectedColumns, col)
@@ -335,6 +344,34 @@ func OnConflictClauseBuilder(c clause.Clause, builder clause.Builder) {
335344
return
336345
}
337346

347+
// exclude primary key, default value columns from merge update clause
348+
if len(onConflict.DoUpdates) > 0 {
349+
hasPrimaryKey := false
350+
351+
for _, assignment := range onConflict.DoUpdates {
352+
field := stmt.Schema.LookUpField(assignment.Column.Name)
353+
if field != nil && field.PrimaryKey {
354+
hasPrimaryKey = true
355+
break
356+
}
357+
}
358+
359+
if hasPrimaryKey {
360+
onConflict.DoUpdates = nil
361+
columns := make([]string, 0, len(values.Columns)-1)
362+
for _, col := range values.Columns {
363+
field := stmt.Schema.LookUpField(col.Name)
364+
365+
if field != nil && !field.PrimaryKey && (!field.HasDefaultValue || field.DefaultValueInterface != nil ||
366+
strings.EqualFold(field.DefaultValue, "NULL")) && field.AutoCreateTime == 0 {
367+
columns = append(columns, col.Name)
368+
}
369+
370+
}
371+
onConflict.DoUpdates = append(onConflict.DoUpdates, clause.AssignmentColumns(columns)...)
372+
}
373+
}
374+
338375
// Build MERGE statement
339376
buildMergeInClause(stmt, onConflict, values, conflictColumns)
340377
}

oracle/create.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,9 +267,11 @@ func buildBulkMergePLSQL(db *gorm.DB, createValues clause.Values, onConflictClau
267267
valuesColumnMap[strings.ToUpper(column.Name)] = true
268268
}
269269

270+
// Filter conflict columns to remove non unique columns
270271
var filteredConflictColumns []clause.Column
271272
for _, conflictCol := range conflictColumns {
272-
if valuesColumnMap[strings.ToUpper(conflictCol.Name)] {
273+
field := stmt.Schema.LookUpField(conflictCol.Name)
274+
if valuesColumnMap[strings.ToUpper(conflictCol.Name)] && (field.Unique || field.AutoIncrement) {
273275
filteredConflictColumns = append(filteredConflictColumns, conflictCol)
274276
}
275277
}
@@ -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 ")
@@ -425,7 +428,7 @@ func buildBulkMergePLSQL(db *gorm.DB, createValues clause.Values, onConflictClau
425428
}
426429
plsqlBuilder.WriteString(" WHEN MATCHED THEN UPDATE SET t.")
427430
writeQuotedIdentifier(&plsqlBuilder, noopCol)
428-
plsqlBuilder.WriteString(" = t.")
431+
plsqlBuilder.WriteString(" = s.")
429432
writeQuotedIdentifier(&plsqlBuilder, noopCol)
430433
plsqlBuilder.WriteString("\n")
431434
}

oracle/oracle.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ func (d Dialector) Initialize(db *gorm.DB) (err error) {
102102
callback.Create().Replace("gorm:create", Create)
103103
callback.Delete().Replace("gorm:delete", Delete)
104104
callback.Update().Replace("gorm:update", Update)
105+
callback.Query().Before("gorm:query").Register("oracle:before_query", BeforeQuery)
105106

106107
maps.Copy(db.ClauseBuilders, OracleClauseBuilders())
107108

oracle/query.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
** Copyright (c) 2025 Oracle and/or its affiliates.
3+
**
4+
** The Universal Permissive License (UPL), Version 1.0
5+
**
6+
** Subject to the condition set forth below, permission is hereby granted to any
7+
** person obtaining a copy of this software, associated documentation and/or data
8+
** (collectively the "Software"), free of charge and under any and all copyright
9+
** rights in the Software, and any and all patent rights owned or freely
10+
** licensable by each licensor hereunder covering either (i) the unmodified
11+
** Software as contributed to or provided by such licensor, or (ii) the Larger
12+
** Works (as defined below), to deal in both
13+
**
14+
** (a) the Software, and
15+
** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
16+
** one is included with the Software (each a "Larger Work" to which the Software
17+
** is contributed by such licensors),
18+
**
19+
** without restriction, including without limitation the rights to copy, create
20+
** derivative works of, display, perform, and distribute the Software and make,
21+
** use, sell, offer for sale, import, export, have made, and have sold the
22+
** Software and the Larger Work(s), and to sublicense the foregoing rights on
23+
** either these or other terms.
24+
**
25+
** This license is subject to the following condition:
26+
** The above copyright notice and either this complete permission notice or at
27+
** a minimum a reference to the UPL must be included in all copies or
28+
** substantial portions of the Software.
29+
**
30+
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31+
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32+
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
33+
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
34+
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
35+
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
36+
** SOFTWARE.
37+
*/
38+
39+
package oracle
40+
41+
import (
42+
"gorm.io/gorm"
43+
"regexp"
44+
"strings"
45+
)
46+
47+
// Identifies the table name alias provided as
48+
// "\"users\" \"u\"" and "\"users\" u". Gorm already handles
49+
// the other formats like "users u", "users AS u" etc.
50+
var tableRegexp = regexp.MustCompile(`^"(\w+)"\s+"?(\w+)"?$`)
51+
52+
func BeforeQuery(db *gorm.DB) {
53+
if db == nil || db.Statement == nil || db.Statement.TableExpr == nil {
54+
return
55+
}
56+
name := db.Statement.TableExpr.SQL
57+
if strings.Contains(name, " ") || strings.Contains(name, "`") {
58+
if results := tableRegexp.FindStringSubmatch(name); len(results) == 3 {
59+
if results[2] != "" {
60+
db.Statement.Table = results[2]
61+
} else {
62+
db.Statement.Table = results[1]
63+
}
64+
}
65+
}
66+
return
67+
}

tests/benchmark_test.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import (
4343
"testing"
4444

4545
. "github.com/oracle-samples/gorm-oracle/tests/utils"
46+
4647
)
4748

4849
func BenchmarkCreate(b *testing.B) {
@@ -120,3 +121,113 @@ func BenchmarkDelete(b *testing.B) {
120121
DB.Delete(&user)
121122
}
122123
}
124+
125+
func BenchmarkCreateInBatches(b *testing.B) {
126+
users := make([]User, 100)
127+
for i := 0; i < len(users); i++ {
128+
users[i] = *GetUser(fmt.Sprintf("batch-%d", i), Config{})
129+
}
130+
131+
b.ResetTimer()
132+
for x := 0; x < b.N; x++ {
133+
DB.CreateInBatches(users, 20)
134+
}
135+
}
136+
137+
func BenchmarkFirst(b *testing.B) {
138+
user := *GetUser("first", Config{})
139+
DB.Create(&user)
140+
141+
var u User
142+
b.ResetTimer()
143+
for x := 0; x < b.N; x++ {
144+
DB.First(&u, user.ID)
145+
}
146+
}
147+
148+
func BenchmarkWhere(b *testing.B) {
149+
user := *GetUser("where", Config{})
150+
DB.Create(&user)
151+
152+
var u User
153+
b.ResetTimer()
154+
for x := 0; x < b.N; x++ {
155+
DB.Where("name = ?", user.Name).First(&u)
156+
}
157+
}
158+
159+
func BenchmarkCount(b *testing.B) {
160+
for i := 0; i < 1000; i++ {
161+
user := *GetUser(fmt.Sprintf("count-%d", i), Config{})
162+
DB.Create(&user)
163+
}
164+
165+
var count int64
166+
b.ResetTimer()
167+
for x := 0; x < b.N; x++ {
168+
DB.Model(&User{}).Count(&count)
169+
}
170+
}
171+
172+
func BenchmarkTransaction(b *testing.B) {
173+
for x := 0; x < b.N; x++ {
174+
DB.Transaction(func(tx *gorm.DB) error {
175+
user := *GetUser(fmt.Sprintf("tx-%d", x), Config{})
176+
return tx.Create(&user).Error
177+
})
178+
}
179+
}
180+
181+
func BenchmarkJoin(b *testing.B) {
182+
user := *GetUser("join-user", Config{})
183+
DB.Create(&user)
184+
185+
type Profile struct {
186+
ID uint
187+
UserID uint
188+
Bio string
189+
}
190+
DB.AutoMigrate(&Profile{})
191+
DB.Create(&Profile{UserID: user.ID, Bio: "benchmark profile"})
192+
193+
var result struct {
194+
User
195+
Profile
196+
}
197+
198+
b.ResetTimer()
199+
for x := 0; x < b.N; x++ {
200+
DB.Table("users").
201+
Select("users.id, users.name, profiles.bio").
202+
Joins("left join profiles on profiles.user_id = users.id").
203+
Where("users.id = ?", user.ID).
204+
Scan(&result)
205+
}
206+
}
207+
208+
func BenchmarkPagination(b *testing.B) {
209+
DB.Exec("delete from users")
210+
for i := 0; i < 10000; i++ {
211+
user := *GetUser(fmt.Sprintf("page-%d", i), Config{})
212+
DB.Create(&user)
213+
}
214+
215+
var users []User
216+
b.ResetTimer()
217+
for x := 0; x < b.N; x++ {
218+
DB.Limit(50).Offset(200).Find(&users)
219+
}
220+
}
221+
222+
func BenchmarkBulkDelete(b *testing.B) {
223+
DB.Exec("delete from users")
224+
for i := 0; i < 5000; i++ {
225+
user := *GetUser(fmt.Sprintf("bulk-%d", i), Config{})
226+
DB.Create(&user)
227+
}
228+
229+
b.ResetTimer()
230+
for x := 0; x < b.N; x++ {
231+
DB.Where("id < ?", 2500).Delete(&User{})
232+
}
233+
}

0 commit comments

Comments
 (0)