Closed mck753 closed 1 month ago
@mck753 你这个需求应该提给Golang官方而不是gorm。 当你传参为一个struct时,golang编译时是会传入struct的拷贝而不是该struct。 你的Expected answer大概是这个样子
type StructA struct {
FieldA int
FieldB string
}
func PassByStruct(aCopy StructA) {
aCopy.FieldA = 1
aCopy.FieldB = "From a copy"
}
func PassByReference(aRef *StructA) {
aRef.FieldA = 2
aRef.FieldB = "From a reference"
}
func main() {
a := StructA{
FieldA: -1,
FieldB: "raw",
}
PassByStruct(a)
// 输出:FieldA: -1, FieldB: raw
fmt.Printf("FieldA: %d, FieldB: %s\n", a.FieldA, a.FieldB)
PassByReference(&a)
// FieldA: 2, FieldB: From a reference
fmt.Printf("FieldA: %d, FieldB: %s\n", a.FieldA, a.FieldB)
}
你想做的就是想让PassByStruct支持修改传入struct的原址值,那应该是提给golang官方而不是gorm。
@mck753 你这个需求应该提给Golang官方而不是gorm。 当你传参为一个struct时,golang编译时是会传入struct的拷贝而不是该struct。 你的Expected answer大概是这个样子
type StructA struct { FieldA int FieldB string } func PassByStruct(aCopy StructA) { aCopy.FieldA = 1 aCopy.FieldB = "From a copy" } func PassByReference(aRef *StructA) { aRef.FieldA = 2 aRef.FieldB = "From a reference" } func main() { a := StructA{ FieldA: -1, FieldB: "raw", } PassByStruct(a) // 输出:FieldA: -1, FieldB: raw fmt.Printf("FieldA: %d, FieldB: %s\n", a.FieldA, a.FieldB) PassByReference(&a) // FieldA: 2, FieldB: From a reference fmt.Printf("FieldA: %d, FieldB: %s\n", a.FieldA, a.FieldB) }
你想做的就是想让PassByStruct支持修改传入struct的原址值,那应该是提给golang官方而不是gorm。
我说的不是这个意思。 使用Updates map时候的SQL为:
UPDATE `t_xxxxxx` SET `entrance_year`=2023,`version`=version + 1 WHERE id = 18386775880800387594
使用Updates struct时候的SQL为:
UPDATE `t_xxxxxx` SET `entrance_year`=2023 WHERE id = 18386775880800387594
我debug看了 BeforeUpdate 钩子会被执行,但是后续的SQL并没有 version 字段,是因为 updates 使用结构体的时候 不支持吗
似乎已经解决,see : https://github.com/go-gorm/gorm/issues/6079
@mck753 你这个需求应该提给Golang官方而不是gorm。 当你传参为一个struct时,golang编译时是会传入struct的拷贝而不是该struct。 你的Expected answer大概是这个样子
type StructA struct { FieldA int FieldB string } func PassByStruct(aCopy StructA) { aCopy.FieldA = 1 aCopy.FieldB = "From a copy" } func PassByReference(aRef *StructA) { aRef.FieldA = 2 aRef.FieldB = "From a reference" } func main() { a := StructA{ FieldA: -1, FieldB: "raw", } PassByStruct(a) // 输出:FieldA: -1, FieldB: raw fmt.Printf("FieldA: %d, FieldB: %s\n", a.FieldA, a.FieldB) PassByReference(&a) // FieldA: 2, FieldB: From a reference fmt.Printf("FieldA: %d, FieldB: %s\n", a.FieldA, a.FieldB) }
你想做的就是想让PassByStruct支持修改传入struct的原址值,那应该是提给golang官方而不是gorm。
我说的不是这个意思。 使用Updates map时候的SQL为:
UPDATE `t_xxxxxx` SET `entrance_year`=2023,`version`=version + 1 WHERE id = 18386775880800387594
使用Updates struct时候的SQL为:
UPDATE `t_xxxxxx` SET `entrance_year`=2023 WHERE id = 18386775880800387594
我debug看了 BeforeUpdate 钩子会被执行,但是后续的SQL并没有 version 字段,是因为 updates 使用结构体的时候 不支持吗
不,支持,而且你的version已经改了。后续使用结构体不支持的原因,是因为你传入是结构体的拷贝,每一份函数拿到的那个结构体值都是一份新的拷贝,你修改的是拷贝A的值,然后用拷贝B去执行后续操作,所以你的后续的SQL并没有 version 字段
似乎已经解决,see : #6079
你可以很明显地看到这个issue里面,对方的操作是PassByReference,而不是跟你一样的PassByStruct。 如果你要跟这个issue一样,你的代码应该改成这样:
fmt.Println("Updates struct :")
updatingData := Table{EntranceYear: 2023}
t := &Table{BaseModel: BaseModel{ID: id}}
err = db.Debug().
Model(&t).
// 改成传递指针(aka引用)的方式,而不是传递struct(aka拷贝)的方式
Updates(&updatingData).
Error
if err != nil {
panic(err)
}
似乎已经解决,see : #6079
你可以很明显地看到这个issue里面,对方的操作是PassByReference,而不是跟你一样的PassByStruct。 如果你要跟这个issue一样,你的代码应该改成这样:
fmt.Println("Updates struct :") updatingData := Table{EntranceYear: 2023} t := &Table{BaseModel: BaseModel{ID: id}} err = db.Debug(). Model(&t). // 改成传递指针(aka引用)的方式,而不是传递struct(aka拷贝)的方式 Updates(&updatingData). Error if err != nil { panic(err) }
我现在的代码是这样,都使用了指针传递,似乎也更新不了
fmt.Println("Updates struct :")
updatingData := Table{EntranceYear: 2023}
t := Table{BaseModel: BaseModel{ID: id}}
err = db.Debug().
Model(&t).
// 改成传递指针(aka引用)的方式,而不是传递struct(aka拷贝)的方式
Updates(&updatingData).
Error
// ouput: UPDATE `t_xxxx` SET `entrance_year`=2023 WHERE `id` = 18386775880800387594
if err != nil {
panic(err)
}
哦,这个啊,你这里是第二个问题,就是如果是个map[string]interface{}的话,是可以使用clause.Expr{}当作其值的。 但如果是个struct的话,clause.Expr{}是不能赋值给你的Version字段的(version是个uint32类型),所以对于Struct来说,你的BeforeUpdate是执行失败的。
哦,这个啊,你这里是第二个问题,就是如果是个map[string]interface{}的话,是可以使用clause.Expr{}当作其值的。 但如果是个struct的话,clause.Expr{}是不能赋值给你的Version字段的(version是个uint32类型),所以对于Struct来说,你的BeforeUpdate是执行失败的。
嗯啊,那就是由于updates过程中,由于Struct不支持clause.Expr{}导致的无法更新指定字段,那有好的解决办法吗?我看我贴的那个issue,给的解决方法,似乎可以
那种方法是可以,直接使用go-gorm/optimisticlock去替换你原来的Version字段。
那个方案的本质其实就是实现了自定义Field字段实现了CreateClauses&UpdateClauses method,然后match了UpdateClausesInterface ,以此来动态修改生成的SQL(model的BeforeUpdate是在构建SQL之前)。
当然如果你不喜欢它的sql.NullInt64定义,你可以仿照着它的代码自己写一个,重点实现以下三个interface就好。 1)UpdateClausesInterface : 支持动态修改生成的SQL 2)sql.Scanner: 把sql传过来的内容Unmarshal到你的字段上 3)driver.Valuer: 把你的字段marshal成sql的内容 对应optimisticlock.Version的CreateClauses, Scan, Value这三个method
那种方法是可以,直接使用go-gorm/optimisticlock去替换你原来的Version字段。
那个方案的本质其实就是实现了自定义Field字段实现了CreateClauses method,然后match了CreateClausesInterface ,以此来动态修改生成的SQL(model的BeforeUpdate是在构建SQL之前)。
当然如果你不喜欢它的sql.NullInt64定义,你可以仿照着它的代码自己写一个,重点实现以下三个interface就好。 1)CreateClausesInterface : 支持动态修改生成的SQL 2)sql.Scanner: 把sql传过来的内容Unmarshal到你的字段上 3)driver.Valuer: 把你的字段marshal成sql的内容 对应optimisticlock.Version的CreateClauses, Scan, Value这三个method
那种方法是可以,直接使用go-gorm/optimisticlock去替换你原来的Version字段。
那个方案的本质其实就是实现了自定义Field字段实现了CreateClauses method,然后match了CreateClausesInterface ,以此来动态修改生成的SQL(model的BeforeUpdate是在构建SQL之前)。
当然如果你不喜欢它的sql.NullInt64定义,你可以仿照着它的代码自己写一个,重点实现以下三个interface就好。 1)CreateClausesInterface : 支持动态修改生成的SQL 2)sql.Scanner: 把sql传过来的内容Unmarshal到你的字段上 3)driver.Valuer: 把你的字段marshal成sql的内容 对应optimisticlock.Version的CreateClauses, Scan, Value这三个method
收到,感谢大佬
那种方法是可以,直接使用go-gorm/optimisticlock去替换你原来的Version字段。 那个方案的本质其实就是实现了自定义Field字段实现了CreateClauses method,然后match了CreateClausesInterface ,以此来动态修改生成的SQL(model的BeforeUpdate是在构建SQL之前)。 当然如果你不喜欢它的sql.NullInt64定义,你可以仿照着它的代码自己写一个,重点实现以下三个interface就好。 1)CreateClausesInterface : 支持动态修改生成的SQL 2)sql.Scanner: 把sql传过来的内容Unmarshal到你的字段上 3)driver.Valuer: 把你的字段marshal成sql的内容 对应optimisticlock.Version的CreateClauses, Scan, Value这三个method
那种方法是可以,直接使用go-gorm/optimisticlock去替换你原来的Version字段。 那个方案的本质其实就是实现了自定义Field字段实现了CreateClauses method,然后match了CreateClausesInterface ,以此来动态修改生成的SQL(model的BeforeUpdate是在构建SQL之前)。 当然如果你不喜欢它的sql.NullInt64定义,你可以仿照着它的代码自己写一个,重点实现以下三个interface就好。 1)CreateClausesInterface : 支持动态修改生成的SQL 2)sql.Scanner: 把sql传过来的内容Unmarshal到你的字段上 3)driver.Valuer: 把你的字段marshal成sql的内容 对应optimisticlock.Version的CreateClauses, Scan, Value这三个method
收到,感谢大佬
一种自定义实现可以是这样
// 自定义的Version,依旧使用uint32
type VersionField uint32
// 保证实现了driver.Valuer以及sql.Scanner和gorm的UpdateClausesInterface
var _ driver.Valuer = (VersionField)(0)
var _ sql.Scanner = (*VersionField)(nil)
var _ schema.UpdateClausesInterface = (*VersionField)(nil)
func (f VersionField) Value() (driver.Value, error) {
return sql.NullInt64{Valid: true, Int64: int64(f)}.Value()
}
func (f *VersionField) Scan(v interface{}) error {
var t sql.NullInt64
if err := t.Scan(v); err != nil {
return err
}
*f = VersionField(t.Int64)
return nil
}
// 以下照抄https://github.com/go-gorm/optimisticlock
func (v *VersionField) UpdateClauses(field *schema.Field) []clause.Interface {
return []clause.Interface{VersionUpdateClause{Field: field}}
}
type VersionUpdateClause struct {
Field *schema.Field
}
func (v VersionUpdateClause) Name() string {
return ""
}
func (v VersionUpdateClause) Build(clause.Builder) {
}
func (v VersionUpdateClause) MergeClause(*clause.Clause) {
}
func (v VersionUpdateClause) ModifyStatement(stmt *gorm.Statement) {
if _, ok := stmt.Clauses["version_enabled"]; ok {
return
}
if c, ok := stmt.Clauses["WHERE"]; ok {
if where, ok := c.Expression.(clause.Where); ok && len(where.Exprs) > 1 {
for _, expr := range where.Exprs {
if orCond, ok := expr.(clause.OrConditions); ok && len(orCond.Exprs) == 1 {
where.Exprs = []clause.Expression{clause.And(where.Exprs...)}
c.Expression = where
stmt.Clauses["WHERE"] = c
break
}
}
}
}
if !stmt.Unscoped {
if val, zero := v.Field.ValueOf(stmt.Context, stmt.ReflectValue); !zero {
if version, ok := val.(VersionField); ok {
stmt.AddClause(clause.Where{Exprs: []clause.Expression{
clause.Eq{Column: clause.Column{Table: clause.CurrentTable, Name: v.Field.DBName}, Value: version},
}})
}
}
}
// convert struct to map[string]interface{}, we need to handle the version field with string, but which is an int64.
dv := reflect.ValueOf(stmt.Dest)
if reflect.Indirect(dv).Kind() == reflect.Struct {
selectColumns, restricted := stmt.SelectAndOmitColumns(false, true)
sd, _ := schema.Parse(stmt.Dest, &sync.Map{}, stmt.DB.NamingStrategy)
d := make(map[string]interface{})
for _, field := range sd.Fields {
if field.DBName == v.Field.DBName {
continue
}
if field.DBName == "" {
continue
}
if v, ok := selectColumns[field.DBName]; (ok && v) || (!ok && (!restricted || !stmt.SkipHooks)) {
if field.AutoUpdateTime > 0 {
continue
}
val, isZero := field.ValueOf(stmt.Context, dv)
if (ok || !isZero) && field.Updatable {
d[field.DBName] = val
}
}
}
stmt.Dest = d
}
stmt.SetColumn(v.Field.DBName, clause.Expr{SQL: stmt.Quote(v.Field.DBName) + "+1"}, true)
stmt.Clauses["version_enabled"] = clause.Clause{}
}
那种方法是可以,直接使用go-gorm/optimisticlock去替换你原来的Version字段。 那个方案的本质其实就是实现了自定义Field字段实现了CreateClauses method,然后match了CreateClausesInterface ,以此来动态修改生成的SQL(model的BeforeUpdate是在构建SQL之前)。 当然如果你不喜欢它的sql.NullInt64定义,你可以仿照着它的代码自己写一个,重点实现以下三个interface就好。 1)CreateClausesInterface : 支持动态修改生成的SQL 2)sql.Scanner: 把sql传过来的内容Unmarshal到你的字段上 3)driver.Valuer: 把你的字段marshal成sql的内容 对应optimisticlock.Version的CreateClauses, Scan, Value这三个method
那种方法是可以,直接使用go-gorm/optimisticlock去替换你原来的Version字段。 那个方案的本质其实就是实现了自定义Field字段实现了CreateClauses method,然后match了CreateClausesInterface ,以此来动态修改生成的SQL(model的BeforeUpdate是在构建SQL之前)。 当然如果你不喜欢它的sql.NullInt64定义,你可以仿照着它的代码自己写一个,重点实现以下三个interface就好。 1)CreateClausesInterface : 支持动态修改生成的SQL 2)sql.Scanner: 把sql传过来的内容Unmarshal到你的字段上 3)driver.Valuer: 把你的字段marshal成sql的内容 对应optimisticlock.Version的CreateClauses, Scan, Value这三个method
收到,感谢大佬
一种自定义实现可以是这样
// 自定义的Version,依旧使用uint32 type VersionField uint32 // 保证实现了driver.Valuer以及sql.Scanner和gorm的UpdateClausesInterface var _ driver.Valuer = (VersionField)(0) var _ sql.Scanner = (*VersionField)(nil) var _ schema.UpdateClausesInterface = (*VersionField)(nil) func (f VersionField) Value() (driver.Value, error) { return sql.NullInt64{Valid: true, Int64: int64(f)}.Value() } func (f *VersionField) Scan(v interface{}) error { var t sql.NullInt64 if err := t.Scan(v); err != nil { return err } *f = VersionField(t.Int64) return nil } // 以下照抄https://github.com/go-gorm/optimisticlock func (v *VersionField) UpdateClauses(field *schema.Field) []clause.Interface { return []clause.Interface{VersionUpdateClause{Field: field}} } type VersionUpdateClause struct { Field *schema.Field } func (v VersionUpdateClause) Name() string { return "" } func (v VersionUpdateClause) Build(clause.Builder) { } func (v VersionUpdateClause) MergeClause(*clause.Clause) { } func (v VersionUpdateClause) ModifyStatement(stmt *gorm.Statement) { if _, ok := stmt.Clauses["version_enabled"]; ok { return } if c, ok := stmt.Clauses["WHERE"]; ok { if where, ok := c.Expression.(clause.Where); ok && len(where.Exprs) > 1 { for _, expr := range where.Exprs { if orCond, ok := expr.(clause.OrConditions); ok && len(orCond.Exprs) == 1 { where.Exprs = []clause.Expression{clause.And(where.Exprs...)} c.Expression = where stmt.Clauses["WHERE"] = c break } } } } if !stmt.Unscoped { if val, zero := v.Field.ValueOf(stmt.Context, stmt.ReflectValue); !zero { if version, ok := val.(VersionField); ok { stmt.AddClause(clause.Where{Exprs: []clause.Expression{ clause.Eq{Column: clause.Column{Table: clause.CurrentTable, Name: v.Field.DBName}, Value: version}, }}) } } } // convert struct to map[string]interface{}, we need to handle the version field with string, but which is an int64. dv := reflect.ValueOf(stmt.Dest) if reflect.Indirect(dv).Kind() == reflect.Struct { selectColumns, restricted := stmt.SelectAndOmitColumns(false, true) sd, _ := schema.Parse(stmt.Dest, &sync.Map{}, stmt.DB.NamingStrategy) d := make(map[string]interface{}) for _, field := range sd.Fields { if field.DBName == v.Field.DBName { continue } if field.DBName == "" { continue } if v, ok := selectColumns[field.DBName]; (ok && v) || (!ok && (!restricted || !stmt.SkipHooks)) { if field.AutoUpdateTime > 0 { continue } val, isZero := field.ValueOf(stmt.Context, dv) if (ok || !isZero) && field.Updatable { d[field.DBName] = val } } } stmt.Dest = d } stmt.SetColumn(v.Field.DBName, clause.Expr{SQL: stmt.Quote(v.Field.DBName) + "+1"}, true) stmt.Clauses["version_enabled"] = clause.Clause{} }
收到,感谢大佬~ 🥳🥳🥳
Your Question
现有结构体:
Updates 更新时是无法使用 BeforeUpdate 钩子函数的吗
The document you expected this should be explained
Expected answer
如何在参数为 struct 的情况下 使用 Updates,会触发version字段的更新