mirror of
https://gitee.com/johng/gf
synced 2026-07-06 21:45:34 +08:00
add Lock* functions for gdb.Model and improve Data/Where functions for gdb.Model
This commit is contained in:
@ -364,7 +364,7 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error
|
||||
n.Name = nodeSchema
|
||||
node = &n
|
||||
}
|
||||
// Cache the underlying connection object by node.
|
||||
// Cache the underlying connection pool object by node.
|
||||
v := c.cache.GetOrSetFuncLock(node.String(), func() interface{} {
|
||||
sqlDb, err = c.DB.Open(node)
|
||||
if err != nil {
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import "database/sql"
|
||||
|
||||
// batchSqlResult is execution result for batch operations.
|
||||
type batchSqlResult struct {
|
||||
rowsAffected int64
|
||||
lastResult sql.Result
|
||||
}
|
||||
|
||||
// see sql.Result.RowsAffected
|
||||
func (r *batchSqlResult) RowsAffected() (int64, error) {
|
||||
return r.rowsAffected, nil
|
||||
}
|
||||
|
||||
// see sql.Result.LastInsertId
|
||||
func (r *batchSqlResult) LastInsertId() (int64, error) {
|
||||
return r.lastResult.LastInsertId()
|
||||
}
|
||||
@ -504,7 +504,7 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
|
||||
holders = append(holders, "?")
|
||||
}
|
||||
// Prepare the batch result pointer.
|
||||
batchResult := new(batchSqlResult)
|
||||
batchResult := new(SqlResult)
|
||||
charL, charR := c.DB.GetChars()
|
||||
keysStr := charL + strings.Join(keys, charR+","+charL) + charR
|
||||
valueHolderStr := "(" + strings.Join(holders, ",") + ")"
|
||||
@ -555,8 +555,8 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
|
||||
if n, err := r.RowsAffected(); err != nil {
|
||||
return r, err
|
||||
} else {
|
||||
batchResult.lastResult = r
|
||||
batchResult.rowsAffected += n
|
||||
batchResult.result = r
|
||||
batchResult.affected += n
|
||||
}
|
||||
params = params[:0]
|
||||
values = values[:0]
|
||||
|
||||
@ -377,7 +377,7 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{},
|
||||
keys = append(keys, k)
|
||||
holders = append(holders, "?")
|
||||
}
|
||||
batchResult := new(batchSqlResult)
|
||||
batchResult := new(SqlResult)
|
||||
charL, charR := d.DB.GetChars()
|
||||
keyStr := charL + strings.Join(keys, charL+","+charR) + charR
|
||||
valueHolderStr := strings.Join(holders, ",")
|
||||
@ -393,8 +393,8 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{},
|
||||
if n, err := r.RowsAffected(); err != nil {
|
||||
return r, err
|
||||
} else {
|
||||
batchResult.lastResult = r
|
||||
batchResult.rowsAffected += n
|
||||
batchResult.result = r
|
||||
batchResult.affected += n
|
||||
}
|
||||
}
|
||||
return batchResult, nil
|
||||
@ -421,8 +421,8 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{},
|
||||
if n, err := r.RowsAffected(); err != nil {
|
||||
return r, err
|
||||
} else {
|
||||
batchResult.lastResult = r
|
||||
batchResult.rowsAffected += n
|
||||
batchResult.result = r
|
||||
batchResult.affected += n
|
||||
}
|
||||
params = params[:0]
|
||||
intoStr = intoStr[:0]
|
||||
@ -437,8 +437,8 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{},
|
||||
if n, err := r.RowsAffected(); err != nil {
|
||||
return r, err
|
||||
} else {
|
||||
batchResult.lastResult = r
|
||||
batchResult.rowsAffected += n
|
||||
batchResult.result = r
|
||||
batchResult.affected += n
|
||||
}
|
||||
}
|
||||
return batchResult, nil
|
||||
|
||||
@ -288,12 +288,19 @@ func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (
|
||||
// Eg: Where/And/Or("uid>=", 1)
|
||||
newWhere += "?"
|
||||
} else if gregex.IsMatchString(`^[\w\.\-]+$`, newWhere) {
|
||||
if len(newArgs) == 1 && utils.IsArray(newArgs[0]) {
|
||||
// Eg: Where("id", []int{1,2,3})
|
||||
newWhere += " IN (?)"
|
||||
} else {
|
||||
// Eg: Where/And/Or("uid", 1)
|
||||
newWhere += "=?"
|
||||
newWhere = db.QuoteString(newWhere)
|
||||
if len(newArgs) > 0 {
|
||||
if utils.IsArray(newArgs[0]) {
|
||||
// Eg: Where("id", []int{1,2,3})
|
||||
newWhere += " IN (?)"
|
||||
} else if empty.IsNil(newArgs[0]) {
|
||||
// Eg: Where("id", nil)
|
||||
newWhere += " IS NULL"
|
||||
newArgs = nil
|
||||
} else {
|
||||
// Eg: Where/And/Or("uid", 1)
|
||||
newWhere += "=?"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -323,7 +330,7 @@ func formatWhereInterfaces(db DB, where []interface{}, buffer *bytes.Buffer, new
|
||||
|
||||
// formatWhereKeyValue handles each key-value pair of the parameter map.
|
||||
func formatWhereKeyValue(db DB, buffer *bytes.Buffer, newArgs []interface{}, key string, value interface{}) []interface{} {
|
||||
key = db.QuoteWord(key)
|
||||
quotedKey := db.QuoteWord(key)
|
||||
if buffer.Len() > 0 {
|
||||
buffer.WriteString(" AND ")
|
||||
}
|
||||
@ -331,36 +338,43 @@ func formatWhereKeyValue(db DB, buffer *bytes.Buffer, newArgs []interface{}, key
|
||||
// the key string, it automatically adds '?' holder chars according to its arguments count
|
||||
// and converts it to "IN" statement.
|
||||
rv := reflect.ValueOf(value)
|
||||
switch rv.Kind() {
|
||||
kind := rv.Kind()
|
||||
switch kind {
|
||||
case reflect.Slice, reflect.Array:
|
||||
count := gstr.Count(key, "?")
|
||||
count := gstr.Count(quotedKey, "?")
|
||||
if count == 0 {
|
||||
buffer.WriteString(key + " IN(?)")
|
||||
buffer.WriteString(quotedKey + " IN(?)")
|
||||
newArgs = append(newArgs, value)
|
||||
} else if count != rv.Len() {
|
||||
buffer.WriteString(key)
|
||||
buffer.WriteString(quotedKey)
|
||||
newArgs = append(newArgs, value)
|
||||
} else {
|
||||
buffer.WriteString(key)
|
||||
buffer.WriteString(quotedKey)
|
||||
newArgs = append(newArgs, gconv.Interfaces(value)...)
|
||||
}
|
||||
default:
|
||||
if value == nil {
|
||||
buffer.WriteString(key)
|
||||
if value == nil || empty.IsNil(rv) {
|
||||
if gregex.IsMatchString(`^[\w\.\-]+$`, key) {
|
||||
// The key is a single field name.
|
||||
buffer.WriteString(quotedKey + " IS NULL")
|
||||
} else {
|
||||
// The key may have operation chars.
|
||||
buffer.WriteString(quotedKey)
|
||||
}
|
||||
} else {
|
||||
// It also supports "LIKE" statement, which we considers it an operator.
|
||||
key = gstr.Trim(key)
|
||||
if gstr.Pos(key, "?") == -1 {
|
||||
quotedKey = gstr.Trim(quotedKey)
|
||||
if gstr.Pos(quotedKey, "?") == -1 {
|
||||
like := " like"
|
||||
if len(key) > len(like) && gstr.Equal(key[len(key)-len(like):], like) {
|
||||
buffer.WriteString(key + " ?")
|
||||
} else if lastOperatorReg.MatchString(key) {
|
||||
buffer.WriteString(key + " ?")
|
||||
if len(quotedKey) > len(like) && gstr.Equal(quotedKey[len(quotedKey)-len(like):], like) {
|
||||
buffer.WriteString(quotedKey + " ?")
|
||||
} else if lastOperatorReg.MatchString(quotedKey) {
|
||||
buffer.WriteString(quotedKey + " ?")
|
||||
} else {
|
||||
buffer.WriteString(key + "=?")
|
||||
buffer.WriteString(quotedKey + "=?")
|
||||
}
|
||||
} else {
|
||||
buffer.WriteString(key)
|
||||
buffer.WriteString(quotedKey)
|
||||
}
|
||||
newArgs = append(newArgs, value)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
36
database/gdb/gdb_model_cache.go
Normal file
36
database/gdb/gdb_model_cache.go
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Cache sets the cache feature for the model. It caches the result of the sql, which means
|
||||
// if there's another same sql request, it just reads and returns the result from cache, it
|
||||
// but not committed and executed into the database.
|
||||
//
|
||||
// If the parameter <duration> < 0, which means it clear the cache with given <name>.
|
||||
// If the parameter <duration> = 0, which means it never expires.
|
||||
// If the parameter <duration> > 0, which means it expires after <duration>.
|
||||
//
|
||||
// The optional parameter <name> is used to bind a name to the cache, which means you can later
|
||||
// control the cache like changing the <duration> or clearing the cache with specified <name>.
|
||||
//
|
||||
// Note that, the cache feature is disabled if the model is operating on a transaction.
|
||||
func (m *Model) Cache(duration time.Duration, name ...string) *Model {
|
||||
model := m.getModel()
|
||||
model.cacheDuration = duration
|
||||
if len(name) > 0 {
|
||||
model.cacheName = name[0]
|
||||
}
|
||||
// It does not support cache on transaction.
|
||||
if model.tx == nil {
|
||||
model.cacheEnabled = true
|
||||
}
|
||||
return model
|
||||
}
|
||||
170
database/gdb/gdb_model_condition.go
Normal file
170
database/gdb/gdb_model_condition.go
Normal file
@ -0,0 +1,170 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import "github.com/gogf/gf/util/gconv"
|
||||
|
||||
// Where sets the condition statement for the model. The parameter <where> can be type of
|
||||
// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times,
|
||||
// multiple conditions will be joined into where statement using "AND".
|
||||
// Eg:
|
||||
// Where("uid=10000")
|
||||
// Where("uid", 10000)
|
||||
// Where("money>? AND name like ?", 99999, "vip_%")
|
||||
// Where("uid", 1).Where("name", "john")
|
||||
// Where("status IN (?)", g.Slice{1,2,3})
|
||||
// Where("age IN(?,?)", 18, 50)
|
||||
// Where(User{ Id : 1, UserName : "john"})
|
||||
func (m *Model) Where(where interface{}, args ...interface{}) *Model {
|
||||
model := m.getModel()
|
||||
if model.whereHolder == nil {
|
||||
model.whereHolder = make([]*whereHolder, 0)
|
||||
}
|
||||
model.whereHolder = append(model.whereHolder, &whereHolder{
|
||||
operator: gWHERE_HOLDER_WHERE,
|
||||
where: where,
|
||||
args: args,
|
||||
})
|
||||
return model
|
||||
}
|
||||
|
||||
// WherePri does the same logic as Model.Where except that if the parameter <where>
|
||||
// is a single condition like int/string/float/slice, it treats the condition as the primary
|
||||
// key value. That is, if primary key is "id" and given <where> parameter as "123", the
|
||||
// WherePri function treats it as "id=123", but Model.Where treats it as string "123".
|
||||
func (m *Model) WherePri(where interface{}, args ...interface{}) *Model {
|
||||
if len(args) > 0 {
|
||||
return m.Where(where, args...)
|
||||
}
|
||||
newWhere := GetPrimaryKeyCondition(m.getPrimaryKey(), where)
|
||||
return m.Where(newWhere[0], newWhere[1:]...)
|
||||
}
|
||||
|
||||
// And adds "AND" condition to the where statement.
|
||||
func (m *Model) And(where interface{}, args ...interface{}) *Model {
|
||||
model := m.getModel()
|
||||
if model.whereHolder == nil {
|
||||
model.whereHolder = make([]*whereHolder, 0)
|
||||
}
|
||||
model.whereHolder = append(model.whereHolder, &whereHolder{
|
||||
operator: gWHERE_HOLDER_AND,
|
||||
where: where,
|
||||
args: args,
|
||||
})
|
||||
return model
|
||||
}
|
||||
|
||||
// Or adds "OR" condition to the where statement.
|
||||
func (m *Model) Or(where interface{}, args ...interface{}) *Model {
|
||||
model := m.getModel()
|
||||
if model.whereHolder == nil {
|
||||
model.whereHolder = make([]*whereHolder, 0)
|
||||
}
|
||||
model.whereHolder = append(model.whereHolder, &whereHolder{
|
||||
operator: gWHERE_HOLDER_OR,
|
||||
where: where,
|
||||
args: args,
|
||||
})
|
||||
return model
|
||||
}
|
||||
|
||||
// Group sets the "GROUP BY" statement for the model.
|
||||
func (m *Model) Group(groupBy string) *Model {
|
||||
model := m.getModel()
|
||||
model.groupBy = m.db.QuoteString(groupBy)
|
||||
return model
|
||||
}
|
||||
|
||||
// GroupBy is alias of Model.Group.
|
||||
// See Model.Group.
|
||||
// Deprecated.
|
||||
func (m *Model) GroupBy(groupBy string) *Model {
|
||||
return m.Group(groupBy)
|
||||
}
|
||||
|
||||
// Order sets the "ORDER BY" statement for the model.
|
||||
func (m *Model) Order(orderBy string) *Model {
|
||||
model := m.getModel()
|
||||
model.orderBy = m.db.QuoteString(orderBy)
|
||||
return model
|
||||
}
|
||||
|
||||
// OrderBy is alias of Model.Order.
|
||||
// See Model.Order.
|
||||
// Deprecated.
|
||||
func (m *Model) OrderBy(orderBy string) *Model {
|
||||
return m.Order(orderBy)
|
||||
}
|
||||
|
||||
// Limit sets the "LIMIT" statement for the model.
|
||||
// The parameter <limit> can be either one or two number, if passed two number is passed,
|
||||
// it then sets "LIMIT limit[0],limit[1]" statement for the model, or else it sets "LIMIT limit[0]"
|
||||
// statement.
|
||||
func (m *Model) Limit(limit ...int) *Model {
|
||||
model := m.getModel()
|
||||
switch len(limit) {
|
||||
case 1:
|
||||
model.limit = limit[0]
|
||||
case 2:
|
||||
model.start = limit[0]
|
||||
model.limit = limit[1]
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
// Offset sets the "OFFSET" statement for the model.
|
||||
// It only makes sense for some databases like SQLServer, PostgreSQL, etc.
|
||||
func (m *Model) Offset(offset int) *Model {
|
||||
model := m.getModel()
|
||||
model.offset = offset
|
||||
return model
|
||||
}
|
||||
|
||||
// Page sets the paging number for the model.
|
||||
// The parameter <page> is started from 1 for paging.
|
||||
// Note that, it differs that the Limit function start from 0 for "LIMIT" statement.
|
||||
func (m *Model) Page(page, limit int) *Model {
|
||||
model := m.getModel()
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
model.start = (page - 1) * limit
|
||||
model.limit = limit
|
||||
return model
|
||||
}
|
||||
|
||||
// ForPage is alias of Model.Page.
|
||||
// See Model.Page.
|
||||
// Deprecated.
|
||||
func (m *Model) ForPage(page, limit int) *Model {
|
||||
return m.Page(page, limit)
|
||||
}
|
||||
|
||||
// getAll does the query from database.
|
||||
func (m *Model) getAll(query string, args ...interface{}) (result Result, err error) {
|
||||
cacheKey := ""
|
||||
// Retrieve from cache.
|
||||
if m.cacheEnabled {
|
||||
cacheKey = m.cacheName
|
||||
if len(cacheKey) == 0 {
|
||||
cacheKey = query + "/" + gconv.String(args)
|
||||
}
|
||||
if v := m.db.GetCache().Get(cacheKey); v != nil {
|
||||
return v.(Result), nil
|
||||
}
|
||||
}
|
||||
result, err = m.db.DoGetAll(m.getLink(false), query, m.mergeArguments(args)...)
|
||||
// Cache the result.
|
||||
if len(cacheKey) > 0 && err == nil {
|
||||
if m.cacheDuration < 0 {
|
||||
m.db.GetCache().Remove(cacheKey)
|
||||
} else {
|
||||
m.db.GetCache().Set(cacheKey, result, m.cacheDuration)
|
||||
}
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
27
database/gdb/gdb_model_delete.go
Normal file
27
database/gdb/gdb_model_delete.go
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
// Delete does "DELETE FROM ... " statement for the model.
|
||||
// The optional parameter <where> is the same as the parameter of Model.Where function,
|
||||
// see Model.Where.
|
||||
func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
|
||||
if len(where) > 0 {
|
||||
return m.Where(where[0], where[1:]...).Delete()
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
m.checkAndRemoveCache()
|
||||
}
|
||||
}()
|
||||
condition, conditionArgs := m.formatCondition(false)
|
||||
return m.db.DoDelete(m.getLink(true), m.tables, condition, conditionArgs...)
|
||||
}
|
||||
94
database/gdb/gdb_model_fields.go
Normal file
94
database/gdb/gdb_model_fields.go
Normal file
@ -0,0 +1,94 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/container/garray"
|
||||
"github.com/gogf/gf/container/gset"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
)
|
||||
|
||||
// Filter marks filtering the fields which does not exist in the fields of the operated table.
|
||||
func (m *Model) Filter() *Model {
|
||||
if gstr.Contains(m.tables, " ") {
|
||||
panic("function Filter supports only single table operations")
|
||||
}
|
||||
model := m.getModel()
|
||||
model.filter = true
|
||||
return model
|
||||
}
|
||||
|
||||
// Fields sets the operation fields of the model, multiple fields joined using char ','.
|
||||
func (m *Model) Fields(fields string) *Model {
|
||||
model := m.getModel()
|
||||
model.fields = fields
|
||||
return model
|
||||
}
|
||||
|
||||
// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','.
|
||||
func (m *Model) FieldsEx(fields string) *Model {
|
||||
if gstr.Contains(m.tables, " ") {
|
||||
panic("function FieldsEx supports only single table operations")
|
||||
}
|
||||
model := m.getModel()
|
||||
model.fieldsEx = fields
|
||||
fieldsExSet := gset.NewStrSetFrom(gstr.SplitAndTrim(fields, ","))
|
||||
if m, err := m.db.TableFields(m.tables); err == nil {
|
||||
model.fields = ""
|
||||
for k, _ := range m {
|
||||
if fieldsExSet.Contains(k) {
|
||||
continue
|
||||
}
|
||||
if len(model.fields) > 0 {
|
||||
model.fields += ","
|
||||
}
|
||||
model.fields += k
|
||||
}
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
// FieldsStr retrieves and returns all fields from the table, joined with char ','.
|
||||
// The optional parameter <prefix> specifies the prefix for each field, eg: FieldsStr("u.").
|
||||
func (m *Model) FieldsStr(prefix ...string) string {
|
||||
prefixStr := ""
|
||||
if len(prefix) > 0 {
|
||||
prefixStr = prefix[0]
|
||||
}
|
||||
if m, err := m.db.TableFields(m.tables); err == nil {
|
||||
fieldsArray := garray.NewStrArraySize(len(m), len(m))
|
||||
for _, field := range m {
|
||||
fieldsArray.Set(field.Index, prefixStr+field.Name)
|
||||
}
|
||||
return fieldsArray.Join(",")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// FieldsExStr retrieves and returns fields which are not in parameter <fields> from the table,
|
||||
// joined with char ','.
|
||||
// The parameter <fields> specifies the fields that are excluded.
|
||||
// The optional parameter <prefix> specifies the prefix for each field, eg: FieldsExStr("id", "u.").
|
||||
func (m *Model) FieldsExStr(fields string, prefix ...string) string {
|
||||
prefixStr := ""
|
||||
if len(prefix) > 0 {
|
||||
prefixStr = prefix[0]
|
||||
}
|
||||
if m, err := m.db.TableFields(m.tables); err == nil {
|
||||
fieldsArray := garray.NewStrArraySize(len(m), len(m))
|
||||
fieldsExSet := gset.NewStrSetFrom(gstr.SplitAndTrim(fields, ","))
|
||||
for _, field := range m {
|
||||
if fieldsExSet.Contains(field.Name) {
|
||||
continue
|
||||
}
|
||||
fieldsArray.Set(field.Index, prefixStr+field.Name)
|
||||
}
|
||||
fieldsArray.FilterEmpty()
|
||||
return fieldsArray.Join(",")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
213
database/gdb/gdb_model_insert.go
Normal file
213
database/gdb/gdb_model_insert.go
Normal file
@ -0,0 +1,213 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Batch sets the batch operation number for the model.
|
||||
func (m *Model) Batch(batch int) *Model {
|
||||
model := m.getModel()
|
||||
model.batch = batch
|
||||
return model
|
||||
}
|
||||
|
||||
// Data sets the operation data for the model.
|
||||
// The parameter <data> can be type of string/map/gmap/slice/struct/*struct, etc.
|
||||
// Eg:
|
||||
// Data("uid=10000")
|
||||
// Data("uid", 10000)
|
||||
// Data("uid=? AND name=?", 10000, "john")
|
||||
// Data(g.Map{"uid": 10000, "name":"john"})
|
||||
// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
|
||||
func (m *Model) Data(data ...interface{}) *Model {
|
||||
model := m.getModel()
|
||||
if len(data) > 1 {
|
||||
s := gconv.String(data[0])
|
||||
if gstr.Contains(s, "?") {
|
||||
model.data = s
|
||||
model.extraArgs = data[1:]
|
||||
} else {
|
||||
m := make(map[string]interface{})
|
||||
for i := 0; i < len(data); i += 2 {
|
||||
m[gconv.String(data[i])] = data[i+1]
|
||||
}
|
||||
model.data = m
|
||||
}
|
||||
} else {
|
||||
switch params := data[0].(type) {
|
||||
case Result:
|
||||
model.data = params.List()
|
||||
case Record:
|
||||
model.data = params.Map()
|
||||
case List:
|
||||
model.data = params
|
||||
case Map:
|
||||
model.data = params
|
||||
default:
|
||||
rv := reflect.ValueOf(params)
|
||||
kind := rv.Kind()
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
switch kind {
|
||||
case reflect.Slice, reflect.Array:
|
||||
list := make(List, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
list[i] = DataToMapDeep(rv.Index(i).Interface())
|
||||
}
|
||||
model.data = list
|
||||
case reflect.Map, reflect.Struct:
|
||||
model.data = DataToMapDeep(data[0])
|
||||
default:
|
||||
model.data = data[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
// Insert does "INSERT INTO ..." statement for the model.
|
||||
// The optional parameter <data> is the same as the parameter of Model.Data function,
|
||||
// see Model.Data.
|
||||
func (m *Model) Insert(data ...interface{}) (result sql.Result, err error) {
|
||||
return m.doInsertWithOption(gINSERT_OPTION_DEFAULT, data...)
|
||||
}
|
||||
|
||||
// InsertIgnore does "INSERT IGNORE INTO ..." statement for the model.
|
||||
// The optional parameter <data> is the same as the parameter of Model.Data function,
|
||||
// see Model.Data.
|
||||
func (m *Model) InsertIgnore(data ...interface{}) (result sql.Result, err error) {
|
||||
return m.doInsertWithOption(gINSERT_OPTION_IGNORE, data...)
|
||||
}
|
||||
|
||||
// doInsertWithOption inserts data with option parameter.
|
||||
func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql.Result, err error) {
|
||||
if len(data) > 0 {
|
||||
return m.Data(data...).Insert()
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
m.checkAndRemoveCache()
|
||||
}
|
||||
}()
|
||||
if m.data == nil {
|
||||
return nil, errors.New("inserting into table with empty data")
|
||||
}
|
||||
if list, ok := m.data.(List); ok {
|
||||
// Batch insert.
|
||||
batch := 10
|
||||
if m.batch > 0 {
|
||||
batch = m.batch
|
||||
}
|
||||
return m.db.DoBatchInsert(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(list),
|
||||
option,
|
||||
batch,
|
||||
)
|
||||
} else if data, ok := m.data.(Map); ok {
|
||||
// Single insert.
|
||||
return m.db.DoInsert(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(data),
|
||||
option,
|
||||
)
|
||||
}
|
||||
return nil, errors.New("inserting into table with invalid data type")
|
||||
}
|
||||
|
||||
// Replace does "REPLACE INTO ..." statement for the model.
|
||||
// The optional parameter <data> is the same as the parameter of Model.Data function,
|
||||
// see Model.Data.
|
||||
func (m *Model) Replace(data ...interface{}) (result sql.Result, err error) {
|
||||
if len(data) > 0 {
|
||||
return m.Data(data...).Replace()
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
m.checkAndRemoveCache()
|
||||
}
|
||||
}()
|
||||
if m.data == nil {
|
||||
return nil, errors.New("replacing into table with empty data")
|
||||
}
|
||||
if list, ok := m.data.(List); ok {
|
||||
// Batch replace.
|
||||
batch := 10
|
||||
if m.batch > 0 {
|
||||
batch = m.batch
|
||||
}
|
||||
return m.db.DoBatchInsert(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(list),
|
||||
gINSERT_OPTION_REPLACE,
|
||||
batch,
|
||||
)
|
||||
} else if data, ok := m.data.(Map); ok {
|
||||
// Single insert.
|
||||
return m.db.DoInsert(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(data),
|
||||
gINSERT_OPTION_REPLACE,
|
||||
)
|
||||
}
|
||||
return nil, errors.New("replacing into table with invalid data type")
|
||||
}
|
||||
|
||||
// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the model.
|
||||
// The optional parameter <data> is the same as the parameter of Model.Data function,
|
||||
// see Model.Data.
|
||||
//
|
||||
// It updates the record if there's primary or unique index in the saving data,
|
||||
// or else it inserts a new record into the table.
|
||||
func (m *Model) Save(data ...interface{}) (result sql.Result, err error) {
|
||||
if len(data) > 0 {
|
||||
return m.Data(data...).Save()
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
m.checkAndRemoveCache()
|
||||
}
|
||||
}()
|
||||
if m.data == nil {
|
||||
return nil, errors.New("saving into table with empty data")
|
||||
}
|
||||
if list, ok := m.data.(List); ok {
|
||||
// Batch save.
|
||||
batch := gDEFAULT_BATCH_NUM
|
||||
if m.batch > 0 {
|
||||
batch = m.batch
|
||||
}
|
||||
return m.db.DoBatchInsert(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(list),
|
||||
gINSERT_OPTION_SAVE,
|
||||
batch,
|
||||
)
|
||||
} else if data, ok := m.data.(Map); ok {
|
||||
// Single save.
|
||||
return m.db.DoInsert(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(data),
|
||||
gINSERT_OPTION_SAVE,
|
||||
)
|
||||
}
|
||||
return nil, errors.New("saving into table with invalid data type")
|
||||
}
|
||||
30
database/gdb/gdb_model_join.go
Normal file
30
database/gdb/gdb_model_join.go
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import "fmt"
|
||||
|
||||
// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
|
||||
func (m *Model) LeftJoin(table string, on string) *Model {
|
||||
model := m.getModel()
|
||||
model.tables += fmt.Sprintf(" LEFT JOIN %s ON (%s)", m.db.QuotePrefixTableName(table), on)
|
||||
return model
|
||||
}
|
||||
|
||||
// RightJoin does "RIGHT JOIN ... ON ..." statement on the model.
|
||||
func (m *Model) RightJoin(table string, on string) *Model {
|
||||
model := m.getModel()
|
||||
model.tables += fmt.Sprintf(" RIGHT JOIN %s ON (%s)", m.db.QuotePrefixTableName(table), on)
|
||||
return model
|
||||
}
|
||||
|
||||
// InnerJoin does "INNER JOIN ... ON ..." statement on the model.
|
||||
func (m *Model) InnerJoin(table string, on string) *Model {
|
||||
model := m.getModel()
|
||||
model.tables += fmt.Sprintf(" INNER JOIN %s ON (%s)", m.db.QuotePrefixTableName(table), on)
|
||||
return model
|
||||
}
|
||||
21
database/gdb/gdb_model_lock.go
Normal file
21
database/gdb/gdb_model_lock.go
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
// LockUpdate sets the lock for update for current operation.
|
||||
func (m *Model) LockUpdate() *Model {
|
||||
model := m.getModel()
|
||||
model.lockInfo = "FOR UPDATE"
|
||||
return model
|
||||
}
|
||||
|
||||
// LockShared sets the lock in share mode for current operation.
|
||||
func (m *Model) LockShared() *Model {
|
||||
model := m.getModel()
|
||||
model.lockInfo = "LOCK IN SHARE MODE"
|
||||
return model
|
||||
}
|
||||
27
database/gdb/gdb_model_option.go
Normal file
27
database/gdb/gdb_model_option.go
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
// Option adds extra operation option for the model.
|
||||
func (m *Model) Option(option int) *Model {
|
||||
model := m.getModel()
|
||||
model.option = model.option | option
|
||||
return model
|
||||
}
|
||||
|
||||
// OptionOmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
|
||||
// the data and where attributes for empty values.
|
||||
// Deprecated, use OmitEmpty instead.
|
||||
func (m *Model) OptionOmitEmpty() *Model {
|
||||
return m.Option(OPTION_OMITEMPTY)
|
||||
}
|
||||
|
||||
// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
|
||||
// the data and where attributes for empty values.
|
||||
func (m *Model) OmitEmpty() *Model {
|
||||
return m.Option(OPTION_OMITEMPTY)
|
||||
}
|
||||
315
database/gdb/gdb_model_select.go
Normal file
315
database/gdb/gdb_model_select.go
Normal file
@ -0,0 +1,315 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Select is alias of Model.All.
|
||||
// See Model.All.
|
||||
// Deprecated.
|
||||
func (m *Model) Select(where ...interface{}) (Result, error) {
|
||||
return m.All(where...)
|
||||
}
|
||||
|
||||
// All does "SELECT FROM ..." statement for the model.
|
||||
// It retrieves the records from table and returns the result as slice type.
|
||||
// It returns nil if there's no record retrieved with the given conditions from table.
|
||||
//
|
||||
// The optional parameter <where> is the same as the parameter of Model.Where function,
|
||||
// see Model.Where.
|
||||
func (m *Model) All(where ...interface{}) (Result, error) {
|
||||
if len(where) > 0 {
|
||||
return m.Where(where[0], where[1:]...).All()
|
||||
}
|
||||
condition, conditionArgs := m.formatCondition(false)
|
||||
return m.getAll(
|
||||
fmt.Sprintf("SELECT %s FROM %s%s", m.fields, m.tables, condition),
|
||||
conditionArgs...,
|
||||
)
|
||||
}
|
||||
|
||||
// Chunk iterates the query result with given size and callback function.
|
||||
func (m *Model) Chunk(limit int, callback func(result Result, err error) bool) {
|
||||
page := m.start
|
||||
if page == 0 {
|
||||
page = 1
|
||||
}
|
||||
model := m
|
||||
for {
|
||||
model = model.Page(page, limit)
|
||||
data, err := model.All()
|
||||
if err != nil {
|
||||
callback(nil, err)
|
||||
break
|
||||
}
|
||||
if len(data) == 0 {
|
||||
break
|
||||
}
|
||||
if callback(data, err) == false {
|
||||
break
|
||||
}
|
||||
if len(data) < limit {
|
||||
break
|
||||
}
|
||||
page++
|
||||
}
|
||||
}
|
||||
|
||||
// One retrieves one record from table and returns the result as map type.
|
||||
// It returns nil if there's no record retrieved with the given conditions from table.
|
||||
//
|
||||
// The optional parameter <where> is the same as the parameter of Model.Where function,
|
||||
// see Model.Where.
|
||||
func (m *Model) One(where ...interface{}) (Record, error) {
|
||||
if len(where) > 0 {
|
||||
return m.Where(where[0], where[1:]...).One()
|
||||
}
|
||||
condition, conditionArgs := m.formatCondition(true)
|
||||
all, err := m.getAll(fmt.Sprintf("SELECT %s FROM %s%s", m.fields, m.tables, condition), conditionArgs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(all) > 0 {
|
||||
return all[0], nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Value retrieves a specified record value from table and returns the result as interface type.
|
||||
// It returns nil if there's no record found with the given conditions from table.
|
||||
//
|
||||
// If the optional parameter <fieldsAndWhere> is given, the fieldsAndWhere[0] is the selected fields
|
||||
// and fieldsAndWhere[1:] is treated as where condition fields.
|
||||
// Also see Model.Fields and Model.Where functions.
|
||||
func (m *Model) Value(fieldsAndWhere ...interface{}) (Value, error) {
|
||||
if len(fieldsAndWhere) > 0 {
|
||||
if len(fieldsAndWhere) > 2 {
|
||||
return m.Fields(gconv.String(fieldsAndWhere[0])).Where(fieldsAndWhere[1], fieldsAndWhere[2:]...).Value()
|
||||
} else if len(fieldsAndWhere) == 2 {
|
||||
return m.Fields(gconv.String(fieldsAndWhere[0])).Where(fieldsAndWhere[1]).Value()
|
||||
} else {
|
||||
return m.Fields(gconv.String(fieldsAndWhere[0])).Value()
|
||||
}
|
||||
}
|
||||
one, err := m.One()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, v := range one {
|
||||
return v, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Array queries and returns data values as slice from database.
|
||||
// Note that if there're multiple columns in the result, it returns just one column values randomly.
|
||||
//
|
||||
// If the optional parameter <fieldsAndWhere> is given, the fieldsAndWhere[0] is the selected fields
|
||||
// and fieldsAndWhere[1:] is treated as where condition fields.
|
||||
// Also see Model.Fields and Model.Where functions.
|
||||
func (m *Model) Array(fieldsAndWhere ...interface{}) ([]Value, error) {
|
||||
if len(fieldsAndWhere) > 0 {
|
||||
if len(fieldsAndWhere) > 2 {
|
||||
return m.Fields(gconv.String(fieldsAndWhere[0])).Where(fieldsAndWhere[1], fieldsAndWhere[2:]...).Array()
|
||||
} else if len(fieldsAndWhere) == 2 {
|
||||
return m.Fields(gconv.String(fieldsAndWhere[0])).Where(fieldsAndWhere[1]).Array()
|
||||
} else {
|
||||
return m.Fields(gconv.String(fieldsAndWhere[0])).Array()
|
||||
}
|
||||
}
|
||||
all, err := m.All()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return all.Array(), nil
|
||||
}
|
||||
|
||||
// Struct retrieves one record from table and converts it into given struct.
|
||||
// The parameter <pointer> should be type of *struct/**struct. If type **struct is given,
|
||||
// it can create the struct internally during converting.
|
||||
//
|
||||
// The optional parameter <where> is the same as the parameter of Model.Where function,
|
||||
// see Model.Where.
|
||||
//
|
||||
// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions
|
||||
// from table.
|
||||
//
|
||||
// Eg:
|
||||
// user := new(User)
|
||||
// err := db.Table("user").Where("id", 1).Struct(user)
|
||||
//
|
||||
// user := (*User)(nil)
|
||||
// err := db.Table("user").Where("id", 1).Struct(&user)
|
||||
func (m *Model) Struct(pointer interface{}, where ...interface{}) error {
|
||||
one, err := m.One(where...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(one) == 0 {
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
return one.Struct(pointer)
|
||||
}
|
||||
|
||||
// Structs retrieves records from table and converts them into given struct slice.
|
||||
// The parameter <pointer> should be type of *[]struct/*[]*struct. It can create and fill the struct
|
||||
// slice internally during converting.
|
||||
//
|
||||
// The optional parameter <where> is the same as the parameter of Model.Where function,
|
||||
// see Model.Where.
|
||||
//
|
||||
// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions
|
||||
// from table.
|
||||
//
|
||||
// Eg:
|
||||
// users := ([]User)(nil)
|
||||
// err := db.Table("user").Structs(&users)
|
||||
//
|
||||
// users := ([]*User)(nil)
|
||||
// err := db.Table("user").Structs(&users)
|
||||
func (m *Model) Structs(pointer interface{}, where ...interface{}) error {
|
||||
all, err := m.All(where...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(all) == 0 {
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
return all.Structs(pointer)
|
||||
}
|
||||
|
||||
// Scan automatically calls Struct or Structs function according to the type of parameter <pointer>.
|
||||
// It calls function Struct if <pointer> is type of *struct/**struct.
|
||||
// It calls function Structs if <pointer> is type of *[]struct/*[]*struct.
|
||||
//
|
||||
// The optional parameter <where> is the same as the parameter of Model.Where function,
|
||||
// see Model.Where.
|
||||
//
|
||||
// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions
|
||||
// from table.
|
||||
//
|
||||
// Eg:
|
||||
// user := new(User)
|
||||
// err := db.Table("user").Where("id", 1).Struct(user)
|
||||
//
|
||||
// user := (*User)(nil)
|
||||
// err := db.Table("user").Where("id", 1).Struct(&user)
|
||||
//
|
||||
// users := ([]User)(nil)
|
||||
// err := db.Table("user").Structs(&users)
|
||||
//
|
||||
// users := ([]*User)(nil)
|
||||
// err := db.Table("user").Structs(&users)
|
||||
func (m *Model) Scan(pointer interface{}, where ...interface{}) error {
|
||||
t := reflect.TypeOf(pointer)
|
||||
k := t.Kind()
|
||||
if k != reflect.Ptr {
|
||||
return fmt.Errorf("params should be type of pointer, but got: %v", k)
|
||||
}
|
||||
switch t.Elem().Kind() {
|
||||
case reflect.Array:
|
||||
case reflect.Slice:
|
||||
return m.Structs(pointer, where...)
|
||||
default:
|
||||
return m.Struct(pointer, where...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Count does "SELECT COUNT(x) FROM ..." statement for the model.
|
||||
// The optional parameter <where> is the same as the parameter of Model.Where function,
|
||||
// see Model.Where.
|
||||
func (m *Model) Count(where ...interface{}) (int, error) {
|
||||
if len(where) > 0 {
|
||||
return m.Where(where[0], where[1:]...).Count()
|
||||
}
|
||||
countFields := "COUNT(1)"
|
||||
if m.fields != "" && m.fields != "*" {
|
||||
countFields = fmt.Sprintf(`COUNT(%s)`, m.fields)
|
||||
}
|
||||
condition, conditionArgs := m.formatCondition(false)
|
||||
s := fmt.Sprintf("SELECT %s FROM %s %s", countFields, m.tables, condition)
|
||||
if len(m.groupBy) > 0 {
|
||||
s = fmt.Sprintf("SELECT COUNT(1) FROM (%s) count_alias", s)
|
||||
}
|
||||
list, err := m.getAll(s, conditionArgs...)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(list) > 0 {
|
||||
for _, v := range list[0] {
|
||||
return v.Int(), nil
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// FindOne retrieves and returns a single Record by Model.WherePri and Model.One.
|
||||
// Also see Model.WherePri and Model.One.
|
||||
func (m *Model) FindOne(where ...interface{}) (Record, error) {
|
||||
if len(where) > 0 {
|
||||
return m.WherePri(where[0], where[1:]...).One()
|
||||
}
|
||||
return m.One()
|
||||
}
|
||||
|
||||
// FindAll retrieves and returns Result by by Model.WherePri and Model.All.
|
||||
// Also see Model.WherePri and Model.All.
|
||||
func (m *Model) FindAll(where ...interface{}) (Result, error) {
|
||||
if len(where) > 0 {
|
||||
return m.WherePri(where[0], where[1:]...).All()
|
||||
}
|
||||
return m.All()
|
||||
}
|
||||
|
||||
// FindValue retrieves and returns single field value by Model.WherePri and Model.Value.
|
||||
// Also see Model.WherePri and Model.Value.
|
||||
func (m *Model) FindValue(fieldsAndWhere ...interface{}) (Value, error) {
|
||||
if len(fieldsAndWhere) >= 2 {
|
||||
return m.WherePri(fieldsAndWhere[1], fieldsAndWhere[2:]...).Fields(gconv.String(fieldsAndWhere[0])).Value()
|
||||
}
|
||||
if len(fieldsAndWhere) == 1 {
|
||||
return m.Fields(gconv.String(fieldsAndWhere[0])).Value()
|
||||
}
|
||||
return m.Value()
|
||||
}
|
||||
|
||||
// FindArray queries and returns data values as slice from database.
|
||||
// Note that if there're multiple columns in the result, it returns just one column values randomly.
|
||||
// Also see Model.WherePri and Model.Value.
|
||||
func (m *Model) FindArray(fieldsAndWhere ...interface{}) ([]Value, error) {
|
||||
if len(fieldsAndWhere) >= 2 {
|
||||
return m.WherePri(fieldsAndWhere[1], fieldsAndWhere[2:]...).Fields(gconv.String(fieldsAndWhere[0])).Array()
|
||||
}
|
||||
if len(fieldsAndWhere) == 1 {
|
||||
return m.Fields(gconv.String(fieldsAndWhere[0])).Array()
|
||||
}
|
||||
return m.Array()
|
||||
}
|
||||
|
||||
// FindCount retrieves and returns the record number by Model.WherePri and Model.Count.
|
||||
// Also see Model.WherePri and Model.Count.
|
||||
func (m *Model) FindCount(where ...interface{}) (int, error) {
|
||||
if len(where) > 0 {
|
||||
return m.WherePri(where[0], where[1:]...).Count()
|
||||
}
|
||||
return m.Count()
|
||||
}
|
||||
|
||||
// FindScan retrieves and returns the record/records by Model.WherePri and Model.Scan.
|
||||
// Also see Model.WherePri and Model.Scan.
|
||||
func (m *Model) FindScan(pointer interface{}, where ...interface{}) error {
|
||||
if len(where) > 0 {
|
||||
return m.WherePri(where[0], where[1:]...).Scan(pointer)
|
||||
}
|
||||
return m.Scan(pointer)
|
||||
}
|
||||
45
database/gdb/gdb_model_update.go
Normal file
45
database/gdb/gdb_model_update.go
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Update does "UPDATE ... " statement for the model.
|
||||
//
|
||||
// If the optional parameter <dataAndWhere> is given, the dataAndWhere[0] is the updated data field,
|
||||
// and dataAndWhere[1:] is treated as where condition fields.
|
||||
// Also see Model.Data and Model.Where functions.
|
||||
func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err error) {
|
||||
if len(dataAndWhere) > 0 {
|
||||
if len(dataAndWhere) > 2 {
|
||||
return m.Data(dataAndWhere[0]).Where(dataAndWhere[1], dataAndWhere[2:]...).Update()
|
||||
} else if len(dataAndWhere) == 2 {
|
||||
return m.Data(dataAndWhere[0]).Where(dataAndWhere[1]).Update()
|
||||
} else {
|
||||
return m.Data(dataAndWhere[0]).Update()
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
m.checkAndRemoveCache()
|
||||
}
|
||||
}()
|
||||
if m.data == nil {
|
||||
return nil, errors.New("updating table with empty data")
|
||||
}
|
||||
condition, conditionArgs := m.formatCondition(false)
|
||||
return m.db.DoUpdate(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(m.data),
|
||||
condition,
|
||||
m.mergeArguments(conditionArgs)...,
|
||||
)
|
||||
}
|
||||
205
database/gdb/gdb_model_utility.go
Normal file
205
database/gdb/gdb_model_utility.go
Normal file
@ -0,0 +1,205 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/gmap"
|
||||
"github.com/gogf/gf/container/gset"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
)
|
||||
|
||||
// getModel creates and returns a cloned model of current model if <safe> is true, or else it returns
|
||||
// the current model.
|
||||
func (m *Model) getModel() *Model {
|
||||
if !m.safe {
|
||||
return m
|
||||
} else {
|
||||
return m.Clone()
|
||||
}
|
||||
}
|
||||
|
||||
// filterDataForInsertOrUpdate does filter feature with data for inserting/updating operations.
|
||||
// Note that, it does not filter list item, which is also type of map, for "omit empty" feature.
|
||||
func (m *Model) filterDataForInsertOrUpdate(data interface{}) interface{} {
|
||||
if list, ok := m.data.(List); ok {
|
||||
for k, item := range list {
|
||||
list[k] = m.doFilterDataMapForInsertOrUpdate(item, false)
|
||||
}
|
||||
return list
|
||||
} else if item, ok := m.data.(Map); ok {
|
||||
return m.doFilterDataMapForInsertOrUpdate(item, true)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// doFilterDataMapForInsertOrUpdate does the filter features for map.
|
||||
// Note that, it does not filter list item, which is also type of map, for "omit empty" feature.
|
||||
func (m *Model) doFilterDataMapForInsertOrUpdate(data Map, allowOmitEmpty bool) Map {
|
||||
if m.filter {
|
||||
data = m.db.filterFields(m.schema, m.tables, data)
|
||||
}
|
||||
// Remove key-value pairs of which the value is empty.
|
||||
if allowOmitEmpty && m.option&OPTION_OMITEMPTY > 0 {
|
||||
m := gmap.NewStrAnyMapFrom(data)
|
||||
m.FilterEmpty()
|
||||
data = m.Map()
|
||||
}
|
||||
|
||||
if len(m.fields) > 0 && m.fields != "*" {
|
||||
// Keep specified fields.
|
||||
set := gset.NewStrSetFrom(gstr.SplitAndTrim(m.fields, ","))
|
||||
for k := range data {
|
||||
if !set.Contains(k) {
|
||||
delete(data, k)
|
||||
}
|
||||
}
|
||||
} else if len(m.fieldsEx) > 0 {
|
||||
// Filter specified fields.
|
||||
for _, v := range gstr.SplitAndTrim(m.fieldsEx, ",") {
|
||||
delete(data, v)
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// getLink returns the underlying database link object with configured <linkType> attribute.
|
||||
// The parameter <master> specifies whether using the master node if master-slave configured.
|
||||
func (m *Model) getLink(master bool) Link {
|
||||
if m.tx != nil {
|
||||
return m.tx.tx
|
||||
}
|
||||
linkType := m.linkType
|
||||
if linkType == 0 {
|
||||
if master {
|
||||
linkType = gLINK_TYPE_MASTER
|
||||
} else {
|
||||
linkType = gLINK_TYPE_SLAVE
|
||||
}
|
||||
}
|
||||
switch linkType {
|
||||
case gLINK_TYPE_MASTER:
|
||||
link, err := m.db.GetMaster(m.schema)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return link
|
||||
case gLINK_TYPE_SLAVE:
|
||||
link, err := m.db.GetSlave(m.schema)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return link
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getPrimaryKey retrieves and returns the primary key name of the model table.
|
||||
// It parses m.tables to retrieve the primary table name, supporting m.tables like:
|
||||
// "user", "user u", "user as u, user_detail as ud".
|
||||
func (m *Model) getPrimaryKey() string {
|
||||
table := gstr.SplitAndTrim(m.tables, " ")[0]
|
||||
tableFields, err := m.db.TableFields(table)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
for name, field := range tableFields {
|
||||
if gstr.ContainsI(field.Key, "pri") {
|
||||
return name
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// checkAndRemoveCache checks and remove the cache if necessary.
|
||||
func (m *Model) checkAndRemoveCache() {
|
||||
if m.cacheEnabled && m.cacheDuration < 0 && len(m.cacheName) > 0 {
|
||||
m.db.GetCache().Remove(m.cacheName)
|
||||
}
|
||||
}
|
||||
|
||||
// formatCondition formats where arguments of the model and returns a new condition sql and its arguments.
|
||||
// Note that this function does not change any attribute value of the <m>.
|
||||
//
|
||||
// The parameter <limit> specifies whether limits querying only one record if m.limit is not set.
|
||||
func (m *Model) formatCondition(limit bool) (condition string, conditionArgs []interface{}) {
|
||||
var where string
|
||||
if len(m.whereHolder) > 0 {
|
||||
for _, v := range m.whereHolder {
|
||||
switch v.operator {
|
||||
case gWHERE_HOLDER_WHERE:
|
||||
if where == "" {
|
||||
newWhere, newArgs := formatWhere(m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0)
|
||||
if len(newWhere) > 0 {
|
||||
where = newWhere
|
||||
conditionArgs = newArgs
|
||||
}
|
||||
continue
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case gWHERE_HOLDER_AND:
|
||||
newWhere, newArgs := formatWhere(m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0)
|
||||
if len(newWhere) > 0 {
|
||||
if where[0] == '(' {
|
||||
where = fmt.Sprintf(`%s AND (%s)`, where, newWhere)
|
||||
} else {
|
||||
where = fmt.Sprintf(`(%s) AND (%s)`, where, newWhere)
|
||||
}
|
||||
conditionArgs = append(conditionArgs, newArgs...)
|
||||
}
|
||||
|
||||
case gWHERE_HOLDER_OR:
|
||||
newWhere, newArgs := formatWhere(m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0)
|
||||
if len(newWhere) > 0 {
|
||||
if where[0] == '(' {
|
||||
where = fmt.Sprintf(`%s OR (%s)`, where, newWhere)
|
||||
} else {
|
||||
where = fmt.Sprintf(`(%s) OR (%s)`, where, newWhere)
|
||||
}
|
||||
conditionArgs = append(conditionArgs, newArgs...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if where != "" {
|
||||
condition += " WHERE " + where
|
||||
}
|
||||
if m.groupBy != "" {
|
||||
condition += " GROUP BY " + m.groupBy
|
||||
}
|
||||
if m.orderBy != "" {
|
||||
condition += " ORDER BY " + m.orderBy
|
||||
}
|
||||
if m.limit != 0 {
|
||||
if m.start >= 0 {
|
||||
condition += fmt.Sprintf(" LIMIT %d,%d", m.start, m.limit)
|
||||
} else {
|
||||
condition += fmt.Sprintf(" LIMIT %d", m.limit)
|
||||
}
|
||||
} else if limit {
|
||||
condition += " LIMIT 1"
|
||||
}
|
||||
if m.offset >= 0 {
|
||||
condition += fmt.Sprintf(" OFFSET %d", m.offset)
|
||||
}
|
||||
if m.lockInfo != "" {
|
||||
condition += " " + m.lockInfo
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// mergeArguments creates and returns new arguments by merging <m.extraArgs> and given <args>.
|
||||
func (m *Model) mergeArguments(args []interface{}) []interface{} {
|
||||
if len(m.extraArgs) > 0 {
|
||||
newArgs := make([]interface{}, len(m.extraArgs)+len(args))
|
||||
copy(newArgs, m.extraArgs)
|
||||
copy(newArgs[len(m.extraArgs):], args)
|
||||
return newArgs
|
||||
}
|
||||
return args
|
||||
}
|
||||
47
database/gdb/gdb_result.go
Normal file
47
database/gdb/gdb_result.go
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import "database/sql"
|
||||
|
||||
// SqlResult is execution result for sql operations.
|
||||
// It also supports batch operation result for rowsAffected.
|
||||
type SqlResult struct {
|
||||
result sql.Result
|
||||
affected int64
|
||||
}
|
||||
|
||||
// MustGetAffected returns the affected rows count, if any error occurs, it panics.
|
||||
func (r *SqlResult) MustGetAffected() int64 {
|
||||
rows, err := r.RowsAffected()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
// MustGetInsertId returns the last insert id, if any error occurs, it panics.
|
||||
func (r *SqlResult) MustGetInsertId() int64 {
|
||||
id, err := r.LastInsertId()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// see sql.Result.RowsAffected
|
||||
func (r *SqlResult) RowsAffected() (int64, error) {
|
||||
if r.affected > 0 {
|
||||
return r.affected, nil
|
||||
}
|
||||
return r.result.RowsAffected()
|
||||
}
|
||||
|
||||
// see sql.Result.LastInsertId
|
||||
func (r *SqlResult) LastInsertId() (int64, error) {
|
||||
return r.result.LastInsertId()
|
||||
}
|
||||
@ -936,6 +936,18 @@ func Test_Model_GroupBy(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Data(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.Case(t, func() {
|
||||
result, err := db.Table(table).Data("nickname=?", "test").Where("id=?", 3).Update()
|
||||
gtest.Assert(err, nil)
|
||||
n, _ := result.RowsAffected()
|
||||
gtest.Assert(n, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Where(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
@ -1179,6 +1191,44 @@ func Test_Model_Where(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Where_ISNULL_1(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.Case(t, func() {
|
||||
//db.SetDebug(true)
|
||||
result, err := db.Table(table).Data("nickname", nil).Where("id", 2).Update()
|
||||
gtest.Assert(err, nil)
|
||||
n, _ := result.RowsAffected()
|
||||
gtest.Assert(n, 1)
|
||||
|
||||
one, err := db.Table(table).Where("nickname", nil).One()
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(one.IsEmpty(), false)
|
||||
gtest.Assert(one["id"], 2)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Where_ISNULL_2(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
// complicated one.
|
||||
gtest.Case(t, func() {
|
||||
//db.SetDebug(true)
|
||||
conditions := g.Map{
|
||||
"nickname like ?": "%name%",
|
||||
"id between ? and ?": g.Slice{1, 3},
|
||||
"id > 0": nil,
|
||||
"create_time > 0": nil,
|
||||
"id": g.Slice{1, 2, 3},
|
||||
}
|
||||
result, err := db.Table(table).WherePri(conditions).Order("id asc").All()
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(len(result), 3)
|
||||
gtest.Assert(result[0]["id"].Int(), 1)
|
||||
})
|
||||
}
|
||||
func Test_Model_WherePri(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
Reference in New Issue
Block a user