GORM Playground Link

https://github.com/go-gorm/playground/pull/707

Description

Related to #4990

The SetColumn updates the wrong column when used inside a BeforeUpdate hook. With the code below one would expect that the price_area column would be updated to "before hook" however the next column gets updated to before hook. When running the code below, the output is:

Screenshot 2024-03-28 at 00 19 06

While updating the before hook to tx.Statement.SetColumn("address", &test) it outputs this:

Screenshot 2024-03-28 at 00 23 30


var id = uuid.New()

func setupDb() (*gorm.DB, error) {
    dbSource := "user=local-user password=local-pwd host=test-local-db dbname=testdb port=5434 sslmode=disable TimeZone=Europe/Oslo"

    return gorm.Open(postgres.Open(dbSource), &gorm.Config{TranslateError: true})
}

type Home struct {
    Id        uuid.UUID `json:"id" gorm:"primaryKey"`
    Address   *string   `json:"address"`
    Zip       *int      `json:"zip"`
    PriceArea *string   `json:"price_area"`
    Type      *string   `json:"type"`
    CreatedAt time.Time
    UpdatedAt time.Time
}

type HomeUpdate struct {
    Zip        *int      `json:"zip"`
    Address    *string   `json:"address"`
    PriceArea  *string   `json:"price_area"`
    Type       *string   `json:"type"`
}

func (home *Home) BeforeUpdate(tx *gorm.DB) error {
    test := "before hook"
    tx.Statement.SetColumn("price_area", &test)
    return nil
}

func main() {
    db, err := setupDb()
    if err != nil {
        fmt.Println(err.Error())
        return
    }
    db.AutoMigrate(&Home{})

    home := Home{
        Id: id,
    }

    dbErr := db.Create(&home).Error
    if dbErr != nil {
        fmt.Println(dbErr.Error())
        return
    }

    tmpString := "test"
    updatePayload := HomeUpdate{
        Address: &tmpString,
    }

    dbErr = db.Model(&home).Updates(&updatePayload).Error
    if dbErr != nil {
        fmt.Println(dbErr.Error())
        return
    }
}

If it turns out to be a bug I am happy to help looking more into the problem and possible find a solution 🔍

Update: Some further research

After som searching it seems like the problem arises from the use fieldIndex in field.ReflectValueOf as it assumes the index of the two structs are the same. So when this line runs https://github.com/go-gorm/gorm/blob/1b48aa072d1210c2ba315aeea18b57fddb634875/schema/field.go#L501-L503 the value is updated using the index in the Home to update the HomeUpdate struct which is the one being used for creating the update. So when this runs https://github.com/go-gorm/gorm/blob/1b48aa072d1210c2ba315aeea18b57fddb634875/statement.go#L584 the 3rd index is updated to "before hook" which is the Type field. If Id is moved under Type in Home everything would work as expected.

Proposed Fix

Moving from updating fields by index to reflect.Indirect(value).FieldsByName in field.Set(stmt.Context, destValue, value) would fix the problem, however the field.ReflectValueOf must points to the new FieldsByName function which I am not sure how to do or it may not be what we want to do.

Comment From: ksn-ark

Gorm Playground Link

https://github.com/ksn-ark/playground

Description

Hey this has been resolved, I'm entirely unsure as to how but I forked your fork and merged it with the original master because the dependency resolutions were failing. The tests pass on every dialect. only alteration is avoiding an unsafe de-reference of a null pointer. This issue should be closed.

Test log

testlog.txt