mirror of
https://gitee.com/johng/gf
synced 2026-06-18 22:35:54 +08:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4cd7e4e5a0 | |||
| e6688b9e86 | |||
| 65131c6f22 | |||
| 7667aca4c2 | |||
| 816791b9c1 | |||
| d6ea2220f7 | |||
| 69dd5db774 | |||
| adca9222ab | |||
| 7144aa6999 | |||
| fbad5f60eb | |||
| 266f592739 | |||
| 7c8bbcb3af | |||
| ba18e2bf6b | |||
| 5fefe97b87 | |||
| 5fba250a14 | |||
| 2606ad83ac | |||
| d450de8e0d | |||
| e4b0de0d4f | |||
| 2af4fd86cc | |||
| ddd171bc18 | |||
| 2679f92aa8 | |||
| cca438d77f | |||
| f2bc29e5c1 | |||
| fe7209e76d | |||
| 7c4a0453b7 | |||
| 97879834bc | |||
| 332535901f | |||
| e68e7a3224 | |||
| 65befd5ac4 | |||
| 78bdb5ef71 | |||
| 6eb7261dfd | |||
| 4f82be5bc0 | |||
| 3ac5772059 | |||
| eb723e47c2 | |||
| 8aa7f08350 | |||
| a54559d016 | |||
| 8e1f6abac5 |
@ -1,12 +1,7 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). 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 driver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"github.com/gogf/gf/database/gdb"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
@ -47,9 +42,9 @@ func (d *MyDriver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) {
|
||||
|
||||
// DoQuery commits the sql string and its arguments to underlying driver
|
||||
// through given link object and returns the execution result.
|
||||
func (d *MyDriver) DoQuery(link gdb.Link, sql string, args ...interface{}) (rows *sql.Rows, err error) {
|
||||
func (d *MyDriver) DoQuery(ctx context.Context, link gdb.Link, sql string, args ...interface{}) (rows *sql.Rows, err error) {
|
||||
tsMilli := gtime.TimestampMilli()
|
||||
rows, err = d.DriverMysql.DoQuery(link, sql, args...)
|
||||
rows, err = d.DriverMysql.DoQuery(ctx, link, sql, args...)
|
||||
link.Exec(
|
||||
"INSERT INTO `monitor`(`sql`,`cost`,`time`,`error`) VALUES(?,?,?,?)",
|
||||
gdb.FormatSqlWithArgs(sql, args),
|
||||
@ -62,9 +57,9 @@ func (d *MyDriver) DoQuery(link gdb.Link, sql string, args ...interface{}) (rows
|
||||
|
||||
// DoExec commits the query string and its arguments to underlying driver
|
||||
// through given link object and returns the execution result.
|
||||
func (d *MyDriver) DoExec(link gdb.Link, sql string, args ...interface{}) (result sql.Result, err error) {
|
||||
func (d *MyDriver) DoExec(ctx context.Context, link gdb.Link, sql string, args ...interface{}) (result sql.Result, err error) {
|
||||
tsMilli := gtime.TimestampMilli()
|
||||
result, err = d.DriverMysql.DoExec(link, sql, args...)
|
||||
result, err = d.DriverMysql.DoExec(ctx, link, sql, args...)
|
||||
link.Exec(
|
||||
"INSERT INTO `monitor`(`sql`,`cost`,`time`,`error`) VALUES(?,?,?,?)",
|
||||
gdb.FormatSqlWithArgs(sql, args),
|
||||
|
||||
@ -10,9 +10,9 @@ func main() {
|
||||
//db := g.DB()
|
||||
|
||||
gdb.AddDefaultConfigNode(gdb.ConfigNode{
|
||||
LinkInfo: "root:12345678@tcp(127.0.0.1:3306)/test?parseTime=true&loc=Local",
|
||||
Type: "mysql",
|
||||
Charset: "utf8",
|
||||
Link: "root:12345678@tcp(127.0.0.1:3306)/test?parseTime=true&loc=Local",
|
||||
Type: "mysql",
|
||||
Charset: "utf8",
|
||||
})
|
||||
db, _ := gdb.New()
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ type StrSet struct {
|
||||
data map[string]struct{}
|
||||
}
|
||||
|
||||
// New create and returns a new set, which contains un-repeated items.
|
||||
// NewStrSet create and returns a new set, which contains un-repeated items.
|
||||
// The parameter <safe> is used to specify whether using set in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewStrSet(safe ...bool) *StrSet {
|
||||
|
||||
@ -38,6 +38,7 @@ type DB interface {
|
||||
// relational databases but also for NoSQL databases in the future. The name
|
||||
// "Table" is not proper for that purpose any more.
|
||||
// Also see Core.Table.
|
||||
// Deprecated.
|
||||
Table(tableNameOrStruct ...interface{}) *Model
|
||||
|
||||
// Model creates and returns a new ORM model from given schema.
|
||||
@ -51,6 +52,9 @@ type DB interface {
|
||||
// Also see Core.Model.
|
||||
Model(tableNameOrStruct ...interface{}) *Model
|
||||
|
||||
// Raw creates and returns a model based on a raw sql not a table.
|
||||
Raw(rawSql string, args ...interface{}) *Model
|
||||
|
||||
// Schema creates and returns a schema.
|
||||
// Also see Core.Schema.
|
||||
Schema(schema string) *Schema
|
||||
@ -83,19 +87,27 @@ type DB interface {
|
||||
// Common APIs for CURD.
|
||||
// ===========================================================================
|
||||
|
||||
Insert(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.Insert.
|
||||
InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.InsertIgnore.
|
||||
InsertAndGetId(table string, data interface{}, batch ...int) (int64, error) // See Core.InsertAndGetId.
|
||||
Replace(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.Replace.
|
||||
Save(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.Save.
|
||||
|
||||
BatchInsert(table string, list interface{}, batch ...int) (sql.Result, error) // See Core.BatchInsert.
|
||||
BatchReplace(table string, list interface{}, batch ...int) (sql.Result, error) // See Core.BatchReplace.
|
||||
BatchSave(table string, list interface{}, batch ...int) (sql.Result, error) // See Core.BatchSave.
|
||||
|
||||
Insert(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.Insert.
|
||||
InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.InsertIgnore.
|
||||
InsertAndGetId(table string, data interface{}, batch ...int) (int64, error) // See Core.InsertAndGetId.
|
||||
Replace(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.Replace.
|
||||
Save(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.Save.
|
||||
Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) // See Core.Update.
|
||||
Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) // See Core.Delete.
|
||||
|
||||
// ===========================================================================
|
||||
// Internal APIs for CURD, which can be overwrote for custom CURD implements.
|
||||
// ===========================================================================
|
||||
|
||||
DoGetAll(ctx context.Context, link Link, sql string, args ...interface{}) (result Result, err error) // See Core.DoGetAll.
|
||||
DoInsert(ctx context.Context, link Link, table string, data List, option DoInsertOption) (result sql.Result, err error) // See Core.DoInsert.
|
||||
DoUpdate(ctx context.Context, link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) // See Core.DoUpdate.
|
||||
DoDelete(ctx context.Context, link Link, table string, condition string, args ...interface{}) (result sql.Result, err error) // See Core.DoDelete.
|
||||
DoQuery(ctx context.Context, link Link, sql string, args ...interface{}) (rows *sql.Rows, err error) // See Core.DoQuery.
|
||||
DoExec(ctx context.Context, link Link, sql string, args ...interface{}) (result sql.Result, err error) // See Core.DoExec.
|
||||
DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}) // See Core.DoCommit.
|
||||
DoPrepare(ctx context.Context, link Link, sql string) (*Stmt, error) // See Core.DoPrepare.
|
||||
|
||||
// ===========================================================================
|
||||
// Query APIs for convenience purpose.
|
||||
// ===========================================================================
|
||||
@ -108,6 +120,8 @@ type DB interface {
|
||||
GetStruct(objPointer interface{}, sql string, args ...interface{}) error // See Core.GetStruct.
|
||||
GetStructs(objPointerSlice interface{}, sql string, args ...interface{}) error // See Core.GetStructs.
|
||||
GetScan(objPointer interface{}, sql string, args ...interface{}) error // See Core.GetScan.
|
||||
Union(unions ...*Model) *Model // See Core.Union.
|
||||
UnionAll(unions ...*Model) *Model // See Core.UnionAll.
|
||||
|
||||
// ===========================================================================
|
||||
// Master/Slave specification support.
|
||||
@ -159,14 +173,7 @@ type DB interface {
|
||||
GetChars() (charLeft string, charRight string) // See Core.GetChars.
|
||||
Tables(ctx context.Context, schema ...string) (tables []string, err error) // See Core.Tables.
|
||||
TableFields(ctx context.Context, table string, schema ...string) (map[string]*TableField, error) // See Core.TableFields.
|
||||
FilteredLinkInfo() string // See Core.FilteredLinkInfo.
|
||||
|
||||
// HandleSqlBeforeCommit is a hook function, which deals with the sql string before
|
||||
// it's committed to underlying driver. The parameter `link` specifies the current
|
||||
// database connection operation object. You can modify the sql string `sql` and its
|
||||
// arguments `args` as you wish before they're committed to driver.
|
||||
// Also see Core.HandleSqlBeforeCommit.
|
||||
HandleSqlBeforeCommit(ctx context.Context, link Link, sql string, args []interface{}) (string, []interface{})
|
||||
FilteredLink() string
|
||||
}
|
||||
|
||||
// Core is the base struct for database management.
|
||||
@ -211,6 +218,14 @@ type Sql struct {
|
||||
IsTransaction bool // IsTransaction marks whether this sql is executed in transaction.
|
||||
}
|
||||
|
||||
// DoInsertOption is the input struct for function DoInsert.
|
||||
type DoInsertOption struct {
|
||||
OnDuplicateStr string
|
||||
OnDuplicateMap map[string]interface{}
|
||||
InsertOption int // Insert operation.
|
||||
BatchCount int // Batch count for batch inserting.
|
||||
}
|
||||
|
||||
// TableField is the struct for table field.
|
||||
type TableField struct {
|
||||
Index int // For ordering purpose as map is unordered.
|
||||
@ -239,6 +254,10 @@ type (
|
||||
)
|
||||
|
||||
const (
|
||||
queryTypeNormal = 0
|
||||
queryTypeCount = 1
|
||||
unionTypeNormal = 0
|
||||
unionTypeAll = 1
|
||||
insertOptionDefault = 0
|
||||
insertOptionReplace = 1
|
||||
insertOptionSave = 2
|
||||
|
||||
@ -119,12 +119,12 @@ func (c *Core) Slave(schema ...string) (*sql.DB, error) {
|
||||
|
||||
// GetAll queries and returns data records from database.
|
||||
func (c *Core) GetAll(sql string, args ...interface{}) (Result, error) {
|
||||
return c.DoGetAll(c.GetCtx(), nil, sql, args...)
|
||||
return c.db.DoGetAll(c.GetCtx(), nil, sql, args...)
|
||||
}
|
||||
|
||||
// DoGetAll queries and returns data records from database.
|
||||
func (c *Core) DoGetAll(ctx context.Context, link Link, sql string, args ...interface{}) (result Result, err error) {
|
||||
rows, err := c.DoQuery(ctx, link, sql, args...)
|
||||
rows, err := c.db.DoQuery(ctx, link, sql, args...)
|
||||
if err != nil || rows == nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -147,7 +147,7 @@ func (c *Core) GetOne(sql string, args ...interface{}) (Record, error) {
|
||||
// GetArray queries and returns data values as slice from database.
|
||||
// Note that if there are multiple columns in the result, it returns just one column values randomly.
|
||||
func (c *Core) GetArray(sql string, args ...interface{}) ([]Value, error) {
|
||||
all, err := c.DoGetAll(c.GetCtx(), nil, sql, args...)
|
||||
all, err := c.db.DoGetAll(c.GetCtx(), nil, sql, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -224,6 +224,39 @@ func (c *Core) GetCount(sql string, args ...interface{}) (int, error) {
|
||||
return value.Int(), nil
|
||||
}
|
||||
|
||||
// Union does "(SELECT xxx FROM xxx) UNION (SELECT xxx FROM xxx) ..." statement.
|
||||
func (c *Core) Union(unions ...*Model) *Model {
|
||||
return c.doUnion(unionTypeNormal, unions...)
|
||||
}
|
||||
|
||||
// UnionAll does "(SELECT xxx FROM xxx) UNION ALL (SELECT xxx FROM xxx) ..." statement.
|
||||
func (c *Core) UnionAll(unions ...*Model) *Model {
|
||||
return c.doUnion(unionTypeAll, unions...)
|
||||
}
|
||||
|
||||
func (c *Core) doUnion(unionType int, unions ...*Model) *Model {
|
||||
var (
|
||||
unionTypeStr string
|
||||
composedSqlStr string
|
||||
composedArgs = make([]interface{}, 0)
|
||||
)
|
||||
if unionType == unionTypeAll {
|
||||
unionTypeStr = "UNION ALL"
|
||||
} else {
|
||||
unionTypeStr = "UNION"
|
||||
}
|
||||
for _, v := range unions {
|
||||
sqlWithHolder, holderArgs := v.getFormattedSqlAndArgs(queryTypeNormal, false)
|
||||
if composedSqlStr == "" {
|
||||
composedSqlStr += fmt.Sprintf(`(%s)`, sqlWithHolder)
|
||||
} else {
|
||||
composedSqlStr += fmt.Sprintf(` %s (%s)`, unionTypeStr, sqlWithHolder)
|
||||
}
|
||||
composedArgs = append(composedArgs, holderArgs...)
|
||||
}
|
||||
return c.db.Raw(composedSqlStr, composedArgs...)
|
||||
}
|
||||
|
||||
// PingMaster pings the master node to check authentication or keeps the connection alive.
|
||||
func (c *Core) PingMaster() error {
|
||||
if master, err := c.db.Master(); err != nil {
|
||||
@ -331,181 +364,15 @@ func (c *Core) Save(table string, data interface{}, batch ...int) (sql.Result, e
|
||||
// 1: replace: if there's unique/primary key in the data, it deletes it from table and inserts a new one;
|
||||
// 2: save: if there's unique/primary key in the data, it updates it or else inserts a new one;
|
||||
// 3: ignore: if there's unique/primary key in the data, it ignores the inserting;
|
||||
func (c *Core) DoInsert(ctx context.Context, link Link, table string, data interface{}, option int, batch ...int) (result sql.Result, err error) {
|
||||
table = c.QuotePrefixTableName(table)
|
||||
func (c *Core) DoInsert(ctx context.Context, link Link, table string, list List, option DoInsertOption) (result sql.Result, err error) {
|
||||
var (
|
||||
fields []string
|
||||
values []string
|
||||
params []interface{}
|
||||
dataMap Map
|
||||
reflectValue = reflect.ValueOf(data)
|
||||
reflectKind = reflectValue.Kind()
|
||||
keys []string // Field names.
|
||||
values []string // Value holder string array, like: (?,?,?)
|
||||
params []interface{} // Values that will be committed to underlying database driver.
|
||||
onDuplicateStr string // onDuplicateStr is used in "ON DUPLICATE KEY UPDATE" statement.
|
||||
)
|
||||
if reflectKind == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
switch reflectKind {
|
||||
case reflect.Slice, reflect.Array:
|
||||
return c.DoBatchInsert(ctx, link, table, data, option, batch...)
|
||||
case reflect.Struct:
|
||||
if _, ok := data.(apiInterfaces); ok {
|
||||
return c.DoBatchInsert(ctx, link, table, data, option, batch...)
|
||||
} else {
|
||||
dataMap = ConvertDataForTableRecord(data)
|
||||
}
|
||||
case reflect.Map:
|
||||
dataMap = ConvertDataForTableRecord(data)
|
||||
default:
|
||||
return result, gerror.New(fmt.Sprint("unsupported data type:", reflectKind))
|
||||
}
|
||||
if len(dataMap) == 0 {
|
||||
return nil, gerror.New("data cannot be empty")
|
||||
}
|
||||
var (
|
||||
charL, charR = c.db.GetChars()
|
||||
operation = GetInsertOperationByOption(option)
|
||||
updateStr = ""
|
||||
)
|
||||
for k, v := range dataMap {
|
||||
fields = append(fields, charL+k+charR)
|
||||
if s, ok := v.(Raw); ok {
|
||||
values = append(values, gconv.String(s))
|
||||
} else {
|
||||
values = append(values, "?")
|
||||
params = append(params, v)
|
||||
}
|
||||
}
|
||||
if option == insertOptionSave {
|
||||
for k, _ := range dataMap {
|
||||
// If it's SAVE operation,
|
||||
// do not automatically update the creating time.
|
||||
if c.isSoftCreatedFiledName(k) {
|
||||
continue
|
||||
}
|
||||
if len(updateStr) > 0 {
|
||||
updateStr += ","
|
||||
}
|
||||
updateStr += fmt.Sprintf(
|
||||
"%s%s%s=VALUES(%s%s%s)",
|
||||
charL, k, charR,
|
||||
charL, k, charR,
|
||||
)
|
||||
}
|
||||
updateStr = fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", updateStr)
|
||||
}
|
||||
if link == nil {
|
||||
if link, err = c.MasterLink(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return c.DoExec(ctx, link, fmt.Sprintf(
|
||||
"%s INTO %s(%s) VALUES(%s) %s",
|
||||
operation, table, strings.Join(fields, ","),
|
||||
strings.Join(values, ","), updateStr,
|
||||
), params...)
|
||||
}
|
||||
|
||||
// BatchInsert batch inserts data.
|
||||
// The parameter `list` must be type of slice of map or struct.
|
||||
func (c *Core) BatchInsert(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(list).Batch(batch[0]).Insert()
|
||||
}
|
||||
return c.Model(table).Data(list).Insert()
|
||||
}
|
||||
|
||||
// BatchInsertIgnore batch inserts data with ignore option.
|
||||
// The parameter `list` must be type of slice of map or struct.
|
||||
func (c *Core) BatchInsertIgnore(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(list).Batch(batch[0]).InsertIgnore()
|
||||
}
|
||||
return c.Model(table).Data(list).InsertIgnore()
|
||||
}
|
||||
|
||||
// BatchReplace batch replaces data.
|
||||
// The parameter `list` must be type of slice of map or struct.
|
||||
func (c *Core) BatchReplace(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(list).Batch(batch[0]).Replace()
|
||||
}
|
||||
return c.Model(table).Data(list).Replace()
|
||||
}
|
||||
|
||||
// BatchSave batch replaces data.
|
||||
// The parameter `list` must be type of slice of map or struct.
|
||||
func (c *Core) BatchSave(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(list).Batch(batch[0]).Save()
|
||||
}
|
||||
return c.Model(table).Data(list).Save()
|
||||
}
|
||||
|
||||
// DoBatchInsert batch inserts/replaces/saves data.
|
||||
// This function is usually used for custom interface definition, you do not need call it manually.
|
||||
func (c *Core) DoBatchInsert(ctx context.Context, link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) {
|
||||
table = c.QuotePrefixTableName(table)
|
||||
var (
|
||||
keys []string // Field names.
|
||||
values []string // Value holder string array, like: (?,?,?)
|
||||
params []interface{} // Values that will be committed to underlying database driver.
|
||||
listMap List // The data list that passed from caller.
|
||||
)
|
||||
switch value := list.(type) {
|
||||
case Result:
|
||||
listMap = value.List()
|
||||
case Record:
|
||||
listMap = List{value.Map()}
|
||||
case List:
|
||||
listMap = value
|
||||
case Map:
|
||||
listMap = List{value}
|
||||
default:
|
||||
var (
|
||||
rv = reflect.ValueOf(list)
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
switch kind {
|
||||
// If it's slice type, it then converts it to List type.
|
||||
case reflect.Slice, reflect.Array:
|
||||
listMap = make(List, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
listMap[i] = ConvertDataForTableRecord(rv.Index(i).Interface())
|
||||
}
|
||||
case reflect.Map:
|
||||
listMap = List{ConvertDataForTableRecord(value)}
|
||||
case reflect.Struct:
|
||||
if v, ok := value.(apiInterfaces); ok {
|
||||
var (
|
||||
array = v.Interfaces()
|
||||
list = make(List, len(array))
|
||||
)
|
||||
for i := 0; i < len(array); i++ {
|
||||
list[i] = ConvertDataForTableRecord(array[i])
|
||||
}
|
||||
listMap = list
|
||||
} else {
|
||||
listMap = List{ConvertDataForTableRecord(value)}
|
||||
}
|
||||
default:
|
||||
return result, gerror.New(fmt.Sprint("unsupported list type:", kind))
|
||||
}
|
||||
}
|
||||
if len(listMap) < 1 {
|
||||
return result, gerror.New("data list cannot be empty")
|
||||
}
|
||||
if link == nil {
|
||||
if link, err = c.MasterLink(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Handle the field names and place holders.
|
||||
for k, _ := range listMap[0] {
|
||||
for k, _ := range list[0] {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
// Prepare the batch result pointer.
|
||||
@ -513,54 +380,35 @@ func (c *Core) DoBatchInsert(ctx context.Context, link Link, table string, list
|
||||
charL, charR = c.db.GetChars()
|
||||
batchResult = new(SqlResult)
|
||||
keysStr = charL + strings.Join(keys, charR+","+charL) + charR
|
||||
operation = GetInsertOperationByOption(option)
|
||||
updateStr = ""
|
||||
operation = GetInsertOperationByOption(option.InsertOption)
|
||||
)
|
||||
if option == insertOptionSave {
|
||||
for _, k := range keys {
|
||||
// If it's SAVE operation,
|
||||
// do not automatically update the creating time.
|
||||
if c.isSoftCreatedFiledName(k) {
|
||||
continue
|
||||
}
|
||||
if len(updateStr) > 0 {
|
||||
updateStr += ","
|
||||
}
|
||||
updateStr += fmt.Sprintf(
|
||||
"%s%s%s=VALUES(%s%s%s)",
|
||||
charL, k, charR,
|
||||
charL, k, charR,
|
||||
)
|
||||
}
|
||||
updateStr = fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", updateStr)
|
||||
}
|
||||
batchNum := defaultBatchNumber
|
||||
if len(batch) > 0 && batch[0] > 0 {
|
||||
batchNum = batch[0]
|
||||
if option.InsertOption == insertOptionSave {
|
||||
onDuplicateStr = c.formatOnDuplicate(keys, option)
|
||||
}
|
||||
var (
|
||||
listMapLen = len(listMap)
|
||||
listLength = len(list)
|
||||
valueHolder = make([]string, 0)
|
||||
)
|
||||
for i := 0; i < listMapLen; i++ {
|
||||
for i := 0; i < listLength; i++ {
|
||||
values = values[:0]
|
||||
// Note that the map type is unordered,
|
||||
// so it should use slice+key to retrieve the value.
|
||||
for _, k := range keys {
|
||||
if s, ok := listMap[i][k].(Raw); ok {
|
||||
if s, ok := list[i][k].(Raw); ok {
|
||||
values = append(values, gconv.String(s))
|
||||
} else {
|
||||
values = append(values, "?")
|
||||
params = append(params, listMap[i][k])
|
||||
params = append(params, list[i][k])
|
||||
}
|
||||
}
|
||||
valueHolder = append(valueHolder, "("+gstr.Join(values, ",")+")")
|
||||
if len(valueHolder) == batchNum || (i == listMapLen-1 && len(valueHolder) > 0) {
|
||||
r, err := c.DoExec(ctx, link, fmt.Sprintf(
|
||||
// Batch package checks: It meets the batch number or it is the last element.
|
||||
if len(valueHolder) == option.BatchCount || (i == listLength-1 && len(valueHolder) > 0) {
|
||||
r, err := c.db.DoExec(ctx, link, fmt.Sprintf(
|
||||
"%s INTO %s(%s) VALUES%s %s",
|
||||
operation, table, keysStr,
|
||||
operation, c.QuotePrefixTableName(table), keysStr,
|
||||
gstr.Join(valueHolder, ","),
|
||||
updateStr,
|
||||
onDuplicateStr,
|
||||
), params...)
|
||||
if err != nil {
|
||||
return r, err
|
||||
@ -578,6 +426,52 @@ func (c *Core) DoBatchInsert(ctx context.Context, link Link, table string, list
|
||||
return batchResult, nil
|
||||
}
|
||||
|
||||
func (c *Core) formatOnDuplicate(columns []string, option DoInsertOption) string {
|
||||
var (
|
||||
onDuplicateStr string
|
||||
)
|
||||
if option.OnDuplicateStr != "" {
|
||||
onDuplicateStr = option.OnDuplicateStr
|
||||
} else if len(option.OnDuplicateMap) > 0 {
|
||||
for k, v := range option.OnDuplicateMap {
|
||||
if len(onDuplicateStr) > 0 {
|
||||
onDuplicateStr += ","
|
||||
}
|
||||
switch v.(type) {
|
||||
case Raw, *Raw:
|
||||
onDuplicateStr += fmt.Sprintf(
|
||||
"%s=%s",
|
||||
c.QuoteWord(k),
|
||||
v,
|
||||
)
|
||||
default:
|
||||
onDuplicateStr += fmt.Sprintf(
|
||||
"%s=VALUES(%s)",
|
||||
c.QuoteWord(k),
|
||||
c.QuoteWord(gconv.String(v)),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, column := range columns {
|
||||
// If it's SAVE operation,
|
||||
// do not automatically update the creating time.
|
||||
if c.isSoftCreatedFilledName(column) {
|
||||
continue
|
||||
}
|
||||
if len(onDuplicateStr) > 0 {
|
||||
onDuplicateStr += ","
|
||||
}
|
||||
onDuplicateStr += fmt.Sprintf(
|
||||
"%s=VALUES(%s)",
|
||||
c.QuoteWord(column),
|
||||
c.QuoteWord(column),
|
||||
)
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", onDuplicateStr)
|
||||
}
|
||||
|
||||
// Update does "UPDATE ... " statement for the table.
|
||||
//
|
||||
// The parameter `data` can be type of string/map/gmap/struct/*struct, etc.
|
||||
@ -663,7 +557,7 @@ func (c *Core) DoUpdate(ctx context.Context, link Link, table string, data inter
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return c.DoExec(ctx, link, fmt.Sprintf("UPDATE %s SET %s%s", table, updates, condition), args...)
|
||||
return c.db.DoExec(ctx, link, fmt.Sprintf("UPDATE %s SET %s%s", table, updates, condition), args...)
|
||||
}
|
||||
|
||||
// Delete does "DELETE FROM ... " statement for the table.
|
||||
@ -690,7 +584,7 @@ func (c *Core) DoDelete(ctx context.Context, link Link, table string, condition
|
||||
}
|
||||
}
|
||||
table = c.QuotePrefixTableName(table)
|
||||
return c.DoExec(ctx, link, fmt.Sprintf("DELETE FROM %s%s", table, condition), args...)
|
||||
return c.db.DoExec(ctx, link, fmt.Sprintf("DELETE FROM %s%s", table, condition), args...)
|
||||
}
|
||||
|
||||
// convertRowsToResult converts underlying data record type sql.Rows to Result type.
|
||||
@ -711,7 +605,7 @@ func (c *Core) convertRowsToResult(rows *sql.Rows) (Result, error) {
|
||||
}
|
||||
var (
|
||||
values = make([]interface{}, len(columnNames))
|
||||
records = make(Result, 0)
|
||||
result = make(Result, 0)
|
||||
scanArgs = make([]interface{}, len(values))
|
||||
)
|
||||
for i := range values {
|
||||
@ -719,22 +613,22 @@ func (c *Core) convertRowsToResult(rows *sql.Rows) (Result, error) {
|
||||
}
|
||||
for {
|
||||
if err := rows.Scan(scanArgs...); err != nil {
|
||||
return records, err
|
||||
return result, err
|
||||
}
|
||||
row := make(Record)
|
||||
record := Record{}
|
||||
for i, value := range values {
|
||||
if value == nil {
|
||||
row[columnNames[i]] = gvar.New(nil)
|
||||
record[columnNames[i]] = gvar.New(nil)
|
||||
} else {
|
||||
row[columnNames[i]] = gvar.New(c.convertFieldValueToLocalValue(value, columnTypes[i]))
|
||||
record[columnNames[i]] = gvar.New(c.convertFieldValueToLocalValue(value, columnTypes[i]))
|
||||
}
|
||||
}
|
||||
records = append(records, row)
|
||||
result = append(result, record)
|
||||
if !rows.Next() {
|
||||
break
|
||||
}
|
||||
}
|
||||
return records, nil
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
@ -778,8 +672,8 @@ func (c *Core) HasTable(name string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// isSoftCreatedFiledName checks and returns whether given filed name is an automatic-filled created time.
|
||||
func (c *Core) isSoftCreatedFiledName(fieldName string) bool {
|
||||
// isSoftCreatedFilledName checks and returns whether given filed name is an automatic-filled created time.
|
||||
func (c *Core) isSoftCreatedFilledName(fieldName string) bool {
|
||||
if fieldName == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -30,13 +30,14 @@ type ConfigNode struct {
|
||||
Pass string `json:"pass"` // Authentication password.
|
||||
Name string `json:"name"` // Default used database name.
|
||||
Type string `json:"type"` // Database type: mysql, sqlite, mssql, pgsql, oracle.
|
||||
Link string `json:"link"` // (Optional) Custom link information, when it is used, configuration Host/Port/User/Pass/Name are ignored.
|
||||
Role string `json:"role"` // (Optional, "master" in default) Node role, used for master-slave mode: master, slave.
|
||||
Debug bool `json:"debug"` // (Optional) Debug mode enables debug information logging and output.
|
||||
Prefix string `json:"prefix"` // (Optional) Table prefix.
|
||||
DryRun bool `json:"dryRun"` // (Optional) Dry run, which does SELECT but no INSERT/UPDATE/DELETE statements.
|
||||
Weight int `json:"weight"` // (Optional) Weight for load balance calculating, it's useless if there's just one node.
|
||||
Charset string `json:"charset"` // (Optional, "utf8mb4" in default) Custom charset when operating on database.
|
||||
LinkInfo string `json:"link"` // (Optional) Custom link information, when it is used, configuration Host/Port/User/Pass/Name are ignored.
|
||||
Timezone string `json:"timezone"` // (Optional) Sets the time zone for displaying and interpreting time stamps.
|
||||
MaxIdleConnCount int `json:"maxIdle"` // (Optional) Max idle connection configuration for underlying connection pool.
|
||||
MaxOpenConnCount int `json:"maxOpen"` // (Optional) Max open connection configuration for underlying connection pool.
|
||||
MaxConnLifeTime time.Duration `json:"maxLifeTime"` // (Optional) Max amount of time a connection may be idle before being closed.
|
||||
@ -186,7 +187,7 @@ func (node *ConfigNode) String() string {
|
||||
node.MaxIdleConnCount,
|
||||
node.MaxOpenConnCount,
|
||||
node.MaxConnLifeTime,
|
||||
node.LinkInfo,
|
||||
node.Link,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -76,8 +76,8 @@ func (c *Core) addSqlToTracing(ctx context.Context, sql *Sql) {
|
||||
if c.db.GetConfig().User != "" {
|
||||
labels = append(labels, attribute.String(tracingAttrDbUser, c.db.GetConfig().User))
|
||||
}
|
||||
if filteredLinkInfo := c.db.FilteredLinkInfo(); filteredLinkInfo != "" {
|
||||
labels = append(labels, attribute.String(tracingAttrDbLink, c.db.FilteredLinkInfo()))
|
||||
if filteredLink := c.db.FilteredLink(); filteredLink != "" {
|
||||
labels = append(labels, attribute.String(tracingAttrDbLink, c.db.FilteredLink()))
|
||||
}
|
||||
if group := c.db.GetGroup(); group != "" {
|
||||
labels = append(labels, attribute.String(tracingAttrDbGroup, group))
|
||||
|
||||
@ -28,6 +28,7 @@ type TX struct {
|
||||
master *sql.DB // master is the raw and underlying database manager.
|
||||
transactionId string // transactionId is an unique id generated by this object for this transaction.
|
||||
transactionCount int // transactionCount marks the times that Begins.
|
||||
isClosed bool // isClosed marks this transaction has already been committed or rolled back.
|
||||
}
|
||||
|
||||
const (
|
||||
@ -162,6 +163,9 @@ func TXFromCtx(ctx context.Context, group string) *TX {
|
||||
v := ctx.Value(transactionKeyForContext(group))
|
||||
if v != nil {
|
||||
tx := v.(*TX)
|
||||
if tx.IsClosed() {
|
||||
return nil
|
||||
}
|
||||
tx.ctx = ctx
|
||||
return tx
|
||||
}
|
||||
@ -210,6 +214,7 @@ func (tx *TX) Commit() error {
|
||||
IsTransaction: true,
|
||||
}
|
||||
)
|
||||
tx.isClosed = true
|
||||
tx.db.GetCore().addSqlToTracing(tx.ctx, sqlObj)
|
||||
if tx.db.GetDebug() {
|
||||
tx.db.GetCore().writeSqlToLogger(tx.ctx, sqlObj)
|
||||
@ -243,6 +248,7 @@ func (tx *TX) Rollback() error {
|
||||
IsTransaction: true,
|
||||
}
|
||||
)
|
||||
tx.isClosed = true
|
||||
tx.db.GetCore().addSqlToTracing(tx.ctx, sqlObj)
|
||||
if tx.db.GetDebug() {
|
||||
tx.db.GetCore().writeSqlToLogger(tx.ctx, sqlObj)
|
||||
@ -250,6 +256,11 @@ func (tx *TX) Rollback() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// IsClosed checks and returns this transaction has already been committed or rolled back.
|
||||
func (tx *TX) IsClosed() bool {
|
||||
return tx.isClosed
|
||||
}
|
||||
|
||||
// Begin starts a nested transaction procedure.
|
||||
func (tx *TX) Begin() error {
|
||||
_, err := tx.Exec("SAVEPOINT " + tx.transactionKeyForNestedPoint())
|
||||
@ -317,13 +328,13 @@ func (tx *TX) Transaction(ctx context.Context, f func(ctx context.Context, tx *T
|
||||
// Query does query operation on transaction.
|
||||
// See Core.Query.
|
||||
func (tx *TX) Query(sql string, args ...interface{}) (rows *sql.Rows, err error) {
|
||||
return tx.db.GetCore().DoQuery(tx.ctx, &txLink{tx.tx}, sql, args...)
|
||||
return tx.db.DoQuery(tx.ctx, &txLink{tx.tx}, sql, args...)
|
||||
}
|
||||
|
||||
// Exec does none query operation on transaction.
|
||||
// See Core.Exec.
|
||||
func (tx *TX) Exec(sql string, args ...interface{}) (sql.Result, error) {
|
||||
return tx.db.GetCore().DoExec(tx.ctx, &txLink{tx.tx}, sql, args...)
|
||||
return tx.db.DoExec(tx.ctx, &txLink{tx.tx}, sql, args...)
|
||||
}
|
||||
|
||||
// Prepare creates a prepared statement for later queries or executions.
|
||||
@ -332,7 +343,7 @@ func (tx *TX) Exec(sql string, args ...interface{}) (sql.Result, error) {
|
||||
// The caller must call the statement's Close method
|
||||
// when the statement is no longer needed.
|
||||
func (tx *TX) Prepare(sql string) (*Stmt, error) {
|
||||
return tx.db.GetCore().DoPrepare(tx.ctx, &txLink{tx.tx}, sql)
|
||||
return tx.db.DoPrepare(tx.ctx, &txLink{tx.tx}, sql)
|
||||
}
|
||||
|
||||
// GetAll queries and returns data records from database.
|
||||
@ -503,42 +514,6 @@ func (tx *TX) Save(table string, data interface{}, batch ...int) (sql.Result, er
|
||||
return tx.Model(table).Ctx(tx.ctx).Data(data).Save()
|
||||
}
|
||||
|
||||
// BatchInsert batch inserts data.
|
||||
// The parameter `list` must be type of slice of map or struct.
|
||||
func (tx *TX) BatchInsert(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Ctx(tx.ctx).Data(list).Batch(batch[0]).Insert()
|
||||
}
|
||||
return tx.Model(table).Ctx(tx.ctx).Data(list).Insert()
|
||||
}
|
||||
|
||||
// BatchInsertIgnore batch inserts data with ignore option.
|
||||
// The parameter `list` must be type of slice of map or struct.
|
||||
func (tx *TX) BatchInsertIgnore(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Ctx(tx.ctx).Data(list).Batch(batch[0]).InsertIgnore()
|
||||
}
|
||||
return tx.Model(table).Ctx(tx.ctx).Data(list).InsertIgnore()
|
||||
}
|
||||
|
||||
// BatchReplace batch replaces data.
|
||||
// The parameter `list` must be type of slice of map or struct.
|
||||
func (tx *TX) BatchReplace(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Ctx(tx.ctx).Data(list).Batch(batch[0]).Replace()
|
||||
}
|
||||
return tx.Model(table).Ctx(tx.ctx).Data(list).Replace()
|
||||
}
|
||||
|
||||
// BatchSave batch replaces data.
|
||||
// The parameter `list` must be type of slice of map or struct.
|
||||
func (tx *TX) BatchSave(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Ctx(tx.ctx).Data(list).Batch(batch[0]).Save()
|
||||
}
|
||||
return tx.Model(table).Ctx(tx.ctx).Data(list).Save()
|
||||
}
|
||||
|
||||
// Update does "UPDATE ... " statement for the table.
|
||||
//
|
||||
// The parameter `data` can be type of string/map/gmap/struct/*struct, etc.
|
||||
|
||||
@ -17,7 +17,7 @@ import (
|
||||
// Query commits one query SQL to underlying driver and returns the execution result.
|
||||
// It is most commonly used for data querying.
|
||||
func (c *Core) Query(sql string, args ...interface{}) (rows *sql.Rows, err error) {
|
||||
return c.DoQuery(c.GetCtx(), nil, sql, args...)
|
||||
return c.db.DoQuery(c.GetCtx(), nil, sql, args...)
|
||||
}
|
||||
|
||||
// DoQuery commits the sql string and its arguments to underlying driver
|
||||
@ -35,7 +35,7 @@ func (c *Core) DoQuery(ctx context.Context, link Link, sql string, args ...inter
|
||||
}
|
||||
// Link execution.
|
||||
sql, args = formatSql(sql, args)
|
||||
sql, args = c.db.HandleSqlBeforeCommit(ctx, link, sql, args)
|
||||
sql, args = c.db.DoCommit(ctx, link, sql, args)
|
||||
if c.GetConfig().QueryTimeout > 0 {
|
||||
ctx, _ = context.WithTimeout(ctx, c.GetConfig().QueryTimeout)
|
||||
}
|
||||
@ -69,7 +69,7 @@ func (c *Core) DoQuery(ctx context.Context, link Link, sql string, args ...inter
|
||||
// Exec commits one query SQL to underlying driver and returns the execution result.
|
||||
// It is most commonly used for data inserting and updating.
|
||||
func (c *Core) Exec(sql string, args ...interface{}) (result sql.Result, err error) {
|
||||
return c.DoExec(c.GetCtx(), nil, sql, args...)
|
||||
return c.db.DoExec(c.GetCtx(), nil, sql, args...)
|
||||
}
|
||||
|
||||
// DoExec commits the sql string and its arguments to underlying driver
|
||||
@ -87,7 +87,7 @@ func (c *Core) DoExec(ctx context.Context, link Link, sql string, args ...interf
|
||||
}
|
||||
// Link execution.
|
||||
sql, args = formatSql(sql, args)
|
||||
sql, args = c.db.HandleSqlBeforeCommit(ctx, link, sql, args)
|
||||
sql, args = c.db.DoCommit(ctx, link, sql, args)
|
||||
if c.GetConfig().ExecTimeout > 0 {
|
||||
var cancelFunc context.CancelFunc
|
||||
ctx, cancelFunc = context.WithTimeout(ctx, c.GetConfig().ExecTimeout)
|
||||
@ -142,7 +142,7 @@ func (c *Core) Prepare(sql string, execOnMaster ...bool) (*Stmt, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return c.DoPrepare(c.GetCtx(), link, sql)
|
||||
return c.db.DoPrepare(c.GetCtx(), link, sql)
|
||||
}
|
||||
|
||||
// DoPrepare calls prepare function on given link object and returns the statement object.
|
||||
|
||||
@ -63,9 +63,11 @@ func (c *Core) GetChars() (charLeft string, charRight string) {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// HandleSqlBeforeCommit handles the sql before posts it to database.
|
||||
// It does nothing in default.
|
||||
func (c *Core) HandleSqlBeforeCommit(sql string) string {
|
||||
// DoCommit is a hook function, which deals with the sql string before it's committed to underlying driver.
|
||||
// The parameter `link` specifies the current database connection operation object. You can modify the sql
|
||||
// string `sql` and its arguments `args` as you wish before they're committed to driver.
|
||||
// Also see Core.DoCommit.
|
||||
func (c *Core) DoCommit(sql string) string {
|
||||
return sql
|
||||
}
|
||||
|
||||
|
||||
@ -42,8 +42,8 @@ func (d *DriverMssql) New(core *Core, node *ConfigNode) (DB, error) {
|
||||
// Open creates and returns a underlying sql.DB object for mssql.
|
||||
func (d *DriverMssql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
source := ""
|
||||
if config.LinkInfo != "" {
|
||||
source = config.LinkInfo
|
||||
if config.Link != "" {
|
||||
source = config.Link
|
||||
} else {
|
||||
source = fmt.Sprintf(
|
||||
"user id=%s;password=%s;server=%s;port=%s;database=%s;encrypt=disable",
|
||||
@ -58,17 +58,17 @@ func (d *DriverMssql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// FilteredLinkInfo retrieves and returns filtered `linkInfo` that can be using for
|
||||
// FilteredLink retrieves and returns filtered `linkInfo` that can be using for
|
||||
// logging or tracing purpose.
|
||||
func (d *DriverMssql) FilteredLinkInfo() string {
|
||||
linkInfo := d.GetConfig().LinkInfo
|
||||
func (d *DriverMssql) FilteredLink() string {
|
||||
linkInfo := d.GetConfig().Link
|
||||
if linkInfo == "" {
|
||||
return ""
|
||||
}
|
||||
s, _ := gregex.ReplaceString(
|
||||
`(.+);\s*password=(.+);\s*server=(.+)`,
|
||||
`$1;password=xxx;server=$3`,
|
||||
d.GetConfig().LinkInfo,
|
||||
d.GetConfig().Link,
|
||||
)
|
||||
return s
|
||||
}
|
||||
@ -78,8 +78,8 @@ func (d *DriverMssql) GetChars() (charLeft string, charRight string) {
|
||||
return "\"", "\""
|
||||
}
|
||||
|
||||
// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *DriverMssql) HandleSqlBeforeCommit(ctx context.Context, link Link, sql string, args []interface{}) (string, []interface{}) {
|
||||
// DoCommit deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *DriverMssql) DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (string, []interface{}) {
|
||||
var index int
|
||||
// Convert place holder char '?' to string "@px".
|
||||
str, _ := gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
|
||||
|
||||
@ -10,13 +10,14 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
_ "github.com/gogf/mysql"
|
||||
)
|
||||
|
||||
// DriverMysql is the driver for mysql database.
|
||||
@ -36,8 +37,8 @@ func (d *DriverMysql) New(core *Core, node *ConfigNode) (DB, error) {
|
||||
// Note that it converts time.Time argument to local timezone in default.
|
||||
func (d *DriverMysql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
var source string
|
||||
if config.LinkInfo != "" {
|
||||
source = config.LinkInfo
|
||||
if config.Link != "" {
|
||||
source = config.Link
|
||||
// Custom changing the schema in runtime.
|
||||
if config.Name != "" {
|
||||
source, _ = gregex.ReplaceString(`/([\w\.\-]+)+`, "/"+config.Name, source)
|
||||
@ -47,6 +48,9 @@ func (d *DriverMysql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
"%s:%s@tcp(%s:%s)/%s?charset=%s",
|
||||
config.User, config.Pass, config.Host, config.Port, config.Name, config.Charset,
|
||||
)
|
||||
if config.Timezone != "" {
|
||||
source = fmt.Sprintf("%s&loc=%s", source, url.QueryEscape(config.Timezone))
|
||||
}
|
||||
}
|
||||
intlog.Printf("Open: %s", source)
|
||||
if db, err := sql.Open("mysql", source); err == nil {
|
||||
@ -56,10 +60,10 @@ func (d *DriverMysql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// FilteredLinkInfo retrieves and returns filtered `linkInfo` that can be using for
|
||||
// FilteredLink retrieves and returns filtered `linkInfo` that can be using for
|
||||
// logging or tracing purpose.
|
||||
func (d *DriverMysql) FilteredLinkInfo() string {
|
||||
linkInfo := d.GetConfig().LinkInfo
|
||||
func (d *DriverMysql) FilteredLink() string {
|
||||
linkInfo := d.GetConfig().Link
|
||||
if linkInfo == "" {
|
||||
return ""
|
||||
}
|
||||
@ -76,8 +80,8 @@ func (d *DriverMysql) GetChars() (charLeft string, charRight string) {
|
||||
return "`", "`"
|
||||
}
|
||||
|
||||
// HandleSqlBeforeCommit handles the sql before posts it to database.
|
||||
func (d *DriverMysql) HandleSqlBeforeCommit(ctx context.Context, link Link, sql string, args []interface{}) (string, []interface{}) {
|
||||
// DoCommit handles the sql before posts it to database.
|
||||
func (d *DriverMysql) DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (string, []interface{}) {
|
||||
return sql, args
|
||||
}
|
||||
|
||||
|
||||
@ -48,8 +48,8 @@ func (d *DriverOracle) New(core *Core, node *ConfigNode) (DB, error) {
|
||||
// Open creates and returns a underlying sql.DB object for oracle.
|
||||
func (d *DriverOracle) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
var source string
|
||||
if config.LinkInfo != "" {
|
||||
source = config.LinkInfo
|
||||
if config.Link != "" {
|
||||
source = config.Link
|
||||
} else {
|
||||
source = fmt.Sprintf(
|
||||
"%s/%s@%s:%s/%s",
|
||||
@ -64,10 +64,10 @@ func (d *DriverOracle) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// FilteredLinkInfo retrieves and returns filtered `linkInfo` that can be using for
|
||||
// FilteredLink retrieves and returns filtered `linkInfo` that can be using for
|
||||
// logging or tracing purpose.
|
||||
func (d *DriverOracle) FilteredLinkInfo() string {
|
||||
linkInfo := d.GetConfig().LinkInfo
|
||||
func (d *DriverOracle) FilteredLink() string {
|
||||
linkInfo := d.GetConfig().Link
|
||||
if linkInfo == "" {
|
||||
return ""
|
||||
}
|
||||
@ -84,8 +84,8 @@ func (d *DriverOracle) GetChars() (charLeft string, charRight string) {
|
||||
return "\"", "\""
|
||||
}
|
||||
|
||||
// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *DriverOracle) HandleSqlBeforeCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}) {
|
||||
// DoCommit deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *DriverOracle) DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}) {
|
||||
var index int
|
||||
// Convert place holder char '?' to string ":vx".
|
||||
newSql, _ = gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
|
||||
@ -264,203 +264,40 @@ func (d *DriverOracle) getTableUniqueIndex(table string) (fields map[string]map[
|
||||
return
|
||||
}
|
||||
|
||||
func (d *DriverOracle) DoInsert(ctx context.Context, link Link, table string, data interface{}, option int, batch ...int) (result sql.Result, err error) {
|
||||
var (
|
||||
fields []string
|
||||
values []string
|
||||
params []interface{}
|
||||
dataMap Map
|
||||
rv = reflect.ValueOf(data)
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
switch kind {
|
||||
case reflect.Slice, reflect.Array:
|
||||
return d.DoBatchInsert(ctx, link, table, data, option, batch...)
|
||||
case reflect.Map:
|
||||
fallthrough
|
||||
case reflect.Struct:
|
||||
dataMap = ConvertDataForTableRecord(data)
|
||||
default:
|
||||
return result, gerror.New(fmt.Sprint("unsupported data type:", kind))
|
||||
}
|
||||
var (
|
||||
indexes = make([]string, 0)
|
||||
indexMap = make(map[string]string)
|
||||
indexExists = false
|
||||
)
|
||||
if option != insertOptionDefault {
|
||||
index, err := d.getTableUniqueIndex(table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(index) > 0 {
|
||||
for _, v := range index {
|
||||
for k, _ := range v {
|
||||
indexes = append(indexes, k)
|
||||
}
|
||||
indexMap = v
|
||||
indexExists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
var (
|
||||
subSqlStr = make([]string, 0)
|
||||
onStr = make([]string, 0)
|
||||
updateStr = make([]string, 0)
|
||||
)
|
||||
charL, charR := d.db.GetChars()
|
||||
for k, v := range dataMap {
|
||||
k = strings.ToUpper(k)
|
||||
|
||||
// 操作类型为REPLACE/SAVE时且存在唯一索引才使用merge,否则使用insert
|
||||
if (option == insertOptionReplace || option == insertOptionSave) && indexExists {
|
||||
fields = append(fields, tableAlias1+"."+charL+k+charR)
|
||||
values = append(values, tableAlias2+"."+charL+k+charR)
|
||||
params = append(params, v)
|
||||
subSqlStr = append(subSqlStr, fmt.Sprintf("%s?%s %s", charL, charR, k))
|
||||
//m erge中的on子句中由唯一索引组成, update子句中不含唯一索引
|
||||
if _, ok := indexMap[k]; ok {
|
||||
onStr = append(onStr, fmt.Sprintf("%s.%s = %s.%s ", tableAlias1, k, tableAlias2, k))
|
||||
} else {
|
||||
updateStr = append(updateStr, fmt.Sprintf("%s.%s = %s.%s ", tableAlias1, k, tableAlias2, k))
|
||||
}
|
||||
} else {
|
||||
fields = append(fields, charL+k+charR)
|
||||
values = append(values, "?")
|
||||
params = append(params, v)
|
||||
}
|
||||
}
|
||||
|
||||
if link == nil {
|
||||
if link, err = d.MasterLink(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if indexExists && option != insertOptionDefault {
|
||||
switch option {
|
||||
case
|
||||
insertOptionReplace,
|
||||
insertOptionSave:
|
||||
tmp := fmt.Sprintf(
|
||||
"MERGE INTO %s %s USING(SELECT %s FROM DUAL) %s ON(%s) WHEN MATCHED THEN UPDATE SET %s WHEN NOT MATCHED THEN INSERT (%s) VALUES(%s)",
|
||||
table, tableAlias1, strings.Join(subSqlStr, ","), tableAlias2,
|
||||
strings.Join(onStr, "AND"), strings.Join(updateStr, ","), strings.Join(fields, ","), strings.Join(values, ","),
|
||||
)
|
||||
return d.DoExec(ctx, link, tmp, params...)
|
||||
|
||||
case insertOptionIgnore:
|
||||
return d.DoExec(ctx, link, fmt.Sprintf(
|
||||
"INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(%s(%s)) */ INTO %s(%s) VALUES(%s)",
|
||||
table, strings.Join(indexes, ","), table, strings.Join(fields, ","), strings.Join(values, ","),
|
||||
), params...)
|
||||
}
|
||||
}
|
||||
|
||||
return d.DoExec(ctx, link,
|
||||
fmt.Sprintf(
|
||||
"INSERT INTO %s(%s) VALUES(%s)",
|
||||
table, strings.Join(fields, ","), strings.Join(values, ","),
|
||||
),
|
||||
params...)
|
||||
}
|
||||
|
||||
func (d *DriverOracle) DoBatchInsert(ctx context.Context, link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) {
|
||||
func (d *DriverOracle) DoInsert(ctx context.Context, link Link, table string, list List, option DoInsertOption) (result sql.Result, err error) {
|
||||
var (
|
||||
keys []string
|
||||
values []string
|
||||
params []interface{}
|
||||
)
|
||||
listMap := (List)(nil)
|
||||
switch v := list.(type) {
|
||||
case Result:
|
||||
listMap = v.List()
|
||||
case Record:
|
||||
listMap = List{v.Map()}
|
||||
case List:
|
||||
listMap = v
|
||||
case Map:
|
||||
listMap = List{v}
|
||||
default:
|
||||
var (
|
||||
rv = reflect.ValueOf(list)
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
switch kind {
|
||||
case reflect.Slice, reflect.Array:
|
||||
listMap = make(List, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
listMap[i] = ConvertDataForTableRecord(rv.Index(i).Interface())
|
||||
}
|
||||
case reflect.Map:
|
||||
fallthrough
|
||||
case reflect.Struct:
|
||||
listMap = List{ConvertDataForTableRecord(list)}
|
||||
default:
|
||||
return result, gerror.New(fmt.Sprint("unsupported list type:", kind))
|
||||
}
|
||||
}
|
||||
if len(listMap) < 1 {
|
||||
return result, gerror.New("empty data list")
|
||||
}
|
||||
if link == nil {
|
||||
if link, err = d.MasterLink(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Retrieve the table fields and length.
|
||||
holders := []string(nil)
|
||||
for k, _ := range listMap[0] {
|
||||
var (
|
||||
listLength = len(list)
|
||||
valueHolder = make([]string, 0)
|
||||
)
|
||||
for k, _ := range list[0] {
|
||||
keys = append(keys, k)
|
||||
holders = append(holders, "?")
|
||||
valueHolder = append(valueHolder, "?")
|
||||
}
|
||||
var (
|
||||
batchResult = new(SqlResult)
|
||||
charL, charR = d.db.GetChars()
|
||||
keyStr = charL + strings.Join(keys, charL+","+charR) + charR
|
||||
valueHolderStr = strings.Join(holders, ",")
|
||||
valueHolderStr = strings.Join(valueHolder, ",")
|
||||
)
|
||||
if option != insertOptionDefault {
|
||||
for _, v := range listMap {
|
||||
r, err := d.DoInsert(ctx, link, table, v, option, 1)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
|
||||
if n, err := r.RowsAffected(); err != nil {
|
||||
return r, err
|
||||
} else {
|
||||
batchResult.result = r
|
||||
batchResult.affected += n
|
||||
}
|
||||
}
|
||||
return batchResult, nil
|
||||
}
|
||||
|
||||
batchNum := defaultBatchNumber
|
||||
if len(batch) > 0 {
|
||||
batchNum = batch[0]
|
||||
}
|
||||
// Format "INSERT...INTO..." statement.
|
||||
intoStr := make([]string, 0)
|
||||
for i := 0; i < len(listMap); i++ {
|
||||
for i := 0; i < len(list); i++ {
|
||||
for _, k := range keys {
|
||||
params = append(params, listMap[i][k])
|
||||
params = append(params, list[i][k])
|
||||
}
|
||||
values = append(values, valueHolderStr)
|
||||
intoStr = append(intoStr, fmt.Sprintf(" INTO %s(%s) VALUES(%s) ", table, keyStr, valueHolderStr))
|
||||
if len(intoStr) == batchNum {
|
||||
r, err := d.DoExec(ctx, link, fmt.Sprintf("INSERT ALL %s SELECT * FROM DUAL", strings.Join(intoStr, " ")), params...)
|
||||
intoStr = append(intoStr, fmt.Sprintf("INTO %s(%s) VALUES(%s)", table, keyStr, valueHolderStr))
|
||||
if len(intoStr) == option.BatchCount || (i == listLength-1 && len(valueHolder) > 0) {
|
||||
r, err := d.DoExec(ctx, link, fmt.Sprintf(
|
||||
"INSERT ALL %s SELECT * FROM DUAL",
|
||||
strings.Join(intoStr, " "),
|
||||
), params...)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
@ -474,18 +311,5 @@ func (d *DriverOracle) DoBatchInsert(ctx context.Context, link Link, table strin
|
||||
intoStr = intoStr[:0]
|
||||
}
|
||||
}
|
||||
// The leftover data.
|
||||
if len(intoStr) > 0 {
|
||||
r, err := d.DoExec(ctx, link, fmt.Sprintf("INSERT ALL %s SELECT * FROM DUAL", strings.Join(intoStr, " ")), params...)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
if n, err := r.RowsAffected(); err != nil {
|
||||
return r, err
|
||||
} else {
|
||||
batchResult.result = r
|
||||
batchResult.affected += n
|
||||
}
|
||||
}
|
||||
return batchResult, nil
|
||||
}
|
||||
|
||||
@ -40,13 +40,16 @@ func (d *DriverPgsql) New(core *Core, node *ConfigNode) (DB, error) {
|
||||
// Open creates and returns a underlying sql.DB object for pgsql.
|
||||
func (d *DriverPgsql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
var source string
|
||||
if config.LinkInfo != "" {
|
||||
source = config.LinkInfo
|
||||
if config.Link != "" {
|
||||
source = config.Link
|
||||
} else {
|
||||
source = fmt.Sprintf(
|
||||
"user=%s password=%s host=%s port=%s dbname=%s sslmode=disable",
|
||||
config.User, config.Pass, config.Host, config.Port, config.Name,
|
||||
)
|
||||
if config.Timezone != "" {
|
||||
source = fmt.Sprintf("%s timezone=%s", source, config.Timezone)
|
||||
}
|
||||
}
|
||||
intlog.Printf("Open: %s", source)
|
||||
if db, err := sql.Open("postgres", source); err == nil {
|
||||
@ -56,10 +59,10 @@ func (d *DriverPgsql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// FilteredLinkInfo retrieves and returns filtered `linkInfo` that can be using for
|
||||
// FilteredLink retrieves and returns filtered `linkInfo` that can be using for
|
||||
// logging or tracing purpose.
|
||||
func (d *DriverPgsql) FilteredLinkInfo() string {
|
||||
linkInfo := d.GetConfig().LinkInfo
|
||||
func (d *DriverPgsql) FilteredLink() string {
|
||||
linkInfo := d.GetConfig().Link
|
||||
if linkInfo == "" {
|
||||
return ""
|
||||
}
|
||||
@ -76,8 +79,8 @@ func (d *DriverPgsql) GetChars() (charLeft string, charRight string) {
|
||||
return "\"", "\""
|
||||
}
|
||||
|
||||
// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *DriverPgsql) HandleSqlBeforeCommit(ctx context.Context, link Link, sql string, args []interface{}) (string, []interface{}) {
|
||||
// DoCommit deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *DriverPgsql) DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (string, []interface{}) {
|
||||
var index int
|
||||
// Convert place holder char '?' to string "$x".
|
||||
sql, _ = gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
|
||||
@ -135,9 +138,9 @@ func (d *DriverPgsql) TableFields(ctx context.Context, table string, schema ...s
|
||||
result Result
|
||||
link, err = d.SlaveLink(useSchema)
|
||||
structureSql = fmt.Sprintf(`
|
||||
SELECT a.attname AS field, t.typname AS type FROM pg_class c, pg_attribute a
|
||||
SELECT a.attname AS field, t.typname AS type FROM pg_class c, pg_attribute a
|
||||
LEFT OUTER JOIN pg_description b ON a.attrelid=b.objoid AND a.attnum = b.objsubid,pg_type t
|
||||
WHERE c.relname = '%s' and a.attnum > 0 and a.attrelid = c.oid and a.atttypid = t.oid
|
||||
WHERE c.relname = '%s' and a.attnum > 0 and a.attrelid = c.oid and a.atttypid = t.oid
|
||||
ORDER BY a.attnum`,
|
||||
strings.ToLower(table),
|
||||
)
|
||||
|
||||
@ -38,8 +38,8 @@ func (d *DriverSqlite) New(core *Core, node *ConfigNode) (DB, error) {
|
||||
// Open creates and returns a underlying sql.DB object for sqlite.
|
||||
func (d *DriverSqlite) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
var source string
|
||||
if config.LinkInfo != "" {
|
||||
source = config.LinkInfo
|
||||
if config.Link != "" {
|
||||
source = config.Link
|
||||
} else {
|
||||
source = config.Name
|
||||
}
|
||||
@ -55,10 +55,10 @@ func (d *DriverSqlite) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// FilteredLinkInfo retrieves and returns filtered `linkInfo` that can be using for
|
||||
// FilteredLink retrieves and returns filtered `linkInfo` that can be using for
|
||||
// logging or tracing purpose.
|
||||
func (d *DriverSqlite) FilteredLinkInfo() string {
|
||||
return d.GetConfig().LinkInfo
|
||||
func (d *DriverSqlite) FilteredLink() string {
|
||||
return d.GetConfig().Link
|
||||
}
|
||||
|
||||
// GetChars returns the security char for this type of database.
|
||||
@ -66,10 +66,10 @@ func (d *DriverSqlite) GetChars() (charLeft string, charRight string) {
|
||||
return "`", "`"
|
||||
}
|
||||
|
||||
// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver.
|
||||
// DoCommit deals with the sql string before commits it to underlying sql driver.
|
||||
// TODO 需要增加对Save方法的支持,可使用正则来实现替换,
|
||||
// TODO 将ON DUPLICATE KEY UPDATE触发器修改为两条SQL语句(INSERT OR IGNORE & UPDATE)
|
||||
func (d *DriverSqlite) HandleSqlBeforeCommit(ctx context.Context, link Link, sql string, args []interface{}) (string, []interface{}) {
|
||||
func (d *DriverSqlite) DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (string, []interface{}) {
|
||||
return sql, args
|
||||
}
|
||||
|
||||
|
||||
@ -142,8 +142,8 @@ func GetInsertOperationByOption(option int) string {
|
||||
// ConvertDataForTableRecord is a very important function, which does converting for any data that
|
||||
// will be inserted into table as a record.
|
||||
//
|
||||
// The parameter `obj` should be type of *map/map/*struct/struct.
|
||||
// It supports inherit struct definition for struct.
|
||||
// The parameter `value` should be type of *map/map/*struct/struct.
|
||||
// It supports embedded struct definition for struct.
|
||||
func ConvertDataForTableRecord(value interface{}) map[string]interface{} {
|
||||
var (
|
||||
rvValue reflect.Value
|
||||
@ -164,12 +164,15 @@ func ConvertDataForTableRecord(value interface{}) map[string]interface{} {
|
||||
// Convert the value to JSON.
|
||||
data[k], _ = json.Marshal(v)
|
||||
}
|
||||
|
||||
case reflect.Struct:
|
||||
switch v.(type) {
|
||||
case time.Time, *time.Time, gtime.Time, *gtime.Time:
|
||||
continue
|
||||
|
||||
case Counter, *Counter:
|
||||
continue
|
||||
|
||||
default:
|
||||
// Use string conversion in default.
|
||||
if s, ok := v.(apiString); ok {
|
||||
@ -186,7 +189,7 @@ func ConvertDataForTableRecord(value interface{}) map[string]interface{} {
|
||||
|
||||
// DataToMapDeep converts `value` to map type recursively.
|
||||
// The parameter `value` should be type of *map/map/*struct/struct.
|
||||
// It supports inherit struct definition for struct.
|
||||
// It supports embedded struct definition for struct.
|
||||
func DataToMapDeep(value interface{}) map[string]interface{} {
|
||||
if v, ok := value.(apiMapStrAny); ok {
|
||||
return v.MapStrAny()
|
||||
@ -445,7 +448,7 @@ func formatSql(sql string, args []interface{}) (newSql string, newArgs []interfa
|
||||
}
|
||||
|
||||
// formatWhere formats where statement and its arguments for `Where` and `Having` statements.
|
||||
func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (newWhere string, newArgs []interface{}) {
|
||||
func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool, schema, table string) (newWhere string, newArgs []interface{}) {
|
||||
var (
|
||||
buffer = bytes.NewBuffer(nil)
|
||||
rv = reflect.ValueOf(where)
|
||||
@ -483,7 +486,12 @@ func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (
|
||||
})
|
||||
break
|
||||
}
|
||||
for key, value := range DataToMapDeep(where) {
|
||||
// Automatically mapping and filtering the struct attribute.
|
||||
data := DataToMapDeep(where)
|
||||
if table != "" {
|
||||
data, _ = db.GetCore().mappingAndFilterData(schema, table, data, true)
|
||||
}
|
||||
for key, value := range data {
|
||||
if omitEmpty && empty.IsEmpty(value) {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -17,10 +17,11 @@ import (
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
)
|
||||
|
||||
// Model is the DAO for ORM.
|
||||
// Model is core struct implementing the DAO for ORM.
|
||||
type Model struct {
|
||||
db DB // Underlying DB interface.
|
||||
tx *TX // Underlying TX interface.
|
||||
rawSql string // rawSql is the raw SQL string which marks a raw SQL based Model not a table based Model.
|
||||
schema string // Custom database schema.
|
||||
linkType int // Mark for operation on master or slave.
|
||||
tablesInit string // Table names when model initialization.
|
||||
@ -48,6 +49,8 @@ type Model struct {
|
||||
cacheName string // Cache name for custom operation.
|
||||
unscoped bool // Disables soft deleting features when select/delete operations.
|
||||
safe bool // If true, it clones and returns a new model object whenever operation done; or else it changes the attribute of current model.
|
||||
onDuplicate interface{} // onDuplicate is used for ON "DUPLICATE KEY UPDATE" statement.
|
||||
onDuplicateEx interface{} // onDuplicateEx is used for excluding some columns ON "DUPLICATE KEY UPDATE" statement.
|
||||
}
|
||||
|
||||
// whereHolder is the holder for where condition preparing.
|
||||
@ -77,29 +80,32 @@ func (c *Core) Table(tableNameQueryOrStruct ...interface{}) *Model {
|
||||
// Model creates and returns a new ORM model from given schema.
|
||||
// The parameter `tableNameQueryOrStruct` can be more than one table names, and also alias name, like:
|
||||
// 1. Model names:
|
||||
// Model("user")
|
||||
// Model("user u")
|
||||
// Model("user, user_detail")
|
||||
// Model("user u, user_detail ud")
|
||||
// 2. Model name with alias: Model("user", "u")
|
||||
// db.Model("user")
|
||||
// db.Model("user u")
|
||||
// db.Model("user, user_detail")
|
||||
// db.Model("user u, user_detail ud")
|
||||
// 2. Model name with alias:
|
||||
// db.Model("user", "u")
|
||||
// 3. Model name with sub-query:
|
||||
// db.Model("? AS a, ? AS b", subQuery1, subQuery2)
|
||||
func (c *Core) Model(tableNameQueryOrStruct ...interface{}) *Model {
|
||||
var (
|
||||
tableStr string
|
||||
tableName string
|
||||
extraArgs []interface{}
|
||||
tableNames = make([]string, len(tableNameQueryOrStruct))
|
||||
tableStr string
|
||||
tableName string
|
||||
extraArgs []interface{}
|
||||
)
|
||||
// Model creation with sub-query.
|
||||
if len(tableNameQueryOrStruct) > 1 {
|
||||
conditionStr := gconv.String(tableNameQueryOrStruct[0])
|
||||
if gstr.Contains(conditionStr, "?") {
|
||||
tableStr, extraArgs = formatWhere(
|
||||
c.db, conditionStr, tableNameQueryOrStruct[1:], false,
|
||||
c.db, conditionStr, tableNameQueryOrStruct[1:], false, "", "",
|
||||
)
|
||||
}
|
||||
}
|
||||
// Normal model creation.
|
||||
if tableStr == "" {
|
||||
tableNames := make([]string, len(tableNameQueryOrStruct))
|
||||
for k, v := range tableNameQueryOrStruct {
|
||||
if s, ok := v.(string); ok {
|
||||
tableNames[k] = s
|
||||
@ -107,7 +113,6 @@ func (c *Core) Model(tableNameQueryOrStruct ...interface{}) *Model {
|
||||
tableNames[k] = tableName
|
||||
}
|
||||
}
|
||||
|
||||
if len(tableNames) > 1 {
|
||||
tableStr = fmt.Sprintf(
|
||||
`%s AS %s`, c.QuotePrefixTableName(tableNames[0]), c.QuoteWord(tableNames[1]),
|
||||
@ -129,17 +134,36 @@ func (c *Core) Model(tableNameQueryOrStruct ...interface{}) *Model {
|
||||
}
|
||||
}
|
||||
|
||||
// Raw creates and returns a model based on a raw sql not a table.
|
||||
// Example:
|
||||
// db.Raw("SELECT * FROM `user` WHERE `name` = ?", "john").Scan(&result)
|
||||
func (c *Core) Raw(rawSql string, args ...interface{}) *Model {
|
||||
model := c.Model()
|
||||
model.rawSql = rawSql
|
||||
model.extraArgs = args
|
||||
return model
|
||||
}
|
||||
|
||||
// Raw creates and returns a model based on a raw sql not a table.
|
||||
// Example:
|
||||
// db.Raw("SELECT * FROM `user` WHERE `name` = ?", "john").Scan(&result)
|
||||
// See Core.Raw.
|
||||
func (m *Model) Raw(rawSql string, args ...interface{}) *Model {
|
||||
model := m.db.Raw(rawSql, args...)
|
||||
model.db = m.db
|
||||
model.tx = m.tx
|
||||
return model
|
||||
}
|
||||
|
||||
func (tx *TX) Raw(rawSql string, args ...interface{}) *Model {
|
||||
return tx.Model().Raw(rawSql, args...)
|
||||
}
|
||||
|
||||
// With creates and returns an ORM model based on meta data of given object.
|
||||
func (c *Core) With(objects ...interface{}) *Model {
|
||||
return c.db.Model().With(objects...)
|
||||
}
|
||||
|
||||
// Table is alias of tx.Model.
|
||||
// Deprecated, use Model instead.
|
||||
func (tx *TX) Table(tableNameQueryOrStruct ...interface{}) *Model {
|
||||
return tx.Model(tableNameQueryOrStruct...)
|
||||
}
|
||||
|
||||
// Model acts like Core.Model except it operates on transaction.
|
||||
// See Core.Model.
|
||||
func (tx *TX) Model(tableNameQueryOrStruct ...interface{}) *Model {
|
||||
|
||||
@ -8,6 +8,7 @@ package gdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"strings"
|
||||
)
|
||||
@ -60,55 +61,86 @@ func (m *Model) WherePri(where interface{}, args ...interface{}) *Model {
|
||||
return m.Where(newWhere[0], newWhere[1:]...)
|
||||
}
|
||||
|
||||
// WhereBetween builds `xxx BETWEEN x AND y` statement.
|
||||
// Wheref builds condition string using fmt.Sprintf and arguments.
|
||||
// Note that if the number of `args` is more than the place holder in `format`,
|
||||
// the extra `args` will be used as the where condition arguments of the Model.
|
||||
func (m *Model) Wheref(format string, args ...interface{}) *Model {
|
||||
var (
|
||||
placeHolderCount = gstr.Count(format, "?")
|
||||
conditionStr = fmt.Sprintf(format, args[:len(args)-placeHolderCount]...)
|
||||
)
|
||||
return m.Where(conditionStr, args[len(args)-placeHolderCount:]...)
|
||||
}
|
||||
|
||||
// WhereLT builds `column < value` statement.
|
||||
func (m *Model) WhereLT(column string, value interface{}) *Model {
|
||||
return m.Wheref(`%s < ?`, column, value)
|
||||
}
|
||||
|
||||
// WhereLTE builds `column <= value` statement.
|
||||
func (m *Model) WhereLTE(column string, value interface{}) *Model {
|
||||
return m.Wheref(`%s <= ?`, column, value)
|
||||
}
|
||||
|
||||
// WhereGT builds `column > value` statement.
|
||||
func (m *Model) WhereGT(column string, value interface{}) *Model {
|
||||
return m.Wheref(`%s > ?`, column, value)
|
||||
}
|
||||
|
||||
// WhereGTE builds `column >= value` statement.
|
||||
func (m *Model) WhereGTE(column string, value interface{}) *Model {
|
||||
return m.Wheref(`%s >= ?`, column, value)
|
||||
}
|
||||
|
||||
// WhereBetween builds `column BETWEEN min AND max` statement.
|
||||
func (m *Model) WhereBetween(column string, min, max interface{}) *Model {
|
||||
return m.Where(fmt.Sprintf(`%s BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column)), min, max)
|
||||
return m.Wheref(`%s BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column), min, max)
|
||||
}
|
||||
|
||||
// WhereLike builds `xxx LIKE x` statement.
|
||||
// WhereLike builds `column LIKE like` statement.
|
||||
func (m *Model) WhereLike(column string, like interface{}) *Model {
|
||||
return m.Where(fmt.Sprintf(`%s LIKE ?`, m.db.GetCore().QuoteWord(column)), like)
|
||||
return m.Wheref(`%s LIKE ?`, m.db.GetCore().QuoteWord(column), like)
|
||||
}
|
||||
|
||||
// WhereIn builds `xxx IN (x)` statement.
|
||||
// WhereIn builds `column IN (in)` statement.
|
||||
func (m *Model) WhereIn(column string, in interface{}) *Model {
|
||||
return m.Where(fmt.Sprintf(`%s IN (?)`, m.db.GetCore().QuoteWord(column)), in)
|
||||
return m.Wheref(`%s IN (?)`, m.db.GetCore().QuoteWord(column), in)
|
||||
}
|
||||
|
||||
// WhereNull builds `xxx IS NULL` statement.
|
||||
// WhereNull builds `columns[0] IS NULL AND columns[1] IS NULL ...` statement.
|
||||
func (m *Model) WhereNull(columns ...string) *Model {
|
||||
model := m
|
||||
for _, column := range columns {
|
||||
model = m.Where(fmt.Sprintf(`%s IS NULL`, m.db.GetCore().QuoteWord(column)))
|
||||
model = m.Wheref(`%s IS NULL`, m.db.GetCore().QuoteWord(column))
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
// WhereNotBetween builds `xxx NOT BETWEEN x AND y` statement.
|
||||
// WhereNotBetween builds `column NOT BETWEEN min AND max` statement.
|
||||
func (m *Model) WhereNotBetween(column string, min, max interface{}) *Model {
|
||||
return m.Where(fmt.Sprintf(`%s NOT BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column)), min, max)
|
||||
return m.Wheref(`%s NOT BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column), min, max)
|
||||
}
|
||||
|
||||
// WhereNotLike builds `xxx NOT LIKE x` statement.
|
||||
// WhereNotLike builds `column NOT LIKE like` statement.
|
||||
func (m *Model) WhereNotLike(column string, like interface{}) *Model {
|
||||
return m.Where(fmt.Sprintf(`%s NOT LIKE ?`, m.db.GetCore().QuoteWord(column)), like)
|
||||
return m.Wheref(`%s NOT LIKE ?`, m.db.GetCore().QuoteWord(column), like)
|
||||
}
|
||||
|
||||
// WhereNot builds `xxx != x` statement.
|
||||
// WhereNot builds `column != value` statement.
|
||||
func (m *Model) WhereNot(column string, value interface{}) *Model {
|
||||
return m.Where(fmt.Sprintf(`%s != ?`, m.db.GetCore().QuoteWord(column)), value)
|
||||
return m.Wheref(`%s != ?`, m.db.GetCore().QuoteWord(column), value)
|
||||
}
|
||||
|
||||
// WhereNotIn builds `xxx NOT IN (x)` statement.
|
||||
// WhereNotIn builds `column NOT IN (in)` statement.
|
||||
func (m *Model) WhereNotIn(column string, in interface{}) *Model {
|
||||
return m.Where(fmt.Sprintf(`%s NOT IN (?)`, m.db.GetCore().QuoteWord(column)), in)
|
||||
return m.Wheref(`%s NOT IN (?)`, m.db.GetCore().QuoteWord(column), in)
|
||||
}
|
||||
|
||||
// WhereNotNull builds `xxx IS NOT NULL` statement.
|
||||
// WhereNotNull builds `columns[0] IS NOT NULL AND columns[1] IS NOT NULL ...` statement.
|
||||
func (m *Model) WhereNotNull(columns ...string) *Model {
|
||||
model := m
|
||||
for _, column := range columns {
|
||||
model = m.Where(fmt.Sprintf(`%s IS NOT NULL`, m.db.GetCore().QuoteWord(column)))
|
||||
model = m.Wheref(`%s IS NOT NULL`, m.db.GetCore().QuoteWord(column))
|
||||
}
|
||||
return model
|
||||
}
|
||||
@ -127,50 +159,79 @@ func (m *Model) WhereOr(where interface{}, args ...interface{}) *Model {
|
||||
return model
|
||||
}
|
||||
|
||||
// WhereOrBetween builds `xxx BETWEEN x AND y` statement in `OR` conditions.
|
||||
// WhereOrf builds `OR` condition string using fmt.Sprintf and arguments.
|
||||
func (m *Model) WhereOrf(format string, args ...interface{}) *Model {
|
||||
var (
|
||||
placeHolderCount = gstr.Count(format, "?")
|
||||
conditionStr = fmt.Sprintf(format, args[:len(args)-placeHolderCount]...)
|
||||
)
|
||||
return m.WhereOr(conditionStr, args[len(args)-placeHolderCount:]...)
|
||||
}
|
||||
|
||||
// WhereOrLT builds `column < value` statement in `OR` conditions..
|
||||
func (m *Model) WhereOrLT(column string, value interface{}) *Model {
|
||||
return m.WhereOrf(`%s < ?`, column, value)
|
||||
}
|
||||
|
||||
// WhereOrLTE builds `column <= value` statement in `OR` conditions..
|
||||
func (m *Model) WhereOrLTE(column string, value interface{}) *Model {
|
||||
return m.WhereOrf(`%s <= ?`, column, value)
|
||||
}
|
||||
|
||||
// WhereOrGT builds `column > value` statement in `OR` conditions..
|
||||
func (m *Model) WhereOrGT(column string, value interface{}) *Model {
|
||||
return m.WhereOrf(`%s > ?`, column, value)
|
||||
}
|
||||
|
||||
// WhereOrGTE builds `column >= value` statement in `OR` conditions..
|
||||
func (m *Model) WhereOrGTE(column string, value interface{}) *Model {
|
||||
return m.WhereOrf(`%s >= ?`, column, value)
|
||||
}
|
||||
|
||||
// WhereOrBetween builds `column BETWEEN min AND max` statement in `OR` conditions.
|
||||
func (m *Model) WhereOrBetween(column string, min, max interface{}) *Model {
|
||||
return m.WhereOr(fmt.Sprintf(`%s BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column)), min, max)
|
||||
return m.WhereOrf(`%s BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column), min, max)
|
||||
}
|
||||
|
||||
// WhereOrLike builds `xxx LIKE x` statement in `OR` conditions.
|
||||
// WhereOrLike builds `column LIKE like` statement in `OR` conditions.
|
||||
func (m *Model) WhereOrLike(column string, like interface{}) *Model {
|
||||
return m.WhereOr(fmt.Sprintf(`%s LIKE ?`, m.db.GetCore().QuoteWord(column)), like)
|
||||
return m.WhereOrf(`%s LIKE ?`, m.db.GetCore().QuoteWord(column), like)
|
||||
}
|
||||
|
||||
// WhereOrIn builds `xxx IN (x)` statement in `OR` conditions.
|
||||
// WhereOrIn builds `column IN (in)` statement in `OR` conditions.
|
||||
func (m *Model) WhereOrIn(column string, in interface{}) *Model {
|
||||
return m.WhereOr(fmt.Sprintf(`%s IN (?)`, m.db.GetCore().QuoteWord(column)), in)
|
||||
return m.WhereOrf(`%s IN (?)`, m.db.GetCore().QuoteWord(column), in)
|
||||
}
|
||||
|
||||
// WhereOrNull builds `xxx IS NULL` statement in `OR` conditions.
|
||||
// WhereOrNull builds `columns[0] IS NULL OR columns[1] IS NULL ...` statement in `OR` conditions.
|
||||
func (m *Model) WhereOrNull(columns ...string) *Model {
|
||||
model := m
|
||||
for _, column := range columns {
|
||||
model = m.WhereOr(fmt.Sprintf(`%s IS NULL`, m.db.GetCore().QuoteWord(column)))
|
||||
model = m.WhereOrf(`%s IS NULL`, m.db.GetCore().QuoteWord(column))
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
// WhereOrNotBetween builds `xxx NOT BETWEEN x AND y` statement in `OR` conditions.
|
||||
// WhereOrNotBetween builds `column NOT BETWEEN min AND max` statement in `OR` conditions.
|
||||
func (m *Model) WhereOrNotBetween(column string, min, max interface{}) *Model {
|
||||
return m.WhereOr(fmt.Sprintf(`%s NOT BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column)), min, max)
|
||||
return m.WhereOrf(`%s NOT BETWEEN ? AND ?`, m.db.GetCore().QuoteWord(column), min, max)
|
||||
}
|
||||
|
||||
// WhereOrNotLike builds `xxx NOT LIKE x` statement in `OR` conditions.
|
||||
// WhereOrNotLike builds `column NOT LIKE like` statement in `OR` conditions.
|
||||
func (m *Model) WhereOrNotLike(column string, like interface{}) *Model {
|
||||
return m.WhereOr(fmt.Sprintf(`%s NOT LIKE ?`, m.db.GetCore().QuoteWord(column)), like)
|
||||
return m.WhereOrf(`%s NOT LIKE ?`, m.db.GetCore().QuoteWord(column), like)
|
||||
}
|
||||
|
||||
// WhereOrNotIn builds `xxx NOT IN (x)` statement.
|
||||
// WhereOrNotIn builds `column NOT IN (in)` statement.
|
||||
func (m *Model) WhereOrNotIn(column string, in interface{}) *Model {
|
||||
return m.WhereOr(fmt.Sprintf(`%s NOT IN (?)`, m.db.GetCore().QuoteWord(column)), in)
|
||||
return m.WhereOrf(`%s NOT IN (?)`, m.db.GetCore().QuoteWord(column), in)
|
||||
}
|
||||
|
||||
// WhereOrNotNull builds `xxx IS NOT NULL` statement in `OR` conditions.
|
||||
// WhereOrNotNull builds `columns[0] IS NOT NULL OR columns[1] IS NOT NULL ...` statement in `OR` conditions.
|
||||
func (m *Model) WhereOrNotNull(columns ...string) *Model {
|
||||
model := m
|
||||
for _, column := range columns {
|
||||
model = m.WhereOr(fmt.Sprintf(`%s IS NOT NULL`, m.db.GetCore().QuoteWord(column)))
|
||||
model = m.WhereOrf(`%s IS NOT NULL`, m.db.GetCore().QuoteWord(column))
|
||||
}
|
||||
return model
|
||||
}
|
||||
@ -216,6 +277,9 @@ func (m *Model) Order(orderBy ...string) *Model {
|
||||
return m
|
||||
}
|
||||
model := m.getModel()
|
||||
if model.orderBy != "" {
|
||||
model.orderBy += ","
|
||||
}
|
||||
model.orderBy = m.db.GetCore().QuoteString(strings.Join(orderBy, " "))
|
||||
return model
|
||||
}
|
||||
@ -226,6 +290,9 @@ func (m *Model) OrderAsc(column string) *Model {
|
||||
return m
|
||||
}
|
||||
model := m.getModel()
|
||||
if model.orderBy != "" {
|
||||
model.orderBy += ","
|
||||
}
|
||||
model.orderBy = m.db.GetCore().QuoteWord(column) + " ASC"
|
||||
return model
|
||||
}
|
||||
@ -236,6 +303,9 @@ func (m *Model) OrderDesc(column string) *Model {
|
||||
return m
|
||||
}
|
||||
model := m.getModel()
|
||||
if model.orderBy != "" {
|
||||
model.orderBy += ","
|
||||
}
|
||||
model.orderBy = m.db.GetCore().QuoteWord(column) + " DESC"
|
||||
return model
|
||||
}
|
||||
@ -316,7 +386,7 @@ func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWh
|
||||
case whereHolderWhere:
|
||||
if conditionWhere == "" {
|
||||
newWhere, newArgs := formatWhere(
|
||||
m.db, v.where, v.args, m.option&OptionOmitEmpty > 0,
|
||||
m.db, v.where, v.args, m.option&OptionOmitEmpty > 0, m.schema, m.tables,
|
||||
)
|
||||
if len(newWhere) > 0 {
|
||||
conditionWhere = newWhere
|
||||
@ -328,7 +398,7 @@ func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWh
|
||||
|
||||
case whereHolderAnd:
|
||||
newWhere, newArgs := formatWhere(
|
||||
m.db, v.where, v.args, m.option&OptionOmitEmpty > 0,
|
||||
m.db, v.where, v.args, m.option&OptionOmitEmpty > 0, m.schema, m.tables,
|
||||
)
|
||||
if len(newWhere) > 0 {
|
||||
if len(conditionWhere) == 0 {
|
||||
@ -343,7 +413,7 @@ func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWh
|
||||
|
||||
case whereHolderOr:
|
||||
newWhere, newArgs := formatWhere(
|
||||
m.db, v.where, v.args, m.option&OptionOmitEmpty > 0,
|
||||
m.db, v.where, v.args, m.option&OptionOmitEmpty > 0, m.schema, m.tables,
|
||||
)
|
||||
if len(newWhere) > 0 {
|
||||
if len(conditionWhere) == 0 {
|
||||
@ -360,7 +430,13 @@ func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWh
|
||||
}
|
||||
// Soft deletion.
|
||||
softDeletingCondition := m.getConditionForSoftDeleting()
|
||||
if !m.unscoped && softDeletingCondition != "" {
|
||||
if m.rawSql != "" && conditionWhere != "" {
|
||||
if gstr.ContainsI(m.rawSql, " WHERE ") {
|
||||
conditionWhere = " AND " + conditionWhere
|
||||
} else {
|
||||
conditionWhere = " WHERE " + conditionWhere
|
||||
}
|
||||
} else if !m.unscoped && softDeletingCondition != "" {
|
||||
if conditionWhere == "" {
|
||||
conditionWhere = fmt.Sprintf(` WHERE %s`, softDeletingCondition)
|
||||
} else {
|
||||
@ -371,6 +447,7 @@ func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWh
|
||||
conditionWhere = " WHERE " + conditionWhere
|
||||
}
|
||||
}
|
||||
|
||||
// GROUP BY.
|
||||
if m.groupBy != "" {
|
||||
conditionExtra += " GROUP BY " + m.groupBy
|
||||
@ -378,7 +455,7 @@ func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWh
|
||||
// HAVING.
|
||||
if len(m.having) > 0 {
|
||||
havingStr, havingArgs := formatWhere(
|
||||
m.db, m.having[0], gconv.Interfaces(m.having[1]), m.option&OptionOmitEmpty > 0,
|
||||
m.db, m.having[0], gconv.Interfaces(m.having[1]), m.option&OptionOmitEmpty > 0, m.schema, m.tables,
|
||||
)
|
||||
if len(havingStr) > 0 {
|
||||
conditionExtra += " HAVING " + havingStr
|
||||
|
||||
@ -33,7 +33,7 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
|
||||
)
|
||||
// Soft deleting.
|
||||
if !m.unscoped && fieldNameDelete != "" {
|
||||
return m.db.GetCore().DoUpdate(
|
||||
return m.db.DoUpdate(
|
||||
m.GetCtx(),
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
@ -46,5 +46,5 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
|
||||
if !gstr.ContainsI(conditionStr, " WHERE ") {
|
||||
return nil, gerror.New("there should be WHERE condition statement for DELETE operation")
|
||||
}
|
||||
return m.db.GetCore().DoDelete(m.GetCtx(), m.getLink(true), m.tables, conditionStr, conditionArgs...)
|
||||
return m.db.DoDelete(m.GetCtx(), m.getLink(true), m.tables, conditionStr, conditionArgs...)
|
||||
}
|
||||
|
||||
@ -8,6 +8,8 @@ package gdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/gset"
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
@ -51,16 +53,20 @@ func (m *Model) Data(data ...interface{}) *Model {
|
||||
switch params := data[0].(type) {
|
||||
case Result:
|
||||
model.data = params.List()
|
||||
|
||||
case Record:
|
||||
model.data = params.Map()
|
||||
|
||||
case List:
|
||||
list := make(List, len(params))
|
||||
for k, v := range params {
|
||||
list[k] = gutil.MapCopy(v)
|
||||
}
|
||||
model.data = list
|
||||
|
||||
case Map:
|
||||
model.data = gutil.MapCopy(params)
|
||||
|
||||
default:
|
||||
var (
|
||||
rv = reflect.ValueOf(params)
|
||||
@ -100,6 +106,48 @@ func (m *Model) Data(data ...interface{}) *Model {
|
||||
return model
|
||||
}
|
||||
|
||||
// OnDuplicate sets the operations when columns conflicts occurs.
|
||||
// In MySQL, this is used for "ON DUPLICATE KEY UPDATE" statement.
|
||||
// The parameter `onDuplicate` can be type of string/Raw/*Raw/map/slice.
|
||||
// Example:
|
||||
// OnDuplicate("nickname, age")
|
||||
// OnDuplicate("nickname", "age")
|
||||
// OnDuplicate(g.Map{
|
||||
// "nickname": gdb.Raw("CONCAT('name_', VALUES(`nickname`))"),
|
||||
// })
|
||||
// OnDuplicate(g.Map{
|
||||
// "nickname": "passport",
|
||||
// })
|
||||
func (m *Model) OnDuplicate(onDuplicate ...interface{}) *Model {
|
||||
model := m.getModel()
|
||||
if len(onDuplicate) > 1 {
|
||||
model.onDuplicate = onDuplicate
|
||||
} else {
|
||||
model.onDuplicate = onDuplicate[0]
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
// OnDuplicateEx sets the excluding columns for operations when columns conflicts occurs.
|
||||
// In MySQL, this is used for "ON DUPLICATE KEY UPDATE" statement.
|
||||
// The parameter `onDuplicateEx` can be type of string/map/slice.
|
||||
// Example:
|
||||
// OnDuplicateEx("passport, password")
|
||||
// OnDuplicateEx("passport", "password")
|
||||
// OnDuplicateEx(g.Map{
|
||||
// "passport": "",
|
||||
// "password": "",
|
||||
// })
|
||||
func (m *Model) OnDuplicateEx(onDuplicateEx ...interface{}) *Model {
|
||||
model := m.getModel()
|
||||
if len(onDuplicateEx) > 1 {
|
||||
model.onDuplicateEx = onDuplicateEx
|
||||
} else {
|
||||
model.onDuplicateEx = onDuplicateEx[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.
|
||||
@ -156,7 +204,7 @@ func (m *Model) Save(data ...interface{}) (result sql.Result, err error) {
|
||||
}
|
||||
|
||||
// doInsertWithOption inserts data with option parameter.
|
||||
func (m *Model) doInsertWithOption(option int) (result sql.Result, err error) {
|
||||
func (m *Model) doInsertWithOption(insertOption int) (result sql.Result, err error) {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
m.checkAndRemoveCache()
|
||||
@ -166,68 +214,206 @@ func (m *Model) doInsertWithOption(option int) (result sql.Result, err error) {
|
||||
return nil, gerror.New("inserting into table with empty data")
|
||||
}
|
||||
var (
|
||||
list List
|
||||
nowString = gtime.Now().String()
|
||||
fieldNameCreate = m.getSoftFieldNameCreated()
|
||||
fieldNameUpdate = m.getSoftFieldNameUpdated()
|
||||
fieldNameDelete = m.getSoftFieldNameDeleted()
|
||||
)
|
||||
// Batch operation.
|
||||
if list, ok := m.data.(List); ok {
|
||||
batch := defaultBatchNumber
|
||||
if m.batch > 0 {
|
||||
batch = m.batch
|
||||
}
|
||||
newData, err := m.filterDataForInsertOrUpdate(list)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list = newData.(List)
|
||||
// Automatic handling for creating/updating time.
|
||||
if !m.unscoped && (fieldNameCreate != "" || fieldNameUpdate != "") {
|
||||
for k, v := range list {
|
||||
gutil.MapDelete(v, fieldNameCreate, fieldNameUpdate, fieldNameDelete)
|
||||
if fieldNameCreate != "" {
|
||||
v[fieldNameCreate] = nowString
|
||||
}
|
||||
if fieldNameUpdate != "" {
|
||||
v[fieldNameUpdate] = nowString
|
||||
}
|
||||
list[k] = v
|
||||
}
|
||||
}
|
||||
return m.db.GetCore().DoBatchInsert(
|
||||
m.GetCtx(),
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
newData,
|
||||
option,
|
||||
batch,
|
||||
)
|
||||
newData, err := m.filterDataForInsertOrUpdate(m.data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Single operation.
|
||||
if data, ok := m.data.(Map); ok {
|
||||
newData, err := m.filterDataForInsertOrUpdate(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
// It converts any data to List type for inserting.
|
||||
switch value := newData.(type) {
|
||||
case Result:
|
||||
list = value.List()
|
||||
|
||||
case Record:
|
||||
list = List{value.Map()}
|
||||
|
||||
case List:
|
||||
list = value
|
||||
for i, v := range list {
|
||||
list[i] = ConvertDataForTableRecord(v)
|
||||
}
|
||||
data = newData.(Map)
|
||||
// Automatic handling for creating/updating time.
|
||||
if !m.unscoped && (fieldNameCreate != "" || fieldNameUpdate != "") {
|
||||
gutil.MapDelete(data, fieldNameCreate, fieldNameUpdate, fieldNameDelete)
|
||||
|
||||
case Map:
|
||||
list = List{ConvertDataForTableRecord(value)}
|
||||
|
||||
default:
|
||||
var (
|
||||
rv = reflect.ValueOf(newData)
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
switch kind {
|
||||
// If it's slice type, it then converts it to List type.
|
||||
case reflect.Slice, reflect.Array:
|
||||
list = make(List, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
list[i] = ConvertDataForTableRecord(rv.Index(i).Interface())
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
list = List{ConvertDataForTableRecord(value)}
|
||||
|
||||
case reflect.Struct:
|
||||
if v, ok := value.(apiInterfaces); ok {
|
||||
var (
|
||||
array = v.Interfaces()
|
||||
)
|
||||
list = make(List, len(array))
|
||||
for i := 0; i < len(array); i++ {
|
||||
list[i] = ConvertDataForTableRecord(array[i])
|
||||
}
|
||||
} else {
|
||||
list = List{ConvertDataForTableRecord(value)}
|
||||
}
|
||||
|
||||
default:
|
||||
return result, gerror.New(fmt.Sprint("unsupported list type:", kind))
|
||||
}
|
||||
}
|
||||
|
||||
if len(list) < 1 {
|
||||
return result, gerror.New("data list cannot be empty")
|
||||
}
|
||||
|
||||
// Automatic handling for creating/updating time.
|
||||
if !m.unscoped && (fieldNameCreate != "" || fieldNameUpdate != "") {
|
||||
for k, v := range list {
|
||||
gutil.MapDelete(v, fieldNameCreate, fieldNameUpdate, fieldNameDelete)
|
||||
if fieldNameCreate != "" {
|
||||
data[fieldNameCreate] = nowString
|
||||
v[fieldNameCreate] = nowString
|
||||
}
|
||||
if fieldNameUpdate != "" {
|
||||
data[fieldNameUpdate] = nowString
|
||||
v[fieldNameUpdate] = nowString
|
||||
}
|
||||
list[k] = v
|
||||
}
|
||||
}
|
||||
// Format DoInsertOption, especially for "ON DUPLICATE KEY UPDATE" statement.
|
||||
columnNames := make([]string, 0, len(list[0]))
|
||||
for k, _ := range list[0] {
|
||||
columnNames = append(columnNames, k)
|
||||
}
|
||||
doInsertOption, err := m.formatDoInsertOption(insertOption, columnNames)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return m.db.DoInsert(m.GetCtx(), m.getLink(true), m.tables, list, doInsertOption)
|
||||
}
|
||||
|
||||
func (m *Model) formatDoInsertOption(insertOption int, columnNames []string) (option DoInsertOption, err error) {
|
||||
option = DoInsertOption{
|
||||
InsertOption: insertOption,
|
||||
BatchCount: m.getBatch(),
|
||||
}
|
||||
if insertOption == insertOptionSave {
|
||||
onDuplicateExKeys, err := m.formatOnDuplicateExKeys(m.onDuplicateEx)
|
||||
if err != nil {
|
||||
return option, err
|
||||
}
|
||||
var (
|
||||
onDuplicateExKeySet = gset.NewStrSetFrom(onDuplicateExKeys)
|
||||
)
|
||||
if m.onDuplicate != nil {
|
||||
switch m.onDuplicate.(type) {
|
||||
case Raw, *Raw:
|
||||
option.OnDuplicateStr = gconv.String(m.onDuplicate)
|
||||
|
||||
default:
|
||||
var (
|
||||
reflectValue = reflect.ValueOf(m.onDuplicate)
|
||||
reflectKind = reflectValue.Kind()
|
||||
)
|
||||
for reflectKind == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
switch reflectKind {
|
||||
case reflect.String:
|
||||
option.OnDuplicateMap = make(map[string]interface{})
|
||||
for _, v := range gstr.SplitAndTrim(reflectValue.String(), ",") {
|
||||
if onDuplicateExKeySet.Contains(v) {
|
||||
continue
|
||||
}
|
||||
option.OnDuplicateMap[v] = v
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
option.OnDuplicateMap = make(map[string]interface{})
|
||||
for k, v := range gconv.Map(m.onDuplicate) {
|
||||
if onDuplicateExKeySet.Contains(k) {
|
||||
continue
|
||||
}
|
||||
option.OnDuplicateMap[k] = v
|
||||
}
|
||||
|
||||
case reflect.Slice, reflect.Array:
|
||||
option.OnDuplicateMap = make(map[string]interface{})
|
||||
for _, v := range gconv.Strings(m.onDuplicate) {
|
||||
if onDuplicateExKeySet.Contains(v) {
|
||||
continue
|
||||
}
|
||||
option.OnDuplicateMap[v] = v
|
||||
}
|
||||
|
||||
default:
|
||||
return option, gerror.Newf(`unsupported OnDuplicate parameter type "%s"`, reflect.TypeOf(m.onDuplicate))
|
||||
}
|
||||
}
|
||||
} else if onDuplicateExKeySet.Size() > 0 {
|
||||
option.OnDuplicateMap = make(map[string]interface{})
|
||||
for _, v := range columnNames {
|
||||
if onDuplicateExKeySet.Contains(v) {
|
||||
continue
|
||||
}
|
||||
option.OnDuplicateMap[v] = v
|
||||
}
|
||||
}
|
||||
return m.db.GetCore().DoInsert(
|
||||
m.GetCtx(),
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
newData,
|
||||
option,
|
||||
)
|
||||
}
|
||||
return nil, gerror.New("inserting into table with invalid data type")
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Model) formatOnDuplicateExKeys(onDuplicateEx interface{}) ([]string, error) {
|
||||
if onDuplicateEx == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var (
|
||||
reflectValue = reflect.ValueOf(onDuplicateEx)
|
||||
reflectKind = reflectValue.Kind()
|
||||
)
|
||||
for reflectKind == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
switch reflectKind {
|
||||
case reflect.String:
|
||||
return gstr.SplitAndTrim(reflectValue.String(), ","), nil
|
||||
|
||||
case reflect.Map:
|
||||
return gutil.Keys(onDuplicateEx), nil
|
||||
|
||||
case reflect.Slice, reflect.Array:
|
||||
return gconv.Strings(onDuplicateEx), nil
|
||||
|
||||
default:
|
||||
return nil, gerror.Newf(`unsupported OnDuplicateEx parameter type "%s"`, reflect.TypeOf(onDuplicateEx))
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) getBatch() int {
|
||||
batch := defaultBatchNumber
|
||||
if m.batch > 0 {
|
||||
batch = m.batch
|
||||
}
|
||||
return batch
|
||||
}
|
||||
|
||||
@ -56,9 +56,9 @@ func (m *Model) InnerJoin(table ...string) *Model {
|
||||
// doJoin does "LEFT/RIGHT/INNER JOIN ... ON ..." statement on the model.
|
||||
// The parameter `table` can be joined table and its joined condition,
|
||||
// and also with its alias name, like:
|
||||
// Table("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
|
||||
// Table("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
|
||||
// Table("user", "u").InnerJoin("SELECT xxx FROM xxx AS a", "a.uid=u.uid")
|
||||
// Model("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
|
||||
// Model("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
|
||||
// Model("user", "u").InnerJoin("SELECT xxx FROM xxx AS a", "a.uid=u.uid")
|
||||
// Related issues:
|
||||
// https://github.com/gogf/gf/issues/1024
|
||||
func (m *Model) doJoin(operator string, table ...string) *Model {
|
||||
|
||||
@ -8,6 +8,7 @@ package gdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/container/gset"
|
||||
@ -18,11 +19,6 @@ import (
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
)
|
||||
|
||||
const (
|
||||
queryTypeNormal = "NormalQuery"
|
||||
queryTypeCount = "CountQuery"
|
||||
)
|
||||
|
||||
// Select is alias of Model.All.
|
||||
// See Model.All.
|
||||
// Deprecated, use All instead.
|
||||
@ -200,6 +196,15 @@ func (m *Model) Array(fieldsAndWhere ...interface{}) ([]Value, error) {
|
||||
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.
|
||||
//
|
||||
// Deprecated, use Scan instead.
|
||||
func (m *Model) Struct(pointer interface{}, where ...interface{}) error {
|
||||
return m.doStruct(pointer, where...)
|
||||
}
|
||||
|
||||
// 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.
|
||||
@ -207,24 +212,38 @@ func (m *Model) Array(fieldsAndWhere ...interface{}) ([]Value, error) {
|
||||
// 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 and `pointer` is not nil.
|
||||
// Note that it returns sql.ErrNoRows if the given parameter `pointer` pointed to a variable that has
|
||||
// default value and there's no record retrieved with the given conditions from table.
|
||||
//
|
||||
// Eg:
|
||||
// Example:
|
||||
// user := new(User)
|
||||
// err := db.Model("user").Where("id", 1).Struct(user)
|
||||
// err := db.Model("user").Where("id", 1).Scan(user)
|
||||
//
|
||||
// user := (*User)(nil)
|
||||
// err := db.Model("user").Where("id", 1).Struct(&user)
|
||||
func (m *Model) Struct(pointer interface{}, where ...interface{}) error {
|
||||
one, err := m.One(where...)
|
||||
// err := db.Model("user").Where("id", 1).Scan(&user)
|
||||
func (m *Model) doStruct(pointer interface{}, where ...interface{}) error {
|
||||
model := m
|
||||
// Auto selecting fields by struct attributes.
|
||||
if model.fieldsEx == "" && (model.fields == "" || model.fields == "*") {
|
||||
model = m.Fields(pointer)
|
||||
}
|
||||
one, err := model.One(where...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = one.Struct(pointer); err != nil {
|
||||
return err
|
||||
}
|
||||
return m.doWithScanStruct(pointer)
|
||||
return model.doWithScanStruct(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.
|
||||
//
|
||||
// Deprecated, use Scan instead.
|
||||
func (m *Model) Structs(pointer interface{}, where ...interface{}) error {
|
||||
return m.doStructs(pointer, where...)
|
||||
}
|
||||
|
||||
// Structs retrieves records from table and converts them into given struct slice.
|
||||
@ -234,37 +253,45 @@ func (m *Model) Struct(pointer interface{}, where ...interface{}) error {
|
||||
// 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 and `pointer` is not empty.
|
||||
// Note that it returns sql.ErrNoRows if the given parameter `pointer` pointed to a variable that has
|
||||
// default value and there's no record retrieved with the given conditions from table.
|
||||
//
|
||||
// Eg:
|
||||
// Example:
|
||||
// users := ([]User)(nil)
|
||||
// err := db.Model("user").Structs(&users)
|
||||
// err := db.Model("user").Scan(&users)
|
||||
//
|
||||
// users := ([]*User)(nil)
|
||||
// err := db.Model("user").Structs(&users)
|
||||
func (m *Model) Structs(pointer interface{}, where ...interface{}) error {
|
||||
all, err := m.All(where...)
|
||||
// err := db.Model("user").Scan(&users)
|
||||
func (m *Model) doStructs(pointer interface{}, where ...interface{}) error {
|
||||
model := m
|
||||
// Auto selecting fields by struct attributes.
|
||||
if model.fieldsEx == "" && (model.fields == "" || model.fields == "*") {
|
||||
model = m.Fields(
|
||||
reflect.New(
|
||||
reflect.ValueOf(pointer).Elem().Type().Elem(),
|
||||
).Interface(),
|
||||
)
|
||||
}
|
||||
all, err := model.All(where...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = all.Structs(pointer); err != nil {
|
||||
return err
|
||||
}
|
||||
return m.doWithScanStructs(pointer)
|
||||
return model.doWithScanStructs(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.
|
||||
// It calls function doStruct if `pointer` is type of *struct/**struct.
|
||||
// It calls function doStructs if `pointer` is type of *[]struct/*[]*struct.
|
||||
//
|
||||
// The optional parameter `where` is the same as the parameter of Model.Where function,
|
||||
// see Model.Where.
|
||||
// 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.
|
||||
// Note that it returns sql.ErrNoRows if the given parameter `pointer` pointed to a variable that has
|
||||
// default value and there's no record retrieved with the given conditions from table.
|
||||
//
|
||||
// Eg:
|
||||
// Example:
|
||||
// user := new(User)
|
||||
// err := db.Model("user").Where("id", 1).Scan(user)
|
||||
//
|
||||
@ -277,16 +304,35 @@ func (m *Model) Structs(pointer interface{}, where ...interface{}) error {
|
||||
// users := ([]*User)(nil)
|
||||
// err := db.Model("user").Scan(&users)
|
||||
func (m *Model) Scan(pointer interface{}, where ...interface{}) error {
|
||||
var reflectType reflect.Type
|
||||
var (
|
||||
reflectValue reflect.Value
|
||||
reflectKind reflect.Kind
|
||||
)
|
||||
if v, ok := pointer.(reflect.Value); ok {
|
||||
reflectType = v.Type()
|
||||
reflectValue = v
|
||||
} else {
|
||||
reflectType = reflect.TypeOf(pointer)
|
||||
reflectValue = reflect.ValueOf(pointer)
|
||||
}
|
||||
if gstr.Contains(reflectType.String(), "[]") {
|
||||
return m.Structs(pointer, where...)
|
||||
|
||||
reflectKind = reflectValue.Kind()
|
||||
if reflectKind != reflect.Ptr {
|
||||
return gerror.New(`the parameter "pointer" for function Scan should type of pointer`)
|
||||
}
|
||||
for reflectKind == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
|
||||
switch reflectKind {
|
||||
case reflect.Slice, reflect.Array:
|
||||
return m.doStructs(pointer, where...)
|
||||
|
||||
case reflect.Struct, reflect.Invalid:
|
||||
return m.doStruct(pointer, where...)
|
||||
|
||||
default:
|
||||
return gerror.New(`element of parameter "pointer" for function Scan should type of struct/*struct/[]struct/[]*struct`)
|
||||
}
|
||||
return m.Struct(pointer, where...)
|
||||
}
|
||||
|
||||
// ScanList converts `r` to struct slice which contains other complex struct attributes.
|
||||
@ -458,6 +504,16 @@ func (m *Model) FindScan(pointer interface{}, where ...interface{}) error {
|
||||
return m.Scan(pointer)
|
||||
}
|
||||
|
||||
// Union does "(SELECT xxx FROM xxx) UNION (SELECT xxx FROM xxx) ..." statement for the model.
|
||||
func (m *Model) Union(unions ...*Model) *Model {
|
||||
return m.db.Union(unions...)
|
||||
}
|
||||
|
||||
// UnionAll does "(SELECT xxx FROM xxx) UNION ALL (SELECT xxx FROM xxx) ..." statement for the model.
|
||||
func (m *Model) UnionAll(unions ...*Model) *Model {
|
||||
return m.db.UnionAll(unions...)
|
||||
}
|
||||
|
||||
// doGetAllBySql does the select statement on the database.
|
||||
func (m *Model) doGetAllBySql(sql string, args ...interface{}) (result Result, err error) {
|
||||
cacheKey := ""
|
||||
@ -483,7 +539,7 @@ func (m *Model) doGetAllBySql(sql string, args ...interface{}) (result Result, e
|
||||
}
|
||||
}
|
||||
}
|
||||
result, err = m.db.GetCore().DoGetAll(
|
||||
result, err = m.db.DoGetAll(
|
||||
m.GetCtx(), m.getLink(false), sql, m.mergeArguments(args)...,
|
||||
)
|
||||
// Cache the result.
|
||||
@ -501,7 +557,7 @@ func (m *Model) doGetAllBySql(sql string, args ...interface{}) (result Result, e
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (m *Model) getFormattedSqlAndArgs(queryType string, limit1 bool) (sqlWithHolder string, holderArgs []interface{}) {
|
||||
func (m *Model) getFormattedSqlAndArgs(queryType int, limit1 bool) (sqlWithHolder string, holderArgs []interface{}) {
|
||||
switch queryType {
|
||||
case queryTypeCount:
|
||||
countFields := "COUNT(1)"
|
||||
@ -510,6 +566,11 @@ func (m *Model) getFormattedSqlAndArgs(queryType string, limit1 bool) (sqlWithHo
|
||||
// DISTINCT t.user_id uid
|
||||
countFields = fmt.Sprintf(`COUNT(%s%s)`, m.distinct, m.fields)
|
||||
}
|
||||
// Raw SQL Model.
|
||||
if m.rawSql != "" {
|
||||
sqlWithHolder = fmt.Sprintf("SELECT %s FROM (%s) AS T", countFields, m.rawSql)
|
||||
return sqlWithHolder, nil
|
||||
}
|
||||
conditionWhere, conditionExtra, conditionArgs := m.formatCondition(false, true)
|
||||
sqlWithHolder = fmt.Sprintf("SELECT %s FROM %s%s", countFields, m.tables, conditionWhere+conditionExtra)
|
||||
if len(m.groupBy) > 0 {
|
||||
@ -519,6 +580,15 @@ func (m *Model) getFormattedSqlAndArgs(queryType string, limit1 bool) (sqlWithHo
|
||||
|
||||
default:
|
||||
conditionWhere, conditionExtra, conditionArgs := m.formatCondition(limit1, false)
|
||||
// Raw SQL Model, especially for UNION/UNION ALL featured SQL.
|
||||
if m.rawSql != "" {
|
||||
sqlWithHolder = fmt.Sprintf(
|
||||
"%s%s",
|
||||
m.rawSql,
|
||||
conditionWhere+conditionExtra,
|
||||
)
|
||||
return sqlWithHolder, conditionArgs
|
||||
}
|
||||
// DO NOT quote the m.fields where, in case of fields like:
|
||||
// DISTINCT t.user_id uid
|
||||
sqlWithHolder = fmt.Sprintf(
|
||||
|
||||
@ -173,6 +173,9 @@ func (m *Model) getConditionOfTableStringForSoftDeleting(s string) string {
|
||||
|
||||
// getPrimaryTableName parses and returns the primary table name.
|
||||
func (m *Model) getPrimaryTableName() string {
|
||||
if m.tables == "" {
|
||||
return ""
|
||||
}
|
||||
array1 := gstr.SplitAndTrim(m.tables, ",")
|
||||
array2 := gstr.SplitAndTrim(array1[0], " ")
|
||||
array3 := gstr.SplitAndTrim(array2[0], ".")
|
||||
|
||||
@ -82,7 +82,7 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
if !gstr.ContainsI(conditionStr, " WHERE ") {
|
||||
return nil, gerror.New("there should be WHERE condition statement for UPDATE operation")
|
||||
}
|
||||
return m.db.GetCore().DoUpdate(
|
||||
return m.db.DoUpdate(
|
||||
m.GetCtx(),
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
@ -93,17 +93,19 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
}
|
||||
|
||||
// Increment increments a column's value by a given amount.
|
||||
func (m *Model) Increment(column string, amount float64) (sql.Result, error) {
|
||||
// The parameter `amount` can be type of float or integer.
|
||||
func (m *Model) Increment(column string, amount interface{}) (sql.Result, error) {
|
||||
return m.getModel().Data(column, &Counter{
|
||||
Field: column,
|
||||
Value: amount,
|
||||
Value: gconv.Float64(amount),
|
||||
}).Update()
|
||||
}
|
||||
|
||||
// Decrement decrements a column's value by a given amount.
|
||||
func (m *Model) Decrement(column string, amount float64) (sql.Result, error) {
|
||||
// The parameter `amount` can be type of float or integer.
|
||||
func (m *Model) Decrement(column string, amount interface{}) (sql.Result, error) {
|
||||
return m.getModel().Data(column, &Counter{
|
||||
Field: column,
|
||||
Value: -amount,
|
||||
Value: -gconv.Float64(amount),
|
||||
}).Update()
|
||||
}
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). 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
|
||||
|
||||
// Deprecated, use Json instead.
|
||||
func (r Record) ToJson() string {
|
||||
return r.Json()
|
||||
}
|
||||
|
||||
// Deprecated, use Xml instead.
|
||||
func (r Record) ToXml(rootTag ...string) string {
|
||||
return r.Xml(rootTag...)
|
||||
}
|
||||
|
||||
// Deprecated, use Map instead.
|
||||
func (r Record) ToMap() Map {
|
||||
return r.Map()
|
||||
}
|
||||
|
||||
// Deprecated, use Struct instead.
|
||||
func (r Record) ToStruct(pointer interface{}) error {
|
||||
return r.Struct(pointer)
|
||||
}
|
||||
@ -153,7 +153,7 @@ func (r Result) MapKeyUint(key string) map[uint]Map {
|
||||
return m
|
||||
}
|
||||
|
||||
// RecordKeyInt converts `r` to a map[int]Record of which key is specified by `key`.
|
||||
// RecordKeyStr converts `r` to a map[string]Record of which key is specified by `key`.
|
||||
func (r Result) RecordKeyStr(key string) map[string]Record {
|
||||
m := make(map[string]Record)
|
||||
for _, item := range r {
|
||||
|
||||
@ -1,57 +0,0 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). 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
|
||||
|
||||
// Deprecated, use Json instead.
|
||||
func (r Result) ToJson() string {
|
||||
return r.Json()
|
||||
}
|
||||
|
||||
// Deprecated, use Xml instead.
|
||||
func (r Result) ToXml(rootTag ...string) string {
|
||||
return r.Xml(rootTag...)
|
||||
}
|
||||
|
||||
// Deprecated, use List instead.
|
||||
func (r Result) ToList() List {
|
||||
return r.List()
|
||||
}
|
||||
|
||||
// Deprecated, use MapKeyStr instead.
|
||||
func (r Result) ToStringMap(key string) map[string]Map {
|
||||
return r.MapKeyStr(key)
|
||||
}
|
||||
|
||||
// Deprecated, use MapKetInt instead.
|
||||
func (r Result) ToIntMap(key string) map[int]Map {
|
||||
return r.MapKeyInt(key)
|
||||
}
|
||||
|
||||
// Deprecated, use MapKeyUint instead.
|
||||
func (r Result) ToUintMap(key string) map[uint]Map {
|
||||
return r.MapKeyUint(key)
|
||||
}
|
||||
|
||||
// Deprecated, use RecordKeyStr instead.
|
||||
func (r Result) ToStringRecord(key string) map[string]Record {
|
||||
return r.RecordKeyStr(key)
|
||||
}
|
||||
|
||||
// Deprecated, use RecordKetInt instead.
|
||||
func (r Result) ToIntRecord(key string) map[int]Record {
|
||||
return r.RecordKeyInt(key)
|
||||
}
|
||||
|
||||
// Deprecated, use RecordKetUint instead.
|
||||
func (r Result) ToUintRecord(key string) map[uint]Record {
|
||||
return r.RecordKeyUint(key)
|
||||
}
|
||||
|
||||
// Deprecated, use Structs instead.
|
||||
func (r Result) ToStructs(pointer interface{}) (err error) {
|
||||
return r.Structs(pointer)
|
||||
}
|
||||
@ -18,9 +18,9 @@ import (
|
||||
|
||||
// MyDriver is a custom database driver, which is used for testing only.
|
||||
// For simplifying the unit testing case purpose, MyDriver struct inherits the mysql driver
|
||||
// gdb.DriverMysql and overwrites its function HandleSqlBeforeCommit.
|
||||
// So if there's any sql execution, it goes through MyDriver.HandleSqlBeforeCommit firstly and
|
||||
// then gdb.DriverMysql.HandleSqlBeforeCommit.
|
||||
// gdb.DriverMysql and overwrites its function DoCommit.
|
||||
// So if there's any sql execution, it goes through MyDriver.DoCommit firstly and
|
||||
// then gdb.DriverMysql.DoCommit.
|
||||
// You can call it sql "HOOK" or "HiJack" as your will.
|
||||
type MyDriver struct {
|
||||
*gdb.DriverMysql
|
||||
@ -41,11 +41,11 @@ func (d *MyDriver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// HandleSqlBeforeCommit handles the sql before posts it to database.
|
||||
// DoCommit handles the sql before posts it to database.
|
||||
// It here overwrites the same method of gdb.DriverMysql and makes some custom changes.
|
||||
func (d *MyDriver) HandleSqlBeforeCommit(ctx context.Context, link gdb.Link, sql string, args []interface{}) (string, []interface{}) {
|
||||
func (d *MyDriver) DoCommit(ctx context.Context, link gdb.Link, sql string, args []interface{}) (string, []interface{}) {
|
||||
latestSqlString.Set(sql)
|
||||
return d.DriverMysql.HandleSqlBeforeCommit(ctx, link, sql, args)
|
||||
return d.DriverMysql.DoCommit(ctx, link, sql, args)
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@ -195,7 +195,7 @@ func createInitTableWithDb(db gdb.DB, table ...string) (name string) {
|
||||
})
|
||||
}
|
||||
|
||||
result, err := db.BatchInsert(name, array.Slice())
|
||||
result, err := db.Insert(name, array.Slice())
|
||||
gtest.AssertNil(err)
|
||||
|
||||
n, e := result.RowsAffected()
|
||||
|
||||
@ -8,11 +8,11 @@ package gdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/go-sql-driver/mysql"
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"github.com/gogf/gf/os/gcmd"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"github.com/gogf/mysql"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
@ -329,7 +329,7 @@ func Test_DB_BatchInsert(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
r, err := db.BatchInsert(table, g.List{
|
||||
r, err := db.Insert(table, g.List{
|
||||
{
|
||||
"id": 2,
|
||||
"passport": "t2",
|
||||
@ -357,7 +357,7 @@ func Test_DB_BatchInsert(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
// []interface{}
|
||||
r, err := db.BatchInsert(table, g.Slice{
|
||||
r, err := db.Insert(table, g.Slice{
|
||||
g.Map{
|
||||
"id": 2,
|
||||
"passport": "t2",
|
||||
@ -382,7 +382,7 @@ func Test_DB_BatchInsert(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
result, err := db.BatchInsert(table, g.Map{
|
||||
result, err := db.Insert(table, g.Map{
|
||||
"id": 1,
|
||||
"passport": "t1",
|
||||
"password": "p1",
|
||||
@ -416,7 +416,7 @@ func Test_DB_BatchInsert_Struct(t *testing.T) {
|
||||
NickName: "T1",
|
||||
CreateTime: gtime.Now(),
|
||||
}
|
||||
result, err := db.BatchInsert(table, user)
|
||||
result, err := db.Insert(table, user)
|
||||
t.AssertNil(err)
|
||||
n, _ := result.RowsAffected()
|
||||
t.Assert(n, 1)
|
||||
@ -1283,7 +1283,7 @@ func Test_DB_Prefix(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
result, err := db.BatchInsert(name, array.Slice())
|
||||
result, err := db.Insert(name, array.Slice())
|
||||
t.AssertNil(err)
|
||||
|
||||
n, e := result.RowsAffected()
|
||||
|
||||
@ -899,7 +899,7 @@ func Test_Model_Struct(t *testing.T) {
|
||||
CreateTime gtime.Time
|
||||
}
|
||||
user := new(User)
|
||||
err := db.Model(table).Where("id=1").Struct(user)
|
||||
err := db.Model(table).Where("id=1").Scan(user)
|
||||
t.AssertNil(err)
|
||||
t.Assert(user.NickName, "name_1")
|
||||
t.Assert(user.CreateTime.String(), "2018-10-24 10:00:00")
|
||||
@ -913,7 +913,7 @@ func Test_Model_Struct(t *testing.T) {
|
||||
CreateTime *gtime.Time
|
||||
}
|
||||
user := new(User)
|
||||
err := db.Model(table).Where("id=1").Struct(user)
|
||||
err := db.Model(table).Where("id=1").Scan(user)
|
||||
t.AssertNil(err)
|
||||
t.Assert(user.NickName, "name_1")
|
||||
t.Assert(user.CreateTime.String(), "2018-10-24 10:00:00")
|
||||
@ -928,7 +928,7 @@ func Test_Model_Struct(t *testing.T) {
|
||||
CreateTime *gtime.Time
|
||||
}
|
||||
user := (*User)(nil)
|
||||
err := db.Model(table).Where("id=1").Struct(&user)
|
||||
err := db.Model(table).Where("id=1").Scan(&user)
|
||||
t.AssertNil(err)
|
||||
t.Assert(user.NickName, "name_1")
|
||||
t.Assert(user.CreateTime.String(), "2018-10-24 10:00:00")
|
||||
@ -960,7 +960,7 @@ func Test_Model_Struct(t *testing.T) {
|
||||
CreateTime *gtime.Time
|
||||
}
|
||||
user := new(User)
|
||||
err := db.Model(table).Where("id=-1").Struct(user)
|
||||
err := db.Model(table).Where("id=-1").Scan(user)
|
||||
t.Assert(err, sql.ErrNoRows)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
@ -972,7 +972,7 @@ func Test_Model_Struct(t *testing.T) {
|
||||
CreateTime *gtime.Time
|
||||
}
|
||||
var user *User
|
||||
err := db.Model(table).Where("id=-1").Struct(&user)
|
||||
err := db.Model(table).Where("id=-1").Scan(&user)
|
||||
t.AssertNil(err)
|
||||
})
|
||||
}
|
||||
@ -992,7 +992,7 @@ func Test_Model_Struct_CustomType(t *testing.T) {
|
||||
CreateTime gtime.Time
|
||||
}
|
||||
user := new(User)
|
||||
err := db.Model(table).Where("id=1").Struct(user)
|
||||
err := db.Model(table).Where("id=1").Scan(user)
|
||||
t.AssertNil(err)
|
||||
t.Assert(user.NickName, "name_1")
|
||||
t.Assert(user.CreateTime.String(), "2018-10-24 10:00:00")
|
||||
@ -1012,7 +1012,7 @@ func Test_Model_Structs(t *testing.T) {
|
||||
CreateTime gtime.Time
|
||||
}
|
||||
var users []User
|
||||
err := db.Model(table).Order("id asc").Structs(&users)
|
||||
err := db.Model(table).Order("id asc").Scan(&users)
|
||||
if err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
@ -1035,7 +1035,7 @@ func Test_Model_Structs(t *testing.T) {
|
||||
CreateTime *gtime.Time
|
||||
}
|
||||
var users []*User
|
||||
err := db.Model(table).Order("id asc").Structs(&users)
|
||||
err := db.Model(table).Order("id asc").Scan(&users)
|
||||
if err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
@ -1081,38 +1081,40 @@ func Test_Model_Structs(t *testing.T) {
|
||||
CreateTime *gtime.Time
|
||||
}
|
||||
var users []*User
|
||||
err := db.Model(table).Where("id<0").Structs(&users)
|
||||
err := db.Model(table).Where("id<0").Scan(&users)
|
||||
t.AssertNil(err)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_StructsWithJsonTag(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Uid int `json:"id"`
|
||||
Passport string
|
||||
Password string
|
||||
Name string `json:"nick_name"`
|
||||
Time gtime.Time `json:"create_time"`
|
||||
}
|
||||
var users []User
|
||||
err := db.Model(table).Order("id asc").Structs(&users)
|
||||
if err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
t.Assert(len(users), TableSize)
|
||||
t.Assert(users[0].Uid, 1)
|
||||
t.Assert(users[1].Uid, 2)
|
||||
t.Assert(users[2].Uid, 3)
|
||||
t.Assert(users[0].Name, "name_1")
|
||||
t.Assert(users[1].Name, "name_2")
|
||||
t.Assert(users[2].Name, "name_3")
|
||||
t.Assert(users[0].Time.String(), "2018-10-24 10:00:00")
|
||||
})
|
||||
}
|
||||
// JSON tag is only used for JSON Marshal/Unmarshal, DO NOT use it in multiple purposes!
|
||||
//func Test_Model_StructsWithJsonTag(t *testing.T) {
|
||||
// table := createInitTable()
|
||||
// defer dropTable(table)
|
||||
//
|
||||
// db.SetDebug(true)
|
||||
// gtest.C(t, func(t *gtest.T) {
|
||||
// type User struct {
|
||||
// Uid int `json:"id"`
|
||||
// Passport string
|
||||
// Password string
|
||||
// Name string `json:"nick_name"`
|
||||
// Time gtime.Time `json:"create_time"`
|
||||
// }
|
||||
// var users []User
|
||||
// err := db.Model(table).Order("id asc").Scan(&users)
|
||||
// if err != nil {
|
||||
// gtest.Error(err)
|
||||
// }
|
||||
// t.Assert(len(users), TableSize)
|
||||
// t.Assert(users[0].Uid, 1)
|
||||
// t.Assert(users[1].Uid, 2)
|
||||
// t.Assert(users[2].Uid, 3)
|
||||
// t.Assert(users[0].Name, "name_1")
|
||||
// t.Assert(users[1].Name, "name_2")
|
||||
// t.Assert(users[2].Name, "name_3")
|
||||
// t.Assert(users[0].Time.String(), "2018-10-24 10:00:00")
|
||||
// })
|
||||
//}
|
||||
|
||||
func Test_Model_Scan(t *testing.T) {
|
||||
table := createInitTable()
|
||||
@ -1469,11 +1471,11 @@ func Test_Model_Where(t *testing.T) {
|
||||
t.Assert(len(result), 3)
|
||||
t.Assert(result[0]["id"].Int(), 1)
|
||||
})
|
||||
// struct
|
||||
// struct, automatic mapping and filtering.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int `json:"id"`
|
||||
Nickname string `gconv:"nickname"`
|
||||
Id int
|
||||
Nickname string
|
||||
}
|
||||
result, err := db.Model(table).Where(User{3, "name_3"}).One()
|
||||
t.AssertNil(err)
|
||||
@ -3098,7 +3100,7 @@ func Test_TimeZoneInsert(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
_, _ = db.Model(tableName).Unscoped().Insert(u)
|
||||
userEntity := &User{}
|
||||
err := db.Model(tableName).Where("id", 1).Unscoped().Struct(&userEntity)
|
||||
err := db.Model(tableName).Where("id", 1).Unscoped().Scan(&userEntity)
|
||||
t.AssertNil(err)
|
||||
t.Assert(userEntity.CreatedAt.String(), "2020-11-22 04:23:45")
|
||||
t.Assert(userEntity.UpdatedAt.String(), "2020-11-22 05:23:45")
|
||||
@ -3129,7 +3131,7 @@ func Test_Model_Fields_Map_Struct(t *testing.T) {
|
||||
XXX_TYPE int
|
||||
}
|
||||
var a = A{}
|
||||
err := db.Model(table).Fields(a).Where("id", 1).Struct(&a)
|
||||
err := db.Model(table).Fields(a).Where("id", 1).Scan(&a)
|
||||
t.AssertNil(err)
|
||||
t.Assert(a.ID, 1)
|
||||
t.Assert(a.PASSPORT, "user_1")
|
||||
@ -3143,7 +3145,7 @@ func Test_Model_Fields_Map_Struct(t *testing.T) {
|
||||
XXX_TYPE int
|
||||
}
|
||||
var a *A
|
||||
err := db.Model(table).Fields(a).Where("id", 1).Struct(&a)
|
||||
err := db.Model(table).Fields(a).Where("id", 1).Scan(&a)
|
||||
t.AssertNil(err)
|
||||
t.Assert(a.ID, 1)
|
||||
t.Assert(a.PASSPORT, "user_1")
|
||||
@ -3157,7 +3159,7 @@ func Test_Model_Fields_Map_Struct(t *testing.T) {
|
||||
XXX_TYPE int
|
||||
}
|
||||
var a *A
|
||||
err := db.Model(table).Fields(&a).Where("id", 1).Struct(&a)
|
||||
err := db.Model(table).Fields(&a).Where("id", 1).Scan(&a)
|
||||
t.AssertNil(err)
|
||||
t.Assert(a.ID, 1)
|
||||
t.Assert(a.PASSPORT, "user_1")
|
||||
@ -3366,6 +3368,104 @@ func Test_Model_WhereOrNotNull(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_WhereLT(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Model(table).WhereLT("id", 3).OrderAsc("id").All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 2)
|
||||
t.Assert(result[0]["id"], 1)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_WhereLTE(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Model(table).WhereLTE("id", 3).OrderAsc("id").All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 3)
|
||||
t.Assert(result[0]["id"], 1)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_WhereGT(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Model(table).WhereGT("id", 8).OrderAsc("id").All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 2)
|
||||
t.Assert(result[0]["id"], 9)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_WhereGTE(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Model(table).WhereGTE("id", 8).OrderAsc("id").All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 3)
|
||||
t.Assert(result[0]["id"], 8)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_WhereOrLT(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Model(table).WhereLT("id", 3).WhereOrLT("id", 4).OrderAsc("id").All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 3)
|
||||
t.Assert(result[0]["id"], 1)
|
||||
t.Assert(result[2]["id"], 3)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_WhereOrLTE(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Model(table).WhereLTE("id", 3).WhereOrLTE("id", 4).OrderAsc("id").All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 4)
|
||||
t.Assert(result[0]["id"], 1)
|
||||
t.Assert(result[3]["id"], 4)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_WhereOrGT(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Model(table).WhereGT("id", 8).WhereOrGT("id", 7).OrderAsc("id").All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 3)
|
||||
t.Assert(result[0]["id"], 8)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_WhereOrGTE(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Model(table).WhereGTE("id", 8).WhereOrGTE("id", 7).OrderAsc("id").All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 4)
|
||||
t.Assert(result[0]["id"], 7)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Min_Max_Avg_Sum(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
@ -3453,3 +3553,179 @@ func Test_Model_Increment_Decrement(t *testing.T) {
|
||||
t.Assert(count, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_OnDuplicate(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
// string.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"passport": "pp1",
|
||||
"password": "pw1",
|
||||
"nickname": "n1",
|
||||
"create_time": "2016-06-06",
|
||||
}
|
||||
_, err := db.Model(table).OnDuplicate("passport,password").Data(data).Save()
|
||||
t.AssertNil(err)
|
||||
one, err := db.Model(table).FindOne(1)
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], data["passport"])
|
||||
t.Assert(one["password"], data["password"])
|
||||
t.Assert(one["nickname"], "name_1")
|
||||
})
|
||||
|
||||
// slice.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"passport": "pp1",
|
||||
"password": "pw1",
|
||||
"nickname": "n1",
|
||||
"create_time": "2016-06-06",
|
||||
}
|
||||
_, err := db.Model(table).OnDuplicate(g.Slice{"passport", "password"}).Data(data).Save()
|
||||
t.AssertNil(err)
|
||||
one, err := db.Model(table).FindOne(1)
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], data["passport"])
|
||||
t.Assert(one["password"], data["password"])
|
||||
t.Assert(one["nickname"], "name_1")
|
||||
})
|
||||
|
||||
// map.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"passport": "pp1",
|
||||
"password": "pw1",
|
||||
"nickname": "n1",
|
||||
"create_time": "2016-06-06",
|
||||
}
|
||||
_, err := db.Model(table).OnDuplicate(g.Map{
|
||||
"passport": "nickname",
|
||||
"password": "nickname",
|
||||
}).Data(data).Save()
|
||||
t.AssertNil(err)
|
||||
one, err := db.Model(table).FindOne(1)
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], data["nickname"])
|
||||
t.Assert(one["password"], data["nickname"])
|
||||
t.Assert(one["nickname"], "name_1")
|
||||
})
|
||||
|
||||
// map+raw.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.MapStrStr{
|
||||
"id": "1",
|
||||
"passport": "pp1",
|
||||
"password": "pw1",
|
||||
"nickname": "n1",
|
||||
"create_time": "2016-06-06",
|
||||
}
|
||||
_, err := db.Model(table).OnDuplicate(g.Map{
|
||||
"passport": gdb.Raw("CONCAT(VALUES(`passport`), '1')"),
|
||||
"password": gdb.Raw("CONCAT(VALUES(`password`), '2')"),
|
||||
}).Data(data).Save()
|
||||
t.AssertNil(err)
|
||||
one, err := db.Model(table).FindOne(1)
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], data["passport"]+"1")
|
||||
t.Assert(one["password"], data["password"]+"2")
|
||||
t.Assert(one["nickname"], "name_1")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_OnDuplicateEx(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
// string.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"passport": "pp1",
|
||||
"password": "pw1",
|
||||
"nickname": "n1",
|
||||
"create_time": "2016-06-06",
|
||||
}
|
||||
_, err := db.Model(table).OnDuplicateEx("nickname,create_time").Data(data).Save()
|
||||
t.AssertNil(err)
|
||||
one, err := db.Model(table).FindOne(1)
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], data["passport"])
|
||||
t.Assert(one["password"], data["password"])
|
||||
t.Assert(one["nickname"], "name_1")
|
||||
})
|
||||
|
||||
// slice.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"passport": "pp1",
|
||||
"password": "pw1",
|
||||
"nickname": "n1",
|
||||
"create_time": "2016-06-06",
|
||||
}
|
||||
_, err := db.Model(table).OnDuplicateEx(g.Slice{"nickname", "create_time"}).Data(data).Save()
|
||||
t.AssertNil(err)
|
||||
one, err := db.Model(table).FindOne(1)
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], data["passport"])
|
||||
t.Assert(one["password"], data["password"])
|
||||
t.Assert(one["nickname"], "name_1")
|
||||
})
|
||||
|
||||
// map.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"passport": "pp1",
|
||||
"password": "pw1",
|
||||
"nickname": "n1",
|
||||
"create_time": "2016-06-06",
|
||||
}
|
||||
_, err := db.Model(table).OnDuplicateEx(g.Map{
|
||||
"nickname": "nickname",
|
||||
"create_time": "nickname",
|
||||
}).Data(data).Save()
|
||||
t.AssertNil(err)
|
||||
one, err := db.Model(table).FindOne(1)
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], data["passport"])
|
||||
t.Assert(one["password"], data["password"])
|
||||
t.Assert(one["nickname"], "name_1")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Raw(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
all, err := db.
|
||||
Raw(fmt.Sprintf("select * from %s where id in (?)", table), g.Slice{1, 5, 7, 8, 9, 10}).
|
||||
WhereLT("id", 8).
|
||||
WhereIn("id", g.Slice{1, 2, 3, 4, 5, 6, 7}).
|
||||
OrderDesc("id").
|
||||
Limit(2).
|
||||
All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(all), 2)
|
||||
t.Assert(all[0]["id"], 7)
|
||||
t.Assert(all[1]["id"], 5)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
count, err := db.
|
||||
Raw(fmt.Sprintf("select * from %s where id in (?)", table), g.Slice{1, 5, 7, 8, 9, 10}).
|
||||
WhereLT("id", 8).
|
||||
WhereIn("id", g.Slice{1, 2, 3, 4, 5, 6, 7}).
|
||||
OrderDesc("id").
|
||||
Limit(2).
|
||||
Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, 6)
|
||||
})
|
||||
}
|
||||
|
||||
@ -121,7 +121,7 @@ func Test_Struct_Pointer_Attribute(t *testing.T) {
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
user := new(User)
|
||||
err := db.Model(table).Struct(user, "id=1")
|
||||
err := db.Model(table).Scan(user, "id=1")
|
||||
t.AssertNil(err)
|
||||
t.Assert(*user.Id, 1)
|
||||
t.Assert(*user.Passport, "user_1")
|
||||
@ -130,7 +130,7 @@ func Test_Struct_Pointer_Attribute(t *testing.T) {
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var user *User
|
||||
err := db.Model(table).Struct(&user, "id=1")
|
||||
err := db.Model(table).Scan(&user, "id=1")
|
||||
t.AssertNil(err)
|
||||
t.Assert(*user.Id, 1)
|
||||
t.Assert(*user.Passport, "user_1")
|
||||
@ -201,7 +201,7 @@ func Test_Structs_Pointer_Attribute(t *testing.T) {
|
||||
// Structs
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
users := make([]User, 0)
|
||||
err := db.Model(table).Structs(&users, "id < 3")
|
||||
err := db.Model(table).Scan(&users, "id < 3")
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(users), 2)
|
||||
t.Assert(*users[0].Id, 1)
|
||||
@ -211,7 +211,7 @@ func Test_Structs_Pointer_Attribute(t *testing.T) {
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
users := make([]*User, 0)
|
||||
err := db.Model(table).Structs(&users, "id < 3")
|
||||
err := db.Model(table).Scan(&users, "id < 3")
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(users), 2)
|
||||
t.Assert(*users[0].Id, 1)
|
||||
@ -221,7 +221,7 @@ func Test_Structs_Pointer_Attribute(t *testing.T) {
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var users []User
|
||||
err := db.Model(table).Structs(&users, "id < 3")
|
||||
err := db.Model(table).Scan(&users, "id < 3")
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(users), 2)
|
||||
t.Assert(*users[0].Id, 1)
|
||||
@ -231,7 +231,7 @@ func Test_Structs_Pointer_Attribute(t *testing.T) {
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var users []*User
|
||||
err := db.Model(table).Structs(&users, "id < 3")
|
||||
err := db.Model(table).Scan(&users, "id < 3")
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(users), 2)
|
||||
t.Assert(*users[0].Id, 1)
|
||||
@ -254,7 +254,7 @@ func Test_Struct_Empty(t *testing.T) {
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
user := new(User)
|
||||
err := db.Model(table).Where("id=100").Struct(user)
|
||||
err := db.Model(table).Where("id=100").Scan(user)
|
||||
t.Assert(err, sql.ErrNoRows)
|
||||
t.AssertNE(user, nil)
|
||||
})
|
||||
@ -269,7 +269,7 @@ func Test_Struct_Empty(t *testing.T) {
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var user *User
|
||||
err := db.Model(table).Where("id=100").Struct(&user)
|
||||
err := db.Model(table).Where("id=100").Scan(&user)
|
||||
t.AssertNil(err)
|
||||
t.Assert(user, nil)
|
||||
})
|
||||
@ -452,3 +452,27 @@ func Test_Model_Scan_Map(t *testing.T) {
|
||||
t.Assert(users[9].CreateTime.String(), CreateTime)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Scan_AutoFilteringByStructAttributes(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
}
|
||||
//db.SetDebug(true)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var user *User
|
||||
err := db.Model(table).OrderAsc("id").Scan(&user)
|
||||
t.AssertNil(err)
|
||||
t.Assert(user.Id, 1)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var users []User
|
||||
err := db.Model(table).OrderAsc("id").Scan(&users)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(users), TableSize)
|
||||
t.Assert(users[0].Id, 1)
|
||||
})
|
||||
}
|
||||
|
||||
@ -163,7 +163,7 @@ func Test_TX_BatchInsert(t *testing.T) {
|
||||
if err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
if _, err := tx.BatchInsert(table, g.List{
|
||||
if _, err := tx.Insert(table, g.List{
|
||||
{
|
||||
"id": 2,
|
||||
"passport": "t",
|
||||
@ -201,7 +201,7 @@ func Test_TX_BatchReplace(t *testing.T) {
|
||||
if err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
if _, err := tx.BatchReplace(table, g.List{
|
||||
if _, err := tx.Replace(table, g.List{
|
||||
{
|
||||
"id": 2,
|
||||
"passport": "USER_2",
|
||||
@ -244,7 +244,7 @@ func Test_TX_BatchSave(t *testing.T) {
|
||||
if err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
if _, err := tx.BatchSave(table, g.List{
|
||||
if _, err := tx.Save(table, g.List{
|
||||
{
|
||||
"id": 4,
|
||||
"passport": "USER_4",
|
||||
@ -349,7 +349,7 @@ func Test_TX_Update(t *testing.T) {
|
||||
if err := tx.Commit(); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
_, err = tx.Table(table).Fields("create_time").Where("id", 3).Value()
|
||||
_, err = tx.Model(table).Fields("create_time").Where("id", 3).Value()
|
||||
t.AssertNE(err, nil)
|
||||
|
||||
if value, err := db.Model(table).Fields("create_time").Where("id", 3).Value(); err != nil {
|
||||
@ -666,7 +666,6 @@ func Test_TX_GetScan(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_TX_Delete(t *testing.T) {
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
@ -685,6 +684,8 @@ func Test_TX_Delete(t *testing.T) {
|
||||
} else {
|
||||
t.Assert(n, 0)
|
||||
}
|
||||
|
||||
t.Assert(tx.IsClosed(), true)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
@ -697,7 +698,7 @@ func Test_TX_Delete(t *testing.T) {
|
||||
if _, err := tx.Delete(table, 1); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
if n, err := tx.Table(table).Count(); err != nil {
|
||||
if n, err := tx.Model(table).Count(); err != nil {
|
||||
gtest.Error(err)
|
||||
} else {
|
||||
t.Assert(n, 0)
|
||||
@ -711,6 +712,8 @@ func Test_TX_Delete(t *testing.T) {
|
||||
t.Assert(n, TableSize)
|
||||
t.AssertNE(n, 0)
|
||||
}
|
||||
|
||||
t.Assert(tx.IsClosed(), true)
|
||||
})
|
||||
}
|
||||
|
||||
@ -721,7 +724,7 @@ func Test_Transaction(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
ctx := context.TODO()
|
||||
err := db.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error {
|
||||
if _, err := tx.Replace(table, g.Map{
|
||||
if _, err := tx.Ctx(ctx).Replace(table, g.Map{
|
||||
"id": 1,
|
||||
"passport": "USER_1",
|
||||
"password": "PASS_1",
|
||||
@ -730,11 +733,12 @@ func Test_Transaction(t *testing.T) {
|
||||
}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Assert(tx.IsClosed(), false)
|
||||
return gerror.New("error")
|
||||
})
|
||||
t.AssertNE(err, nil)
|
||||
|
||||
if value, err := db.Model(table).Fields("nickname").Where("id", 1).Value(); err != nil {
|
||||
if value, err := db.Model(table).Ctx(ctx).Fields("nickname").Where("id", 1).Value(); err != nil {
|
||||
gtest.Error(err)
|
||||
} else {
|
||||
t.Assert(value.String(), "name_1")
|
||||
@ -956,8 +960,8 @@ func Test_Transaction_Nested_TX_Transaction_UseDB(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
db.SetDebug(true)
|
||||
defer db.SetDebug(false)
|
||||
//db.SetDebug(true)
|
||||
//defer db.SetDebug(false)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
|
||||
@ -87,7 +87,7 @@ func Test_Types(t *testing.T) {
|
||||
TinyInt bool
|
||||
}
|
||||
var obj *T
|
||||
err = db.Model("types").Struct(&obj)
|
||||
err = db.Model("types").Scan(&obj)
|
||||
t.AssertNil(err)
|
||||
t.Assert(obj.Id, 1)
|
||||
t.Assert(obj.Blob, data["blob"])
|
||||
|
||||
146
database/gdb/gdb_z_mysql_union_test.go
Normal file
146
database/gdb/gdb_z_mysql_union_test.go
Normal file
@ -0,0 +1,146 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). 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_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
)
|
||||
|
||||
func Test_Union(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.Union(
|
||||
db.Model(table).Where("id", 1),
|
||||
db.Model(table).Where("id", 2),
|
||||
db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).OrderDesc("id"),
|
||||
).OrderDesc("id").All()
|
||||
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 3)
|
||||
t.Assert(r[0]["id"], 3)
|
||||
t.Assert(r[1]["id"], 2)
|
||||
t.Assert(r[2]["id"], 1)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.Union(
|
||||
db.Model(table).Where("id", 1),
|
||||
db.Model(table).Where("id", 2),
|
||||
db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).OrderDesc("id"),
|
||||
).OrderDesc("id").One()
|
||||
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(r["id"], 3)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_UnionAll(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.UnionAll(
|
||||
db.Model(table).Where("id", 1),
|
||||
db.Model(table).Where("id", 2),
|
||||
db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).OrderDesc("id"),
|
||||
).OrderDesc("id").All()
|
||||
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 5)
|
||||
t.Assert(r[0]["id"], 3)
|
||||
t.Assert(r[1]["id"], 2)
|
||||
t.Assert(r[2]["id"], 2)
|
||||
t.Assert(r[3]["id"], 1)
|
||||
t.Assert(r[4]["id"], 1)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.UnionAll(
|
||||
db.Model(table).Where("id", 1),
|
||||
db.Model(table).Where("id", 2),
|
||||
db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).OrderDesc("id"),
|
||||
).OrderDesc("id").One()
|
||||
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(r["id"], 3)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Union(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.Model(table).Union(
|
||||
db.Model(table).Where("id", 1),
|
||||
db.Model(table).Where("id", 2),
|
||||
db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).OrderDesc("id"),
|
||||
).OrderDesc("id").All()
|
||||
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 3)
|
||||
t.Assert(r[0]["id"], 3)
|
||||
t.Assert(r[1]["id"], 2)
|
||||
t.Assert(r[2]["id"], 1)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.Model(table).Union(
|
||||
db.Model(table).Where("id", 1),
|
||||
db.Model(table).Where("id", 2),
|
||||
db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).OrderDesc("id"),
|
||||
).OrderDesc("id").One()
|
||||
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(r["id"], 3)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_UnionAll(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.Model(table).UnionAll(
|
||||
db.Model(table).Where("id", 1),
|
||||
db.Model(table).Where("id", 2),
|
||||
db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).OrderDesc("id"),
|
||||
).OrderDesc("id").All()
|
||||
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(r), 5)
|
||||
t.Assert(r[0]["id"], 3)
|
||||
t.Assert(r[1]["id"], 2)
|
||||
t.Assert(r[2]["id"], 2)
|
||||
t.Assert(r[3]["id"], 1)
|
||||
t.Assert(r[4]["id"], 1)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.Model(table).UnionAll(
|
||||
db.Model(table).Where("id", 1),
|
||||
db.Model(table).Where("id", 2),
|
||||
db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).OrderDesc("id"),
|
||||
).OrderDesc("id").One()
|
||||
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(r["id"], 3)
|
||||
})
|
||||
}
|
||||
@ -8,6 +8,7 @@ package gdebug
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/utils"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@ -53,11 +54,13 @@ func Caller(skip ...int) (function string, path string, line int) {
|
||||
//
|
||||
// The parameter <filter> is used to filter the path of the caller.
|
||||
func CallerWithFilter(filter string, skip ...int) (function string, path string, line int) {
|
||||
number := 0
|
||||
var (
|
||||
number = 0
|
||||
ok = true
|
||||
)
|
||||
if len(skip) > 0 {
|
||||
number = skip[0]
|
||||
}
|
||||
ok := true
|
||||
pc, file, line, start := callerFromIndex([]string{filter})
|
||||
if start != -1 {
|
||||
for i := start + number; i < maxCallerDepth; i++ {
|
||||
@ -65,12 +68,6 @@ func CallerWithFilter(filter string, skip ...int) (function string, path string,
|
||||
pc, file, line, ok = runtime.Caller(i)
|
||||
}
|
||||
if ok {
|
||||
if filter != "" && strings.Contains(file, filter) {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(file, stackFilterKey) {
|
||||
continue
|
||||
}
|
||||
function := ""
|
||||
if fn := runtime.FuncForPC(pc); fn == nil {
|
||||
function = "unknown"
|
||||
@ -104,8 +101,14 @@ func callerFromIndex(filters []string) (pc uintptr, file string, line int, index
|
||||
if filtered {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(file, stackFilterKey) {
|
||||
continue
|
||||
if !utils.IsDebugEnabled() {
|
||||
if strings.Contains(file, utils.StackFilterKeyForGoFrame) {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if strings.Contains(file, stackFilterKey) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if index > 0 {
|
||||
index--
|
||||
|
||||
@ -80,14 +80,17 @@ func StackWithFilters(filters []string, skip ...int) string {
|
||||
if filtered {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(file, stackFilterKey) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !utils.IsDebugEnabled() {
|
||||
if strings.Contains(file, utils.StackFilterKeyForGoFrame) {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if strings.Contains(file, stackFilterKey) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if fn := runtime.FuncForPC(pc); fn == nil {
|
||||
name = "unknown"
|
||||
} else {
|
||||
|
||||
@ -237,7 +237,7 @@ func WrapCodeSkipf(code, skip int, err error, format string, args ...interface{}
|
||||
}
|
||||
}
|
||||
|
||||
// Cause returns the error code of current error.
|
||||
// Code returns the error code of current error.
|
||||
// It returns -1 if it has no error code or it does not implements interface Code.
|
||||
func Code(err error) int {
|
||||
if err != nil {
|
||||
|
||||
@ -104,7 +104,7 @@ func Database(name ...string) gdb.DB {
|
||||
// which is the default group configuration.
|
||||
if node := parseDBConfigNode(configMap); node != nil {
|
||||
cg := gdb.ConfigGroup{}
|
||||
if node.LinkInfo != "" || node.Host != "" {
|
||||
if node.Link != "" || node.Host != "" {
|
||||
cg = append(cg, *node)
|
||||
}
|
||||
|
||||
@ -156,15 +156,19 @@ func parseDBConfigNode(value interface{}) *gdb.ConfigNode {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if _, v := gutil.MapPossibleItemByKey(nodeMap, "link"); v != nil {
|
||||
node.LinkInfo = gconv.String(v)
|
||||
// To be compatible with old version.
|
||||
if _, v := gutil.MapPossibleItemByKey(nodeMap, "LinkInfo"); v != nil {
|
||||
node.Link = gconv.String(v)
|
||||
}
|
||||
if _, v := gutil.MapPossibleItemByKey(nodeMap, "Link"); v != nil {
|
||||
node.Link = gconv.String(v)
|
||||
}
|
||||
// Parse link syntax.
|
||||
if node.LinkInfo != "" && node.Type == "" {
|
||||
match, _ := gregex.MatchString(`([a-z]+):(.+)`, node.LinkInfo)
|
||||
if node.Link != "" && node.Type == "" {
|
||||
match, _ := gregex.MatchString(`([a-z]+):(.+)`, node.Link)
|
||||
if len(match) == 3 {
|
||||
node.Type = gstr.Trim(match[1])
|
||||
node.LinkInfo = gstr.Trim(match[2])
|
||||
node.Link = gstr.Trim(match[2])
|
||||
}
|
||||
}
|
||||
return node
|
||||
|
||||
2
go.mod
2
go.mod
@ -6,7 +6,7 @@ require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/gogf/mysql v1.6.1-0.20210603073548-16164ae25579
|
||||
github.com/gomodule/redigo v2.0.0+incompatible
|
||||
github.com/gorilla/websocket v1.4.1
|
||||
github.com/grokify/html-strip-tags-go v0.0.0-20190921062105-daaa06bf1aaf
|
||||
|
||||
4
go.sum
4
go.sum
@ -6,8 +6,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/gogf/mysql v1.6.1-0.20210603073548-16164ae25579 h1:pP/uEy52biKDytlgK/ug8kiYPAiYu6KajKVUHfGrtyw=
|
||||
github.com/gogf/mysql v1.6.1-0.20210603073548-16164ae25579/go.mod h1:52e6mXyNnHAsFrXrSnj5JPRSKsZKpHylVtA3j4AtMz8=
|
||||
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
|
||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
|
||||
@ -11,8 +11,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
debugKey = "gf.debug" // Debug key for checking if in debug mode.
|
||||
StackFilterKeyForGoFrame = "/github.com/gogf/gf/" // Stack filtering key for all GoFrame module paths.
|
||||
debugKey = "gf.debug" // Debug key for checking if in debug mode.
|
||||
StackFilterKeyForGoFrame = "github.com/gogf/gf@" // Stack filtering key for all GoFrame module paths.
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@ -621,3 +621,89 @@ func Test_Params_Parse_Validation(t *testing.T) {
|
||||
t.Assert(client.GetContent("/parse?name=john11&password1=123456&password2=123456"), `ok`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Params_Parse_EmbeddedWithAliasName1(t *testing.T) {
|
||||
// 获取内容列表
|
||||
type ContentGetListInput struct {
|
||||
Type string
|
||||
CategoryId uint
|
||||
Page int
|
||||
Size int
|
||||
Sort int
|
||||
UserId uint
|
||||
}
|
||||
// 获取内容列表
|
||||
type ContentGetListReq struct {
|
||||
ContentGetListInput
|
||||
CategoryId uint `p:"cate"`
|
||||
Page int `d:"1" v:"min:0#分页号码错误"`
|
||||
Size int `d:"10" v:"max:50#分页数量最大50条"`
|
||||
}
|
||||
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.BindHandler("/parse", func(r *ghttp.Request) {
|
||||
var req *ContentGetListReq
|
||||
if err := r.Parse(&req); err != nil {
|
||||
r.Response.Write(err)
|
||||
} else {
|
||||
r.Response.Write(req.ContentGetListInput)
|
||||
}
|
||||
})
|
||||
s.SetPort(p)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
prefix := fmt.Sprintf("http://127.0.0.1:%d", p)
|
||||
client := g.Client()
|
||||
client.SetPrefix(prefix)
|
||||
|
||||
t.Assert(client.GetContent("/parse?cate=1&page=2&size=10"), `{"Type":"","CategoryId":0,"Page":2,"Size":10,"Sort":0,"UserId":0}`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Params_Parse_EmbeddedWithAliasName2(t *testing.T) {
|
||||
// 获取内容列表
|
||||
type ContentGetListInput struct {
|
||||
Type string
|
||||
CategoryId uint `p:"cate"`
|
||||
Page int
|
||||
Size int
|
||||
Sort int
|
||||
UserId uint
|
||||
}
|
||||
// 获取内容列表
|
||||
type ContentGetListReq struct {
|
||||
ContentGetListInput
|
||||
CategoryId uint `p:"cate"`
|
||||
Page int `d:"1" v:"min:0#分页号码错误"`
|
||||
Size int `d:"10" v:"max:50#分页数量最大50条"`
|
||||
}
|
||||
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.BindHandler("/parse", func(r *ghttp.Request) {
|
||||
var req *ContentGetListReq
|
||||
if err := r.Parse(&req); err != nil {
|
||||
r.Response.Write(err)
|
||||
} else {
|
||||
r.Response.Write(req.ContentGetListInput)
|
||||
}
|
||||
})
|
||||
s.SetPort(p)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
prefix := fmt.Sprintf("http://127.0.0.1:%d", p)
|
||||
client := g.Client()
|
||||
client.SetPrefix(prefix)
|
||||
|
||||
t.Assert(client.GetContent("/parse?cate=1&page=2&size=10"), `{"Type":"","CategoryId":1,"Page":2,"Size":10,"Sort":0,"UserId":0}`)
|
||||
})
|
||||
}
|
||||
|
||||
@ -62,7 +62,6 @@ func New() *Logger {
|
||||
init: gtype.NewBool(),
|
||||
config: DefaultConfig(),
|
||||
}
|
||||
logger.config.Handlers = []Handler{defaultHandler}
|
||||
return logger
|
||||
}
|
||||
|
||||
|
||||
@ -250,5 +250,5 @@ func (l *Logger) SetPrefix(prefix string) {
|
||||
|
||||
// SetHandlers sets the logging handlers for current logger.
|
||||
func (l *Logger) SetHandlers(handlers ...Handler) {
|
||||
l.config.Handlers = append(handlers, defaultHandler)
|
||||
l.config.Handlers = handlers
|
||||
}
|
||||
|
||||
@ -75,5 +75,8 @@ func (i *HandlerInput) Next() {
|
||||
if len(i.logger.config.Handlers)-1 > i.index {
|
||||
i.index++
|
||||
i.logger.config.Handlers[i.index](i.Ctx, i)
|
||||
} else {
|
||||
// The last handler is the default handler.
|
||||
defaultHandler(i.Ctx, i)
|
||||
}
|
||||
}
|
||||
|
||||
@ -301,7 +301,7 @@ func doMapConvertForMapOrStructValue(isRoot bool, value interface{}, recursive b
|
||||
// It means this attribute field has desired tag.
|
||||
dataMap[mapKey] = doMapConvertForMapOrStructValue(false, rvAttrInterface, true, tags...)
|
||||
} else {
|
||||
dataMap[mapKey] = doMapConvertForMapOrStructValue(false, rvAttrInterface, false, tags...)
|
||||
dataMap[mapKey] = doMapConvertForMapOrStructValue(false, rvAttrInterface, recursive, tags...)
|
||||
}
|
||||
|
||||
// The struct attribute is type of slice.
|
||||
|
||||
@ -84,6 +84,8 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string
|
||||
if rv, ok := pointer.(reflect.Value); ok {
|
||||
if rv.Kind() == reflect.Ptr {
|
||||
return json.UnmarshalUseNumber(r, rv.Interface())
|
||||
} else if rv.CanAddr() {
|
||||
return json.UnmarshalUseNumber(r, rv.Addr().Interface())
|
||||
}
|
||||
} else {
|
||||
return json.UnmarshalUseNumber(r, pointer)
|
||||
@ -94,6 +96,8 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string
|
||||
if rv, ok := pointer.(reflect.Value); ok {
|
||||
if rv.Kind() == reflect.Ptr {
|
||||
return json.UnmarshalUseNumber(paramsBytes, rv.Interface())
|
||||
} else if rv.CanAddr() {
|
||||
return json.UnmarshalUseNumber(paramsBytes, rv.Addr().Interface())
|
||||
}
|
||||
} else {
|
||||
return json.UnmarshalUseNumber(paramsBytes, pointer)
|
||||
|
||||
@ -317,6 +317,60 @@ func Test_MapDeep2(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_MapDeep3(t *testing.T) {
|
||||
type Base struct {
|
||||
Id int `c:"id"`
|
||||
Date string `c:"date"`
|
||||
}
|
||||
type User struct {
|
||||
UserBase Base `c:"base"`
|
||||
Passport string `c:"passport"`
|
||||
Password string `c:"password"`
|
||||
Nickname string `c:"nickname"`
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
user := &User{
|
||||
UserBase: Base{
|
||||
Id: 1,
|
||||
Date: "2019-10-01",
|
||||
},
|
||||
Passport: "john",
|
||||
Password: "123456",
|
||||
Nickname: "JohnGuo",
|
||||
}
|
||||
m := gconv.MapDeep(user)
|
||||
t.Assert(m, g.Map{
|
||||
"base": g.Map{
|
||||
"id": user.UserBase.Id,
|
||||
"date": user.UserBase.Date,
|
||||
},
|
||||
"passport": user.Passport,
|
||||
"password": user.Password,
|
||||
"nickname": user.Nickname,
|
||||
})
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
user := &User{
|
||||
UserBase: Base{
|
||||
Id: 1,
|
||||
Date: "2019-10-01",
|
||||
},
|
||||
Passport: "john",
|
||||
Password: "123456",
|
||||
Nickname: "JohnGuo",
|
||||
}
|
||||
m := gconv.Map(user)
|
||||
t.Assert(m, g.Map{
|
||||
"base": user.UserBase,
|
||||
"passport": user.Passport,
|
||||
"password": user.Password,
|
||||
"nickname": user.Nickname,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_MapDeepWithAttributeTag(t *testing.T) {
|
||||
type Ids struct {
|
||||
Id int `c:"id"`
|
||||
|
||||
@ -25,6 +25,7 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
// Note that `currentMode` is not concurrent safe.
|
||||
currentMode = NOT_SET
|
||||
)
|
||||
|
||||
|
||||
@ -77,8 +77,8 @@ func ComparatorUint64(a, b interface{}) int {
|
||||
|
||||
// ComparatorFloat32 provides a basic comparison on float32.
|
||||
func ComparatorFloat32(a, b interface{}) int {
|
||||
aFloat := gconv.Float64(a)
|
||||
bFloat := gconv.Float64(b)
|
||||
aFloat := gconv.Float32(a)
|
||||
bFloat := gconv.Float32(b)
|
||||
if aFloat == bFloat {
|
||||
return 0
|
||||
}
|
||||
|
||||
@ -65,3 +65,29 @@ func SliceToMap(slice interface{}) map[string]interface{} {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SliceToMapWithColumnAsKey converts slice type variable `slice` to `map[interface{}]interface{}`
|
||||
// The value of specified column use as the key for returned map.
|
||||
// Eg:
|
||||
// SliceToMapWithColumnAsKey([{"K1": "v1", "K2": 1}, {"K1": "v2", "K2": 2}], "K1") => {"v1": {"K1": "v1", "K2": 1}, "v2": {"K1": "v2", "K2": 2}}
|
||||
// SliceToMapWithColumnAsKey([{"K1": "v1", "K2": 1}, {"K1": "v2", "K2": 2}], "K2") => {1: {"K1": "v1", "K2": 1}, 2: {"K1": "v2", "K2": 2}}
|
||||
func SliceToMapWithColumnAsKey(slice interface{}, key interface{}) map[interface{}]interface{} {
|
||||
var (
|
||||
reflectValue = reflect.ValueOf(slice)
|
||||
reflectKind = reflectValue.Kind()
|
||||
)
|
||||
for reflectKind == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
data := make(map[interface{}]interface{})
|
||||
switch reflectKind {
|
||||
case reflect.Slice, reflect.Array:
|
||||
for i := 0; i < reflectValue.Len(); i++ {
|
||||
if k, ok := ItemValue(reflectValue.Index(i), key); ok {
|
||||
data[k] = reflectValue.Index(i).Interface()
|
||||
}
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
@ -35,3 +35,23 @@ func Test_SliceToMap(t *testing.T) {
|
||||
t.Assert(m, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_SliceToMapWithColumnAsKey(t *testing.T) {
|
||||
m1 := g.Map{"K1": "v1", "K2": 1}
|
||||
m2 := g.Map{"K1": "v2", "K2": 2}
|
||||
s := g.Slice{m1, m2}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gutil.SliceToMapWithColumnAsKey(s, "K1")
|
||||
t.Assert(m, g.MapAnyAny{
|
||||
"v1": m1,
|
||||
"v2": m2,
|
||||
})
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gutil.SliceToMapWithColumnAsKey(s, "K2")
|
||||
t.Assert(m, g.MapAnyAny{
|
||||
1: m1,
|
||||
2: m2,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package gf
|
||||
|
||||
const VERSION = "v1.16.0"
|
||||
const VERSION = "v1.16.4"
|
||||
const AUTHORS = "john<john@goframe.org>"
|
||||
|
||||
Reference in New Issue
Block a user