Repository: go-gorm/gorm
Pull request #6611 (2023-09-22 13:26:33)

*datatypes.JSON in model causes panic on tx.Statement.Changed
- [x] Do only one thing
- [x] Non breaking API changes
- [x] Tested

### User Case Description

I got the following panic

```
panic: value method gorm.io/datatypes.JSON.Value called using nil *JSON pointer

goroutine 134 [running]:
gorm.io/datatypes.(*JSON).Value(0x135c7c0?)
        <autogenerated>:1 +0x56
gorm.io/gorm/utils.AssertEqual({0x135c7c0, 0xc0007e06f0}, {0x135c7c0, 0x0})
        /home/mgoeppe/go/pkg/mod/gorm.io/gorm@v1.25.2/utils/utils.go:99 +0x9f
gorm.io/gorm.(*Statement).Changed.func1(0xc0003eaf00)
        /home/mgoeppe/go/pkg/mod/gorm.io/gorm@v1.25.2/statement.go:643 +0x251
gorm.io/gorm.(*Statement).Changed(0xc0007e4a80, {0xc00079c4c8, 0x1, 0x41123d?})
        /home/mgoeppe/go/pkg/mod/gorm.io/gorm@v1.25.2/statement.go:658 +0x298
```

when using a `*datatypes.JSON` model field where the *datatypes.JSON is not nil but refers to a []byte which is nil. The panic is raised when valuer.Value() is called.

>**NOTE:** It might be questionable using a `*datatypes.JSON` as the underlying json.RawMessage byte slice already is a pointer type, but still the code should not panic I guess.

### What did this pull request do?

I added a test reproduce the issue taking the exact same reflection steps as done in the `Changed` func. I also provided a fix of the `AssertEqual` func itself but I am not sure if the problem resides in the reflection code of the `Changed` func itself - to be honest I do not understand all details here.

I am looking forward to your reply. Thanks a lot!


<sub>Mathias Zeller <mathias.zeller@mercedes-benz.com> Mercedes-Benz Tech Innovation GmbH ([ProviderInformation](https://github.com/mercedes-benz/daimler-foss/blob/master/PROVIDER_INFORMATION.md))
</sub>


Patch:
diff --git a/clause/expression.go b/clause/expression.go
index 8d01052..3140846 100644
--- a/clause/expression.go
+++ b/clause/expression.go
@@ -126,7 +126,7 @@ func (expr NamedExpr) Build(builder Builder) {
 	for _, v := range []byte(expr.SQL) {
 		if v == '@' && !inName {
 			inName = true
-			name = []byte{}
+			name = name[:0]
 		} else if v == ' ' || v == ',' || v == ')' || v == '"' || v == '\'' || v == '`' || v == '\r' || v == '\n' || v == ';' {
 			if inName {
 				if nv, ok := namedMap[string(name)]; ok {
diff --git a/migrator/migrator.go b/migrator/migrator.go
index b15a43e..49bc937 100644
--- a/migrator/migrator.go
+++ b/migrator/migrator.go
@@ -217,7 +217,7 @@ func (m Migrator) CreateTable(values ...interface{}) error {
 				field := stmt.Schema.FieldsByDBName[dbName]
 				if !field.IgnoreMigration {
 					createTableSQL += "? ?"
-					hasPrimaryKeyInDataType = hasPrimaryKeyInDataType || strings.Contains(strings.ToUpper(string(field.DataType)), "PRIMARY KEY")
+					hasPrimaryKeyInDataType = hasPrimaryKeyInDataType || strings.Contains(strings.ToUpper(m.DataTypeOf(field)), "PRIMARY KEY")
 					values = append(values, clause.Column{Name: dbName}, m.DB.Migrator().FullDataTypeOf(field))
 					createTableSQL += ","
 				}
diff --git a/utils/utils.go b/utils/utils.go
index ddbca60..c8fec5b 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -89,19 +89,28 @@ func Contains(elems []string, elem string) bool {
 	return false
 }
 
-func AssertEqual(src, dst interface{}) bool {
-	if !reflect.DeepEqual(src, dst) {
-		if valuer, ok := src.(driver.Valuer); ok {
-			src, _ = valuer.Value()
-		}
+func AssertEqual(x, y interface{}) bool {
+	if reflect.DeepEqual(x, y) {
+		return true
+	}
+	if x == nil || y == nil {
+		return false
+	}
 
-		if valuer, ok := dst.(driver.Valuer); ok {
-			dst, _ = valuer.Value()
-		}
+	xval := reflect.ValueOf(x)
+	yval := reflect.ValueOf(y)
+	if xval.Kind() == reflect.Ptr && xval.IsNil() ||
+		yval.Kind() == reflect.Ptr && yval.IsNil() {
+		return false
+	}
 
-		return reflect.DeepEqual(src, dst)
+	if valuer, ok := x.(driver.Valuer); ok {
+		x, _ = valuer.Value()
+	}
+	if valuer, ok := y.(driver.Valuer); ok {
+		y, _ = valuer.Value()
 	}
-	return true
+	return reflect.DeepEqual(x, y)
 }
 
 func ToString(value interface{}) string {


Test patch:
diff --git a/tests/go.mod b/tests/go.mod
index aef26e3..5a0aedd 100644
--- a/tests/go.mod
+++ b/tests/go.mod
@@ -8,7 +8,7 @@ require (
 	github.com/lib/pq v1.10.9
 	gorm.io/driver/mysql v1.5.2-0.20230612053416-48b6526a21f0
 	gorm.io/driver/postgres v1.5.3-0.20230607070428-18bc84b75196
-	gorm.io/driver/sqlite v1.5.3
+	gorm.io/driver/sqlite v1.5.4
 	gorm.io/driver/sqlserver v1.5.2-0.20230613072041-6e2cde390b0a
 	gorm.io/gorm v1.25.4
 )
@@ -22,9 +22,9 @@ require (
 	github.com/jackc/pgx/v5 v5.4.3 // indirect
 	github.com/jinzhu/inflection v1.0.0 // indirect
 	github.com/mattn/go-sqlite3 v1.14.17 // indirect
-	github.com/microsoft/go-mssqldb v1.5.0 // indirect
-	golang.org/x/crypto v0.12.0 // indirect
-	golang.org/x/text v0.12.0 // indirect
+	github.com/microsoft/go-mssqldb v1.6.0 // indirect
+	golang.org/x/crypto v0.14.0 // indirect
+	golang.org/x/text v0.13.0 // indirect
 )
 
 replace gorm.io/gorm => ../
diff --git a/tests/migrate_test.go b/tests/migrate_test.go
index 849e2b7..cfd3e0a 100644
--- a/tests/migrate_test.go
+++ b/tests/migrate_test.go
@@ -862,6 +862,48 @@ func TestMigrateWithSpecialName(t *testing.T) {
 	AssertEqual(t, true, DB.Migrator().HasTable("coupon_product_2"))
 }
 
+// https://github.com/go-gorm/gorm/issues/4760
+func TestMigrateAutoIncrement(t *testing.T) {
+	type AutoIncrementStruct struct {
+		ID     int64   `gorm:"primarykey;autoIncrement"`
+		Field1 uint32  `gorm:"column:field1"`
+		Field2 float32 `gorm:"column:field2"`
+	}
+
+	if err := DB.AutoMigrate(&AutoIncrementStruct{}); err != nil {
+		t.Fatalf("AutoMigrate err: %v", err)
+	}
+
+	const ROWS = 10
+	for idx := 0; idx < ROWS; idx++ {
+		if err := DB.Create(&AutoIncrementStruct{}).Error; err != nil {
+			t.Fatalf("create auto_increment_struct fail, err: %v", err)
+		}
+	}
+
+	rows := make([]*AutoIncrementStruct, 0, ROWS)
+	if err := DB.Order("id ASC").Find(&rows).Error; err != nil {
+		t.Fatalf("find auto_increment_struct fail, err: %v", err)
+	}
+
+	ids := make([]int64, 0, len(rows))
+	for _, row := range rows {
+		ids = append(ids, row.ID)
+	}
+	lastID := ids[len(ids)-1]
+
+	if err := DB.Where("id IN (?)", ids).Delete(&AutoIncrementStruct{}).Error; err != nil {
+		t.Fatalf("delete auto_increment_struct fail, err: %v", err)
+	}
+
+	newRow := &AutoIncrementStruct{}
+	if err := DB.Create(newRow).Error; err != nil {
+		t.Fatalf("create auto_increment_struct fail, err: %v", err)
+	}
+
+	AssertEqual(t, newRow.ID, lastID+1)
+}
+
 // https://github.com/go-gorm/gorm/issues/5320
 func TestPrimarykeyID(t *testing.T) {
 	if DB.Dialector.Name() != "postgres" {
diff --git a/utils/utils_test.go b/utils/utils_test.go
index 71eef96..d048682 100644
--- a/utils/utils_test.go
+++ b/utils/utils_test.go
@@ -98,6 +98,7 @@ func TestAssertEqual(t *testing.T) {
 		{"error not equal", errors.New("1"), errors.New("2"), false},
 		{"driver.Valuer equal", ModifyAt{Time: now, Valid: true}, ModifyAt{Time: now, Valid: true}, true},
 		{"driver.Valuer not equal", ModifyAt{Time: now, Valid: true}, ModifyAt{Time: now.Add(time.Second), Valid: true}, false},
+		{"driver.Valuer equal (ptr to nil ptr)", (*ModifyAt)(nil), &ModifyAt{}, false},
 	}
 	for _, test := range assertEqualTests {
 		t.Run(test.name, func(t *testing.T) {