From b510fc584f361a42cd9df19c887a45fc88863bb1 Mon Sep 17 00:00:00 2001 From: Kazuhiro Oinuma Date: Mon, 3 Nov 2014 22:03:12 +0900 Subject: [PATCH 1/4] Add softdelete feature --- engine.go | 9 +++++++++ session.go | 36 +++++++++++++++++++++++++++++++++--- statement.go | 7 +++++++ 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/engine.go b/engine.go index cc498441..81eae28a 100644 --- a/engine.go +++ b/engine.go @@ -45,6 +45,7 @@ type Engine struct { TZLocation *time.Location disableGlobalCache bool + unscoped bool } func (engine *Engine) SetDisableGlobalCache(disable bool) { @@ -796,6 +797,8 @@ func (engine *Engine) mapType(v reflect.Value) *core.Table { if !hasNoCacheTag { hasNoCacheTag = true } + case k == "SOFTDELETE": + col.IsSoftDelete = true case k == "NOT": default: if strings.HasPrefix(k, "'") && strings.HasSuffix(k, "'") { @@ -1414,3 +1417,9 @@ func (engine *Engine) FormatTime(sqlTypeName string, t time.Time) (v interface{} } return } + +// Disable soft delete +func (engine *Engine) Unscoped() *Engine { + engine.unscoped = true + return engine +} diff --git a/session.go b/session.go index 9af02bfe..c6002b5c 100644 --- a/session.go +++ b/session.go @@ -3402,13 +3402,39 @@ func (session *Session) Delete(bean interface{}) (int64, error) { return 0, ErrNeedDeletedCond } - sqlStr := fmt.Sprintf("DELETE FROM %v WHERE %v", - session.Engine.Quote(session.Statement.TableName()), condition) + sqlStr, sqlStrForCache := "", "" + argsForCache := make([]interface{}, 0, len(args) * 2) + if session.Engine.unscoped || table.SoftDeleteColumn() == nil { // softdelete is disabled + sqlStr = fmt.Sprintf("DELETE FROM %v WHERE %v", + session.Engine.Quote(session.Statement.TableName()), condition) + + sqlStrForCache = sqlStr + copy(argsForCache, args) + argsForCache = append(session.Statement.Params, argsForCache...) + } else { + // !oinume! sqlStrForCache and argsForCache is needed to behave as executing "DELETE FROM ..." for cache. + sqlStrForCache = fmt.Sprintf("DELETE FROM %v WHERE %v", + session.Engine.Quote(session.Statement.TableName()), condition) + copy(argsForCache, args) + argsForCache = append(session.Statement.Params, argsForCache...) + + softDeleteCol := table.SoftDeleteColumn() + sqlStr = fmt.Sprintf("UPDATE %v SET %v = ? WHERE %v", + session.Engine.Quote(session.Statement.TableName()), + session.Engine.Quote(softDeleteCol.Name), + condition) + + // !oinume! Insert NowTime to the head of session.Statement.Params + session.Statement.Params = append(session.Statement.Params, "") + paramsLen := len(session.Statement.Params) + copy(session.Statement.Params[1:paramsLen], session.Statement.Params[0:paramsLen-1]) + session.Statement.Params[0] = session.Engine.NowTime(softDeleteCol.SQLType.Name) + } args = append(session.Statement.Params, args...) if cacher := session.Engine.getCacher2(session.Statement.RefTable); cacher != nil && session.Statement.UseCache { - session.cacheDelete(sqlStr, args...) + session.cacheDelete(sqlStrForCache, argsForCache...) } res, err := session.exec(sqlStr, args...) @@ -3651,6 +3677,10 @@ func genCols(table *core.Table, session *Session, bean interface{}, useCol bool, } } + if col.IsSoftDelete { + continue + } + if session.Statement.ColumnStr != "" { if _, ok := session.Statement.columnMap[lColName]; !ok { continue diff --git a/statement.go b/statement.go index 7391629d..ade939f5 100644 --- a/statement.go +++ b/statement.go @@ -286,6 +286,9 @@ func buildUpdates(engine *Engine, table *core.Table, bean interface{}, if !includeAutoIncr && col.IsAutoIncrement { continue } + if col.IsSoftDelete { + continue + } if engine.dialect.DBType() == core.MSSQL && col.SQLType.Name == core.Text { continue @@ -490,6 +493,10 @@ func buildConditions(engine *Engine, table *core.Table, bean interface{}, continue } + if col.IsSoftDelete && !engine.unscoped { // softdelete enabled + colNames = append(colNames, fmt.Sprintf("%v IS NULL", engine.Quote(col.Name))) + } + fieldValue := *fieldValuePtr if fieldValue.Interface() == nil { continue From 42f0fc27ea77fe2d38086feb5dec112796cce5a7 Mon Sep 17 00:00:00 2001 From: oinume Date: Wed, 5 Nov 2014 15:29:34 +0900 Subject: [PATCH 2/4] Tag name changed: softdelete -> deleted --- engine.go | 4 ++-- session.go | 10 +++++----- statement.go | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/engine.go b/engine.go index 81eae28a..f5e61bed 100644 --- a/engine.go +++ b/engine.go @@ -777,6 +777,8 @@ func (engine *Engine) mapType(v reflect.Value) *core.Table { col.Default = "1" case k == "UPDATED": col.IsUpdated = true + case k == "DELETED": + col.IsDeleted = true case strings.HasPrefix(k, "INDEX(") && strings.HasSuffix(k, ")"): indexName := k[len("INDEX")+1 : len(k)-1] indexNames[indexName] = core.IndexType @@ -797,8 +799,6 @@ func (engine *Engine) mapType(v reflect.Value) *core.Table { if !hasNoCacheTag { hasNoCacheTag = true } - case k == "SOFTDELETE": - col.IsSoftDelete = true case k == "NOT": default: if strings.HasPrefix(k, "'") && strings.HasSuffix(k, "'") { diff --git a/session.go b/session.go index c6002b5c..84a02eee 100644 --- a/session.go +++ b/session.go @@ -3404,7 +3404,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) { sqlStr, sqlStrForCache := "", "" argsForCache := make([]interface{}, 0, len(args) * 2) - if session.Engine.unscoped || table.SoftDeleteColumn() == nil { // softdelete is disabled + if session.Engine.unscoped || table.DeletedColumn() == nil { // deleted is disabled sqlStr = fmt.Sprintf("DELETE FROM %v WHERE %v", session.Engine.Quote(session.Statement.TableName()), condition) @@ -3418,17 +3418,17 @@ func (session *Session) Delete(bean interface{}) (int64, error) { copy(argsForCache, args) argsForCache = append(session.Statement.Params, argsForCache...) - softDeleteCol := table.SoftDeleteColumn() + deletedColumn := table.DeletedColumn() sqlStr = fmt.Sprintf("UPDATE %v SET %v = ? WHERE %v", session.Engine.Quote(session.Statement.TableName()), - session.Engine.Quote(softDeleteCol.Name), + session.Engine.Quote(deletedColumn.Name), condition) // !oinume! Insert NowTime to the head of session.Statement.Params session.Statement.Params = append(session.Statement.Params, "") paramsLen := len(session.Statement.Params) copy(session.Statement.Params[1:paramsLen], session.Statement.Params[0:paramsLen-1]) - session.Statement.Params[0] = session.Engine.NowTime(softDeleteCol.SQLType.Name) + session.Statement.Params[0] = session.Engine.NowTime(deletedColumn.SQLType.Name) } args = append(session.Statement.Params, args...) @@ -3677,7 +3677,7 @@ func genCols(table *core.Table, session *Session, bean interface{}, useCol bool, } } - if col.IsSoftDelete { + if col.IsDeleted { continue } diff --git a/statement.go b/statement.go index ade939f5..bb28c651 100644 --- a/statement.go +++ b/statement.go @@ -286,7 +286,7 @@ func buildUpdates(engine *Engine, table *core.Table, bean interface{}, if !includeAutoIncr && col.IsAutoIncrement { continue } - if col.IsSoftDelete { + if col.IsDeleted { continue } @@ -493,7 +493,7 @@ func buildConditions(engine *Engine, table *core.Table, bean interface{}, continue } - if col.IsSoftDelete && !engine.unscoped { // softdelete enabled + if col.IsDeleted && !engine.unscoped { // deleted enabled colNames = append(colNames, fmt.Sprintf("%v IS NULL", engine.Quote(col.Name))) } From f08792908206053e6f3f321aeeb6697fad2c17c7 Mon Sep 17 00:00:00 2001 From: oinume Date: Wed, 5 Nov 2014 16:04:53 +0900 Subject: [PATCH 3/4] Move 'unscoped' field from Engine to Statement. See https://github.com/go-xorm/xorm/pull/175#issuecomment-61599948 --- engine.go | 10 +++++----- session.go | 14 ++++++++++---- statement.go | 17 +++++++++++++---- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/engine.go b/engine.go index f5e61bed..6c974d7a 100644 --- a/engine.go +++ b/engine.go @@ -45,7 +45,6 @@ type Engine struct { TZLocation *time.Location disableGlobalCache bool - unscoped bool } func (engine *Engine) SetDisableGlobalCache(disable bool) { @@ -1418,8 +1417,9 @@ func (engine *Engine) FormatTime(sqlTypeName string, t time.Time) (v interface{} return } -// Disable soft delete -func (engine *Engine) Unscoped() *Engine { - engine.unscoped = true - return engine +// Always disable struct tag "deleted" +func (engine *Engine) Unscoped() *Session { + session := engine.NewSession() + defer session.Close() + return session.Unscoped() } diff --git a/session.go b/session.go index 84a02eee..acc52420 100644 --- a/session.go +++ b/session.go @@ -1079,7 +1079,7 @@ func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{}) if len(condiBean) > 0 { colNames, args := buildConditions(session.Engine, table, condiBean[0], true, true, false, true, session.Statement.allUseBool, session.Statement.useAllCols, - session.Statement.mustColumnMap) + session.Statement.unscoped, session.Statement.mustColumnMap) session.Statement.ConditionStr = strings.Join(colNames, " AND ") session.Statement.BeanArgs = args } @@ -3172,7 +3172,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 if len(condiBean) > 0 { condiColNames, condiArgs = buildConditions(session.Engine, session.Statement.RefTable, condiBean[0], true, true, false, true, session.Statement.allUseBool, session.Statement.useAllCols, - session.Statement.mustColumnMap) + session.Statement.unscoped, session.Statement.mustColumnMap) } var condition = "" @@ -3376,7 +3376,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) { session.Statement.RefTable = table colNames, args := buildConditions(session.Engine, table, bean, true, true, false, true, session.Statement.allUseBool, session.Statement.useAllCols, - session.Statement.mustColumnMap) + session.Statement.unscoped, session.Statement.mustColumnMap) var condition = "" var andStr = session.Engine.dialect.AndStr() @@ -3404,7 +3404,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) { sqlStr, sqlStrForCache := "", "" argsForCache := make([]interface{}, 0, len(args) * 2) - if session.Engine.unscoped || table.DeletedColumn() == nil { // deleted is disabled + if session.Statement.unscoped || table.DeletedColumn() == nil { // tag "deleted" is disabled sqlStr = fmt.Sprintf("DELETE FROM %v WHERE %v", session.Engine.Quote(session.Statement.TableName()), condition) @@ -3638,6 +3638,12 @@ func (s *Session) Sync2(beans ...interface{}) error { return nil } +// Always disable struct tag "deleted" +func (session *Session) Unscoped() *Session { + session.Statement.Unscoped() + return session +} + func genCols(table *core.Table, session *Session, bean interface{}, useCol bool, includeQuote bool) ([]string, []interface{}, error) { colNames := make([]string, 0) args := make([]interface{}, 0) diff --git a/statement.go b/statement.go index bb28c651..12006128 100644 --- a/statement.go +++ b/statement.go @@ -57,6 +57,7 @@ type Statement struct { IsDistinct bool allUseBool bool checkVersion bool + unscoped bool mustColumnMap map[string]bool inColumns map[string]*inParam incrColumns map[string]incrParam @@ -91,6 +92,7 @@ func (statement *Statement) Init() { statement.useAllCols = false statement.mustColumnMap = make(map[string]bool) statement.checkVersion = true + statement.unscoped = false statement.inColumns = make(map[string]*inParam) statement.incrColumns = make(map[string]incrParam) statement.decrColumns = make(map[string]decrParam) @@ -468,7 +470,7 @@ func buildUpdates(engine *Engine, table *core.Table, bean interface{}, // Auto generating conditions according a struct func buildConditions(engine *Engine, table *core.Table, bean interface{}, includeVersion bool, includeUpdated bool, includeNil bool, - includeAutoIncr bool, allUseBool bool, useAllCols bool, + includeAutoIncr bool, allUseBool bool, useAllCols bool, unscoped bool, mustColumnMap map[string]bool) ([]string, []interface{}) { colNames := make([]string, 0) @@ -493,7 +495,7 @@ func buildConditions(engine *Engine, table *core.Table, bean interface{}, continue } - if col.IsDeleted && !engine.unscoped { // deleted enabled + if col.IsDeleted && !unscoped { // tag "deleted" is enabled colNames = append(colNames, fmt.Sprintf("%v IS NULL", engine.Quote(col.Name))) } @@ -933,6 +935,12 @@ func (statement *Statement) Having(conditions string) *Statement { return statement } +// Always disable struct tag "deleted" +func (statement *Statement) Unscoped() *Statement { + statement.unscoped = true + return statement +} + func (statement *Statement) genColumnStr() string { table := statement.RefTable colNames := make([]string, 0) @@ -1037,7 +1045,7 @@ func (statement *Statement) genGetSql(bean interface{}) (string, []interface{}) colNames, args := buildConditions(statement.Engine, table, bean, true, true, false, true, statement.allUseBool, statement.useAllCols, - statement.mustColumnMap) + statement.unscoped, statement.mustColumnMap) statement.ConditionStr = strings.Join(colNames, " "+statement.Engine.dialect.AndStr()+" ") statement.BeanArgs = args @@ -1083,7 +1091,8 @@ func (statement *Statement) genCountSql(bean interface{}) (string, []interface{} statement.RefTable = table colNames, args := buildConditions(statement.Engine, table, bean, true, true, false, - true, statement.allUseBool, statement.useAllCols, statement.mustColumnMap) + true, statement.allUseBool, statement.useAllCols, + statement.unscoped, statement.mustColumnMap) statement.ConditionStr = strings.Join(colNames, " "+statement.Engine.Dialect().AndStr()+" ") statement.BeanArgs = args From 7db23ba469d2650da16740f94e858c81c1b8b2a0 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 5 Nov 2014 15:40:44 +0800 Subject: [PATCH 4/4] bug fixed for unscoped --- engine.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine.go b/engine.go index 6c974d7a..07a23d30 100644 --- a/engine.go +++ b/engine.go @@ -1420,6 +1420,6 @@ func (engine *Engine) FormatTime(sqlTypeName string, t time.Time) (v interface{} // Always disable struct tag "deleted" func (engine *Engine) Unscoped() *Session { session := engine.NewSession() - defer session.Close() + session.IsAutoClose = true return session.Unscoped() }