2021-11-07 20:44:48 +01:00
|
|
|
// 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"
|
|
|
|
|
2022-06-05 23:07:32 +02:00
|
|
|
"xorm.io/builder"
|
2021-11-07 20:44:48 +01:00
|
|
|
"xorm.io/xorm/caches"
|
2022-06-05 23:07:32 +02:00
|
|
|
"xorm.io/xorm/internal/utils"
|
2021-11-07 20:44:48 +01:00
|
|
|
"xorm.io/xorm/schemas"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
// ErrNeedDeletedCond delete needs less one condition error
|
|
|
|
ErrNeedDeletedCond = errors.New("Delete action needs at least one condition")
|
|
|
|
|
|
|
|
// ErrNotImplemented not implemented
|
|
|
|
ErrNotImplemented = errors.New("Not implemented")
|
|
|
|
)
|
|
|
|
|
|
|
|
func (session *Session) cacheDelete(table *schemas.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)
|
|
|
|
}
|
|
|
|
|
|
|
|
newsql := session.statement.ConvertIDSQL(sqlStr)
|
|
|
|
if newsql == "" {
|
|
|
|
return ErrCacheFailed
|
|
|
|
}
|
|
|
|
|
|
|
|
cacher := session.engine.cacherMgr.GetCacher(tableName)
|
|
|
|
pkColumns := table.PKColumns()
|
|
|
|
ids, err := caches.GetCacheSql(cacher, tableName, newsql, args)
|
|
|
|
if err != nil {
|
2022-06-05 23:07:32 +02:00
|
|
|
rows, err := session.queryRows(newsql, args...)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
|
|
|
|
resultsSlice, err := session.engine.ScanStringMaps(rows)
|
2021-11-07 20:44:48 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
ids = make([]schemas.PK, 0)
|
|
|
|
if len(resultsSlice) > 0 {
|
|
|
|
for _, data := range resultsSlice {
|
|
|
|
var id int64
|
|
|
|
var pk schemas.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() {
|
2022-06-05 23:07:32 +02:00
|
|
|
pk = append(pk, v)
|
2021-11-07 20:44:48 +01:00
|
|
|
} else if col.SQLType.IsNumeric() {
|
2022-06-05 23:07:32 +02:00
|
|
|
id, err = strconv.ParseInt(v, 10, 64)
|
2021-11-07 20:44:48 +01:00
|
|
|
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.Debugf("[cache] delete cache obj: %v, %v", tableName, id)
|
|
|
|
sid, err := id.ToString()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
cacher.DelBean(tableName, sid)
|
|
|
|
}
|
|
|
|
session.engine.logger.Debugf("[cache] clear cache table: %v", tableName)
|
|
|
|
cacher.ClearIds(tableName)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete records, bean's non-empty fields are conditions
|
|
|
|
func (session *Session) Delete(beans ...interface{}) (int64, error) {
|
|
|
|
if session.isAutoClose {
|
|
|
|
defer session.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
if session.statement.LastError != nil {
|
|
|
|
return 0, session.statement.LastError
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
2022-06-05 23:07:32 +02:00
|
|
|
condWriter = builder.NewWriter()
|
|
|
|
err error
|
|
|
|
bean interface{}
|
2021-11-07 20:44:48 +01:00
|
|
|
)
|
|
|
|
if len(beans) > 0 {
|
|
|
|
bean = beans[0]
|
|
|
|
if err = session.statement.SetRefBean(bean); err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
executeBeforeClosures(session, bean)
|
|
|
|
|
|
|
|
if processor, ok := interface{}(bean).(BeforeDeleteProcessor); ok {
|
|
|
|
processor.BeforeDelete()
|
|
|
|
}
|
|
|
|
|
2022-06-05 23:07:32 +02:00
|
|
|
if err = session.statement.MergeConds(bean); err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
2021-11-07 20:44:48 +01:00
|
|
|
}
|
2022-06-05 23:07:32 +02:00
|
|
|
|
|
|
|
if err = session.statement.Conds().WriteTo(session.statement.QuoteReplacer(condWriter)); err != nil {
|
2021-11-07 20:44:48 +01:00
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
pLimitN := session.statement.LimitN
|
2022-06-05 23:07:32 +02:00
|
|
|
if condWriter.Len() == 0 && (pLimitN == nil || *pLimitN == 0) {
|
2021-11-07 20:44:48 +01:00
|
|
|
return 0, ErrNeedDeletedCond
|
|
|
|
}
|
|
|
|
|
2022-06-05 23:07:32 +02:00
|
|
|
tableNameNoQuote := session.statement.TableName()
|
|
|
|
tableName := session.engine.Quote(tableNameNoQuote)
|
|
|
|
table := session.statement.RefTable
|
|
|
|
deleteSQLWriter := builder.NewWriter()
|
|
|
|
fmt.Fprintf(deleteSQLWriter, "DELETE FROM %v", tableName)
|
|
|
|
if condWriter.Len() > 0 {
|
|
|
|
fmt.Fprintf(deleteSQLWriter, " WHERE %v", condWriter.String())
|
|
|
|
deleteSQLWriter.Append(condWriter.Args()...)
|
2021-11-07 20:44:48 +01:00
|
|
|
}
|
|
|
|
|
2022-06-05 23:07:32 +02:00
|
|
|
orderSQLWriter := builder.NewWriter()
|
|
|
|
if err := session.statement.WriteOrderBy(orderSQLWriter); err != nil {
|
|
|
|
return 0, err
|
2021-11-07 20:44:48 +01:00
|
|
|
}
|
2022-06-05 23:07:32 +02:00
|
|
|
|
2021-11-07 20:44:48 +01:00
|
|
|
if pLimitN != nil && *pLimitN > 0 {
|
|
|
|
limitNValue := *pLimitN
|
2022-06-05 23:07:32 +02:00
|
|
|
if _, err := fmt.Fprintf(orderSQLWriter, " LIMIT %d", limitNValue); err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
2021-11-07 20:44:48 +01:00
|
|
|
}
|
|
|
|
|
2022-06-05 23:07:32 +02:00
|
|
|
orderCondWriter := builder.NewWriter()
|
|
|
|
if orderSQLWriter.Len() > 0 {
|
2021-11-07 20:44:48 +01:00
|
|
|
switch session.engine.dialect.URI().DBType {
|
|
|
|
case schemas.POSTGRES:
|
2022-06-05 23:07:32 +02:00
|
|
|
if condWriter.Len() > 0 {
|
|
|
|
fmt.Fprintf(orderCondWriter, " AND ")
|
2021-11-07 20:44:48 +01:00
|
|
|
} else {
|
2022-06-05 23:07:32 +02:00
|
|
|
fmt.Fprintf(orderCondWriter, " WHERE ")
|
2021-11-07 20:44:48 +01:00
|
|
|
}
|
2022-06-05 23:07:32 +02:00
|
|
|
fmt.Fprintf(orderCondWriter, "ctid IN (SELECT ctid FROM %s%s)", tableName, orderSQLWriter.String())
|
|
|
|
orderCondWriter.Append(orderSQLWriter.Args()...)
|
2021-11-07 20:44:48 +01:00
|
|
|
case schemas.SQLITE:
|
2022-06-05 23:07:32 +02:00
|
|
|
if condWriter.Len() > 0 {
|
|
|
|
fmt.Fprintf(orderCondWriter, " AND ")
|
2021-11-07 20:44:48 +01:00
|
|
|
} else {
|
2022-06-05 23:07:32 +02:00
|
|
|
fmt.Fprintf(orderCondWriter, " WHERE ")
|
2021-11-07 20:44:48 +01:00
|
|
|
}
|
2022-06-05 23:07:32 +02:00
|
|
|
fmt.Fprintf(orderCondWriter, "rowid IN (SELECT rowid FROM %s%s)", tableName, orderSQLWriter.String())
|
2021-11-07 20:44:48 +01:00
|
|
|
// TODO: how to handle delete limit on mssql?
|
|
|
|
case schemas.MSSQL:
|
|
|
|
return 0, ErrNotImplemented
|
|
|
|
default:
|
2022-06-05 23:07:32 +02:00
|
|
|
fmt.Fprint(orderCondWriter, orderSQLWriter.String())
|
|
|
|
orderCondWriter.Append(orderSQLWriter.Args()...)
|
2021-11-07 20:44:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-05 23:07:32 +02:00
|
|
|
realSQLWriter := builder.NewWriter()
|
|
|
|
argsForCache := make([]interface{}, 0, len(deleteSQLWriter.Args())*2)
|
|
|
|
copy(argsForCache, deleteSQLWriter.Args())
|
|
|
|
argsForCache = append(deleteSQLWriter.Args(), argsForCache...)
|
2021-11-07 20:44:48 +01:00
|
|
|
if session.statement.GetUnscoped() || table == nil || table.DeletedColumn() == nil { // tag "deleted" is disabled
|
2022-06-05 23:07:32 +02:00
|
|
|
if err := utils.WriteBuilder(realSQLWriter, deleteSQLWriter, orderCondWriter); err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
2021-11-07 20:44:48 +01:00
|
|
|
} else {
|
|
|
|
deletedColumn := table.DeletedColumn()
|
2022-06-05 23:07:32 +02:00
|
|
|
if _, err := fmt.Fprintf(realSQLWriter, "UPDATE %v SET %v = ? WHERE %v",
|
2021-11-07 20:44:48 +01:00
|
|
|
session.engine.Quote(session.statement.TableName()),
|
|
|
|
session.engine.Quote(deletedColumn.Name),
|
2022-06-05 23:07:32 +02:00
|
|
|
condWriter.String()); err != nil {
|
|
|
|
return 0, err
|
2021-11-07 20:44:48 +01:00
|
|
|
}
|
|
|
|
val, t, err := session.engine.nowTime(deletedColumn)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
2022-06-05 23:07:32 +02:00
|
|
|
realSQLWriter.Append(val)
|
|
|
|
realSQLWriter.Append(condWriter.Args()...)
|
|
|
|
|
|
|
|
if err := utils.WriteBuilder(realSQLWriter, orderCondWriter); err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
2021-11-07 20:44:48 +01:00
|
|
|
|
2022-06-05 23:07:32 +02:00
|
|
|
colName := deletedColumn.Name
|
2021-11-07 20:44:48 +01:00
|
|
|
session.afterClosures = append(session.afterClosures, func(bean interface{}) {
|
|
|
|
col := table.GetColumn(colName)
|
|
|
|
setColumnTime(bean, col, t)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
if cacher := session.engine.GetCacher(tableNameNoQuote); cacher != nil && session.statement.UseCache {
|
2022-06-05 23:07:32 +02:00
|
|
|
_ = session.cacheDelete(table, tableNameNoQuote, deleteSQLWriter.String(), argsForCache...)
|
2021-11-07 20:44:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
session.statement.RefTable = table
|
2022-06-05 23:07:32 +02:00
|
|
|
res, err := session.exec(realSQLWriter.String(), realSQLWriter.Args()...)
|
2021-11-07 20:44:48 +01:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if bean != nil {
|
|
|
|
// 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 && len(beans) > 0 {
|
|
|
|
if value, has := session.afterDeleteBeans[beans[0]]; 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()
|
|
|
|
}
|