Repository: go-gorm/gorm (go)

Issue:
Behavior of `.Not(Where().Or())` has changed
## GORM Playground Link

<!--
To ensure your issue be handled, the issue *MUST* include a GORM Playground Pull Request Link that can reproduce the bug, which is important to help others understand your issue effectively and make sure the issue hasn't been fixed, refer: https://github.com/go-gorm/playground

Without the link, your issue most likely will be IGNORED

CHANGE FOLLOWING URL TO YOUR PLAYGROUND LINK
-->

https://github.com/go-gorm/playground/pull/711
https://github.com/go-gorm/playground/pull/710

## Description

This is related to https://github.com/go-gorm/gorm/issues/6845.

The behavior of `.Not(DB.Where().Or())` has changed.

In the sample code,

for v1.25.5, the playground code produces: `WHERE NOT (name = 'a' OR name = 'b')`
for master, the playground code produces: `WHERE NOT (name = 'a' AND name = 'b')`


Pull request description:
fix: not clause with or condition
<!--
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
- [x] Tested

### What did this pull request do?

<!--
provide a general description of the code changes in your pull request
-->
close #6942

### User Case Description

<!-- Your use case -->


Patch:
diff --git a/callbacks/preload.go b/callbacks/preload.go
index 09f151c..112343f 100644
--- a/callbacks/preload.go
+++ b/callbacks/preload.go
@@ -123,8 +123,18 @@ func preloadEntryPoint(db *gorm.DB, joins []string, relationships *schema.Relati
 			if joined, nestedJoins := isJoined(name); joined {
 				switch rv := db.Statement.ReflectValue; rv.Kind() {
 				case reflect.Slice, reflect.Array:
-					for i := 0; i < rv.Len(); i++ {
-						reflectValue := rel.Field.ReflectValueOf(db.Statement.Context, rv.Index(i))
+					if rv.Len() > 0 {
+						reflectValue := rel.FieldSchema.MakeSlice().Elem()
+						reflectValue.SetLen(rv.Len())
+						for i := 0; i < rv.Len(); i++ {
+							frv := rel.Field.ReflectValueOf(db.Statement.Context, rv.Index(i))
+							if frv.Kind() != reflect.Ptr {
+								reflectValue.Index(i).Set(frv.Addr())
+							} else {
+								reflectValue.Index(i).Set(frv)
+							}
+						}
+
 						tx := preloadDB(db, reflectValue, reflectValue.Interface())
 						if err := preloadEntryPoint(tx, nestedJoins, &tx.Statement.Schema.Relationships, preloadMap[name], associationsConds); err != nil {
 							return err
diff --git a/clause/where.go b/clause/where.go
index 9ac7857..2c3c90f 100644
--- a/clause/where.go
+++ b/clause/where.go
@@ -215,7 +215,12 @@ func (not NotConditions) Build(builder Builder) {
 
 		for idx, c := range not.Exprs {
 			if idx > 0 {
-				builder.WriteString(AndWithSpace)
+				switch c.(type) {
+				case OrConditions:
+					builder.WriteString(OrWithSpace)
+				default:
+					builder.WriteString(AndWithSpace)
+				}
 			}
 
 			e, wrapInParentheses := c.(Expr)


Test patch:
diff --git a/clause/where_test.go b/clause/where_test.go
index 7d5aca1..ad23a4e 100644
--- a/clause/where_test.go
+++ b/clause/where_test.go
@@ -113,6 +113,22 @@ func TestWhere(t *testing.T) {
 			"SELECT * FROM `users` WHERE NOT (`score` <= ? AND `age` <= ?)",
 			[]interface{}{100, 60},
 		},
+		{
+			[]clause.Interface{clause.Select{}, clause.From{}, clause.Where{
+				Exprs: []clause.Expression{
+					clause.Not(clause.AndConditions{
+						Exprs: []clause.Expression{
+							clause.Eq{Column: clause.PrimaryColumn, Value: "1"},
+							clause.Gt{Column: "age", Value: 18},
+						}}, clause.OrConditions{
+						Exprs: []clause.Expression{
+							clause.Lt{Column: "score", Value: 100},
+						},
+					}),
+				}}},
+			"SELECT * FROM `users` WHERE NOT ((`users`.`id` = ? AND `age` > ?) OR `score` < ?)",
+			[]interface{}{"1", 18, 100},
+		},
 	}
 
 	for idx, result := range results {
diff --git a/tests/go.mod b/tests/go.mod
index d58469c..10fa7ec 100644
--- a/tests/go.mod
+++ b/tests/go.mod
@@ -26,7 +26,7 @@ require (
 	github.com/jinzhu/inflection v1.0.0 // indirect
 	github.com/kr/text v0.2.0 // indirect
 	github.com/mattn/go-sqlite3 v1.14.22 // indirect
-	github.com/microsoft/go-mssqldb v1.7.0 // indirect
+	github.com/microsoft/go-mssqldb v1.7.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/rogpeppe/go-internal v1.12.0 // indirect
 	golang.org/x/crypto v0.22.0 // indirect
@@ -37,3 +37,5 @@ require (
 replace gorm.io/gorm => ../
 
 replace github.com/jackc/pgx/v5 => github.com/jackc/pgx/v5 v5.4.3
+
+replace github.com/microsoft/go-mssqldb => github.com/microsoft/go-mssqldb v1.7.0
diff --git a/tests/preload_test.go b/tests/preload_test.go
index 5c87534..6e0e91b 100644
--- a/tests/preload_test.go
+++ b/tests/preload_test.go
@@ -1,14 +1,14 @@
 package tests_test
 
 import (
+	"context"
 	"encoding/json"
 	"regexp"
 	"sort"
 	"strconv"
 	"sync"
 	"testing"
-
-	"github.com/stretchr/testify/require"
+	"time"
 
 	"gorm.io/gorm"
 	"gorm.io/gorm/clause"
@@ -337,7 +337,7 @@ func TestNestedPreloadWithNestedJoin(t *testing.T) {
 	DB.Migrator().DropTable(&Preload{}, &Join{}, &Nested{}, &Value{})
 	DB.Migrator().AutoMigrate(&Preload{}, &Join{}, &Nested{}, &Value{})
 
-	value := Value{
+	value1 := Value{
 		Name: "value",
 		Nested: Nested{
 			Preloads: []*Preload{
@@ -346,32 +346,98 @@ func TestNestedPreloadWithNestedJoin(t *testing.T) {
 			Join: Join{Value: "j1"},
 		},
 	}
-	if err := DB.Create(&value).Error; err != nil {
+	value2 := Value{
+		Name: "value2",
+		Nested: Nested{
+			Preloads: []*Preload{
+				{Value: "p3"}, {Value: "p4"}, {Value: "p5"},
+			},
+			Join: Join{Value: "j2"},
+		},
+	}
+
+	values := []*Value{&value1, &value2}
+	if err := DB.Create(&values).Error; err != nil {
 		t.Errorf("failed to create value, got err: %v", err)
 	}
 
 	var find1 Value
-	err := DB.Joins("Nested").Joins("Nested.Join").Preload("Nested.Preloads").First(&find1).Error
+	err := DB.Joins("Nested").Joins("Nested.Join").Preload("Nested.Preloads").First(&find1, value1.ID).Error
 	if err != nil {
 		t.Errorf("failed to find value, got err: %v", err)
 	}
-	AssertEqual(t, find1, value)
+	AssertEqual(t, find1, value1)
 
 	var find2 Value
 	// Joins will automatically add Nested queries.
-	err = DB.Joins("Nested.Join").Preload("Nested.Preloads").First(&find2).Error
+	err = DB.Joins("Nested.Join").Preload("Nested.Preloads").First(&find2, value2.ID).Error
 	if err != nil {
 		t.Errorf("failed to find value, got err: %v", err)
 	}
-	AssertEqual(t, find2, value)
+	AssertEqual(t, find2, value2)
 
 	var finds []Value
 	err = DB.Joins("Nested.Join").Joins("Nested").Preload("Nested.Preloads").Find(&finds).Error
 	if err != nil {
 		t.Errorf("failed to find value, got err: %v", err)
 	}
-	require.Len(t, finds, 1)
-	AssertEqual(t, finds[0], value)
+	AssertEqual(t, len(finds), 2)
+	AssertEqual(t, finds[0], value1)
+	AssertEqual(t, finds[1], value2)
+}
+
+func TestMergeNestedPreloadWithNestedJoin(t *testing.T) {
+	users := []User{
+		{
+			Name: "TestMergeNestedPreloadWithNestedJoin-1",
+			Manager: &User{
+				Name: "Alexis Manager",
+				Tools: []Tools{
+					{Name: "Alexis Tool 1"},
+					{Name: "Alexis Tool 2"},
+				},
+			},
+		},
+		{
+			Name: "TestMergeNestedPreloadWithNestedJoin-2",
+			Manager: &User{
+				Name: "Jinzhu Manager",
+				Tools: []Tools{
+					{Name: "Jinzhu Tool 1"},
+					{Name: "Jinzhu Tool 2"},
+				},
+			},
+		},
+	}
+
+	DB.Create(&users)
+
+	query := make([]string, 0)
+	sess := DB.Session(&gorm.Session{Logger: Tracer{
+		Logger: DB.Config.Logger,
+		Test: func(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
+			sql, _ := fc()
+			query = append(query, sql)
+		},
+	}})
+
+	var result []User
+	err := sess.
+		Joins("Manager").
+		Preload("Manager.Tools").
+		Where("users.name Like ?", "TestMergeNestedPreloadWithNestedJoin%").
+		Find(&result).Error
+
+	if err != nil {
+		t.Fatalf("failed to preload and find users: %v", err)
+	}
+
+	AssertEqual(t, result, users)
+	AssertEqual(t, len(query), 2) // Check preload queries are merged
+
+	if !regexp.MustCompile(`SELECT \* FROM .*tools.* WHERE .*IN.*`).MatchString(query[0]) {
+		t.Fatalf("Expected first query to preload manager tools, got: %s", query[0])
+	}
 }
 
 func TestEmbedPreload(t *testing.T) {
diff --git a/tests/query_test.go b/tests/query_test.go
index c0259a1..d0ed675 100644
--- a/tests/query_test.go
+++ b/tests/query_test.go
@@ -559,6 +559,11 @@ func TestNot(t *testing.T) {
 	if !regexp.MustCompile("SELECT \\* FROM .*users.* WHERE NOT \\(manager IS NULL AND age >= .+\\) AND .users.\\..deleted_at. IS NULL").MatchString(result.Statement.SQL.String()) {
 		t.Fatalf("Build NOT condition, but got %v", result.Statement.SQL.String())
 	}
+
+	result = dryDB.Not(DB.Where("manager IS NULL").Or("age >= ?", 20)).Find(&User{})
+	if !regexp.MustCompile(`SELECT \* FROM .*users.* WHERE NOT \(manager IS NULL OR age >= .+\) AND .users.\..deleted_at. IS NULL`).MatchString(result.Statement.SQL.String()) {
+		t.Fatalf("Build NOT condition, but got %v", result.Statement.SQL.String())
+	}
 }
 
 func TestNotWithAllFields(t *testing.T) {