Repository: go-gorm/gorm

Issue:
Preload panics if at least one of parent model field's value is NULL 
## GORM Playground Link

https://github.com/go-gorm/playground/pull/537

## Description

`User` and `UserProp` models are saved independently in different API endpoints so they are _not_ connected by a foreign key. But in another API endpoint they should load together so used `Preload("UserProps")` with multiple foreign keys to achieve this.
```go
type Company struct {
	ID   int
	Name string
}

type User struct {
	gorm.Model
	Name      string
	CompanyID *int
	Company   Company
	ManagerID *uint
	Manager   *User
	UserProps *UserProp `gorm:"foreignkey:CompanyID,ManagerID;references:CompanyID,ManagerID"`
}

type UserProp struct {
	ID        int
	CompanyID int
	ManagerID uint
	Value     string
}
```

### Expected
`UserProp` and `User` connects by `CompanyID` and `ManagerID`. Since those fields are nullable in `User` model it leads to `User.UserProps` emptiness.
```go
	company := &Company{Name: "ACME"}
	DB.Create(company)

	user := &User{
		Name:      "manager",
		CompanyID: &company.ID,
		// have no manager
		ManagerID: nil,
	}
	DB.Create(user)

	var resultUser User
	DB.Preload("UserProps").First(&resultUser, user.ID)
```
So, the following SQL is expected to be generated (will return 0 rows, and that's the idea: no panics):
```sql
 SELECT * FROM "user_props" WHERE ("user_props"."company_id","user_props"."manager_id") IN ((1,NULL))
```

### Actual 
We got panic in case of one of values is NULL:
```sql
2022/10/24 09:42:39 testing postgres...
=== RUN   TestGORM

2022/10/24 09:42:39 /mnt/c/Users/crab/Go Projects/gorm-playground/main_test.go:13
[3.820ms] [rows:1] INSERT INTO "companies" ("name") VALUES ('ACME') RETURNING "id"

2022/10/24 09:42:39 /mnt/c/Users/crab/Go Projects/gorm-playground/main_test.go:21
[2.940ms] [rows:1] INSERT INTO "users" ("created_at","updated_at","deleted_at","name","company_id","manager_id") VALUES ('2022-10-24 09:42:39.32','2022-10-24 09:42:39.32',NULL,'manager',1,NULL) RETURNING "id"

2022/10/24 09:42:39 /mnt/c/Users/crab/Go Projects/gorm-playground/main_test.go:28
[2.340ms] [rows:1] INSERT INTO "users" ("created_at","updated_at","deleted_at","name","company_id","manager_id") VALUES ('2022-10-24 09:42:39.324','2022-10-24 09:42:39.324',NULL,'jinzhu',1,1) RETURNING "id"

2022/10/24 09:42:39 /mnt/c/Users/crab/Go Projects/gorm-playground/main_test.go:37
[2.806ms] [rows:1] INSERT INTO "user_props" ("company_id","manager_id","value") VALUES (1,1,'foo') RETURNING "id"
=== RUN   TestGORM/user_has_props

2022/10/24 09:42:39 /mnt/c/Users/crab/Go Projects/gorm-playground/main_test.go:46
[0.578ms] [rows:1] SELECT * FROM "user_props" WHERE ("user_props"."company_id","user_props"."manager_id") IN ((1,1))

2022/10/24 09:42:39 /mnt/c/Users/crab/Go Projects/gorm-playground/main_test.go:46
[1.575ms] [rows:1] SELECT * FROM "users" WHERE "users"."id" = 2 AND "users"."deleted_at" IS NULL ORDER BY "users"."id" LIMIT 1
=== RUN   TestGORM/user_without_props
--- FAIL: TestGORM (0.02s)
    --- PASS: TestGORM/user_has_props (0.00s)
    --- FAIL: TestGORM/user_without_props (0.00s)
panic: reflect: call of reflect.Value.Interface on zero Value [recovered]
        panic: reflect: call of reflect.Value.Interface on zero Value

goroutine 16 [running]:
testing.tRunner.func1.2({0xce5dc0, 0xc0000142b8})
        /usr/local/go/src/testing/testing.go:1209 +0x36c
testing.tRunner.func1()
        /usr/local/go/src/testing/testing.go:1212 +0x3b6
panic({0xce5dc0, 0xc0000142b8})
        /usr/local/go/src/runtime/panic.go:1047 +0x266
reflect.valueInterface({0x0, 0x0, 0x0}, 0x1)
        /usr/local/go/src/reflect/value.go:1356 +0x21e
reflect.Value.Interface(...)
        /usr/local/go/src/reflect/value.go:1351
...
created by testing.(*T).Run
        /usr/local/go/src/testing/testing.go:1306 +0x727
FAIL    gorm.io/playground      0.102s
FAIL
```

Discussion:
@jasonzbao: Running into the same issue

Solution patch:
diff --git a/utils/utils.go b/utils/utils.go
index a4d8ac2..347a331 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -74,7 +74,11 @@ func ToStringKey(values ...interface{}) string {
 		case uint:
 			results[idx] = strconv.FormatUint(uint64(v), 10)
 		default:
-			results[idx] = fmt.Sprint(reflect.Indirect(reflect.ValueOf(v)).Interface())
+			results[idx] = "nil"
+			vv := reflect.ValueOf(v)
+			if vv.IsValid() && !vv.IsZero() {
+				results[idx] = fmt.Sprint(reflect.Indirect(vv).Interface())
+			}
 		}
 	}
 



Test patch:
diff --git a/utils/utils_test.go b/utils/utils_test.go
index d048682..8ff42af 100644
--- a/utils/utils_test.go
+++ b/utils/utils_test.go
@@ -48,8 +48,10 @@ func TestToStringKey(t *testing.T) {
 	}{
 		{[]interface{}{"a"}, "a"},
 		{[]interface{}{1, 2, 3}, "1_2_3"},
+		{[]interface{}{1, nil, 3}, "1_nil_3"},
 		{[]interface{}{[]interface{}{1, 2, 3}}, "[1 2 3]"},
 		{[]interface{}{[]interface{}{"1", "2", "3"}}, "[1 2 3]"},
+		{[]interface{}{[]interface{}{"1", nil, "3"}}, "[1 <nil> 3]"},
 	}
 	for _, c := range cases {
 		if key := ToStringKey(c.values...); key != c.key {