All checks were successful
test mariadb / test mariadb (push) Successful in 5m2s
test cockroach / test cockroach (push) Successful in 7m30s
test mssql / test mssql (push) Successful in 6m10s
test mysql / test mysql (push) Successful in 4m55s
test mysql8 / test mysql8 (push) Successful in 5m16s
test postgres / test postgres (push) Successful in 5m55s
test tidb / test tidb (push) Successful in 5m30s
test sqlite / unit test & test sqlite (push) Successful in 8m37s
Reviewed-on: #2310
320 lines
8.7 KiB
Go
320 lines
8.7 KiB
Go
// Copyright 2016 The Xorm Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package xorm
|
|
|
|
import (
|
|
"reflect"
|
|
|
|
"xorm.io/builder"
|
|
"xorm.io/xorm/internal/statements"
|
|
"xorm.io/xorm/internal/utils"
|
|
"xorm.io/xorm/schemas"
|
|
)
|
|
|
|
// enumerated all errors
|
|
var (
|
|
ErrNoColumnsTobeUpdated = statements.ErrNoColumnsTobeUpdated
|
|
)
|
|
|
|
func (session *Session) genAutoCond(condiBean interface{}) (builder.Cond, error) {
|
|
if session.statement.NoAutoCondition {
|
|
return builder.NewCond(), nil
|
|
}
|
|
|
|
if c, ok := condiBean.(map[string]interface{}); ok {
|
|
eq := make(builder.Eq)
|
|
for k, v := range c {
|
|
eq[session.engine.Quote(k)] = v
|
|
}
|
|
|
|
if session.statement.RefTable != nil {
|
|
if col := session.statement.RefTable.DeletedColumn(); col != nil && !session.statement.GetUnscoped() { // tag "deleted" is enabled
|
|
return eq.And(session.statement.CondDeleted(col)), nil
|
|
}
|
|
}
|
|
return eq, nil
|
|
}
|
|
|
|
ct := reflect.TypeOf(condiBean)
|
|
k := ct.Kind()
|
|
if k == reflect.Ptr {
|
|
k = ct.Elem().Kind()
|
|
}
|
|
if k != reflect.Struct {
|
|
return nil, ErrConditionType
|
|
}
|
|
|
|
condTable, err := session.engine.TableInfo(condiBean)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return session.statement.BuildConds(condTable, condiBean, true, true, false, true, false)
|
|
}
|
|
|
|
// Update records, bean's non-empty fields are updated contents,
|
|
// condiBean' non-empty filds are conditions
|
|
// CAUTION:
|
|
//
|
|
// 1.bool will defaultly be updated content nor conditions
|
|
// You should call UseBool if you have bool to use.
|
|
// 2.float32 & float64 may be not inexact as conditions
|
|
func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int64, error) {
|
|
if session.isAutoClose {
|
|
defer session.Close()
|
|
}
|
|
|
|
defer session.resetStatement()
|
|
|
|
if session.statement.LastError != nil {
|
|
return 0, session.statement.LastError
|
|
}
|
|
|
|
v := utils.ReflectValue(bean)
|
|
t := v.Type()
|
|
|
|
// handle before update processors
|
|
for _, closure := range session.beforeClosures {
|
|
closure(bean)
|
|
}
|
|
cleanupProcessorsClosures(&session.beforeClosures) // cleanup after used
|
|
if processor, ok := interface{}(bean).(BeforeUpdateProcessor); ok {
|
|
processor.BeforeUpdate()
|
|
}
|
|
// --
|
|
|
|
var colNames []string
|
|
var args []interface{}
|
|
var err error
|
|
isMap := t.Kind() == reflect.Map
|
|
isStruct := t.Kind() == reflect.Struct
|
|
if isStruct {
|
|
if err := session.statement.SetRefBean(bean); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if len(session.statement.TableName()) == 0 {
|
|
return 0, ErrTableNotFound
|
|
}
|
|
|
|
if session.statement.ColumnStr() == "" {
|
|
colNames, args, err = session.statement.BuildUpdates(v, false, false,
|
|
false, false, true)
|
|
} else {
|
|
colNames, args, err = session.genUpdateColumns(bean)
|
|
}
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
} else if isMap {
|
|
colNames = make([]string, 0)
|
|
args = make([]interface{}, 0)
|
|
bValue := reflect.Indirect(reflect.ValueOf(bean))
|
|
|
|
for _, v := range bValue.MapKeys() {
|
|
colNames = append(colNames, session.engine.Quote(v.String())+" = ?")
|
|
args = append(args, bValue.MapIndex(v).Interface())
|
|
}
|
|
} else {
|
|
return 0, ErrParamsType
|
|
}
|
|
|
|
table := session.statement.RefTable
|
|
|
|
if session.statement.UseAutoTime && table != nil && table.Updated != "" {
|
|
if !session.statement.ColumnMap.Contain(table.Updated) &&
|
|
!session.statement.OmitColumnMap.Contain(table.Updated) {
|
|
colNames = append(colNames, session.engine.Quote(table.Updated)+" = ?")
|
|
col := table.UpdatedColumn()
|
|
val, t, err := session.engine.nowTime(col)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if session.engine.dialect.URI().DBType == schemas.ORACLE {
|
|
args = append(args, t)
|
|
} else {
|
|
args = append(args, val)
|
|
}
|
|
|
|
colName := col.Name
|
|
if isStruct {
|
|
session.afterClosures = append(session.afterClosures, func(bean interface{}) {
|
|
col := table.GetColumn(colName)
|
|
setColumnTime(bean, col, t)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
if err = session.statement.ProcessIDParam(); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
var autoCond builder.Cond
|
|
if len(condiBean) > 0 {
|
|
autoCond, err = session.genAutoCond(condiBean[0])
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
} else if table != nil {
|
|
if col := table.DeletedColumn(); col != nil && !session.statement.GetUnscoped() { // tag "deleted" is enabled
|
|
autoCond1 := session.statement.CondDeleted(col)
|
|
|
|
if autoCond == nil {
|
|
autoCond = autoCond1
|
|
} else {
|
|
autoCond = autoCond.And(autoCond1)
|
|
}
|
|
}
|
|
}
|
|
|
|
var (
|
|
cond = session.statement.Conds().And(autoCond)
|
|
doIncVer = isStruct && (table != nil && table.Version != "" && session.statement.CheckVersion)
|
|
verValue *reflect.Value
|
|
)
|
|
if doIncVer {
|
|
verValue, err = table.VersionColumn().ValueOfV(&v)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if verValue != nil {
|
|
cond = cond.And(builder.Eq{session.engine.Quote(table.Version): verValue.Interface()})
|
|
}
|
|
}
|
|
|
|
updateWriter := builder.NewWriter()
|
|
if err := session.statement.WriteUpdate(updateWriter, cond, v, colNames, args); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
tableName := session.statement.TableName() // table name must been get before exec because statement will be reset
|
|
useCache := session.statement.UseCache
|
|
|
|
res, err := session.exec(updateWriter.String(), updateWriter.Args()...)
|
|
if err != nil {
|
|
return 0, err
|
|
} else if doIncVer {
|
|
if verValue != nil && verValue.IsValid() && verValue.CanSet() {
|
|
session.incrVersionFieldValue(verValue)
|
|
}
|
|
}
|
|
|
|
if cacher := session.engine.GetCacher(tableName); cacher != nil && useCache {
|
|
session.engine.logger.Debugf("[cache] clear table: %v", tableName)
|
|
cacher.ClearIds(tableName)
|
|
cacher.ClearBeans(tableName)
|
|
}
|
|
|
|
// handle after update processors
|
|
if session.isAutoCommit {
|
|
for _, closure := range session.afterClosures {
|
|
closure(bean)
|
|
}
|
|
if processor, ok := interface{}(bean).(AfterUpdateProcessor); ok {
|
|
session.engine.logger.Debugf("[event] %v has after update processor", tableName)
|
|
processor.AfterUpdate()
|
|
}
|
|
} else {
|
|
lenAfterClosures := len(session.afterClosures)
|
|
if lenAfterClosures > 0 {
|
|
if value, has := session.afterUpdateBeans[bean]; has && value != nil {
|
|
*value = append(*value, session.afterClosures...)
|
|
} else {
|
|
afterClosures := make([]func(interface{}), lenAfterClosures)
|
|
copy(afterClosures, session.afterClosures)
|
|
// FIXME: if bean is a map type, it will panic because map cannot be as map key
|
|
session.afterUpdateBeans[bean] = &afterClosures
|
|
}
|
|
} else {
|
|
if _, ok := interface{}(bean).(AfterUpdateProcessor); ok {
|
|
session.afterUpdateBeans[bean] = nil
|
|
}
|
|
}
|
|
}
|
|
cleanupProcessorsClosures(&session.afterClosures) // cleanup after used
|
|
// --
|
|
|
|
return res.RowsAffected()
|
|
}
|
|
|
|
func (session *Session) genUpdateColumns(bean interface{}) ([]string, []interface{}, error) {
|
|
table := session.statement.RefTable
|
|
colNames := make([]string, 0, len(table.ColumnsSeq()))
|
|
args := make([]interface{}, 0, len(table.ColumnsSeq()))
|
|
|
|
for _, col := range table.Columns() {
|
|
if !col.IsVersion && !col.IsCreated && !col.IsUpdated {
|
|
if session.statement.OmitColumnMap.Contain(col.Name) {
|
|
continue
|
|
}
|
|
}
|
|
if col.MapType == schemas.ONLYFROMDB {
|
|
continue
|
|
}
|
|
|
|
fieldValuePtr, err := col.ValueOf(bean)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
fieldValue := *fieldValuePtr
|
|
|
|
if col.IsAutoIncrement && utils.IsValueZero(fieldValue) {
|
|
continue
|
|
}
|
|
|
|
if (col.IsDeleted && !session.statement.GetUnscoped()) || col.IsCreated {
|
|
continue
|
|
}
|
|
|
|
// if only update specify columns
|
|
if len(session.statement.ColumnMap) > 0 && !session.statement.ColumnMap.Contain(col.Name) {
|
|
continue
|
|
}
|
|
|
|
if session.statement.IncrColumns.IsColExist(col.Name) {
|
|
continue
|
|
} else if session.statement.DecrColumns.IsColExist(col.Name) {
|
|
continue
|
|
} else if session.statement.ExprColumns.IsColExist(col.Name) {
|
|
continue
|
|
}
|
|
|
|
// !evalphobia! set fieldValue as nil when column is nullable and zero-value
|
|
if _, ok := getFlagForColumn(session.statement.NullableMap, col); ok {
|
|
if col.Nullable && utils.IsValueZero(fieldValue) {
|
|
var nilValue *int
|
|
fieldValue = reflect.ValueOf(nilValue)
|
|
}
|
|
}
|
|
|
|
if col.IsUpdated && session.statement.UseAutoTime /*&& isZero(fieldValue.Interface())*/ {
|
|
// if time is non-empty, then set to auto time
|
|
val, t, err := session.engine.nowTime(col)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
args = append(args, val)
|
|
|
|
colName := col.Name
|
|
session.afterClosures = append(session.afterClosures, func(bean interface{}) {
|
|
col := table.GetColumn(colName)
|
|
setColumnTime(bean, col, t)
|
|
})
|
|
} else if col.IsVersion && session.statement.CheckVersion {
|
|
args = append(args, 1)
|
|
} else {
|
|
arg, err := session.statement.Value2Interface(col, fieldValue)
|
|
if err != nil {
|
|
return colNames, args, err
|
|
}
|
|
args = append(args, arg)
|
|
}
|
|
|
|
colNames = append(colNames, session.engine.Quote(col.Name)+" = ?")
|
|
}
|
|
return colNames, args, nil
|
|
}
|