Skip to content

Commit 6b6a5e8

Browse files
committed
fix: isolate zero-value Model on Statement clone
Prevent stale primary-key WHERE leaking across cloned sessions during Updates. Fixes #7725
1 parent 4380dd6 commit 6b6a5e8

File tree

2 files changed

+31
-1
lines changed

2 files changed

+31
-1
lines changed

statement.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -527,10 +527,20 @@ func (stmt *Statement) ParseWithSpecialTableName(value interface{}, specialTable
527527
}
528528

529529
func (stmt *Statement) clone() *Statement {
530+
var clonedModel interface{} = stmt.Model
531+
if rv := reflect.ValueOf(stmt.Model); rv.IsValid() && rv.Kind() == reflect.Ptr && !rv.IsNil() && rv.Elem().Kind() == reflect.Struct {
532+
zero := reflect.New(rv.Elem().Type()).Elem()
533+
if reflect.DeepEqual(rv.Elem().Interface(), zero.Interface()) {
534+
cp := reflect.New(rv.Elem().Type())
535+
cp.Elem().Set(rv.Elem())
536+
clonedModel = cp.Interface()
537+
}
538+
}
539+
530540
newStmt := &Statement{
531541
TableExpr: stmt.TableExpr,
532542
Table: stmt.Table,
533-
Model: stmt.Model,
543+
Model: clonedModel,
534544
Unscoped: stmt.Unscoped,
535545
Dest: stmt.Dest,
536546
ReflectValue: stmt.ReflectValue,

statement_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,26 @@ func TestWhereCloneCorruption(t *testing.T) {
3535
}
3636
}
3737

38+
func TestStatementCloneDoesNotShareZeroModelPointer(t *testing.T) {
39+
type TestUser struct {
40+
Model
41+
Age uint
42+
}
43+
44+
base := new(Statement)
45+
base.Model = &TestUser{}
46+
47+
cloned := base.clone()
48+
if reflect.ValueOf(cloned.Model).Pointer() == reflect.ValueOf(base.Model).Pointer() {
49+
t.Fatalf("expected cloned.Model to be isolated from base.Model")
50+
}
51+
52+
reflect.ValueOf(cloned.Model).Elem().FieldByName("ID").SetUint(111)
53+
if id := reflect.ValueOf(base.Model).Elem().FieldByName("ID").Uint(); id != 0 {
54+
t.Fatalf("expected base.Model ID to remain 0, got %d", id)
55+
}
56+
}
57+
3858
func TestNilCondition(t *testing.T) {
3959
s := new(Statement)
4060
if len(s.BuildCondition(nil)) != 0 {

0 commit comments

Comments
 (0)