gf/database/gdb/gdb_func.go

871 lines
25 KiB
Go
Raw Normal View History

// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
2018-12-14 18:35:51 +08:00
//
// 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.
2018-12-14 18:35:51 +08:00
package gdb
import (
2019-10-21 19:13:25 +08:00
"bytes"
"database/sql"
"fmt"
2021-10-11 21:41:56 +08:00
"github.com/gogf/gf/v2/errors/gcode"
"reflect"
"regexp"
"strings"
"time"
2021-10-11 21:41:56 +08:00
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/empty"
"github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/internal/utils"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gmeta"
"github.com/gogf/gf/v2/util/gutil"
2021-10-11 21:41:56 +08:00
"github.com/gogf/gf/v2/internal/structs"
2019-08-30 20:29:12 +08:00
2021-10-11 21:41:56 +08:00
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
2018-12-14 18:35:51 +08:00
)
// iString is the type assert api for String.
type iString interface {
2019-05-16 13:56:49 +08:00
String() string
}
// iIterator is the type assert api for Iterator.
type iIterator interface {
2019-10-21 19:13:25 +08:00
Iterator(f func(key, value interface{}) bool)
}
// iInterfaces is the type assert api for Interfaces.
type iInterfaces interface {
2019-10-21 19:13:25 +08:00
Interfaces() []interface{}
}
// iMapStrAny is the interface support for converting struct parameter to map.
type iMapStrAny interface {
MapStrAny() map[string]interface{}
}
// iTableName is the interface for retrieving table name fro struct.
type iTableName interface {
TableName() string
}
2019-08-30 20:29:12 +08:00
const (
OrmTagForStruct = "orm"
OrmTagForUnique = "unique"
OrmTagForPrimary = "primary"
OrmTagForTable = "table"
OrmTagForWith = "with"
OrmTagForWithWhere = "where"
OrmTagForWithOrder = "order"
2019-08-30 20:29:12 +08:00
)
var (
// quoteWordReg is the regular expression object for a word check.
quoteWordReg = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`)
2020-07-25 10:54:48 +08:00
// Priority tags for struct converting for orm field mapping.
structTagPriority = append([]string{OrmTagForStruct}, gconv.StructTagPriority...)
)
// guessPrimaryTableName parses and returns the primary table name.
func (m *Model) guessPrimaryTableName(tableStr string) string {
if tableStr == "" {
return ""
}
var (
guessedTableName = ""
array1 = gstr.SplitAndTrim(tableStr, ",")
array2 = gstr.SplitAndTrim(array1[0], " ")
array3 = gstr.SplitAndTrim(array2[0], ".")
)
if len(array3) >= 2 {
guessedTableName = array3[1]
2021-07-15 22:19:00 +08:00
} else {
guessedTableName = array3[0]
}
charL, charR := m.db.GetChars()
if charL != "" || charR != "" {
guessedTableName = gstr.Trim(guessedTableName, charL+charR)
}
if !gregex.IsMatchString(regularFieldNameRegPattern, guessedTableName) {
return ""
}
return guessedTableName
}
2021-02-08 17:57:21 +08:00
// getTableNameFromOrmTag retrieves and returns the table name from struct object.
func getTableNameFromOrmTag(object interface{}) string {
var tableName string
// Use the interface value.
if r, ok := object.(iTableName); ok {
2021-02-08 17:57:21 +08:00
tableName = r.TableName()
}
// User meta data tag "orm".
if tableName == "" {
if ormTag := gmeta.Get(object, OrmTagForStruct); !ormTag.IsEmpty() {
match, _ := gregex.MatchString(
fmt.Sprintf(`%s\s*:\s*([^,]+)`, OrmTagForTable),
ormTag.String(),
)
if len(match) > 1 {
tableName = match[1]
}
}
}
// Use the struct name of snake case.
if tableName == "" {
if t, err := structs.StructType(object); err != nil {
panic(err)
} else {
2021-02-08 17:57:21 +08:00
tableName = gstr.CaseSnakeFirstUpper(
gstr.StrEx(t.String(), "."),
)
}
}
2021-02-08 17:57:21 +08:00
return tableName
}
2021-02-08 17:57:21 +08:00
// ListItemValues retrieves and returns the elements of all item struct/map with key `key`.
// Note that the parameter `list` should be type of slice which contains elements of map or struct,
// or else it returns an empty slice.
//
2021-02-08 17:57:21 +08:00
// The parameter `list` supports types like:
// []map[string]interface{}
// []map[string]sub-map
// []struct
// []struct:sub-struct
2021-02-08 17:57:21 +08:00
// Note that the sub-map/sub-struct makes sense only if the optional parameter `subKey` is given.
2020-07-05 11:54:37 +08:00
// See gutil.ListItemValues.
func ListItemValues(list interface{}, key interface{}, subKey ...interface{}) (values []interface{}) {
return gutil.ListItemValues(list, key, subKey...)
}
2021-02-08 17:57:21 +08:00
// ListItemValuesUnique retrieves and returns the unique elements of all struct/map with key `key`.
// Note that the parameter `list` should be type of slice which contains elements of map or struct,
// or else it returns an empty slice.
// See gutil.ListItemValuesUnique.
func ListItemValuesUnique(list interface{}, key string, subKey ...interface{}) []interface{} {
return gutil.ListItemValuesUnique(list, key, subKey...)
}
2021-02-08 17:57:21 +08:00
// GetInsertOperationByOption returns proper insert option with given parameter `option`.
func GetInsertOperationByOption(option int) string {
var operator string
switch option {
case insertOptionReplace:
operator = "REPLACE"
case insertOptionIgnore:
operator = "INSERT IGNORE"
default:
operator = "INSERT"
}
return operator
}
// ConvertDataForTableRecord is a very important function, which does converting for any data that
// will be inserted into table as a record.
//
// 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
rvKind reflect.Kind
data = DataToMapDeep(value)
)
for k, v := range data {
rvValue = reflect.ValueOf(v)
rvKind = rvValue.Kind()
for rvKind == reflect.Ptr {
rvValue = rvValue.Elem()
rvKind = rvValue.Kind()
}
switch rvKind {
case reflect.Slice, reflect.Array, reflect.Map:
// It should ignore the bytes type.
if _, ok := v.([]byte); !ok {
// Convert the value to JSON.
data[k], _ = json.Marshal(v)
}
case reflect.Struct:
switch r := v.(type) {
// If the time is zero, it then updates it to nil,
// which will insert/update the value to database as "null".
case time.Time:
if r.IsZero() {
data[k] = nil
}
case gtime.Time:
if r.IsZero() {
data[k] = nil
}
2021-08-30 22:26:02 +08:00
case *gtime.Time:
if r.IsZero() {
data[k] = nil
}
case *time.Time:
continue
case Counter, *Counter:
continue
default:
// Use string conversion in default.
if s, ok := v.(iString); ok {
data[k] = s.String()
} else {
// Convert the value to JSON.
data[k], _ = json.Marshal(v)
}
}
}
}
return data
}
// DataToMapDeep converts `value` to map type recursively(if attribute struct is embedded).
2021-02-08 17:57:21 +08:00
// The parameter `value` should be type of *map/map/*struct/struct.
// It supports embedded struct definition for struct.
2020-07-25 10:54:48 +08:00
func DataToMapDeep(value interface{}) map[string]interface{} {
m := gconv.Map(value, structTagPriority...)
for k, v := range m {
switch v.(type) {
case time.Time, *time.Time, gtime.Time, *gtime.Time:
m[k] = v
2020-07-25 10:54:48 +08:00
default:
// Use string conversion in default.
if s, ok := v.(iString); ok {
m[k] = s.String()
2020-07-25 10:54:48 +08:00
} else {
m[k] = v
}
}
}
return m
}
// doHandleTableName adds prefix string and quote chars for the table. It handles table string like:
// "user", "user u", "user,user_detail", "user u, user_detail ut", "user as u, user_detail as ut",
// "user.user u", "`user`.`user` u".
//
// Note that, this will automatically checks the table prefix whether already added, if true it does
// nothing to the table name, or else adds the prefix to the table name.
func doHandleTableName(table, prefix, charLeft, charRight string) string {
var (
index = 0
chars = charLeft + charRight
array1 = gstr.SplitAndTrim(table, ",")
)
for k1, v1 := range array1 {
array2 := gstr.SplitAndTrim(v1, " ")
// Trim the security chars.
array2[0] = gstr.Trim(array2[0], chars)
// Check whether it has database name.
array3 := gstr.Split(gstr.Trim(array2[0]), ".")
for k, v := range array3 {
array3[k] = gstr.Trim(v, chars)
}
index = len(array3) - 1
// If the table name already has the prefix, skips the prefix adding.
if len(array3[index]) <= len(prefix) || array3[index][:len(prefix)] != prefix {
array3[index] = prefix + array3[index]
}
array2[0] = gstr.Join(array3, ".")
// Add the security chars.
array2[0] = doQuoteString(array2[0], charLeft, charRight)
array1[k1] = gstr.Join(array2, " ")
}
return gstr.Join(array1, ",")
}
2021-02-08 17:57:21 +08:00
// doQuoteWord checks given string `s` a word, if true quotes it with `charLeft` and `charRight`
// and returns the quoted string; or else returns `s` without any change.
func doQuoteWord(s, charLeft, charRight string) string {
if quoteWordReg.MatchString(s) && !gstr.ContainsAny(s, charLeft+charRight) {
return charLeft + s + charRight
}
return s
}
2021-08-04 13:29:06 +08:00
// doQuoteString quotes string with quote chars.
// For example, if quote char is '`':
// "user" => "`user`"
// "user u" => "`user` u"
// "user,user_detail" => "`user`,`user_detail`"
// "user u, user_detail ut" => "`user` u,`user_detail` ut"
// "user.user u, user.user_detail ut" => "`user`.`user` u,`user`.`user_detail` ut"
// "u.id, u.name, u.age" => "`u`.`id`,`u`.`name`,`u`.`age`"
// "u.id asc" => "`u`.`id` asc"
func doQuoteString(s, charLeft, charRight string) string {
array1 := gstr.SplitAndTrim(s, ",")
for k1, v1 := range array1 {
array2 := gstr.SplitAndTrim(v1, " ")
array3 := gstr.Split(gstr.Trim(array2[0]), ".")
if len(array3) == 1 {
array3[0] = doQuoteWord(array3[0], charLeft, charRight)
} else if len(array3) >= 2 {
array3[0] = doQuoteWord(array3[0], charLeft, charRight)
// Note:
// mysql: u.uid
// mssql double dots: Database..Table
array3[len(array3)-1] = doQuoteWord(array3[len(array3)-1], charLeft, charRight)
}
array2[0] = gstr.Join(array3, ".")
array1[k1] = gstr.Join(array2, " ")
}
return gstr.Join(array1, ",")
}
func getFieldsFromStructOrMap(structOrMap interface{}) (fields []string) {
fields = []string{}
if utils.IsStruct(structOrMap) {
structFields, _ := structs.Fields(structs.FieldsInput{
Pointer: structOrMap,
RecursiveOption: structs.RecursiveOptionEmbeddedNoTag,
})
for _, structField := range structFields {
if tag := structField.Tag(OrmTagForStruct); tag != "" && gregex.IsMatchString(regularFieldNameRegPattern, tag) {
fields = append(fields, tag)
} else {
fields = append(fields, structField.Name())
}
}
} else {
fields = gutil.Keys(structOrMap)
}
return
}
2019-12-16 20:50:27 +08:00
// GetPrimaryKeyCondition returns a new where condition by primary field name.
2021-02-08 17:57:21 +08:00
// The optional parameter `where` is like follows:
// 123 => primary=123
// []int{1, 2, 3} => primary IN(1,2,3)
// "john" => primary='john'
// []string{"john", "smith"} => primary IN('john','smith')
// g.Map{"id": g.Slice{1,2,3}} => id IN(1,2,3)
// g.Map{"id": 1, "name": "john"} => id=1 AND name='john'
// etc.
2019-12-16 20:50:27 +08:00
//
2021-02-08 17:57:21 +08:00
// Note that it returns the given `where` parameter directly if the `primary` is empty
// or length of `where` > 1.
2019-12-16 20:50:27 +08:00
func GetPrimaryKeyCondition(primary string, where ...interface{}) (newWhereCondition []interface{}) {
if len(where) == 0 {
return nil
}
if primary == "" {
return where
}
if len(where) == 1 {
var (
rv = reflect.ValueOf(where[0])
kind = rv.Kind()
)
2019-12-16 20:50:27 +08:00
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
case reflect.Map, reflect.Struct:
2021-02-08 17:57:21 +08:00
// Ignore the parameter `primary`.
2019-12-16 20:50:27 +08:00
break
default:
return []interface{}{map[string]interface{}{
primary: where[0],
}}
}
}
return where
}
2020-03-22 23:26:15 +08:00
// formatSql formats the sql string and its arguments before executing.
// The internal handleArguments function might be called twice during the SQL procedure,
// but do not worry about it, it's safe and efficient.
func formatSql(sql string, args []interface{}) (newSql string, newArgs []interface{}) {
// DO NOT do this as there may be multiple lines and comments in the sql.
// sql = gstr.Trim(sql)
// sql = gstr.Replace(sql, "\n", " ")
// sql, _ = gregex.ReplaceString(`\s{2,}`, ` `, sql)
2020-03-22 23:26:15 +08:00
return handleArguments(sql, args)
}
2021-08-13 11:28:47 +08:00
type formatWhereInput struct {
Where interface{}
Args []interface{}
OmitNil bool
OmitEmpty bool
Schema string
Table string
Prefix string // Field prefix, eg: "user.", "order.".
2021-08-13 11:28:47 +08:00
}
2021-05-27 22:18:16 +08:00
// formatWhere formats where statement and its arguments for `Where` and `Having` statements.
2021-08-13 11:28:47 +08:00
func formatWhere(db DB, in formatWhereInput) (newWhere string, newArgs []interface{}) {
var (
buffer = bytes.NewBuffer(nil)
reflectInfo = utils.OriginValueAndKind(in.Where)
)
switch reflectInfo.OriginKind {
2019-10-21 19:13:25 +08:00
case reflect.Array, reflect.Slice:
2021-08-13 11:28:47 +08:00
newArgs = formatWhereInterfaces(db, gconv.Interfaces(in.Where), buffer, newArgs)
2019-10-21 19:13:25 +08:00
case reflect.Map:
2021-08-13 11:28:47 +08:00
for key, value := range DataToMapDeep(in.Where) {
if gregex.IsMatchString(regularFieldNameRegPattern, key) {
if in.OmitNil && empty.IsNil(value) {
continue
}
if in.OmitEmpty && empty.IsEmpty(value) {
continue
}
2019-10-21 19:13:25 +08:00
}
newArgs = formatWhereKeyValue(formatWhereKeyValueInput{
Db: db,
Buffer: buffer,
Args: newArgs,
Key: key,
Value: value,
Prefix: in.Prefix,
})
2019-10-21 19:13:25 +08:00
}
case reflect.Struct:
// If `where` struct implements iIterator interface,
// it then uses its Iterate function to iterate its key-value pairs.
2019-10-21 19:13:25 +08:00
// For example, ListMap and TreeMap are ordered map,
// which implement iIterator interface and are index-friendly for where conditions.
if iterator, ok := in.Where.(iIterator); ok {
2019-10-21 19:13:25 +08:00
iterator.Iterator(func(key, value interface{}) bool {
ketStr := gconv.String(key)
2021-08-13 11:28:47 +08:00
if gregex.IsMatchString(regularFieldNameRegPattern, ketStr) {
if in.OmitNil && empty.IsNil(value) {
return true
}
if in.OmitEmpty && empty.IsEmpty(value) {
return true
}
2019-10-21 19:13:25 +08:00
}
newArgs = formatWhereKeyValue(formatWhereKeyValueInput{
Db: db,
Buffer: buffer,
Args: newArgs,
Key: ketStr,
Value: value,
OmitEmpty: in.OmitEmpty,
Prefix: in.Prefix,
})
2019-10-21 19:13:25 +08:00
return true
})
break
}
// Automatically mapping and filtering the struct attribute.
var (
reflectType = reflectInfo.OriginValue.Type()
structField reflect.StructField
)
2021-08-13 11:28:47 +08:00
data := DataToMapDeep(in.Where)
if in.Table != "" {
data, _ = db.GetCore().mappingAndFilterData(in.Schema, in.Table, data, true)
}
// Put the struct attributes in sequence in Where statement.
for i := 0; i < reflectType.NumField(); i++ {
structField = reflectType.Field(i)
foundKey, foundValue := gutil.MapPossibleItemByKey(data, structField.Name)
if foundKey != "" {
if in.OmitNil && empty.IsNil(foundValue) {
continue
}
if in.OmitEmpty && empty.IsEmpty(foundValue) {
continue
}
newArgs = formatWhereKeyValue(formatWhereKeyValueInput{
Db: db,
Buffer: buffer,
Args: newArgs,
Key: foundKey,
Value: foundValue,
OmitEmpty: in.OmitEmpty,
Prefix: in.Prefix,
})
2019-10-21 19:13:25 +08:00
}
}
default:
2021-05-27 22:18:16 +08:00
// Usually a string.
var (
2021-08-13 11:28:47 +08:00
whereStr = gconv.String(in.Where)
2021-05-27 22:18:16 +08:00
)
// Is `whereStr` a field name which composed as a key-value condition?
// Eg:
// Where("id", 1)
// Where("id", g.Slice{1,2,3})
if gregex.IsMatchString(regularFieldNameWithoutDotRegPattern, whereStr) && len(in.Args) == 1 {
newArgs = formatWhereKeyValue(formatWhereKeyValueInput{
Db: db,
Buffer: buffer,
Args: newArgs,
Key: whereStr,
Value: in.Args[0],
OmitEmpty: in.OmitEmpty,
Prefix: in.Prefix,
})
in.Args = in.Args[:0]
break
}
// Regular string and parameter place holder handling.
// Eg:
// Where("id in(?) and name=?", g.Slice{1,2,3}, "john")
var (
i = 0
)
2021-05-27 22:18:16 +08:00
for {
2021-08-13 11:28:47 +08:00
if i >= len(in.Args) {
2021-05-27 22:18:16 +08:00
break
}
// Sub query, which is always used along with a string condition.
2021-08-13 11:28:47 +08:00
if model, ok := in.Args[i].(*Model); ok {
2021-05-27 22:18:16 +08:00
var (
index = -1
)
whereStr, _ = gregex.ReplaceStringFunc(`(\?)`, whereStr, func(s string) string {
index++
if i+len(newArgs) == index {
sqlWithHolder, holderArgs := model.getFormattedSqlAndArgs(queryTypeNormal, false)
newArgs = append(newArgs, holderArgs...)
// Automatically adding the brackets.
return "(" + sqlWithHolder + ")"
}
return s
})
2021-08-13 11:28:47 +08:00
in.Args = gutil.SliceDelete(in.Args, i)
2021-05-27 22:18:16 +08:00
continue
}
i++
}
buffer.WriteString(whereStr)
2019-10-21 19:13:25 +08:00
}
if buffer.Len() == 0 {
2021-08-13 11:28:47 +08:00
return "", in.Args
2019-10-21 19:13:25 +08:00
}
if len(in.Args) > 0 {
newArgs = append(newArgs, in.Args...)
}
2019-10-21 19:13:25 +08:00
newWhere = buffer.String()
if len(newArgs) > 0 {
if gstr.Pos(newWhere, "?") == -1 {
if gregex.IsMatchString(lastOperatorRegPattern, newWhere) {
2020-03-11 23:17:41 +08:00
// Eg: Where/And/Or("uid>=", 1)
2019-10-21 19:13:25 +08:00
newWhere += "?"
} else if gregex.IsMatchString(regularFieldNameRegPattern, newWhere) {
newWhere = db.GetCore().QuoteString(newWhere)
if len(newArgs) > 0 {
if utils.IsArray(newArgs[0]) {
// Eg:
// Where("id", []int{1,2,3})
// Where("user.id", []int{1,2,3})
newWhere += " IN (?)"
} else if empty.IsNil(newArgs[0]) {
// Eg:
// Where("id", nil)
// Where("user.id", nil)
newWhere += " IS NULL"
newArgs = nil
} else {
// Eg:
// Where/And/Or("uid", 1)
// Where/And/Or("user.uid", 1)
newWhere += "=?"
}
2020-03-11 23:17:41 +08:00
}
2019-10-21 19:13:25 +08:00
}
}
}
return handleArguments(newWhere, newArgs)
2019-10-21 19:13:25 +08:00
}
2021-02-08 17:57:21 +08:00
// formatWhereInterfaces formats `where` as []interface{}.
2019-10-21 19:13:25 +08:00
func formatWhereInterfaces(db DB, where []interface{}, buffer *bytes.Buffer, newArgs []interface{}) []interface{} {
if len(where) == 0 {
return newArgs
}
if len(where)%2 != 0 {
buffer.WriteString(gstr.Join(gconv.Strings(where), ""))
return newArgs
}
2019-10-21 19:13:25 +08:00
var str string
for i := 0; i < len(where); i += 2 {
str = gconv.String(where[i])
if buffer.Len() > 0 {
buffer.WriteString(" AND " + db.GetCore().QuoteWord(str) + "=?")
2019-10-21 19:13:25 +08:00
} else {
buffer.WriteString(db.GetCore().QuoteWord(str) + "=?")
2019-10-21 19:13:25 +08:00
}
if s, ok := where[i+1].(Raw); ok {
buffer.WriteString(gconv.String(s))
} else {
newArgs = append(newArgs, where[i+1])
}
2019-10-21 19:13:25 +08:00
}
return newArgs
}
type formatWhereKeyValueInput struct {
Db DB
Buffer *bytes.Buffer
Args []interface{}
Key string
Value interface{}
OmitEmpty bool
Prefix string // Field prefix, eg: "user.", "order.".
}
2019-10-21 19:13:25 +08:00
// formatWhereKeyValue handles each key-value pair of the parameter map.
func formatWhereKeyValue(in formatWhereKeyValueInput) (newArgs []interface{}) {
var (
quotedKey = in.Db.GetCore().QuoteWord(in.Key)
holderCount = gstr.Count(quotedKey, "?")
)
// Eg:
// Where("id", []int{}).All() -> SELECT xxx FROM xxx WHERE 0=1
// Where("name", "").All() -> SELECT xxx FROM xxx WHERE `name`=''
// OmitEmpty().Where("id", []int{}).All() -> SELECT xxx FROM xxx
// OmitEmpty().("name", "").All() -> SELECT xxx FROM xxx
if in.OmitEmpty && holderCount == 0 && gutil.IsEmpty(in.Value) {
return in.Args
}
if in.Prefix != "" && !gstr.Contains(quotedKey, ".") {
quotedKey = in.Prefix + "." + quotedKey
}
if in.Buffer.Len() > 0 {
in.Buffer.WriteString(" AND ")
}
// If the value is type of slice, and there's only one '?' holder in
// the key string, it automatically adds '?' holder chars according to its arguments count
// and converts it to "IN" statement.
var (
reflectValue = reflect.ValueOf(in.Value)
reflectKind = reflectValue.Kind()
)
switch reflectKind {
// Slice argument.
2019-10-21 19:13:25 +08:00
case reflect.Slice, reflect.Array:
if holderCount == 0 {
in.Buffer.WriteString(quotedKey + " IN(?)")
in.Args = append(in.Args, in.Value)
2019-10-21 19:13:25 +08:00
} else {
if holderCount != reflectValue.Len() {
in.Buffer.WriteString(quotedKey)
in.Args = append(in.Args, in.Value)
} else {
in.Buffer.WriteString(quotedKey)
in.Args = append(in.Args, gconv.Interfaces(in.Value)...)
}
2019-10-21 19:13:25 +08:00
}
2019-10-21 19:13:25 +08:00
default:
if in.Value == nil || empty.IsNil(reflectValue) {
if gregex.IsMatchString(regularFieldNameRegPattern, in.Key) {
// The key is a single field name.
in.Buffer.WriteString(quotedKey + " IS NULL")
} else {
// The key may have operation chars.
in.Buffer.WriteString(quotedKey)
}
2019-10-21 19:13:25 +08:00
} else {
// It also supports "LIKE" statement, which we consider it an operator.
quotedKey = gstr.Trim(quotedKey)
if gstr.Pos(quotedKey, "?") == -1 {
like := " LIKE"
if len(quotedKey) > len(like) && gstr.Equal(quotedKey[len(quotedKey)-len(like):], like) {
// Eg: Where(g.Map{"name like": "john%"})
in.Buffer.WriteString(quotedKey + " ?")
} else if gregex.IsMatchString(lastOperatorRegPattern, quotedKey) {
// Eg: Where(g.Map{"age > ": 16})
in.Buffer.WriteString(quotedKey + " ?")
} else if gregex.IsMatchString(regularFieldNameRegPattern, in.Key) {
// The key is a regular field name.
in.Buffer.WriteString(quotedKey + "=?")
} else {
// The key is not a regular field name.
// Eg: Where(g.Map{"age > 16": nil})
// Issue: https://github.com/gogf/gf/issues/765
if empty.IsEmpty(in.Value) {
in.Buffer.WriteString(quotedKey)
break
} else {
in.Buffer.WriteString(quotedKey + "=?")
}
2019-10-21 19:13:25 +08:00
}
} else {
in.Buffer.WriteString(quotedKey)
2019-10-21 19:13:25 +08:00
}
if s, ok := in.Value.(Raw); ok {
in.Buffer.WriteString(gconv.String(s))
} else {
in.Args = append(in.Args, in.Value)
}
2019-10-21 19:13:25 +08:00
}
}
return in.Args
2019-10-21 19:13:25 +08:00
}
// handleArguments is an important function, which handles the sql and all its arguments
// before committing them to underlying driver.
2020-03-22 23:26:15 +08:00
func handleArguments(sql string, args []interface{}) (newSql string, newArgs []interface{}) {
newSql = sql
// insertHolderCount is used to calculate the inserting position for the '?' holder.
insertHolderCount := 0
// Handles the slice arguments.
if len(args) > 0 {
for index, arg := range args {
reflectInfo := utils.OriginValueAndKind(arg)
switch reflectInfo.OriginKind {
case reflect.Slice, reflect.Array:
// It does not split the type of []byte.
// Eg: table.Where("name = ?", []byte("john"))
2019-07-22 23:03:55 +08:00
if _, ok := arg.([]byte); ok {
newArgs = append(newArgs, arg)
continue
}
if reflectInfo.OriginValue.Len() == 0 {
// Empty slice argument, it converts the sql to a false sql.
// Eg:
// Query("select * from xxx where id in(?)", g.Slice{}) -> select * from xxx where 0=1
// Where("id in(?)", g.Slice{}) -> WHERE 0=1
if gstr.Contains(newSql, "?") {
whereKeyWord := " WHERE "
if p := gstr.PosI(newSql, whereKeyWord); p == -1 {
return "0=1", []interface{}{}
} else {
return gstr.SubStr(newSql, 0, p+len(whereKeyWord)) + "0=1", []interface{}{}
}
}
} else {
for i := 0; i < reflectInfo.OriginValue.Len(); i++ {
newArgs = append(newArgs, reflectInfo.OriginValue.Index(i).Interface())
}
}
// If the '?' holder count equals the length of the slice,
// it does not implement the arguments splitting logic.
// Eg: db.Query("SELECT ?+?", g.Slice{1, 2})
if len(args) == 1 && gstr.Count(newSql, "?") == reflectInfo.OriginValue.Len() {
break
}
// counter is used to finding the inserting position for the '?' holder.
var (
counter = 0
replaced = false
)
2020-03-22 23:26:15 +08:00
newSql, _ = gregex.ReplaceStringFunc(`\?`, newSql, func(s string) string {
if replaced {
return s
}
counter++
if counter == index+insertHolderCount+1 {
replaced = true
insertHolderCount += reflectInfo.OriginValue.Len() - 1
return "?" + strings.Repeat(",?", reflectInfo.OriginValue.Len()-1)
}
return s
})
// Special struct handling.
case reflect.Struct:
switch v := arg.(type) {
// The underlying driver supports time.Time/*time.Time types.
case time.Time, *time.Time:
newArgs = append(newArgs, arg)
continue
// Special handling for gtime.Time/*gtime.Time.
//
// DO NOT use its underlying gtime.Time.Time as its argument,
// because the std time.Time will be converted to certain timezone
// according to underlying driver. And the underlying driver also
// converts the time.Time to string automatically as the following does.
case gtime.Time:
newArgs = append(newArgs, v.String())
continue
case *gtime.Time:
newArgs = append(newArgs, v.String())
continue
default:
// It converts the struct to string in default
// if it has implemented the String interface.
if v, ok := arg.(iString); ok {
newArgs = append(newArgs, v.String())
continue
}
}
newArgs = append(newArgs, arg)
default:
newArgs = append(newArgs, arg)
}
}
}
return
}
// formatError customizes and returns the SQL error.
func formatError(err error, s string, args ...interface{}) error {
if err != nil && err != sql.ErrNoRows {
return gerror.NewCodef(gcode.CodeDbOperationError, "%s, %s\n", err.Error(), FormatSqlWithArgs(s, args))
2019-06-19 09:06:52 +08:00
}
return err
2018-12-14 18:35:51 +08:00
}
2020-03-22 23:26:15 +08:00
// FormatSqlWithArgs binds the arguments to the sql string and returns a complete
// sql string, just for debugging.
2020-03-22 23:26:15 +08:00
func FormatSqlWithArgs(sql string, args []interface{}) string {
index := -1
newQuery, _ := gregex.ReplaceStringFunc(
2021-07-13 19:37:16 +08:00
`(\?|:v\d+|\$\d+|@p\d+)`,
sql,
func(s string) string {
index++
if len(args) > index {
if args[index] == nil {
2019-10-21 19:13:25 +08:00
return "null"
}
var (
reflectInfo = utils.OriginValueAndKind(args[index])
)
if reflectInfo.OriginKind == reflect.Ptr &&
(reflectInfo.OriginValue.IsNil() || !reflectInfo.OriginValue.IsValid()) {
return "null"
2019-10-25 19:54:02 +08:00
}
switch reflectInfo.OriginKind {
case reflect.String, reflect.Map, reflect.Slice, reflect.Array:
return `'` + gstr.QuoteMeta(gconv.String(args[index]), `'`) + `'`
2021-07-13 19:37:16 +08:00
case reflect.Struct:
if t, ok := args[index].(time.Time); ok {
return `'` + t.Format(`2006-01-02 15:04:05`) + `'`
}
return `'` + gstr.QuoteMeta(gconv.String(args[index]), `'`) + `'`
}
return gconv.String(args[index])
}
return s
})
return newQuery
}