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.

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.

    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):

 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:

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

Comment From: jasonzbao

Running into the same issue