Repository: go-gorm/gorm
Pull request #7298 (2024-12-02 16:00:20)

Fix concurrent map writes #7297
<!--
Make sure these boxes checked before submitting your pull request.

For significant changes, please open an issue to make an agreement on an implementation design/plan first before starting it.
-->

- [x] Do only one thing
- [x] Non breaking API changes
- [ ] Tested

### What did this pull request do?

Fixes `fatal error: concurrent map writes` error (https://github.com/go-gorm/gorm/issues/7297)
<!--
provide a general description of the code changes in your pull request
-->

### User Case Description
Using normalized tables
<!-- Your use case -->



Patch:
diff --git a/LICENSE b/LICENSE
index 037e165..52964f1 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 The MIT License (MIT)
 
-Copyright (c) 2013-NOW  Jinzhu <wosmvp@gmail.com>
+Copyright (c) 2013-present  Jinzhu <wosmvp@gmail.com>
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/clause/returning.go b/clause/returning.go
index d94b7a4..76064c4 100644
--- a/clause/returning.go
+++ b/clause/returning.go
@@ -26,9 +26,12 @@ func (returning Returning) Build(builder Builder) {
 
 // MergeClause merge order by clauses
 func (returning Returning) MergeClause(clause *Clause) {
-	if v, ok := clause.Expression.(Returning); ok {
-		returning.Columns = append(v.Columns, returning.Columns...)
+	if v, ok := clause.Expression.(Returning); ok && len(returning.Columns) > 0 {
+		if v.Columns != nil {
+			returning.Columns = append(v.Columns, returning.Columns...)
+		} else {
+			returning.Columns = nil
+		}
 	}
-
 	clause.Expression = returning
 }
diff --git a/logger/logger.go b/logger/logger.go
index 253f032..8088cde 100644
--- a/logger/logger.go
+++ b/logger/logger.go
@@ -80,6 +80,11 @@ var (
 	})
 	// Recorder logger records running SQL into a recorder instance
 	Recorder = traceRecorder{Interface: Default, BeginAt: time.Now()}
+
+	// RecorderParamsFilter defaults to no-op, allows to be run-over by a different implementation
+	RecorderParamsFilter = func(ctx context.Context, sql string, params ...interface{}) (string, []interface{}) {
+		return sql, params
+	}
 )
 
 // New initialize logger
@@ -211,3 +216,10 @@ func (l *traceRecorder) Trace(ctx context.Context, begin time.Time, fc func() (s
 	l.SQL, l.RowsAffected = fc()
 	l.Err = err
 }
+
+func (l *traceRecorder) ParamsFilter(ctx context.Context, sql string, params ...interface{}) (string, []interface{}) {
+	if RecorderParamsFilter == nil {
+		return sql, params
+	}
+	return RecorderParamsFilter(ctx, sql, params...)
+}
diff --git a/schema/index.go b/schema/index.go
index 786e210..72fc6ca 100644
--- a/schema/index.go
+++ b/schema/index.go
@@ -23,7 +23,7 @@ type IndexOption struct {
 	Sort       string // DESC, ASC
 	Collate    string
 	Length     int
-	priority   int
+	Priority   int
 }
 
 // ParseIndexes parse schema indexes
@@ -64,7 +64,7 @@ func (schema *Schema) ParseIndexes() []*Index {
 
 				idx.Fields = append(idx.Fields, index.Fields...)
 				sort.Slice(idx.Fields, func(i, j int) bool {
-					return idx.Fields[i].priority < idx.Fields[j].priority
+					return idx.Fields[i].Priority < idx.Fields[j].Priority
 				})
 			}
 		}
@@ -155,7 +155,7 @@ func parseFieldIndexes(field *Field) (indexes []Index, err error) {
 						Sort:       settings["SORT"],
 						Collate:    settings["COLLATE"],
 						Length:     length,
-						priority:   priority,
+						Priority:   priority,
 					}},
 				})
 			}
diff --git a/schema/relationship.go b/schema/relationship.go
index 32676b3..def4a59 100644
--- a/schema/relationship.go
+++ b/schema/relationship.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"reflect"
 	"strings"
+	"sync"
 
 	"github.com/jinzhu/inflection"
 	"golang.org/x/text/cases"
@@ -32,6 +33,8 @@ type Relationships struct {
 	Relations map[string]*Relationship
 
 	EmbeddedRelations map[string]*Relationships
+
+	Mux sync.RWMutex
 }
 
 type Relationship struct {
@@ -98,9 +101,10 @@ func (schema *Schema) parseRelation(field *Field) *Relationship {
 	}
 
 	if relation.Type == has {
-		// don't add relations to embedded schema, which might be shared
 		if relation.FieldSchema != relation.Schema && relation.Polymorphic == nil && field.OwnerSchema == nil {
+			relation.FieldSchema.Relationships.Mux.Lock()
 			relation.FieldSchema.Relationships.Relations["_"+relation.Schema.Name+"_"+relation.Name] = relation
+			relation.FieldSchema.Relationships.Mux.Unlock()
 		}
 
 		switch field.IndirectFieldType.Kind() {


Test patch:
diff --git a/clause/returning_test.go b/clause/returning_test.go
index bd0ecce..b7a408b 100644
--- a/clause/returning_test.go
+++ b/clause/returning_test.go
@@ -26,6 +26,22 @@ func TestReturning(t *testing.T) {
 			}},
 			"SELECT * FROM `users` RETURNING `users`.`id`,`name`,`age`", nil,
 		},
+		{
+			[]clause.Interface{clause.Select{}, clause.From{}, clause.Returning{
+				[]clause.Column{clause.PrimaryColumn},
+			}, clause.Returning{}, clause.Returning{
+				[]clause.Column{{Name: "name"}, {Name: "age"}},
+			}},
+			"SELECT * FROM `users` RETURNING *", nil,
+		},
+		{
+			[]clause.Interface{clause.Select{}, clause.From{}, clause.Returning{
+				[]clause.Column{clause.PrimaryColumn},
+			}, clause.Returning{
+				[]clause.Column{{Name: "name"}, {Name: "age"}},
+			}, clause.Returning{}},
+			"SELECT * FROM `users` RETURNING *", nil,
+		},
 	}
 
 	for idx, result := range results {