Simple and Powerful ORM for Go, support mysql,postgres,tidb,sqlite3,sqlite,mssql,oracle,cockroach
https://xorm.io
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
257 lines
7.1 KiB
257 lines
7.1 KiB
// 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" |
|
|
|
"xorm.io/builder" |
|
"xorm.io/xorm/caches" |
|
"xorm.io/xorm/internal/utils" |
|
"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 { |
|
rows, err := session.queryRows(newsql, args...) |
|
if err != nil { |
|
return err |
|
} |
|
defer rows.Close() |
|
|
|
resultsSlice, err := session.engine.ScanStringMaps(rows) |
|
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() { |
|
pk = append(pk, v) |
|
} else if col.SQLType.IsNumeric() { |
|
id, err = strconv.ParseInt(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.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 ( |
|
condWriter = builder.NewWriter() |
|
err error |
|
bean interface{} |
|
) |
|
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() |
|
} |
|
|
|
if err = session.statement.MergeConds(bean); err != nil { |
|
return 0, err |
|
} |
|
} |
|
|
|
if err = session.statement.Conds().WriteTo(session.statement.QuoteReplacer(condWriter)); err != nil { |
|
return 0, err |
|
} |
|
|
|
pLimitN := session.statement.LimitN |
|
if condWriter.Len() == 0 && (pLimitN == nil || *pLimitN == 0) { |
|
return 0, ErrNeedDeletedCond |
|
} |
|
|
|
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()...) |
|
} |
|
|
|
orderSQLWriter := builder.NewWriter() |
|
if err := session.statement.WriteOrderBy(orderSQLWriter); err != nil { |
|
return 0, err |
|
} |
|
|
|
if pLimitN != nil && *pLimitN > 0 { |
|
limitNValue := *pLimitN |
|
if _, err := fmt.Fprintf(orderSQLWriter, " LIMIT %d", limitNValue); err != nil { |
|
return 0, err |
|
} |
|
} |
|
|
|
orderCondWriter := builder.NewWriter() |
|
if orderSQLWriter.Len() > 0 { |
|
switch session.engine.dialect.URI().DBType { |
|
case schemas.POSTGRES: |
|
if condWriter.Len() > 0 { |
|
fmt.Fprintf(orderCondWriter, " AND ") |
|
} else { |
|
fmt.Fprintf(orderCondWriter, " WHERE ") |
|
} |
|
fmt.Fprintf(orderCondWriter, "ctid IN (SELECT ctid FROM %s%s)", tableName, orderSQLWriter.String()) |
|
orderCondWriter.Append(orderSQLWriter.Args()...) |
|
case schemas.SQLITE: |
|
if condWriter.Len() > 0 { |
|
fmt.Fprintf(orderCondWriter, " AND ") |
|
} else { |
|
fmt.Fprintf(orderCondWriter, " WHERE ") |
|
} |
|
fmt.Fprintf(orderCondWriter, "rowid IN (SELECT rowid FROM %s%s)", tableName, orderSQLWriter.String()) |
|
// TODO: how to handle delete limit on mssql? |
|
case schemas.MSSQL: |
|
return 0, ErrNotImplemented |
|
default: |
|
fmt.Fprint(orderCondWriter, orderSQLWriter.String()) |
|
orderCondWriter.Append(orderSQLWriter.Args()...) |
|
} |
|
} |
|
|
|
realSQLWriter := builder.NewWriter() |
|
argsForCache := make([]interface{}, 0, len(deleteSQLWriter.Args())*2) |
|
copy(argsForCache, deleteSQLWriter.Args()) |
|
argsForCache = append(deleteSQLWriter.Args(), argsForCache...) |
|
if session.statement.GetUnscoped() || table == nil || table.DeletedColumn() == nil { // tag "deleted" is disabled |
|
if err := utils.WriteBuilder(realSQLWriter, deleteSQLWriter, orderCondWriter); err != nil { |
|
return 0, err |
|
} |
|
} else { |
|
deletedColumn := table.DeletedColumn() |
|
if _, err := fmt.Fprintf(realSQLWriter, "UPDATE %v SET %v = ? WHERE %v", |
|
session.engine.Quote(session.statement.TableName()), |
|
session.engine.Quote(deletedColumn.Name), |
|
condWriter.String()); err != nil { |
|
return 0, err |
|
} |
|
val, t, err := session.engine.nowTime(deletedColumn) |
|
if err != nil { |
|
return 0, err |
|
} |
|
realSQLWriter.Append(val) |
|
realSQLWriter.Append(condWriter.Args()...) |
|
|
|
if err := utils.WriteBuilder(realSQLWriter, orderCondWriter); err != nil { |
|
return 0, err |
|
} |
|
|
|
colName := deletedColumn.Name |
|
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 { |
|
_ = session.cacheDelete(table, tableNameNoQuote, deleteSQLWriter.String(), argsForCache...) |
|
} |
|
|
|
session.statement.RefTable = table |
|
res, err := session.exec(realSQLWriter.String(), realSQLWriter.Args()...) |
|
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() |
|
}
|
|
|