mirror of https://github.com/gogits/gogs.git
Unknwon
8 years ago
25 changed files with 4355 additions and 4277 deletions
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,238 @@ |
|||||||
|
// 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(sqlStr string, args ...interface{}) error { |
||||||
|
if session.Statement.RefTable == nil || |
||||||
|
session.Tx != nil { |
||||||
|
return ErrCacheFailed |
||||||
|
} |
||||||
|
|
||||||
|
for _, filter := range session.Engine.dialect.Filters() { |
||||||
|
sqlStr = filter.Do(sqlStr, session.Engine.dialect, session.Statement.RefTable) |
||||||
|
} |
||||||
|
|
||||||
|
newsql := session.Statement.convertIDSQL(sqlStr) |
||||||
|
if newsql == "" { |
||||||
|
return ErrCacheFailed |
||||||
|
} |
||||||
|
|
||||||
|
cacher := session.Engine.getCacher2(session.Statement.RefTable) |
||||||
|
tableName := session.Statement.TableName() |
||||||
|
ids, err := core.GetCacheSql(cacher, tableName, newsql, args) |
||||||
|
if err != nil { |
||||||
|
resultsSlice, err := session.query(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 session.Statement.RefTable.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) |
||||||
|
} |
||||||
|
} |
||||||
|
} /*else { |
||||||
|
session.Engine.LogDebug("delete cache sql %v", newsql) |
||||||
|
cacher.DelIds(tableName, genSqlKey(newsql, args)) |
||||||
|
}*/ |
||||||
|
|
||||||
|
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 sql", tableName) |
||||||
|
cacher.ClearIds(tableName) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Delete records, bean's non-empty fields are conditions
|
||||||
|
func (session *Session) Delete(bean interface{}) (int64, error) { |
||||||
|
defer session.resetStatement() |
||||||
|
if session.IsAutoClose { |
||||||
|
defer session.Close() |
||||||
|
} |
||||||
|
|
||||||
|
session.Statement.setRefValue(rValue(bean)) |
||||||
|
var table = session.Statement.RefTable |
||||||
|
|
||||||
|
// 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, _ := session.Statement.genConds(bean) |
||||||
|
if len(condSQL) == 0 && session.Statement.LimitN == 0 { |
||||||
|
return 0, ErrNeedDeletedCond |
||||||
|
} |
||||||
|
|
||||||
|
var tableName = session.Engine.Quote(session.Statement.TableName()) |
||||||
|
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.NowTime2(deletedColumn.SQLType.Name) |
||||||
|
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(session.Statement.RefTable); cacher != nil && session.Statement.UseCache { |
||||||
|
session.cacheDelete(deleteSQL, argsForCache...) |
||||||
|
} |
||||||
|
|
||||||
|
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() |
||||||
|
} |
@ -0,0 +1,458 @@ |
|||||||
|
// 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" |
||||||
|
"reflect" |
||||||
|
"strconv" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"github.com/go-xorm/builder" |
||||||
|
"github.com/go-xorm/core" |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
tpStruct = iota |
||||||
|
tpNonStruct |
||||||
|
) |
||||||
|
|
||||||
|
// Find retrieve records from table, condiBeans's non-empty fields
|
||||||
|
// are conditions. beans could be []Struct, []*Struct, map[int64]Struct
|
||||||
|
// map[int64]*Struct
|
||||||
|
func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{}) error { |
||||||
|
defer session.resetStatement() |
||||||
|
if session.IsAutoClose { |
||||||
|
defer session.Close() |
||||||
|
} |
||||||
|
|
||||||
|
sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr)) |
||||||
|
if sliceValue.Kind() != reflect.Slice && sliceValue.Kind() != reflect.Map { |
||||||
|
return errors.New("needs a pointer to a slice or a map") |
||||||
|
} |
||||||
|
|
||||||
|
sliceElementType := sliceValue.Type().Elem() |
||||||
|
|
||||||
|
var tp = tpStruct |
||||||
|
if session.Statement.RefTable == nil { |
||||||
|
if sliceElementType.Kind() == reflect.Ptr { |
||||||
|
if sliceElementType.Elem().Kind() == reflect.Struct { |
||||||
|
pv := reflect.New(sliceElementType.Elem()) |
||||||
|
session.Statement.setRefValue(pv.Elem()) |
||||||
|
} else { |
||||||
|
//return errors.New("slice type")
|
||||||
|
tp = tpNonStruct |
||||||
|
} |
||||||
|
} else if sliceElementType.Kind() == reflect.Struct { |
||||||
|
pv := reflect.New(sliceElementType) |
||||||
|
session.Statement.setRefValue(pv.Elem()) |
||||||
|
} else { |
||||||
|
//return errors.New("slice type")
|
||||||
|
tp = tpNonStruct |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var table = session.Statement.RefTable |
||||||
|
|
||||||
|
var addedTableName = (len(session.Statement.JoinStr) > 0) |
||||||
|
var autoCond builder.Cond |
||||||
|
if tp == tpStruct { |
||||||
|
if !session.Statement.noAutoCondition && len(condiBean) > 0 { |
||||||
|
var err error |
||||||
|
autoCond, err = session.Statement.buildConds(table, condiBean[0], true, true, false, true, addedTableName) |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
} else { |
||||||
|
// !oinume! Add "<col> IS NULL" to WHERE whatever condiBean is given.
|
||||||
|
// See https://github.com/go-xorm/xorm/issues/179
|
||||||
|
if col := table.DeletedColumn(); col != nil && !session.Statement.unscoped { // tag "deleted" is enabled
|
||||||
|
var colName = session.Engine.Quote(col.Name) |
||||||
|
if addedTableName { |
||||||
|
var nm = session.Statement.TableName() |
||||||
|
if len(session.Statement.TableAlias) > 0 { |
||||||
|
nm = session.Statement.TableAlias |
||||||
|
} |
||||||
|
colName = session.Engine.Quote(nm) + "." + colName |
||||||
|
} |
||||||
|
if session.Engine.dialect.DBType() == core.MSSQL { |
||||||
|
autoCond = builder.IsNull{colName} |
||||||
|
} else { |
||||||
|
autoCond = builder.IsNull{colName}.Or(builder.Eq{colName: "0001-01-01 00:00:00"}) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var sqlStr string |
||||||
|
var args []interface{} |
||||||
|
if session.Statement.RawSQL == "" { |
||||||
|
if len(session.Statement.TableName()) <= 0 { |
||||||
|
return ErrTableNotFound |
||||||
|
} |
||||||
|
|
||||||
|
var columnStr = session.Statement.ColumnStr |
||||||
|
if len(session.Statement.selectStr) > 0 { |
||||||
|
columnStr = session.Statement.selectStr |
||||||
|
} else { |
||||||
|
if session.Statement.JoinStr == "" { |
||||||
|
if columnStr == "" { |
||||||
|
if session.Statement.GroupByStr != "" { |
||||||
|
columnStr = session.Statement.Engine.Quote(strings.Replace(session.Statement.GroupByStr, ",", session.Engine.Quote(","), -1)) |
||||||
|
} else { |
||||||
|
columnStr = session.Statement.genColumnStr() |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
if columnStr == "" { |
||||||
|
if session.Statement.GroupByStr != "" { |
||||||
|
columnStr = session.Statement.Engine.Quote(strings.Replace(session.Statement.GroupByStr, ",", session.Engine.Quote(","), -1)) |
||||||
|
} else { |
||||||
|
columnStr = "*" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
if columnStr == "" { |
||||||
|
columnStr = "*" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
condSQL, condArgs, _ := builder.ToSQL(session.Statement.cond.And(autoCond)) |
||||||
|
|
||||||
|
args = append(session.Statement.joinArgs, condArgs...) |
||||||
|
sqlStr = session.Statement.genSelectSQL(columnStr, condSQL) |
||||||
|
// for mssql and use limit
|
||||||
|
qs := strings.Count(sqlStr, "?") |
||||||
|
if len(args)*2 == qs { |
||||||
|
args = append(args, args...) |
||||||
|
} |
||||||
|
} else { |
||||||
|
sqlStr = session.Statement.RawSQL |
||||||
|
args = session.Statement.RawParams |
||||||
|
} |
||||||
|
|
||||||
|
var err error |
||||||
|
if session.canCache() { |
||||||
|
if cacher := session.Engine.getCacher2(table); cacher != nil && |
||||||
|
!session.Statement.IsDistinct && |
||||||
|
!session.Statement.unscoped { |
||||||
|
err = session.cacheFind(sliceElementType, sqlStr, rowsSlicePtr, args...) |
||||||
|
if err != ErrCacheFailed { |
||||||
|
return err |
||||||
|
} |
||||||
|
err = nil // !nashtsai! reset err to nil for ErrCacheFailed
|
||||||
|
session.Engine.logger.Warn("Cache Find Failed") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if sliceValue.Kind() != reflect.Map { |
||||||
|
return session.noCacheFind(sliceValue, sqlStr, args...) |
||||||
|
} |
||||||
|
|
||||||
|
resultsSlice, err := session.query(sqlStr, args...) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
keyType := sliceValue.Type().Key() |
||||||
|
|
||||||
|
for _, results := range resultsSlice { |
||||||
|
var newValue reflect.Value |
||||||
|
if sliceElementType.Kind() == reflect.Ptr { |
||||||
|
newValue = reflect.New(sliceElementType.Elem()) |
||||||
|
} else { |
||||||
|
newValue = reflect.New(sliceElementType) |
||||||
|
} |
||||||
|
err := session.scanMapIntoStruct(newValue.Interface(), results) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
var key interface{} |
||||||
|
// if there is only one pk, we can put the id as map key.
|
||||||
|
if len(table.PrimaryKeys) == 1 { |
||||||
|
key, err = str2PK(string(results[table.PrimaryKeys[0]]), keyType) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} else { |
||||||
|
if keyType.Kind() != reflect.Slice { |
||||||
|
panic("don't support multiple primary key's map has non-slice key type") |
||||||
|
} else { |
||||||
|
var keys core.PK = make([]interface{}, 0, len(table.PrimaryKeys)) |
||||||
|
for _, pk := range table.PrimaryKeys { |
||||||
|
skey, err := str2PK(string(results[pk]), keyType) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
keys = append(keys, skey) |
||||||
|
} |
||||||
|
key = keys |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if sliceElementType.Kind() == reflect.Ptr { |
||||||
|
sliceValue.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(newValue.Interface())) |
||||||
|
} else { |
||||||
|
sliceValue.SetMapIndex(reflect.ValueOf(key), reflect.Indirect(reflect.ValueOf(newValue.Interface()))) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (session *Session) noCacheFind(sliceValue reflect.Value, sqlStr string, args ...interface{}) error { |
||||||
|
var rawRows *core.Rows |
||||||
|
var err error |
||||||
|
|
||||||
|
session.queryPreprocess(&sqlStr, args...) |
||||||
|
if session.IsAutoCommit { |
||||||
|
_, rawRows, err = session.innerQuery(sqlStr, args...) |
||||||
|
} else { |
||||||
|
rawRows, err = session.Tx.Query(sqlStr, args...) |
||||||
|
} |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer rawRows.Close() |
||||||
|
|
||||||
|
fields, err := rawRows.Columns() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
var newElemFunc func() reflect.Value |
||||||
|
sliceElementType := sliceValue.Type().Elem() |
||||||
|
if sliceElementType.Kind() == reflect.Ptr { |
||||||
|
newElemFunc = func() reflect.Value { |
||||||
|
return reflect.New(sliceElementType.Elem()) |
||||||
|
} |
||||||
|
} else { |
||||||
|
newElemFunc = func() reflect.Value { |
||||||
|
return reflect.New(sliceElementType) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var sliceValueSetFunc func(*reflect.Value) |
||||||
|
|
||||||
|
if sliceValue.Kind() == reflect.Slice { |
||||||
|
if sliceElementType.Kind() == reflect.Ptr { |
||||||
|
sliceValueSetFunc = func(newValue *reflect.Value) { |
||||||
|
sliceValue.Set(reflect.Append(sliceValue, reflect.ValueOf(newValue.Interface()))) |
||||||
|
} |
||||||
|
} else { |
||||||
|
sliceValueSetFunc = func(newValue *reflect.Value) { |
||||||
|
sliceValue.Set(reflect.Append(sliceValue, reflect.Indirect(reflect.ValueOf(newValue.Interface())))) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var newValue = newElemFunc() |
||||||
|
dataStruct := rValue(newValue.Interface()) |
||||||
|
if dataStruct.Kind() == reflect.Struct { |
||||||
|
return session.rows2Beans(rawRows, fields, len(fields), session.Engine.autoMapType(dataStruct), newElemFunc, sliceValueSetFunc) |
||||||
|
} |
||||||
|
|
||||||
|
for rawRows.Next() { |
||||||
|
var newValue = newElemFunc() |
||||||
|
bean := newValue.Interface() |
||||||
|
|
||||||
|
if err := rawRows.Scan(bean); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
sliceValueSetFunc(&newValue) |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr interface{}, args ...interface{}) (err error) { |
||||||
|
if !session.canCache() || |
||||||
|
indexNoCase(sqlStr, "having") != -1 || |
||||||
|
indexNoCase(sqlStr, "group by") != -1 { |
||||||
|
return ErrCacheFailed |
||||||
|
} |
||||||
|
|
||||||
|
for _, filter := range session.Engine.dialect.Filters() { |
||||||
|
sqlStr = filter.Do(sqlStr, session.Engine.dialect, session.Statement.RefTable) |
||||||
|
} |
||||||
|
|
||||||
|
newsql := session.Statement.convertIDSQL(sqlStr) |
||||||
|
if newsql == "" { |
||||||
|
return ErrCacheFailed |
||||||
|
} |
||||||
|
|
||||||
|
tableName := session.Statement.TableName() |
||||||
|
|
||||||
|
table := session.Statement.RefTable |
||||||
|
cacher := session.Engine.getCacher2(table) |
||||||
|
ids, err := core.GetCacheSql(cacher, tableName, newsql, args) |
||||||
|
if err != nil { |
||||||
|
rows, err := session.DB().Query(newsql, args...) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer rows.Close() |
||||||
|
|
||||||
|
var i int |
||||||
|
ids = make([]core.PK, 0) |
||||||
|
for rows.Next() { |
||||||
|
i++ |
||||||
|
if i > 500 { |
||||||
|
session.Engine.logger.Debug("[cacheFind] ids length > 500, no cache") |
||||||
|
return ErrCacheFailed |
||||||
|
} |
||||||
|
var res = make([]string, len(table.PrimaryKeys)) |
||||||
|
err = rows.ScanSlice(&res) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
var pk core.PK = make([]interface{}, len(table.PrimaryKeys)) |
||||||
|
for i, col := range table.PKColumns() { |
||||||
|
if col.SQLType.IsNumeric() { |
||||||
|
n, err := strconv.ParseInt(res[i], 10, 64) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
pk[i] = n |
||||||
|
} else if col.SQLType.IsText() { |
||||||
|
pk[i] = res[i] |
||||||
|
} else { |
||||||
|
return errors.New("not supported") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ids = append(ids, pk) |
||||||
|
} |
||||||
|
|
||||||
|
session.Engine.logger.Debug("[cacheFind] cache sql:", ids, tableName, newsql, args) |
||||||
|
err = core.PutCacheSql(cacher, ids, tableName, newsql, args) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} else { |
||||||
|
session.Engine.logger.Debug("[cacheFind] cache hit sql:", newsql, args) |
||||||
|
} |
||||||
|
|
||||||
|
sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr)) |
||||||
|
|
||||||
|
ididxes := make(map[string]int) |
||||||
|
var ides []core.PK |
||||||
|
var temps = make([]interface{}, len(ids)) |
||||||
|
|
||||||
|
for idx, id := range ids { |
||||||
|
sid, err := id.ToString() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
bean := cacher.GetBean(tableName, sid) |
||||||
|
if bean == nil { |
||||||
|
ides = append(ides, id) |
||||||
|
ididxes[sid] = idx |
||||||
|
} else { |
||||||
|
session.Engine.logger.Debug("[cacheFind] cache hit bean:", tableName, id, bean) |
||||||
|
|
||||||
|
pk := session.Engine.IdOf(bean) |
||||||
|
xid, err := pk.ToString() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if sid != xid { |
||||||
|
session.Engine.logger.Error("[cacheFind] error cache", xid, sid, bean) |
||||||
|
return ErrCacheFailed |
||||||
|
} |
||||||
|
temps[idx] = bean |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if len(ides) > 0 { |
||||||
|
newSession := session.Engine.NewSession() |
||||||
|
defer newSession.Close() |
||||||
|
|
||||||
|
slices := reflect.New(reflect.SliceOf(t)) |
||||||
|
beans := slices.Interface() |
||||||
|
|
||||||
|
if len(table.PrimaryKeys) == 1 { |
||||||
|
ff := make([]interface{}, 0, len(ides)) |
||||||
|
for _, ie := range ides { |
||||||
|
ff = append(ff, ie[0]) |
||||||
|
} |
||||||
|
|
||||||
|
newSession.In("`"+table.PrimaryKeys[0]+"`", ff...) |
||||||
|
} else { |
||||||
|
for _, ie := range ides { |
||||||
|
cond := builder.NewCond() |
||||||
|
for i, name := range table.PrimaryKeys { |
||||||
|
cond = cond.And(builder.Eq{"`" + name + "`": ie[i]}) |
||||||
|
} |
||||||
|
newSession.Or(cond) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
err = newSession.NoCache().Find(beans) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
vs := reflect.Indirect(reflect.ValueOf(beans)) |
||||||
|
for i := 0; i < vs.Len(); i++ { |
||||||
|
rv := vs.Index(i) |
||||||
|
if rv.Kind() != reflect.Ptr { |
||||||
|
rv = rv.Addr() |
||||||
|
} |
||||||
|
id := session.Engine.IdOfV(rv) |
||||||
|
sid, err := id.ToString() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
bean := rv.Interface() |
||||||
|
temps[ididxes[sid]] = bean |
||||||
|
session.Engine.logger.Debug("[cacheFind] cache bean:", tableName, id, bean, temps) |
||||||
|
cacher.PutBean(tableName, sid, bean) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for j := 0; j < len(temps); j++ { |
||||||
|
bean := temps[j] |
||||||
|
if bean == nil { |
||||||
|
session.Engine.logger.Warn("[cacheFind] cache no hit:", tableName, ids[j], temps) |
||||||
|
// return errors.New("cache error") // !nashtsai! no need to return error, but continue instead
|
||||||
|
continue |
||||||
|
} |
||||||
|
if sliceValue.Kind() == reflect.Slice { |
||||||
|
if t.Kind() == reflect.Ptr { |
||||||
|
sliceValue.Set(reflect.Append(sliceValue, reflect.ValueOf(bean))) |
||||||
|
} else { |
||||||
|
sliceValue.Set(reflect.Append(sliceValue, reflect.Indirect(reflect.ValueOf(bean)))) |
||||||
|
} |
||||||
|
} else if sliceValue.Kind() == reflect.Map { |
||||||
|
var key = ids[j] |
||||||
|
keyType := sliceValue.Type().Key() |
||||||
|
var ikey interface{} |
||||||
|
if len(key) == 1 { |
||||||
|
ikey, err = str2PK(fmt.Sprintf("%v", key[0]), keyType) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} else { |
||||||
|
if keyType.Kind() != reflect.Slice { |
||||||
|
return errors.New("table have multiple primary keys, key is not core.PK or slice") |
||||||
|
} |
||||||
|
ikey = key |
||||||
|
} |
||||||
|
|
||||||
|
if t.Kind() == reflect.Ptr { |
||||||
|
sliceValue.SetMapIndex(reflect.ValueOf(ikey), reflect.ValueOf(bean)) |
||||||
|
} else { |
||||||
|
sliceValue.SetMapIndex(reflect.ValueOf(ikey), reflect.Indirect(reflect.ValueOf(bean))) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,177 @@ |
|||||||
|
// 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" |
||||||
|
"reflect" |
||||||
|
"strconv" |
||||||
|
|
||||||
|
"github.com/go-xorm/core" |
||||||
|
) |
||||||
|
|
||||||
|
// Get retrieve one record from database, bean's non-empty fields
|
||||||
|
// will be as conditions
|
||||||
|
func (session *Session) Get(bean interface{}) (bool, error) { |
||||||
|
defer session.resetStatement() |
||||||
|
if session.IsAutoClose { |
||||||
|
defer session.Close() |
||||||
|
} |
||||||
|
|
||||||
|
session.Statement.setRefValue(rValue(bean)) |
||||||
|
|
||||||
|
var sqlStr string |
||||||
|
var args []interface{} |
||||||
|
|
||||||
|
if session.Statement.RawSQL == "" { |
||||||
|
if len(session.Statement.TableName()) <= 0 { |
||||||
|
return false, ErrTableNotFound |
||||||
|
} |
||||||
|
session.Statement.Limit(1) |
||||||
|
sqlStr, args = session.Statement.genGetSQL(bean) |
||||||
|
} else { |
||||||
|
sqlStr = session.Statement.RawSQL |
||||||
|
args = session.Statement.RawParams |
||||||
|
} |
||||||
|
|
||||||
|
if session.canCache() { |
||||||
|
if cacher := session.Engine.getCacher2(session.Statement.RefTable); cacher != nil && |
||||||
|
!session.Statement.unscoped { |
||||||
|
has, err := session.cacheGet(bean, sqlStr, args...) |
||||||
|
if err != ErrCacheFailed { |
||||||
|
return has, err |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return session.nocacheGet(bean, sqlStr, args...) |
||||||
|
} |
||||||
|
|
||||||
|
func (session *Session) nocacheGet(bean interface{}, sqlStr string, args ...interface{}) (bool, error) { |
||||||
|
var rawRows *core.Rows |
||||||
|
var err error |
||||||
|
session.queryPreprocess(&sqlStr, args...) |
||||||
|
if session.IsAutoCommit { |
||||||
|
_, rawRows, err = session.innerQuery(sqlStr, args...) |
||||||
|
} else { |
||||||
|
rawRows, err = session.Tx.Query(sqlStr, args...) |
||||||
|
} |
||||||
|
if err != nil { |
||||||
|
return false, err |
||||||
|
} |
||||||
|
|
||||||
|
defer rawRows.Close() |
||||||
|
|
||||||
|
if rawRows.Next() { |
||||||
|
fields, err := rawRows.Columns() |
||||||
|
if err == nil { |
||||||
|
err = session.row2Bean(rawRows, fields, len(fields), bean) |
||||||
|
} |
||||||
|
return true, err |
||||||
|
} |
||||||
|
return false, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interface{}) (has bool, err error) { |
||||||
|
// if has no reftable, then don't use cache currently
|
||||||
|
if !session.canCache() { |
||||||
|
return false, ErrCacheFailed |
||||||
|
} |
||||||
|
|
||||||
|
for _, filter := range session.Engine.dialect.Filters() { |
||||||
|
sqlStr = filter.Do(sqlStr, session.Engine.dialect, session.Statement.RefTable) |
||||||
|
} |
||||||
|
newsql := session.Statement.convertIDSQL(sqlStr) |
||||||
|
if newsql == "" { |
||||||
|
return false, ErrCacheFailed |
||||||
|
} |
||||||
|
|
||||||
|
cacher := session.Engine.getCacher2(session.Statement.RefTable) |
||||||
|
tableName := session.Statement.TableName() |
||||||
|
session.Engine.logger.Debug("[cacheGet] find sql:", newsql, args) |
||||||
|
ids, err := core.GetCacheSql(cacher, tableName, newsql, args) |
||||||
|
table := session.Statement.RefTable |
||||||
|
if err != nil { |
||||||
|
var res = make([]string, len(table.PrimaryKeys)) |
||||||
|
rows, err := session.DB().Query(newsql, args...) |
||||||
|
if err != nil { |
||||||
|
return false, err |
||||||
|
} |
||||||
|
defer rows.Close() |
||||||
|
|
||||||
|
if rows.Next() { |
||||||
|
err = rows.ScanSlice(&res) |
||||||
|
if err != nil { |
||||||
|
return false, err |
||||||
|
} |
||||||
|
} else { |
||||||
|
return false, ErrCacheFailed |
||||||
|
} |
||||||
|
|
||||||
|
var pk core.PK = make([]interface{}, len(table.PrimaryKeys)) |
||||||
|
for i, col := range table.PKColumns() { |
||||||
|
if col.SQLType.IsText() { |
||||||
|
pk[i] = res[i] |
||||||
|
} else if col.SQLType.IsNumeric() { |
||||||
|
n, err := strconv.ParseInt(res[i], 10, 64) |
||||||
|
if err != nil { |
||||||
|
return false, err |
||||||
|
} |
||||||
|
pk[i] = n |
||||||
|
} else { |
||||||
|
return false, errors.New("unsupported") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ids = []core.PK{pk} |
||||||
|
session.Engine.logger.Debug("[cacheGet] cache ids:", newsql, ids) |
||||||
|
err = core.PutCacheSql(cacher, ids, tableName, newsql, args) |
||||||
|
if err != nil { |
||||||
|
return false, err |
||||||
|
} |
||||||
|
} else { |
||||||
|
session.Engine.logger.Debug("[cacheGet] cache hit sql:", newsql) |
||||||
|
} |
||||||
|
|
||||||
|
if len(ids) > 0 { |
||||||
|
structValue := reflect.Indirect(reflect.ValueOf(bean)) |
||||||
|
id := ids[0] |
||||||
|
session.Engine.logger.Debug("[cacheGet] get bean:", tableName, id) |
||||||
|
sid, err := id.ToString() |
||||||
|
if err != nil { |
||||||
|
return false, err |
||||||
|
} |
||||||
|
cacheBean := cacher.GetBean(tableName, sid) |
||||||
|
if cacheBean == nil { |
||||||
|
/*newSession := session.Engine.NewSession() |
||||||
|
defer newSession.Close() |
||||||
|
cacheBean = reflect.New(structValue.Type()).Interface() |
||||||
|
newSession.Id(id).NoCache() |
||||||
|
if session.Statement.AltTableName != "" { |
||||||
|
newSession.Table(session.Statement.AltTableName) |
||||||
|
} |
||||||
|
if !session.Statement.UseCascade { |
||||||
|
newSession.NoCascade() |
||||||
|
} |
||||||
|
has, err = newSession.Get(cacheBean) |
||||||
|
*/ |
||||||
|
cacheBean = bean |
||||||
|
has, err = session.nocacheGet(cacheBean, sqlStr, args...) |
||||||
|
if err != nil || !has { |
||||||
|
return has, err |
||||||
|
} |
||||||
|
|
||||||
|
session.Engine.logger.Debug("[cacheGet] cache bean:", tableName, id, cacheBean) |
||||||
|
cacher.PutBean(tableName, sid, cacheBean) |
||||||
|
} else { |
||||||
|
session.Engine.logger.Debug("[cacheGet] cache hit bean:", tableName, id, cacheBean) |
||||||
|
has = true |
||||||
|
} |
||||||
|
structValue.Set(reflect.Indirect(reflect.ValueOf(cacheBean))) |
||||||
|
|
||||||
|
return has, nil |
||||||
|
} |
||||||
|
return false, nil |
||||||
|
} |
@ -0,0 +1,527 @@ |
|||||||
|
// 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" |
||||||
|
"reflect" |
||||||
|
"strconv" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"github.com/go-xorm/core" |
||||||
|
) |
||||||
|
|
||||||
|
// Insert insert one or more beans
|
||||||
|
func (session *Session) Insert(beans ...interface{}) (int64, error) { |
||||||
|
var affected int64 |
||||||
|
var err error |
||||||
|
|
||||||
|
if session.IsAutoClose { |
||||||
|
defer session.Close() |
||||||
|
} |
||||||
|
defer session.resetStatement() |
||||||
|
|
||||||
|
for _, bean := range beans { |
||||||
|
sliceValue := reflect.Indirect(reflect.ValueOf(bean)) |
||||||
|
if sliceValue.Kind() == reflect.Slice { |
||||||
|
size := sliceValue.Len() |
||||||
|
if size > 0 { |
||||||
|
if session.Engine.SupportInsertMany() { |
||||||
|
cnt, err := session.innerInsertMulti(bean) |
||||||
|
if err != nil { |
||||||
|
return affected, err |
||||||
|
} |
||||||
|
affected += cnt |
||||||
|
} else { |
||||||
|
for i := 0; i < size; i++ { |
||||||
|
cnt, err := session.innerInsert(sliceValue.Index(i).Interface()) |
||||||
|
if err != nil { |
||||||
|
return affected, err |
||||||
|
} |
||||||
|
affected += cnt |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
cnt, err := session.innerInsert(bean) |
||||||
|
if err != nil { |
||||||
|
return affected, err |
||||||
|
} |
||||||
|
affected += cnt |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return affected, err |
||||||
|
} |
||||||
|
|
||||||
|
func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error) { |
||||||
|
sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr)) |
||||||
|
if sliceValue.Kind() != reflect.Slice { |
||||||
|
return 0, errors.New("needs a pointer to a slice") |
||||||
|
} |
||||||
|
|
||||||
|
if sliceValue.Len() <= 0 { |
||||||
|
return 0, errors.New("could not insert a empty slice") |
||||||
|
} |
||||||
|
|
||||||
|
session.Statement.setRefValue(sliceValue.Index(0)) |
||||||
|
|
||||||
|
if len(session.Statement.TableName()) <= 0 { |
||||||
|
return 0, ErrTableNotFound |
||||||
|
} |
||||||
|
|
||||||
|
table := session.Statement.RefTable |
||||||
|
size := sliceValue.Len() |
||||||
|
|
||||||
|
var colNames []string |
||||||
|
var colMultiPlaces []string |
||||||
|
var args []interface{} |
||||||
|
var cols []*core.Column |
||||||
|
|
||||||
|
for i := 0; i < size; i++ { |
||||||
|
v := sliceValue.Index(i) |
||||||
|
vv := reflect.Indirect(v) |
||||||
|
elemValue := v.Interface() |
||||||
|
var colPlaces []string |
||||||
|
|
||||||
|
// handle BeforeInsertProcessor
|
||||||
|
// !nashtsai! does user expect it's same slice to passed closure when using Before()/After() when insert multi??
|
||||||
|
for _, closure := range session.beforeClosures { |
||||||
|
closure(elemValue) |
||||||
|
} |
||||||
|
|
||||||
|
if processor, ok := interface{}(elemValue).(BeforeInsertProcessor); ok { |
||||||
|
processor.BeforeInsert() |
||||||
|
} |
||||||
|
// --
|
||||||
|
|
||||||
|
if i == 0 { |
||||||
|
for _, col := range table.Columns() { |
||||||
|
ptrFieldValue, err := col.ValueOfV(&vv) |
||||||
|
if err != nil { |
||||||
|
return 0, err |
||||||
|
} |
||||||
|
fieldValue := *ptrFieldValue |
||||||
|
if col.IsAutoIncrement && isZero(fieldValue.Interface()) { |
||||||
|
continue |
||||||
|
} |
||||||
|
if col.MapType == core.ONLYFROMDB { |
||||||
|
continue |
||||||
|
} |
||||||
|
if col.IsDeleted { |
||||||
|
continue |
||||||
|
} |
||||||
|
if session.Statement.ColumnStr != "" { |
||||||
|
if _, ok := getFlagForColumn(session.Statement.columnMap, col); !ok { |
||||||
|
continue |
||||||
|
} |
||||||
|
} |
||||||
|
if session.Statement.OmitStr != "" { |
||||||
|
if _, ok := getFlagForColumn(session.Statement.columnMap, col); ok { |
||||||
|
continue |
||||||
|
} |
||||||
|
} |
||||||
|
if (col.IsCreated || col.IsUpdated) && session.Statement.UseAutoTime { |
||||||
|
val, t := session.Engine.NowTime2(col.SQLType.Name) |
||||||
|
args = append(args, val) |
||||||
|
|
||||||
|
var 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) |
||||||
|
var colName = col.Name |
||||||
|
session.afterClosures = append(session.afterClosures, func(bean interface{}) { |
||||||
|
col := table.GetColumn(colName) |
||||||
|
setColumnInt(bean, col, 1) |
||||||
|
}) |
||||||
|
} else { |
||||||
|
arg, err := session.value2Interface(col, fieldValue) |
||||||
|
if err != nil { |
||||||
|
return 0, err |
||||||
|
} |
||||||
|
args = append(args, arg) |
||||||
|
} |
||||||
|
|
||||||
|
colNames = append(colNames, col.Name) |
||||||
|
cols = append(cols, col) |
||||||
|
colPlaces = append(colPlaces, "?") |
||||||
|
} |
||||||
|
} else { |
||||||
|
for _, col := range cols { |
||||||
|
ptrFieldValue, err := col.ValueOfV(&vv) |
||||||
|
if err != nil { |
||||||
|
return 0, err |
||||||
|
} |
||||||
|
fieldValue := *ptrFieldValue |
||||||
|
|
||||||
|
if col.IsAutoIncrement && isZero(fieldValue.Interface()) { |
||||||
|
continue |
||||||
|
} |
||||||
|
if col.MapType == core.ONLYFROMDB { |
||||||
|
continue |
||||||
|
} |
||||||
|
if col.IsDeleted { |
||||||
|
continue |
||||||
|
} |
||||||
|
if session.Statement.ColumnStr != "" { |
||||||
|
if _, ok := getFlagForColumn(session.Statement.columnMap, col); !ok { |
||||||
|
continue |
||||||
|
} |
||||||
|
} |
||||||
|
if session.Statement.OmitStr != "" { |
||||||
|
if _, ok := getFlagForColumn(session.Statement.columnMap, col); ok { |
||||||
|
continue |
||||||
|
} |
||||||
|
} |
||||||
|
if (col.IsCreated || col.IsUpdated) && session.Statement.UseAutoTime { |
||||||
|
val, t := session.Engine.NowTime2(col.SQLType.Name) |
||||||
|
args = append(args, val) |
||||||
|
|
||||||
|
var 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) |
||||||
|
var colName = col.Name |
||||||
|
session.afterClosures = append(session.afterClosures, func(bean interface{}) { |
||||||
|
col := table.GetColumn(colName) |
||||||
|
setColumnInt(bean, col, 1) |
||||||
|
}) |
||||||
|
} else { |
||||||
|
arg, err := session.value2Interface(col, fieldValue) |
||||||
|
if err != nil { |
||||||
|
return 0, err |
||||||
|
} |
||||||
|
args = append(args, arg) |
||||||
|
} |
||||||
|
|
||||||
|
colPlaces = append(colPlaces, "?") |
||||||
|
} |
||||||
|
} |
||||||
|
colMultiPlaces = append(colMultiPlaces, strings.Join(colPlaces, ", ")) |
||||||
|
} |
||||||
|
cleanupProcessorsClosures(&session.beforeClosures) |
||||||
|
|
||||||
|
statement := fmt.Sprintf("INSERT INTO %s (%v%v%v) VALUES (%v)", |
||||||
|
session.Engine.Quote(session.Statement.TableName()), |
||||||
|
session.Engine.QuoteStr(), |
||||||
|
strings.Join(colNames, session.Engine.QuoteStr()+", "+session.Engine.QuoteStr()), |
||||||
|
session.Engine.QuoteStr(), |
||||||
|
strings.Join(colMultiPlaces, "),(")) |
||||||
|
|
||||||
|
res, err := session.exec(statement, args...) |
||||||
|
if err != nil { |
||||||
|
return 0, err |
||||||
|
} |
||||||
|
|
||||||
|
if cacher := session.Engine.getCacher2(table); cacher != nil && session.Statement.UseCache { |
||||||
|
session.cacheInsert(session.Statement.TableName()) |
||||||
|
} |
||||||
|
|
||||||
|
lenAfterClosures := len(session.afterClosures) |
||||||
|
for i := 0; i < size; i++ { |
||||||
|
elemValue := reflect.Indirect(sliceValue.Index(i)).Addr().Interface() |
||||||
|
|
||||||
|
// handle AfterInsertProcessor
|
||||||
|
if session.IsAutoCommit { |
||||||
|
// !nashtsai! does user expect it's same slice to passed closure when using Before()/After() when insert multi??
|
||||||
|
for _, closure := range session.afterClosures { |
||||||
|
closure(elemValue) |
||||||
|
} |
||||||
|
if processor, ok := interface{}(elemValue).(AfterInsertProcessor); ok { |
||||||
|
processor.AfterInsert() |
||||||
|
} |
||||||
|
} else { |
||||||
|
if lenAfterClosures > 0 { |
||||||
|
if value, has := session.afterInsertBeans[elemValue]; has && value != nil { |
||||||
|
*value = append(*value, session.afterClosures...) |
||||||
|
} else { |
||||||
|
afterClosures := make([]func(interface{}), lenAfterClosures) |
||||||
|
copy(afterClosures, session.afterClosures) |
||||||
|
session.afterInsertBeans[elemValue] = &afterClosures |
||||||
|
} |
||||||
|
} else { |
||||||
|
if _, ok := interface{}(elemValue).(AfterInsertProcessor); ok { |
||||||
|
session.afterInsertBeans[elemValue] = nil |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
cleanupProcessorsClosures(&session.afterClosures) |
||||||
|
return res.RowsAffected() |
||||||
|
} |
||||||
|
|
||||||
|
// InsertMulti insert multiple records
|
||||||
|
func (session *Session) InsertMulti(rowsSlicePtr interface{}) (int64, error) { |
||||||
|
defer session.resetStatement() |
||||||
|
if session.IsAutoClose { |
||||||
|
defer session.Close() |
||||||
|
} |
||||||
|
|
||||||
|
sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr)) |
||||||
|
if sliceValue.Kind() != reflect.Slice { |
||||||
|
return 0, ErrParamsType |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
if sliceValue.Len() <= 0 { |
||||||
|
return 0, nil |
||||||
|
} |
||||||
|
|
||||||
|
return session.innerInsertMulti(rowsSlicePtr) |
||||||
|
} |
||||||
|
|
||||||
|
func (session *Session) innerInsert(bean interface{}) (int64, error) { |
||||||
|
session.Statement.setRefValue(rValue(bean)) |
||||||
|
if len(session.Statement.TableName()) <= 0 { |
||||||
|
return 0, ErrTableNotFound |
||||||
|
} |
||||||
|
|
||||||
|
table := session.Statement.RefTable |
||||||
|
|
||||||
|
// handle BeforeInsertProcessor
|
||||||
|
for _, closure := range session.beforeClosures { |
||||||
|
closure(bean) |
||||||
|
} |
||||||
|
cleanupProcessorsClosures(&session.beforeClosures) // cleanup after used
|
||||||
|
|
||||||
|
if processor, ok := interface{}(bean).(BeforeInsertProcessor); ok { |
||||||
|
processor.BeforeInsert() |
||||||
|
} |
||||||
|
// --
|
||||||
|
colNames, args, err := genCols(session.Statement.RefTable, session, bean, false, false) |
||||||
|
if err != nil { |
||||||
|
return 0, err |
||||||
|
} |
||||||
|
// insert expr columns, override if exists
|
||||||
|
exprColumns := session.Statement.getExpr() |
||||||
|
exprColVals := make([]string, 0, len(exprColumns)) |
||||||
|
for _, v := range exprColumns { |
||||||
|
// remove the expr columns
|
||||||
|
for i, colName := range colNames { |
||||||
|
if colName == v.colName { |
||||||
|
colNames = append(colNames[:i], colNames[i+1:]...) |
||||||
|
args = append(args[:i], args[i+1:]...) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// append expr column to the end
|
||||||
|
colNames = append(colNames, v.colName) |
||||||
|
exprColVals = append(exprColVals, v.expr) |
||||||
|
} |
||||||
|
|
||||||
|
colPlaces := strings.Repeat("?, ", len(colNames)-len(exprColumns)) |
||||||
|
if len(exprColVals) > 0 { |
||||||
|
colPlaces = colPlaces + strings.Join(exprColVals, ", ") |
||||||
|
} else { |
||||||
|
colPlaces = colPlaces[0 : len(colPlaces)-2] |
||||||
|
} |
||||||
|
|
||||||
|
sqlStr := fmt.Sprintf("INSERT INTO %s (%v%v%v) VALUES (%v)", |
||||||
|
session.Engine.Quote(session.Statement.TableName()), |
||||||
|
session.Engine.QuoteStr(), |
||||||
|
strings.Join(colNames, session.Engine.Quote(", ")), |
||||||
|
session.Engine.QuoteStr(), |
||||||
|
colPlaces) |
||||||
|
|
||||||
|
handleAfterInsertProcessorFunc := func(bean interface{}) { |
||||||
|
if session.IsAutoCommit { |
||||||
|
for _, closure := range session.afterClosures { |
||||||
|
closure(bean) |
||||||
|
} |
||||||
|
if processor, ok := interface{}(bean).(AfterInsertProcessor); ok { |
||||||
|
processor.AfterInsert() |
||||||
|
} |
||||||
|
} else { |
||||||
|
lenAfterClosures := len(session.afterClosures) |
||||||
|
if lenAfterClosures > 0 { |
||||||
|
if value, has := session.afterInsertBeans[bean]; has && value != nil { |
||||||
|
*value = append(*value, session.afterClosures...) |
||||||
|
} else { |
||||||
|
afterClosures := make([]func(interface{}), lenAfterClosures) |
||||||
|
copy(afterClosures, session.afterClosures) |
||||||
|
session.afterInsertBeans[bean] = &afterClosures |
||||||
|
} |
||||||
|
|
||||||
|
} else { |
||||||
|
if _, ok := interface{}(bean).(AfterInsertProcessor); ok { |
||||||
|
session.afterInsertBeans[bean] = nil |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
cleanupProcessorsClosures(&session.afterClosures) // cleanup after used
|
||||||
|
} |
||||||
|
|
||||||
|
// for postgres, many of them didn't implement lastInsertId, so we should
|
||||||
|
// implemented it ourself.
|
||||||
|
if session.Engine.dialect.DBType() == core.ORACLE && len(table.AutoIncrement) > 0 { |
||||||
|
//assert table.AutoIncrement != ""
|
||||||
|
res, err := session.query("select seq_atable.currval from dual", args...) |
||||||
|
if err != nil { |
||||||
|
return 0, err |
||||||
|
} |
||||||
|
|
||||||
|
handleAfterInsertProcessorFunc(bean) |
||||||
|
|
||||||
|
if cacher := session.Engine.getCacher2(table); cacher != nil && session.Statement.UseCache { |
||||||
|
session.cacheInsert(session.Statement.TableName()) |
||||||
|
} |
||||||
|
|
||||||
|
if table.Version != "" && session.Statement.checkVersion { |
||||||
|
verValue, err := table.VersionColumn().ValueOf(bean) |
||||||
|
if err != nil { |
||||||
|
session.Engine.logger.Error(err) |
||||||
|
} else if verValue.IsValid() && verValue.CanSet() { |
||||||
|
verValue.SetInt(1) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if len(res) < 1 { |
||||||
|
return 0, errors.New("insert no error but not returned id") |
||||||
|
} |
||||||
|
|
||||||
|
idByte := res[0][table.AutoIncrement] |
||||||
|
id, err := strconv.ParseInt(string(idByte), 10, 64) |
||||||
|
if err != nil || id <= 0 { |
||||||
|
return 1, err |
||||||
|
} |
||||||
|
|
||||||
|
aiValue, err := table.AutoIncrColumn().ValueOf(bean) |
||||||
|
if err != nil { |
||||||
|
session.Engine.logger.Error(err) |
||||||
|
} |
||||||
|
|
||||||
|
if aiValue == nil || !aiValue.IsValid() || !aiValue.CanSet() { |
||||||
|
return 1, nil |
||||||
|
} |
||||||
|
|
||||||
|
aiValue.Set(int64ToIntValue(id, aiValue.Type())) |
||||||
|
|
||||||
|
return 1, nil |
||||||
|
} else if session.Engine.dialect.DBType() == core.POSTGRES && len(table.AutoIncrement) > 0 { |
||||||
|
//assert table.AutoIncrement != ""
|
||||||
|
sqlStr = sqlStr + " RETURNING " + session.Engine.Quote(table.AutoIncrement) |
||||||
|
res, err := session.query(sqlStr, args...) |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
return 0, err |
||||||
|
} |
||||||
|
handleAfterInsertProcessorFunc(bean) |
||||||
|
|
||||||
|
if cacher := session.Engine.getCacher2(table); cacher != nil && session.Statement.UseCache { |
||||||
|
session.cacheInsert(session.Statement.TableName()) |
||||||
|
} |
||||||
|
|
||||||
|
if table.Version != "" && session.Statement.checkVersion { |
||||||
|
verValue, err := table.VersionColumn().ValueOf(bean) |
||||||
|
if err != nil { |
||||||
|
session.Engine.logger.Error(err) |
||||||
|
} else if verValue.IsValid() && verValue.CanSet() { |
||||||
|
verValue.SetInt(1) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if len(res) < 1 { |
||||||
|
return 0, errors.New("insert no error but not returned id") |
||||||
|
} |
||||||
|
|
||||||
|
idByte := res[0][table.AutoIncrement] |
||||||
|
id, err := strconv.ParseInt(string(idByte), 10, 64) |
||||||
|
if err != nil || id <= 0 { |
||||||
|
return 1, err |
||||||
|
} |
||||||
|
|
||||||
|
aiValue, err := table.AutoIncrColumn().ValueOf(bean) |
||||||
|
if err != nil { |
||||||
|
session.Engine.logger.Error(err) |
||||||
|
} |
||||||
|
|
||||||
|
if aiValue == nil || !aiValue.IsValid() || !aiValue.CanSet() { |
||||||
|
return 1, nil |
||||||
|
} |
||||||
|
|
||||||
|
aiValue.Set(int64ToIntValue(id, aiValue.Type())) |
||||||
|
|
||||||
|
return 1, nil |
||||||
|
} else { |
||||||
|
res, err := session.exec(sqlStr, args...) |
||||||
|
if err != nil { |
||||||
|
return 0, err |
||||||
|
} |
||||||
|
|
||||||
|
defer handleAfterInsertProcessorFunc(bean) |
||||||
|
|
||||||
|
if cacher := session.Engine.getCacher2(table); cacher != nil && session.Statement.UseCache { |
||||||
|
session.cacheInsert(session.Statement.TableName()) |
||||||
|
} |
||||||
|
|
||||||
|
if table.Version != "" && session.Statement.checkVersion { |
||||||
|
verValue, err := table.VersionColumn().ValueOf(bean) |
||||||
|
if err != nil { |
||||||
|
session.Engine.logger.Error(err) |
||||||
|
} else if verValue.IsValid() && verValue.CanSet() { |
||||||
|
verValue.SetInt(1) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if table.AutoIncrement == "" { |
||||||
|
return res.RowsAffected() |
||||||
|
} |
||||||
|
|
||||||
|
var id int64 |
||||||
|
id, err = res.LastInsertId() |
||||||
|
if err != nil || id <= 0 { |
||||||
|
return res.RowsAffected() |
||||||
|
} |
||||||
|
|
||||||
|
aiValue, err := table.AutoIncrColumn().ValueOf(bean) |
||||||
|
if err != nil { |
||||||
|
session.Engine.logger.Error(err) |
||||||
|
} |
||||||
|
|
||||||
|
if aiValue == nil || !aiValue.IsValid() || !aiValue.CanSet() { |
||||||
|
return res.RowsAffected() |
||||||
|
} |
||||||
|
|
||||||
|
aiValue.Set(int64ToIntValue(id, aiValue.Type())) |
||||||
|
|
||||||
|
return res.RowsAffected() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// InsertOne insert only one struct into database as a record.
|
||||||
|
// The in parameter bean must a struct or a point to struct. The return
|
||||||
|
// parameter is inserted and error
|
||||||
|
func (session *Session) InsertOne(bean interface{}) (int64, error) { |
||||||
|
defer session.resetStatement() |
||||||
|
if session.IsAutoClose { |
||||||
|
defer session.Close() |
||||||
|
} |
||||||
|
|
||||||
|
return session.innerInsert(bean) |
||||||
|
} |
||||||
|
|
||||||
|
func (session *Session) cacheInsert(tables ...string) error { |
||||||
|
if session.Statement.RefTable == nil { |
||||||
|
return ErrCacheFailed |
||||||
|
} |
||||||
|
|
||||||
|
table := session.Statement.RefTable |
||||||
|
cacher := session.Engine.getCacher2(table) |
||||||
|
|
||||||
|
for _, t := range tables { |
||||||
|
session.Engine.logger.Debug("[cache] clear sql:", t) |
||||||
|
cacher.ClearIds(t) |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,42 @@ |
|||||||
|
// 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" |
||||||
|
|
||||||
|
// IterFunc only use by Iterate
|
||||||
|
type IterFunc func(idx int, bean interface{}) error |
||||||
|
|
||||||
|
// Rows return sql.Rows compatible Rows obj, as a forward Iterator object for iterating record by record, bean's non-empty fields
|
||||||
|
// are conditions.
|
||||||
|
func (session *Session) Rows(bean interface{}) (*Rows, error) { |
||||||
|
return newRows(session, bean) |
||||||
|
} |
||||||
|
|
||||||
|
// Iterate record by record handle records from table, condiBeans's non-empty fields
|
||||||
|
// are conditions. beans could be []Struct, []*Struct, map[int64]Struct
|
||||||
|
// map[int64]*Struct
|
||||||
|
func (session *Session) Iterate(bean interface{}, fun IterFunc) error { |
||||||
|
rows, err := session.Rows(bean) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer rows.Close() |
||||||
|
|
||||||
|
i := 0 |
||||||
|
for rows.Next() { |
||||||
|
b := reflect.New(rows.beanType).Interface() |
||||||
|
err = rows.Scan(b) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
err = fun(i, b) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
i++ |
||||||
|
} |
||||||
|
return err |
||||||
|
} |
@ -0,0 +1,144 @@ |
|||||||
|
// 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 ( |
||||||
|
"database/sql" |
||||||
|
|
||||||
|
"github.com/go-xorm/core" |
||||||
|
) |
||||||
|
|
||||||
|
func (session *Session) query(sqlStr string, paramStr ...interface{}) (resultsSlice []map[string][]byte, err error) { |
||||||
|
session.queryPreprocess(&sqlStr, paramStr...) |
||||||
|
|
||||||
|
if session.IsAutoCommit { |
||||||
|
return session.innerQuery2(sqlStr, paramStr...) |
||||||
|
} |
||||||
|
return session.txQuery(session.Tx, sqlStr, paramStr...) |
||||||
|
} |
||||||
|
|
||||||
|
func (session *Session) txQuery(tx *core.Tx, sqlStr string, params ...interface{}) (resultsSlice []map[string][]byte, err error) { |
||||||
|
rows, err := tx.Query(sqlStr, params...) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
defer rows.Close() |
||||||
|
|
||||||
|
return rows2maps(rows) |
||||||
|
} |
||||||
|
|
||||||
|
func (session *Session) innerQuery(sqlStr string, params ...interface{}) (*core.Stmt, *core.Rows, error) { |
||||||
|
var callback func() (*core.Stmt, *core.Rows, error) |
||||||
|
if session.prepareStmt { |
||||||
|
callback = func() (*core.Stmt, *core.Rows, error) { |
||||||
|
stmt, err := session.doPrepare(sqlStr) |
||||||
|
if err != nil { |
||||||
|
return nil, nil, err |
||||||
|
} |
||||||
|
rows, err := stmt.Query(params...) |
||||||
|
if err != nil { |
||||||
|
return nil, nil, err |
||||||
|
} |
||||||
|
return stmt, rows, nil |
||||||
|
} |
||||||
|
} else { |
||||||
|
callback = func() (*core.Stmt, *core.Rows, error) { |
||||||
|
rows, err := session.DB().Query(sqlStr, params...) |
||||||
|
if err != nil { |
||||||
|
return nil, nil, err |
||||||
|
} |
||||||
|
return nil, rows, err |
||||||
|
} |
||||||
|
} |
||||||
|
stmt, rows, err := session.Engine.logSQLQueryTime(sqlStr, params, callback) |
||||||
|
if err != nil { |
||||||
|
return nil, nil, err |
||||||
|
} |
||||||
|
return stmt, rows, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (session *Session) innerQuery2(sqlStr string, params ...interface{}) ([]map[string][]byte, error) { |
||||||
|
_, rows, err := session.innerQuery(sqlStr, params...) |
||||||
|
if rows != nil { |
||||||
|
defer rows.Close() |
||||||
|
} |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return rows2maps(rows) |
||||||
|
} |
||||||
|
|
||||||
|
// Query a raw sql and return records as []map[string][]byte
|
||||||
|
func (session *Session) Query(sqlStr string, paramStr ...interface{}) (resultsSlice []map[string][]byte, err error) { |
||||||
|
defer session.resetStatement() |
||||||
|
if session.IsAutoClose { |
||||||
|
defer session.Close() |
||||||
|
} |
||||||
|
|
||||||
|
return session.query(sqlStr, paramStr...) |
||||||
|
} |
||||||
|
|
||||||
|
// =============================
|
||||||
|
// for string
|
||||||
|
// =============================
|
||||||
|
func (session *Session) query2(sqlStr string, paramStr ...interface{}) (resultsSlice []map[string]string, err error) { |
||||||
|
session.queryPreprocess(&sqlStr, paramStr...) |
||||||
|
|
||||||
|
if session.IsAutoCommit { |
||||||
|
return query2(session.DB(), sqlStr, paramStr...) |
||||||
|
} |
||||||
|
return txQuery2(session.Tx, sqlStr, paramStr...) |
||||||
|
} |
||||||
|
|
||||||
|
// Execute sql
|
||||||
|
func (session *Session) innerExec(sqlStr string, args ...interface{}) (sql.Result, error) { |
||||||
|
if session.prepareStmt { |
||||||
|
stmt, err := session.doPrepare(sqlStr) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
res, err := stmt.Exec(args...) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return res, nil |
||||||
|
} |
||||||
|
|
||||||
|
return session.DB().Exec(sqlStr, args...) |
||||||
|
} |
||||||
|
|
||||||
|
func (session *Session) exec(sqlStr string, args ...interface{}) (sql.Result, error) { |
||||||
|
for _, filter := range session.Engine.dialect.Filters() { |
||||||
|
// TODO: for table name, it's no need to RefTable
|
||||||
|
sqlStr = filter.Do(sqlStr, session.Engine.dialect, session.Statement.RefTable) |
||||||
|
} |
||||||
|
|
||||||
|
session.saveLastSQL(sqlStr, args...) |
||||||
|
|
||||||
|
return session.Engine.logSQLExecutionTime(sqlStr, args, func() (sql.Result, error) { |
||||||
|
if session.IsAutoCommit { |
||||||
|
// FIXME: oci8 can not auto commit (github.com/mattn/go-oci8)
|
||||||
|
if session.Engine.dialect.DBType() == core.ORACLE { |
||||||
|
session.Begin() |
||||||
|
r, err := session.Tx.Exec(sqlStr, args...) |
||||||
|
session.Commit() |
||||||
|
return r, err |
||||||
|
} |
||||||
|
return session.innerExec(sqlStr, args...) |
||||||
|
} |
||||||
|
return session.Tx.Exec(sqlStr, args...) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
// Exec raw sql
|
||||||
|
func (session *Session) Exec(sqlStr string, args ...interface{}) (sql.Result, error) { |
||||||
|
defer session.resetStatement() |
||||||
|
if session.IsAutoClose { |
||||||
|
defer session.Close() |
||||||
|
} |
||||||
|
|
||||||
|
return session.exec(sqlStr, args...) |
||||||
|
} |
@ -0,0 +1,486 @@ |
|||||||
|
// 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 ( |
||||||
|
"database/sql" |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"reflect" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"github.com/go-xorm/core" |
||||||
|
) |
||||||
|
|
||||||
|
// Ping test if database is ok
|
||||||
|
func (session *Session) Ping() error { |
||||||
|
defer session.resetStatement() |
||||||
|
if session.IsAutoClose { |
||||||
|
defer session.Close() |
||||||
|
} |
||||||
|
|
||||||
|
return session.DB().Ping() |
||||||
|
} |
||||||
|
|
||||||
|
// CreateTable create a table according a bean
|
||||||
|
func (session *Session) CreateTable(bean interface{}) error { |
||||||
|
v := rValue(bean) |
||||||
|
session.Statement.setRefValue(v) |
||||||
|
|
||||||
|
defer session.resetStatement() |
||||||
|
if session.IsAutoClose { |
||||||
|
defer session.Close() |
||||||
|
} |
||||||
|
|
||||||
|
return session.createOneTable() |
||||||
|
} |
||||||
|
|
||||||
|
// CreateIndexes create indexes
|
||||||
|
func (session *Session) CreateIndexes(bean interface{}) error { |
||||||
|
v := rValue(bean) |
||||||
|
session.Statement.setRefValue(v) |
||||||
|
|
||||||
|
defer session.resetStatement() |
||||||
|
if session.IsAutoClose { |
||||||
|
defer session.Close() |
||||||
|
} |
||||||
|
|
||||||
|
sqls := session.Statement.genIndexSQL() |
||||||
|
for _, sqlStr := range sqls { |
||||||
|
_, err := session.exec(sqlStr) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// CreateUniques create uniques
|
||||||
|
func (session *Session) CreateUniques(bean interface{}) error { |
||||||
|
v := rValue(bean) |
||||||
|
session.Statement.setRefValue(v) |
||||||
|
|
||||||
|
defer session.resetStatement() |
||||||
|
if session.IsAutoClose { |
||||||
|
defer session.Close() |
||||||
|
} |
||||||
|
|
||||||
|
sqls := session.Statement.genUniqueSQL() |
||||||
|
for _, sqlStr := range sqls { |
||||||
|
_, err := session.exec(sqlStr) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (session *Session) createOneTable() error { |
||||||
|
sqlStr := session.Statement.genCreateTableSQL() |
||||||
|
_, err := session.exec(sqlStr) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// to be deleted
|
||||||
|
func (session *Session) createAll() error { |
||||||
|
if session.IsAutoClose { |
||||||
|
defer session.Close() |
||||||
|
} |
||||||
|
|
||||||
|
for _, table := range session.Engine.Tables { |
||||||
|
session.Statement.RefTable = table |
||||||
|
session.Statement.tableName = table.Name |
||||||
|
err := session.createOneTable() |
||||||
|
session.resetStatement() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// DropIndexes drop indexes
|
||||||
|
func (session *Session) DropIndexes(bean interface{}) error { |
||||||
|
v := rValue(bean) |
||||||
|
session.Statement.setRefValue(v) |
||||||
|
|
||||||
|
defer session.resetStatement() |
||||||
|
if session.IsAutoClose { |
||||||
|
defer session.Close() |
||||||
|
} |
||||||
|
|
||||||
|
sqls := session.Statement.genDelIndexSQL() |
||||||
|
for _, sqlStr := range sqls { |
||||||
|
_, err := session.exec(sqlStr) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// DropTable drop table will drop table if exist, if drop failed, it will return error
|
||||||
|
func (session *Session) DropTable(beanOrTableName interface{}) error { |
||||||
|
tableName, err := session.Engine.tableName(beanOrTableName) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
var needDrop = true |
||||||
|
if !session.Engine.dialect.SupportDropIfExists() { |
||||||
|
sqlStr, args := session.Engine.dialect.TableCheckSql(tableName) |
||||||
|
results, err := session.query(sqlStr, args...) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
needDrop = len(results) > 0 |
||||||
|
} |
||||||
|
|
||||||
|
if needDrop { |
||||||
|
sqlStr := session.Engine.Dialect().DropTableSql(tableName) |
||||||
|
_, err = session.exec(sqlStr) |
||||||
|
return err |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// IsTableExist if a table is exist
|
||||||
|
func (session *Session) IsTableExist(beanOrTableName interface{}) (bool, error) { |
||||||
|
tableName, err := session.Engine.tableName(beanOrTableName) |
||||||
|
if err != nil { |
||||||
|
return false, err |
||||||
|
} |
||||||
|
|
||||||
|
return session.isTableExist(tableName) |
||||||
|
} |
||||||
|
|
||||||
|
func (session *Session) isTableExist(tableName string) (bool, error) { |
||||||
|
defer session.resetStatement() |
||||||
|
if session.IsAutoClose { |
||||||
|
defer session.Close() |
||||||
|
} |
||||||
|
sqlStr, args := session.Engine.dialect.TableCheckSql(tableName) |
||||||
|
results, err := session.query(sqlStr, args...) |
||||||
|
return len(results) > 0, err |
||||||
|
} |
||||||
|
|
||||||
|
// IsTableEmpty if table have any records
|
||||||
|
func (session *Session) IsTableEmpty(bean interface{}) (bool, error) { |
||||||
|
v := rValue(bean) |
||||||
|
t := v.Type() |
||||||
|
|
||||||
|
if t.Kind() == reflect.String { |
||||||
|
return session.isTableEmpty(bean.(string)) |
||||||
|
} else if t.Kind() == reflect.Struct { |
||||||
|
rows, err := session.Count(bean) |
||||||
|
return rows == 0, err |
||||||
|
} |
||||||
|
return false, errors.New("bean should be a struct or struct's point") |
||||||
|
} |
||||||
|
|
||||||
|
func (session *Session) isTableEmpty(tableName string) (bool, error) { |
||||||
|
defer session.resetStatement() |
||||||
|
if session.IsAutoClose { |
||||||
|
defer session.Close() |
||||||
|
} |
||||||
|
|
||||||
|
var total int64 |
||||||
|
sqlStr := fmt.Sprintf("select count(*) from %s", session.Engine.Quote(tableName)) |
||||||
|
err := session.DB().QueryRow(sqlStr).Scan(&total) |
||||||
|
session.saveLastSQL(sqlStr) |
||||||
|
if err != nil { |
||||||
|
if err == sql.ErrNoRows { |
||||||
|
err = nil |
||||||
|
} |
||||||
|
return true, err |
||||||
|
} |
||||||
|
|
||||||
|
return total == 0, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (session *Session) isIndexExist(tableName, idxName string, unique bool) (bool, error) { |
||||||
|
defer session.resetStatement() |
||||||
|
if session.IsAutoClose { |
||||||
|
defer session.Close() |
||||||
|
} |
||||||
|
var idx string |
||||||
|
if unique { |
||||||
|
idx = uniqueName(tableName, idxName) |
||||||
|
} else { |
||||||
|
idx = indexName(tableName, idxName) |
||||||
|
} |
||||||
|
sqlStr, args := session.Engine.dialect.IndexCheckSql(tableName, idx) |
||||||
|
results, err := session.query(sqlStr, args...) |
||||||
|
return len(results) > 0, err |
||||||
|
} |
||||||
|
|
||||||
|
// find if index is exist according cols
|
||||||
|
func (session *Session) isIndexExist2(tableName string, cols []string, unique bool) (bool, error) { |
||||||
|
defer session.resetStatement() |
||||||
|
if session.IsAutoClose { |
||||||
|
defer session.Close() |
||||||
|
} |
||||||
|
|
||||||
|
indexes, err := session.Engine.dialect.GetIndexes(tableName) |
||||||
|
if err != nil { |
||||||
|
return false, err |
||||||
|
} |
||||||
|
|
||||||
|
for _, index := range indexes { |
||||||
|
if sliceEq(index.Cols, cols) { |
||||||
|
if unique { |
||||||
|
return index.Type == core.UniqueType, nil |
||||||
|
} |
||||||
|
return index.Type == core.IndexType, nil |
||||||
|
} |
||||||
|
} |
||||||
|
return false, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (session *Session) addColumn(colName string) error { |
||||||
|
defer session.resetStatement() |
||||||
|
if session.IsAutoClose { |
||||||
|
defer session.Close() |
||||||
|
} |
||||||
|
|
||||||
|
col := session.Statement.RefTable.GetColumn(colName) |
||||||
|
sql, args := session.Statement.genAddColumnStr(col) |
||||||
|
_, err := session.exec(sql, args...) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
func (session *Session) addIndex(tableName, idxName string) error { |
||||||
|
defer session.resetStatement() |
||||||
|
if session.IsAutoClose { |
||||||
|
defer session.Close() |
||||||
|
} |
||||||
|
index := session.Statement.RefTable.Indexes[idxName] |
||||||
|
sqlStr := session.Engine.dialect.CreateIndexSql(tableName, index) |
||||||
|
|
||||||
|
_, err := session.exec(sqlStr) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
func (session *Session) addUnique(tableName, uqeName string) error { |
||||||
|
defer session.resetStatement() |
||||||
|
if session.IsAutoClose { |
||||||
|
defer session.Close() |
||||||
|
} |
||||||
|
index := session.Statement.RefTable.Indexes[uqeName] |
||||||
|
sqlStr := session.Engine.dialect.CreateIndexSql(tableName, index) |
||||||
|
_, err := session.exec(sqlStr) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// To be deleted
|
||||||
|
func (session *Session) dropAll() error { |
||||||
|
defer session.resetStatement() |
||||||
|
if session.IsAutoClose { |
||||||
|
defer session.Close() |
||||||
|
} |
||||||
|
|
||||||
|
for _, table := range session.Engine.Tables { |
||||||
|
session.Statement.Init() |
||||||
|
session.Statement.RefTable = table |
||||||
|
sqlStr := session.Engine.Dialect().DropTableSql(session.Statement.TableName()) |
||||||
|
_, err := session.exec(sqlStr) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Sync2 synchronize structs to database tables
|
||||||
|
func (session *Session) Sync2(beans ...interface{}) error { |
||||||
|
engine := session.Engine |
||||||
|
|
||||||
|
tables, err := engine.DBMetas() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
var structTables []*core.Table |
||||||
|
|
||||||
|
for _, bean := range beans { |
||||||
|
v := rValue(bean) |
||||||
|
table := engine.mapType(v) |
||||||
|
structTables = append(structTables, table) |
||||||
|
var tbName = session.tbNameNoSchema(table) |
||||||
|
|
||||||
|
var oriTable *core.Table |
||||||
|
for _, tb := range tables { |
||||||
|
if strings.EqualFold(tb.Name, tbName) { |
||||||
|
oriTable = tb |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if oriTable == nil { |
||||||
|
err = session.StoreEngine(session.Statement.StoreEngine).CreateTable(bean) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
err = session.CreateUniques(bean) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
err = session.CreateIndexes(bean) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} else { |
||||||
|
for _, col := range table.Columns() { |
||||||
|
var oriCol *core.Column |
||||||
|
for _, col2 := range oriTable.Columns() { |
||||||
|
if strings.EqualFold(col.Name, col2.Name) { |
||||||
|
oriCol = col2 |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if oriCol != nil { |
||||||
|
expectedType := engine.dialect.SqlType(col) |
||||||
|
curType := engine.dialect.SqlType(oriCol) |
||||||
|
if expectedType != curType { |
||||||
|
if expectedType == core.Text && |
||||||
|
strings.HasPrefix(curType, core.Varchar) { |
||||||
|
// currently only support mysql & postgres
|
||||||
|
if engine.dialect.DBType() == core.MYSQL || |
||||||
|
engine.dialect.DBType() == core.POSTGRES { |
||||||
|
engine.logger.Infof("Table %s column %s change type from %s to %s\n", |
||||||
|
tbName, col.Name, curType, expectedType) |
||||||
|
_, err = engine.Exec(engine.dialect.ModifyColumnSql(table.Name, col)) |
||||||
|
} else { |
||||||
|
engine.logger.Warnf("Table %s column %s db type is %s, struct type is %s\n", |
||||||
|
tbName, col.Name, curType, expectedType) |
||||||
|
} |
||||||
|
} else if strings.HasPrefix(curType, core.Varchar) && strings.HasPrefix(expectedType, core.Varchar) { |
||||||
|
if engine.dialect.DBType() == core.MYSQL { |
||||||
|
if oriCol.Length < col.Length { |
||||||
|
engine.logger.Infof("Table %s column %s change type from varchar(%d) to varchar(%d)\n", |
||||||
|
tbName, col.Name, oriCol.Length, col.Length) |
||||||
|
_, err = engine.Exec(engine.dialect.ModifyColumnSql(table.Name, col)) |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
if !(strings.HasPrefix(curType, expectedType) && curType[len(expectedType)] == '(') { |
||||||
|
engine.logger.Warnf("Table %s column %s db type is %s, struct type is %s", |
||||||
|
tbName, col.Name, curType, expectedType) |
||||||
|
} |
||||||
|
} |
||||||
|
} else if expectedType == core.Varchar { |
||||||
|
if engine.dialect.DBType() == core.MYSQL { |
||||||
|
if oriCol.Length < col.Length { |
||||||
|
engine.logger.Infof("Table %s column %s change type from varchar(%d) to varchar(%d)\n", |
||||||
|
tbName, col.Name, oriCol.Length, col.Length) |
||||||
|
_, err = engine.Exec(engine.dialect.ModifyColumnSql(table.Name, col)) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
if col.Default != oriCol.Default { |
||||||
|
engine.logger.Warnf("Table %s Column %s db default is %s, struct default is %s", |
||||||
|
tbName, col.Name, oriCol.Default, col.Default) |
||||||
|
} |
||||||
|
if col.Nullable != oriCol.Nullable { |
||||||
|
engine.logger.Warnf("Table %s Column %s db nullable is %v, struct nullable is %v", |
||||||
|
tbName, col.Name, oriCol.Nullable, col.Nullable) |
||||||
|
} |
||||||
|
} else { |
||||||
|
session := engine.NewSession() |
||||||
|
session.Statement.RefTable = table |
||||||
|
session.Statement.tableName = tbName |
||||||
|
defer session.Close() |
||||||
|
err = session.addColumn(col.Name) |
||||||
|
} |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var foundIndexNames = make(map[string]bool) |
||||||
|
var addedNames = make(map[string]*core.Index) |
||||||
|
|
||||||
|
for name, index := range table.Indexes { |
||||||
|
var oriIndex *core.Index |
||||||
|
for name2, index2 := range oriTable.Indexes { |
||||||
|
if index.Equal(index2) { |
||||||
|
oriIndex = index2 |
||||||
|
foundIndexNames[name2] = true |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if oriIndex != nil { |
||||||
|
if oriIndex.Type != index.Type { |
||||||
|
sql := engine.dialect.DropIndexSql(tbName, oriIndex) |
||||||
|
_, err = engine.Exec(sql) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
oriIndex = nil |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if oriIndex == nil { |
||||||
|
addedNames[name] = index |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for name2, index2 := range oriTable.Indexes { |
||||||
|
if _, ok := foundIndexNames[name2]; !ok { |
||||||
|
sql := engine.dialect.DropIndexSql(tbName, index2) |
||||||
|
_, err = engine.Exec(sql) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for name, index := range addedNames { |
||||||
|
if index.Type == core.UniqueType { |
||||||
|
session := engine.NewSession() |
||||||
|
session.Statement.RefTable = table |
||||||
|
session.Statement.tableName = tbName |
||||||
|
defer session.Close() |
||||||
|
err = session.addUnique(tbName, name) |
||||||
|
} else if index.Type == core.IndexType { |
||||||
|
session := engine.NewSession() |
||||||
|
session.Statement.RefTable = table |
||||||
|
session.Statement.tableName = tbName |
||||||
|
defer session.Close() |
||||||
|
err = session.addIndex(tbName, name) |
||||||
|
} |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for _, table := range tables { |
||||||
|
var oriTable *core.Table |
||||||
|
for _, structTable := range structTables { |
||||||
|
if strings.EqualFold(table.Name, session.tbNameNoSchema(structTable)) { |
||||||
|
oriTable = structTable |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if oriTable == nil { |
||||||
|
//engine.LogWarnf("Table %s has no struct to mapping it", table.Name)
|
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
for _, colName := range table.ColumnsSeq() { |
||||||
|
if oriTable.GetColumn(colName) == nil { |
||||||
|
engine.logger.Warnf("Table %s has column %s but struct has not related field", table.Name, colName) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,137 @@ |
|||||||
|
// 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 "database/sql" |
||||||
|
|
||||||
|
// Count counts the records. bean's non-empty fields
|
||||||
|
// are conditions.
|
||||||
|
func (session *Session) Count(bean interface{}) (int64, error) { |
||||||
|
defer session.resetStatement() |
||||||
|
if session.IsAutoClose { |
||||||
|
defer session.Close() |
||||||
|
} |
||||||
|
|
||||||
|
var sqlStr string |
||||||
|
var args []interface{} |
||||||
|
if session.Statement.RawSQL == "" { |
||||||
|
sqlStr, args = session.Statement.genCountSQL(bean) |
||||||
|
} else { |
||||||
|
sqlStr = session.Statement.RawSQL |
||||||
|
args = session.Statement.RawParams |
||||||
|
} |
||||||
|
|
||||||
|
session.queryPreprocess(&sqlStr, args...) |
||||||
|
|
||||||
|
var err error |
||||||
|
var total int64 |
||||||
|
if session.IsAutoCommit { |
||||||
|
err = session.DB().QueryRow(sqlStr, args...).Scan(&total) |
||||||
|
} else { |
||||||
|
err = session.Tx.QueryRow(sqlStr, args...).Scan(&total) |
||||||
|
} |
||||||
|
|
||||||
|
if err == sql.ErrNoRows || err == nil { |
||||||
|
return total, nil |
||||||
|
} |
||||||
|
|
||||||
|
return 0, err |
||||||
|
} |
||||||
|
|
||||||
|
// Sum call sum some column. bean's non-empty fields are conditions.
|
||||||
|
func (session *Session) Sum(bean interface{}, columnName string) (float64, error) { |
||||||
|
defer session.resetStatement() |
||||||
|
if session.IsAutoClose { |
||||||
|
defer session.Close() |
||||||
|
} |
||||||
|
|
||||||
|
var sqlStr string |
||||||
|
var args []interface{} |
||||||
|
if len(session.Statement.RawSQL) == 0 { |
||||||
|
sqlStr, args = session.Statement.genSumSQL(bean, columnName) |
||||||
|
} else { |
||||||
|
sqlStr = session.Statement.RawSQL |
||||||
|
args = session.Statement.RawParams |
||||||
|
} |
||||||
|
|
||||||
|
session.queryPreprocess(&sqlStr, args...) |
||||||
|
|
||||||
|
var err error |
||||||
|
var res float64 |
||||||
|
if session.IsAutoCommit { |
||||||
|
err = session.DB().QueryRow(sqlStr, args...).Scan(&res) |
||||||
|
} else { |
||||||
|
err = session.Tx.QueryRow(sqlStr, args...).Scan(&res) |
||||||
|
} |
||||||
|
|
||||||
|
if err == sql.ErrNoRows || err == nil { |
||||||
|
return res, nil |
||||||
|
} |
||||||
|
return 0, err |
||||||
|
} |
||||||
|
|
||||||
|
// Sums call sum some columns. bean's non-empty fields are conditions.
|
||||||
|
func (session *Session) Sums(bean interface{}, columnNames ...string) ([]float64, error) { |
||||||
|
defer session.resetStatement() |
||||||
|
if session.IsAutoClose { |
||||||
|
defer session.Close() |
||||||
|
} |
||||||
|
|
||||||
|
var sqlStr string |
||||||
|
var args []interface{} |
||||||
|
if len(session.Statement.RawSQL) == 0 { |
||||||
|
sqlStr, args = session.Statement.genSumSQL(bean, columnNames...) |
||||||
|
} else { |
||||||
|
sqlStr = session.Statement.RawSQL |
||||||
|
args = session.Statement.RawParams |
||||||
|
} |
||||||
|
|
||||||
|
session.queryPreprocess(&sqlStr, args...) |
||||||
|
|
||||||
|
var err error |
||||||
|
var res = make([]float64, len(columnNames), len(columnNames)) |
||||||
|
if session.IsAutoCommit { |
||||||
|
err = session.DB().QueryRow(sqlStr, args...).ScanSlice(&res) |
||||||
|
} else { |
||||||
|
err = session.Tx.QueryRow(sqlStr, args...).ScanSlice(&res) |
||||||
|
} |
||||||
|
|
||||||
|
if err == sql.ErrNoRows || err == nil { |
||||||
|
return res, nil |
||||||
|
} |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
// SumsInt sum specify columns and return as []int64 instead of []float64
|
||||||
|
func (session *Session) SumsInt(bean interface{}, columnNames ...string) ([]int64, error) { |
||||||
|
defer session.resetStatement() |
||||||
|
if session.IsAutoClose { |
||||||
|
defer session.Close() |
||||||
|
} |
||||||
|
|
||||||
|
var sqlStr string |
||||||
|
var args []interface{} |
||||||
|
if len(session.Statement.RawSQL) == 0 { |
||||||
|
sqlStr, args = session.Statement.genSumSQL(bean, columnNames...) |
||||||
|
} else { |
||||||
|
sqlStr = session.Statement.RawSQL |
||||||
|
args = session.Statement.RawParams |
||||||
|
} |
||||||
|
|
||||||
|
session.queryPreprocess(&sqlStr, args...) |
||||||
|
|
||||||
|
var err error |
||||||
|
var res = make([]int64, 0, len(columnNames)) |
||||||
|
if session.IsAutoCommit { |
||||||
|
err = session.DB().QueryRow(sqlStr, args...).ScanSlice(&res) |
||||||
|
} else { |
||||||
|
err = session.Tx.QueryRow(sqlStr, args...).ScanSlice(&res) |
||||||
|
} |
||||||
|
|
||||||
|
if err == sql.ErrNoRows || err == nil { |
||||||
|
return res, nil |
||||||
|
} |
||||||
|
return nil, err |
||||||
|
} |
@ -0,0 +1,83 @@ |
|||||||
|
// 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 |
||||||
|
|
||||||
|
// Begin a transaction
|
||||||
|
func (session *Session) Begin() error { |
||||||
|
if session.IsAutoCommit { |
||||||
|
tx, err := session.DB().Begin() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
session.IsAutoCommit = false |
||||||
|
session.IsCommitedOrRollbacked = false |
||||||
|
session.Tx = tx |
||||||
|
session.saveLastSQL("BEGIN TRANSACTION") |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Rollback When using transaction, you can rollback if any error
|
||||||
|
func (session *Session) Rollback() error { |
||||||
|
if !session.IsAutoCommit && !session.IsCommitedOrRollbacked { |
||||||
|
session.saveLastSQL(session.Engine.dialect.RollBackStr()) |
||||||
|
session.IsCommitedOrRollbacked = true |
||||||
|
return session.Tx.Rollback() |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Commit When using transaction, Commit will commit all operations.
|
||||||
|
func (session *Session) Commit() error { |
||||||
|
if !session.IsAutoCommit && !session.IsCommitedOrRollbacked { |
||||||
|
session.saveLastSQL("COMMIT") |
||||||
|
session.IsCommitedOrRollbacked = true |
||||||
|
var err error |
||||||
|
if err = session.Tx.Commit(); err == nil { |
||||||
|
// handle processors after tx committed
|
||||||
|
|
||||||
|
closureCallFunc := func(closuresPtr *[]func(interface{}), bean interface{}) { |
||||||
|
|
||||||
|
if closuresPtr != nil { |
||||||
|
for _, closure := range *closuresPtr { |
||||||
|
closure(bean) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for bean, closuresPtr := range session.afterInsertBeans { |
||||||
|
closureCallFunc(closuresPtr, bean) |
||||||
|
|
||||||
|
if processor, ok := interface{}(bean).(AfterInsertProcessor); ok { |
||||||
|
processor.AfterInsert() |
||||||
|
} |
||||||
|
} |
||||||
|
for bean, closuresPtr := range session.afterUpdateBeans { |
||||||
|
closureCallFunc(closuresPtr, bean) |
||||||
|
|
||||||
|
if processor, ok := interface{}(bean).(AfterUpdateProcessor); ok { |
||||||
|
processor.AfterUpdate() |
||||||
|
} |
||||||
|
} |
||||||
|
for bean, closuresPtr := range session.afterDeleteBeans { |
||||||
|
closureCallFunc(closuresPtr, bean) |
||||||
|
|
||||||
|
if processor, ok := interface{}(bean).(AfterDeleteProcessor); ok { |
||||||
|
processor.AfterDelete() |
||||||
|
} |
||||||
|
} |
||||||
|
cleanUpFunc := func(slices *map[interface{}]*[]func(interface{})) { |
||||||
|
if len(*slices) > 0 { |
||||||
|
*slices = make(map[interface{}]*[]func(interface{}), 0) |
||||||
|
} |
||||||
|
} |
||||||
|
cleanUpFunc(&session.afterInsertBeans) |
||||||
|
cleanUpFunc(&session.afterUpdateBeans) |
||||||
|
cleanUpFunc(&session.afterDeleteBeans) |
||||||
|
} |
||||||
|
return err |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,345 @@ |
|||||||
|
// 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" |
||||||
|
"reflect" |
||||||
|
"strconv" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"github.com/go-xorm/builder" |
||||||
|
"github.com/go-xorm/core" |
||||||
|
) |
||||||
|
|
||||||
|
func (session *Session) cacheUpdate(sqlStr string, args ...interface{}) error { |
||||||
|
if session.Statement.RefTable == nil || |
||||||
|
session.Tx != nil { |
||||||
|
return ErrCacheFailed |
||||||
|
} |
||||||
|
|
||||||
|
oldhead, newsql := session.Statement.convertUpdateSQL(sqlStr) |
||||||
|
if newsql == "" { |
||||||
|
return ErrCacheFailed |
||||||
|
} |
||||||
|
for _, filter := range session.Engine.dialect.Filters() { |
||||||
|
newsql = filter.Do(newsql, session.Engine.dialect, session.Statement.RefTable) |
||||||
|
} |
||||||
|
session.Engine.logger.Debug("[cacheUpdate] new sql", oldhead, newsql) |
||||||
|
|
||||||
|
var nStart int |
||||||
|
if len(args) > 0 { |
||||||
|
if strings.Index(sqlStr, "?") > -1 { |
||||||
|
nStart = strings.Count(oldhead, "?") |
||||||
|
} else { |
||||||
|
// only for pq, TODO: if any other databse?
|
||||||
|
nStart = strings.Count(oldhead, "$") |
||||||
|
} |
||||||
|
} |
||||||
|
table := session.Statement.RefTable |
||||||
|
cacher := session.Engine.getCacher2(table) |
||||||
|
tableName := session.Statement.TableName() |
||||||
|
session.Engine.logger.Debug("[cacheUpdate] get cache sql", newsql, args[nStart:]) |
||||||
|
ids, err := core.GetCacheSql(cacher, tableName, newsql, args[nStart:]) |
||||||
|
if err != nil { |
||||||
|
rows, err := session.DB().Query(newsql, args[nStart:]...) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer rows.Close() |
||||||
|
|
||||||
|
ids = make([]core.PK, 0) |
||||||
|
for rows.Next() { |
||||||
|
var res = make([]string, len(table.PrimaryKeys)) |
||||||
|
err = rows.ScanSlice(&res) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
var pk core.PK = make([]interface{}, len(table.PrimaryKeys)) |
||||||
|
for i, col := range table.PKColumns() { |
||||||
|
if col.SQLType.IsNumeric() { |
||||||
|
n, err := strconv.ParseInt(res[i], 10, 64) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
pk[i] = n |
||||||
|
} else if col.SQLType.IsText() { |
||||||
|
pk[i] = res[i] |
||||||
|
} else { |
||||||
|
return errors.New("not supported") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ids = append(ids, pk) |
||||||
|
} |
||||||
|
session.Engine.logger.Debug("[cacheUpdate] find updated id", ids) |
||||||
|
} /*else { |
||||||
|
session.Engine.LogDebug("[xorm:cacheUpdate] del cached sql:", tableName, newsql, args) |
||||||
|
cacher.DelIds(tableName, genSqlKey(newsql, args)) |
||||||
|
}*/ |
||||||
|
|
||||||
|
for _, id := range ids { |
||||||
|
sid, err := id.ToString() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
if bean := cacher.GetBean(tableName, sid); bean != nil { |
||||||
|
sqls := splitNNoCase(sqlStr, "where", 2) |
||||||
|
if len(sqls) == 0 || len(sqls) > 2 { |
||||||
|
return ErrCacheFailed |
||||||
|
} |
||||||
|
|
||||||
|
sqls = splitNNoCase(sqls[0], "set", 2) |
||||||
|
if len(sqls) != 2 { |
||||||
|
return ErrCacheFailed |
||||||
|
} |
||||||
|
kvs := strings.Split(strings.TrimSpace(sqls[1]), ",") |
||||||
|
for idx, kv := range kvs { |
||||||
|
sps := strings.SplitN(kv, "=", 2) |
||||||
|
sps2 := strings.Split(sps[0], ".") |
||||||
|
colName := sps2[len(sps2)-1] |
||||||
|
if strings.Contains(colName, "`") { |
||||||
|
colName = strings.TrimSpace(strings.Replace(colName, "`", "", -1)) |
||||||
|
} else if strings.Contains(colName, session.Engine.QuoteStr()) { |
||||||
|
colName = strings.TrimSpace(strings.Replace(colName, session.Engine.QuoteStr(), "", -1)) |
||||||
|
} else { |
||||||
|
session.Engine.logger.Debug("[cacheUpdate] cannot find column", tableName, colName) |
||||||
|
return ErrCacheFailed |
||||||
|
} |
||||||
|
|
||||||
|
if col := table.GetColumn(colName); col != nil { |
||||||
|
fieldValue, err := col.ValueOf(bean) |
||||||
|
if err != nil { |
||||||
|
session.Engine.logger.Error(err) |
||||||
|
} else { |
||||||
|
session.Engine.logger.Debug("[cacheUpdate] set bean field", bean, colName, fieldValue.Interface()) |
||||||
|
if col.IsVersion && session.Statement.checkVersion { |
||||||
|
fieldValue.SetInt(fieldValue.Int() + 1) |
||||||
|
} else { |
||||||
|
fieldValue.Set(reflect.ValueOf(args[idx])) |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
session.Engine.logger.Errorf("[cacheUpdate] ERROR: column %v is not table %v's", |
||||||
|
colName, table.Name) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
session.Engine.logger.Debug("[cacheUpdate] update cache", tableName, id, bean) |
||||||
|
cacher.PutBean(tableName, sid, bean) |
||||||
|
} |
||||||
|
} |
||||||
|
session.Engine.logger.Debug("[cacheUpdate] clear cached table sql:", tableName) |
||||||
|
cacher.ClearIds(tableName) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// 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) { |
||||||
|
defer session.resetStatement() |
||||||
|
if session.IsAutoClose { |
||||||
|
defer session.Close() |
||||||
|
} |
||||||
|
|
||||||
|
v := rValue(bean) |
||||||
|
t := v.Type() |
||||||
|
|
||||||
|
var colNames []string |
||||||
|
var args []interface{} |
||||||
|
|
||||||
|
// 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 err error |
||||||
|
var isMap = t.Kind() == reflect.Map |
||||||
|
var isStruct = t.Kind() == reflect.Struct |
||||||
|
if isStruct { |
||||||
|
session.Statement.setRefValue(v) |
||||||
|
|
||||||
|
if len(session.Statement.TableName()) <= 0 { |
||||||
|
return 0, ErrTableNotFound |
||||||
|
} |
||||||
|
|
||||||
|
if session.Statement.ColumnStr == "" { |
||||||
|
colNames, args = buildUpdates(session.Engine, session.Statement.RefTable, bean, false, false, |
||||||
|
false, false, session.Statement.allUseBool, session.Statement.useAllCols, |
||||||
|
session.Statement.mustColumnMap, session.Statement.nullableMap, |
||||||
|
session.Statement.columnMap, true, session.Statement.unscoped) |
||||||
|
} else { |
||||||
|
colNames, args, err = genCols(session.Statement.RefTable, session, bean, true, true) |
||||||
|
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 != "" { |
||||||
|
colNames = append(colNames, session.Engine.Quote(table.Updated)+" = ?") |
||||||
|
col := table.UpdatedColumn() |
||||||
|
val, t := session.Engine.NowTime2(col.SQLType.Name) |
||||||
|
args = append(args, val) |
||||||
|
|
||||||
|
var colName = col.Name |
||||||
|
if isStruct { |
||||||
|
session.afterClosures = append(session.afterClosures, func(bean interface{}) { |
||||||
|
col := table.GetColumn(colName) |
||||||
|
setColumnTime(bean, col, t) |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//for update action to like "column = column + ?"
|
||||||
|
incColumns := session.Statement.getInc() |
||||||
|
for _, v := range incColumns { |
||||||
|
colNames = append(colNames, session.Engine.Quote(v.colName)+" = "+session.Engine.Quote(v.colName)+" + ?") |
||||||
|
args = append(args, v.arg) |
||||||
|
} |
||||||
|
//for update action to like "column = column - ?"
|
||||||
|
decColumns := session.Statement.getDec() |
||||||
|
for _, v := range decColumns { |
||||||
|
colNames = append(colNames, session.Engine.Quote(v.colName)+" = "+session.Engine.Quote(v.colName)+" - ?") |
||||||
|
args = append(args, v.arg) |
||||||
|
} |
||||||
|
//for update action to like "column = expression"
|
||||||
|
exprColumns := session.Statement.getExpr() |
||||||
|
for _, v := range exprColumns { |
||||||
|
colNames = append(colNames, session.Engine.Quote(v.colName)+" = "+v.expr) |
||||||
|
} |
||||||
|
|
||||||
|
session.Statement.processIDParam() |
||||||
|
|
||||||
|
var autoCond builder.Cond |
||||||
|
if !session.Statement.noAutoCondition && len(condiBean) > 0 { |
||||||
|
var err error |
||||||
|
autoCond, err = session.Statement.buildConds(session.Statement.RefTable, condiBean[0], true, true, false, true, false) |
||||||
|
if err != nil { |
||||||
|
return 0, err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
st := session.Statement |
||||||
|
defer session.resetStatement() |
||||||
|
|
||||||
|
var sqlStr string |
||||||
|
var condArgs []interface{} |
||||||
|
var condSQL string |
||||||
|
cond := session.Statement.cond.And(autoCond) |
||||||
|
|
||||||
|
doIncVer := false |
||||||
|
var verValue *reflect.Value |
||||||
|
if table != nil && table.Version != "" && session.Statement.checkVersion { |
||||||
|
verValue, err = table.VersionColumn().ValueOf(bean) |
||||||
|
if err != nil { |
||||||
|
return 0, err |
||||||
|
} |
||||||
|
|
||||||
|
cond = cond.And(builder.Eq{session.Engine.Quote(table.Version): verValue.Interface()}) |
||||||
|
condSQL, condArgs, _ = builder.ToSQL(cond) |
||||||
|
|
||||||
|
if len(condSQL) > 0 { |
||||||
|
condSQL = "WHERE " + condSQL |
||||||
|
} |
||||||
|
|
||||||
|
if st.LimitN > 0 { |
||||||
|
condSQL = condSQL + fmt.Sprintf(" LIMIT %d", st.LimitN) |
||||||
|
} |
||||||
|
|
||||||
|
sqlStr = fmt.Sprintf("UPDATE %v SET %v, %v %v", |
||||||
|
session.Engine.Quote(session.Statement.TableName()), |
||||||
|
strings.Join(colNames, ", "), |
||||||
|
session.Engine.Quote(table.Version)+" = "+session.Engine.Quote(table.Version)+" + 1", |
||||||
|
condSQL) |
||||||
|
|
||||||
|
doIncVer = true |
||||||
|
} else { |
||||||
|
condSQL, condArgs, _ = builder.ToSQL(cond) |
||||||
|
if len(condSQL) > 0 { |
||||||
|
condSQL = "WHERE " + condSQL |
||||||
|
} |
||||||
|
|
||||||
|
if st.LimitN > 0 { |
||||||
|
condSQL = condSQL + fmt.Sprintf(" LIMIT %d", st.LimitN) |
||||||
|
} |
||||||
|
|
||||||
|
sqlStr = fmt.Sprintf("UPDATE %v SET %v %v", |
||||||
|
session.Engine.Quote(session.Statement.TableName()), |
||||||
|
strings.Join(colNames, ", "), |
||||||
|
condSQL) |
||||||
|
} |
||||||
|
|
||||||
|
res, err := session.exec(sqlStr, append(args, condArgs...)...) |
||||||
|
if err != nil { |
||||||
|
return 0, err |
||||||
|
} else if doIncVer { |
||||||
|
if verValue != nil && verValue.IsValid() && verValue.CanSet() { |
||||||
|
verValue.SetInt(verValue.Int() + 1) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if table != nil { |
||||||
|
if cacher := session.Engine.getCacher2(table); cacher != nil && session.Statement.UseCache { |
||||||
|
cacher.ClearIds(session.Statement.TableName()) |
||||||
|
cacher.ClearBeans(session.Statement.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.Debug("[event]", session.Statement.TableName(), " has after update processor") |
||||||
|
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() |
||||||
|
} |
Loading…
Reference in new issue