..

记一个 GORM 奇怪报错

业务伪代码是

s := findStruct()
tx := db.Save(&s)
if tx.Error != nil {
    log.Fatal(tx.Error)
}

打印出来的错误是WHERE conditions required

产生 bug 的原因是 findStruct 返回的是指向 struct 的指针,调用保存的参数 &s 变成了双指针。

save 的代码在 gorm@v1.22.2/finisher_api.go:

// Save update value in database, if the value doesn't have primary key, will insert it
func (db *DB) Save(value interface{}) (tx *DB) {
	tx = db.getInstance()
	tx.Statement.Dest = value

	reflectValue := reflect.Indirect(reflect.ValueOf(value))
	switch reflectValue.Kind() {
	case reflect.Slice, reflect.Array:
        // ...
	case reflect.Struct:
        // ...
	default:
        // ...
		tx = tx.callbacks.Update().Execute(tx)
        // ...
	}

	return
}

因为是双指针,reflectValue.Kind() 就不是 reflect.Struct,就会执行 default 代码段中的 update 逻辑。

Update 的代码在 gorm@v1.22.2/callbacks/update.go:

func Update(config *Config) func(db *gorm.DB) {
	supportReturning := utils.Contains(config.UpdateClauses, "RETURNING")

	return func(db *gorm.DB) {
        // ...
		if db.Statement.SQL.String() == "" {
			db.Statement.SQL.Grow(180)
			db.Statement.AddClauseIfNotExists(clause.Update{})
			if set := ConvertToAssignments(db.Statement); len(set) != 0 {
				db.Statement.AddClause(set)
			} else {
				return
			}
			db.Statement.Build(db.Statement.BuildClauses...)
		}

		if _, ok := db.Statement.Clauses["WHERE"]; !db.AllowGlobalUpdate && !ok {
			db.AddError(gorm.ErrMissingWhereClause) // 报错消息
			return
		}

        // ...
	}
}

可以看到因为 Statement 中没有声明 Where 条件,所以就抛了 gorm.ErrMissingWhereClause 错误。

至此就找到了产生这个奇怪的错误消息的地方。