grafana/vendor/github.com/go-xorm/xorm/session_delete.go
Marcus Efraimsson 5a96863eed
pin versions of xorm to resolve sql tests
Resolves issue with postgres tests.
Also sets timezone of test instance and database to utc to resolve
mysql tests.
Closes #12065
2018-05-28 13:07:41 +02:00

241 lines
6.5 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 (
"errors"
"fmt"
"strconv"
"github.com/go-xorm/core"
)
func (session *Session) cacheDelete(table *core.Table, tableName, sqlStr string, args ...interface{}) error {
if table == nil ||
session.tx != nil {
return ErrCacheFailed
}
for _, filter := range session.engine.dialect.Filters() {
sqlStr = filter.Do(sqlStr, session.engine.dialect, table)
}
newsql := session.statement.convertIDSQL(sqlStr)
if newsql == "" {
return ErrCacheFailed
}
cacher := session.engine.getCacher2(table)
pkColumns := table.PKColumns()
ids, err := core.GetCacheSql(cacher, tableName, newsql, args)
if err != nil {
resultsSlice, err := session.queryBytes(newsql, args...)
if err != nil {
return err
}
ids = make([]core.PK, 0)
if len(resultsSlice) > 0 {
for _, data := range resultsSlice {
var id int64
var pk core.PK = make([]interface{}, 0)
for _, col := range pkColumns {
if v, ok := data[col.Name]; !ok {
return errors.New("no id")
} else if col.SQLType.IsText() {
pk = append(pk, string(v))
} else if col.SQLType.IsNumeric() {
id, err = strconv.ParseInt(string(v), 10, 64)
if err != nil {
return err
}
pk = append(pk, id)
} else {
return errors.New("not supported primary key type")
}
}
ids = append(ids, pk)
}
}
}
for _, id := range ids {
session.engine.logger.Debug("[cacheDelete] delete cache obj:", tableName, id)
sid, err := id.ToString()
if err != nil {
return err
}
cacher.DelBean(tableName, sid)
}
session.engine.logger.Debug("[cacheDelete] clear cache table:", tableName)
cacher.ClearIds(tableName)
return nil
}
// Delete records, bean's non-empty fields are conditions
func (session *Session) Delete(bean interface{}) (int64, error) {
if session.isAutoClose {
defer session.Close()
}
if err := session.statement.setRefValue(rValue(bean)); err != nil {
return 0, err
}
// handle before delete processors
for _, closure := range session.beforeClosures {
closure(bean)
}
cleanupProcessorsClosures(&session.beforeClosures)
if processor, ok := interface{}(bean).(BeforeDeleteProcessor); ok {
processor.BeforeDelete()
}
condSQL, condArgs, err := session.statement.genConds(bean)
if err != nil {
return 0, err
}
if len(condSQL) == 0 && session.statement.LimitN == 0 {
return 0, ErrNeedDeletedCond
}
var tableNameNoQuote = session.statement.TableName()
var tableName = session.engine.Quote(tableNameNoQuote)
var table = session.statement.RefTable
var deleteSQL string
if len(condSQL) > 0 {
deleteSQL = fmt.Sprintf("DELETE FROM %v WHERE %v", tableName, condSQL)
} else {
deleteSQL = fmt.Sprintf("DELETE FROM %v", tableName)
}
var orderSQL string
if len(session.statement.OrderStr) > 0 {
orderSQL += fmt.Sprintf(" ORDER BY %s", session.statement.OrderStr)
}
if session.statement.LimitN > 0 {
orderSQL += fmt.Sprintf(" LIMIT %d", session.statement.LimitN)
}
if len(orderSQL) > 0 {
switch session.engine.dialect.DBType() {
case core.POSTGRES:
inSQL := fmt.Sprintf("ctid IN (SELECT ctid FROM %s%s)", tableName, orderSQL)
if len(condSQL) > 0 {
deleteSQL += " AND " + inSQL
} else {
deleteSQL += " WHERE " + inSQL
}
case core.SQLITE:
inSQL := fmt.Sprintf("rowid IN (SELECT rowid FROM %s%s)", tableName, orderSQL)
if len(condSQL) > 0 {
deleteSQL += " AND " + inSQL
} else {
deleteSQL += " WHERE " + inSQL
}
// TODO: how to handle delete limit on mssql?
case core.MSSQL:
return 0, ErrNotImplemented
default:
deleteSQL += orderSQL
}
}
var realSQL string
argsForCache := make([]interface{}, 0, len(condArgs)*2)
if session.statement.unscoped || table.DeletedColumn() == nil { // tag "deleted" is disabled
realSQL = deleteSQL
copy(argsForCache, condArgs)
argsForCache = append(condArgs, argsForCache...)
} else {
// !oinume! sqlStrForCache and argsForCache is needed to behave as executing "DELETE FROM ..." for cache.
copy(argsForCache, condArgs)
argsForCache = append(condArgs, argsForCache...)
deletedColumn := table.DeletedColumn()
realSQL = fmt.Sprintf("UPDATE %v SET %v = ? WHERE %v",
session.engine.Quote(session.statement.TableName()),
session.engine.Quote(deletedColumn.Name),
condSQL)
if len(orderSQL) > 0 {
switch session.engine.dialect.DBType() {
case core.POSTGRES:
inSQL := fmt.Sprintf("ctid IN (SELECT ctid FROM %s%s)", tableName, orderSQL)
if len(condSQL) > 0 {
realSQL += " AND " + inSQL
} else {
realSQL += " WHERE " + inSQL
}
case core.SQLITE:
inSQL := fmt.Sprintf("rowid IN (SELECT rowid FROM %s%s)", tableName, orderSQL)
if len(condSQL) > 0 {
realSQL += " AND " + inSQL
} else {
realSQL += " WHERE " + inSQL
}
// TODO: how to handle delete limit on mssql?
case core.MSSQL:
return 0, ErrNotImplemented
default:
realSQL += orderSQL
}
}
// !oinume! Insert nowTime to the head of session.statement.Params
condArgs = append(condArgs, "")
paramsLen := len(condArgs)
copy(condArgs[1:paramsLen], condArgs[0:paramsLen-1])
val, t := session.engine.nowTime(deletedColumn)
condArgs[0] = val
var colName = deletedColumn.Name
session.afterClosures = append(session.afterClosures, func(bean interface{}) {
col := table.GetColumn(colName)
setColumnTime(bean, col, t)
})
}
if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache {
session.cacheDelete(table, tableNameNoQuote, deleteSQL, argsForCache...)
}
session.statement.RefTable = table
res, err := session.exec(realSQL, condArgs...)
if err != nil {
return 0, err
}
// handle after delete processors
if session.isAutoCommit {
for _, closure := range session.afterClosures {
closure(bean)
}
if processor, ok := interface{}(bean).(AfterDeleteProcessor); ok {
processor.AfterDelete()
}
} else {
lenAfterClosures := len(session.afterClosures)
if lenAfterClosures > 0 {
if value, has := session.afterDeleteBeans[bean]; has && value != nil {
*value = append(*value, session.afterClosures...)
} else {
afterClosures := make([]func(interface{}), lenAfterClosures)
copy(afterClosures, session.afterClosures)
session.afterDeleteBeans[bean] = &afterClosures
}
} else {
if _, ok := interface{}(bean).(AfterDeleteProcessor); ok {
session.afterDeleteBeans[bean] = nil
}
}
}
cleanupProcessorsClosures(&session.afterClosures)
// --
return res.RowsAffected()
}