Breaking change: `Raw().Scan()` resets non-selected struct fields to zero values in v1.31.1
type:question
## Description
### Summary
Upgrading `gorm.io/gorm` from **v1.25.5** to **v1.31.1** introduces a breaking change in `Raw().Scan()` behavior when scanning into a struct that already contains pre-filled values.
### Expected Behavior
When executing:
```go
err := db.Raw(`SELECT id FROM user WHERE id = 1;`).Scan(&user).Error
```
Only the selected fields (`id`) should be updated in the destination struct. Other fields should retain their original values.
Result in **v1.25.5**:
```json
{"ID":1,"Account":"100","Password":"100","Salt":"100","PasswordStrength":100}
```
### Actual Behavior
In **v1.31.1**, fields that are not selected in the query are reset to their zero values.
Result:
```json
{"ID":1,"Account":"","Password":"","Salt":"","PasswordStrength":0}
```
### Impact
This change breaks backward compatibility and affects existing codebases that rely on partial field updates using `Raw().Scan()`.
### Reproducible Example
```go
package main
import (
"encoding/json"
"log"
"os"
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
)
var (
db *gorm.DB
)
func init() {
var err error
db, err = gorm.Open(mysql.Open(`root:******@tcp(******:3306)/account?charset=utf8mb4&parseTime=True`), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
Logger: logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags),
logger.Config{
SlowThreshold: time.Second,
LogLevel: logger.Info,
IgnoreRecordNotFoundError: false,
ParameterizedQueries: false,
Colorful: true,
},
),
DisableForeignKeyConstraintWhenMigrating: true,
})
if err != nil {
log.Println(err)
return
}
}
type User struct {
ID uint `gorm:"column:id"`
Account string `gorm:"column:account"`
Password string `gorm:"column:password"`
Salt string `gorm:"column:salt"`
PasswordStrength int8 `gorm:"-"`
}
func (m *User) TableName() string {
return "user"
}
func main() {
user := User{
ID: 100,
Account: "100",
Password: "100",
Salt: "100",
PasswordStrength: 100,
}
err := db.Raw(`SELECT id FROM user WHERE id = 1;`).Scan(&user).Error
if err != nil {
log.Println(err)
return
}
userBytes, err := json.Marshal(&user)
if err != nil {
log.Println(err)
return
}
log.Println(string(userBytes))
}
```
### Questions
1. Is this behavior change intentional?
2. If yes, is there a recommended way to preserve existing struct values when scanning partial columns?
3. If not intentional, could this be considered a regression?
### Additional Notes
This behavior change can silently introduce data loss in application logic where partial field queries are expected to be non-destructive.
1 条评论