github.com/gogf/gf/v2@v2.7.4/database/gdb/gdb_func.go (about) 1 // Copyright GoFrame Author(https://goframe.org). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/gogf/gf. 6 7 package gdb 8 9 import ( 10 "bytes" 11 "context" 12 "fmt" 13 "reflect" 14 "regexp" 15 "strings" 16 "time" 17 18 "github.com/gogf/gf/v2/container/garray" 19 "github.com/gogf/gf/v2/encoding/ghash" 20 "github.com/gogf/gf/v2/encoding/gjson" 21 "github.com/gogf/gf/v2/internal/empty" 22 "github.com/gogf/gf/v2/internal/intlog" 23 "github.com/gogf/gf/v2/internal/json" 24 "github.com/gogf/gf/v2/internal/reflection" 25 "github.com/gogf/gf/v2/internal/utils" 26 "github.com/gogf/gf/v2/os/gstructs" 27 "github.com/gogf/gf/v2/os/gtime" 28 "github.com/gogf/gf/v2/text/gregex" 29 "github.com/gogf/gf/v2/text/gstr" 30 "github.com/gogf/gf/v2/util/gconv" 31 "github.com/gogf/gf/v2/util/gmeta" 32 "github.com/gogf/gf/v2/util/gtag" 33 "github.com/gogf/gf/v2/util/gutil" 34 ) 35 36 // iString is the type assert api for String. 37 type iString interface { 38 String() string 39 } 40 41 // iIterator is the type assert api for Iterator. 42 type iIterator interface { 43 Iterator(f func(key, value interface{}) bool) 44 } 45 46 // iInterfaces is the type assert api for Interfaces. 47 type iInterfaces interface { 48 Interfaces() []interface{} 49 } 50 51 // iNil if the type assert api for IsNil. 52 type iNil interface { 53 IsNil() bool 54 } 55 56 // iTableName is the interface for retrieving table name for struct. 57 type iTableName interface { 58 TableName() string 59 } 60 61 const ( 62 OrmTagForStruct = "orm" 63 OrmTagForTable = "table" 64 OrmTagForWith = "with" 65 OrmTagForWithWhere = "where" 66 OrmTagForWithOrder = "order" 67 OrmTagForWithUnscoped = "unscoped" 68 OrmTagForDo = "do" 69 ) 70 71 var ( 72 // quoteWordReg is the regular expression object for a word check. 73 quoteWordReg = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`) 74 75 // structTagPriority tags for struct converting for orm field mapping. 76 structTagPriority = append([]string{OrmTagForStruct}, gtag.StructTagPriority...) 77 ) 78 79 // WithDB injects given db object into context and returns a new context. 80 func WithDB(ctx context.Context, db DB) context.Context { 81 if db == nil { 82 return ctx 83 } 84 dbCtx := db.GetCtx() 85 if ctxDb := DBFromCtx(dbCtx); ctxDb != nil { 86 return dbCtx 87 } 88 ctx = context.WithValue(ctx, ctxKeyForDB, db) 89 return ctx 90 } 91 92 // DBFromCtx retrieves and returns DB object from context. 93 func DBFromCtx(ctx context.Context) DB { 94 if ctx == nil { 95 return nil 96 } 97 v := ctx.Value(ctxKeyForDB) 98 if v != nil { 99 return v.(DB) 100 } 101 return nil 102 } 103 104 // ToSQL formats and returns the last one of sql statements in given closure function 105 // WITHOUT TRULY EXECUTING IT. 106 // Be caution that, all the following sql statements should use the context object passing by function `f`. 107 func ToSQL(ctx context.Context, f func(ctx context.Context) error) (sql string, err error) { 108 var manager = &CatchSQLManager{ 109 SQLArray: garray.NewStrArray(), 110 DoCommit: false, 111 } 112 ctx = context.WithValue(ctx, ctxKeyCatchSQL, manager) 113 err = f(ctx) 114 sql, _ = manager.SQLArray.PopRight() 115 return 116 } 117 118 // CatchSQL catches and returns all sql statements that are EXECUTED in given closure function. 119 // Be caution that, all the following sql statements should use the context object passing by function `f`. 120 func CatchSQL(ctx context.Context, f func(ctx context.Context) error) (sqlArray []string, err error) { 121 var manager = &CatchSQLManager{ 122 SQLArray: garray.NewStrArray(), 123 DoCommit: true, 124 } 125 ctx = context.WithValue(ctx, ctxKeyCatchSQL, manager) 126 err = f(ctx) 127 return manager.SQLArray.Slice(), err 128 } 129 130 // isDoStruct checks and returns whether given type is a DO struct. 131 func isDoStruct(object interface{}) bool { 132 // It checks by struct name like "XxxForDao", to be compatible with old version. 133 // TODO remove this compatible codes in future. 134 reflectType := reflect.TypeOf(object) 135 if gstr.HasSuffix(reflectType.String(), modelForDaoSuffix) { 136 return true 137 } 138 // It checks by struct meta for DO struct in version. 139 if ormTag := gmeta.Get(object, OrmTagForStruct); !ormTag.IsEmpty() { 140 match, _ := gregex.MatchString( 141 fmt.Sprintf(`%s\s*:\s*([^,]+)`, OrmTagForDo), 142 ormTag.String(), 143 ) 144 if len(match) > 1 { 145 return gconv.Bool(match[1]) 146 } 147 } 148 return false 149 } 150 151 // getTableNameFromOrmTag retrieves and returns the table name from struct object. 152 func getTableNameFromOrmTag(object interface{}) string { 153 var tableName string 154 // Use the interface value. 155 if r, ok := object.(iTableName); ok { 156 tableName = r.TableName() 157 } 158 // User meta data tag "orm". 159 if tableName == "" { 160 if ormTag := gmeta.Get(object, OrmTagForStruct); !ormTag.IsEmpty() { 161 match, _ := gregex.MatchString( 162 fmt.Sprintf(`%s\s*:\s*([^,]+)`, OrmTagForTable), 163 ormTag.String(), 164 ) 165 if len(match) > 1 { 166 tableName = match[1] 167 } 168 } 169 } 170 // Use the struct name of snake case. 171 if tableName == "" { 172 if t, err := gstructs.StructType(object); err != nil { 173 panic(err) 174 } else { 175 tableName = gstr.CaseSnakeFirstUpper( 176 gstr.StrEx(t.String(), "."), 177 ) 178 } 179 } 180 return tableName 181 } 182 183 // ListItemValues retrieves and returns the elements of all item struct/map with key `key`. 184 // Note that the parameter `list` should be type of slice which contains elements of map or struct, 185 // or else it returns an empty slice. 186 // 187 // The parameter `list` supports types like: 188 // []map[string]interface{} 189 // []map[string]sub-map 190 // []struct 191 // []struct:sub-struct 192 // Note that the sub-map/sub-struct makes sense only if the optional parameter `subKey` is given. 193 // See gutil.ListItemValues. 194 func ListItemValues(list interface{}, key interface{}, subKey ...interface{}) (values []interface{}) { 195 return gutil.ListItemValues(list, key, subKey...) 196 } 197 198 // ListItemValuesUnique retrieves and returns the unique elements of all struct/map with key `key`. 199 // Note that the parameter `list` should be type of slice which contains elements of map or struct, 200 // or else it returns an empty slice. 201 // See gutil.ListItemValuesUnique. 202 func ListItemValuesUnique(list interface{}, key string, subKey ...interface{}) []interface{} { 203 return gutil.ListItemValuesUnique(list, key, subKey...) 204 } 205 206 // GetInsertOperationByOption returns proper insert option with given parameter `option`. 207 func GetInsertOperationByOption(option InsertOption) string { 208 var operator string 209 switch option { 210 case InsertOptionReplace: 211 operator = InsertOperationReplace 212 case InsertOptionIgnore: 213 operator = InsertOperationIgnore 214 default: 215 operator = InsertOperationInsert 216 } 217 return operator 218 } 219 220 func anyValueToMapBeforeToRecord(value interface{}) map[string]interface{} { 221 convertedMap := gconv.Map(value, gconv.MapOption{ 222 Tags: structTagPriority, 223 OmitEmpty: true, // To be compatible with old version from v2.6.0. 224 }) 225 if gutil.OriginValueAndKind(value).OriginKind != reflect.Struct { 226 return convertedMap 227 } 228 // It here converts all struct/map slice attributes to json string. 229 for k, v := range convertedMap { 230 originValueAndKind := gutil.OriginValueAndKind(v) 231 switch originValueAndKind.OriginKind { 232 // Check map item slice item. 233 case reflect.Array, reflect.Slice: 234 mapItemValue := originValueAndKind.OriginValue 235 if mapItemValue.Len() == 0 { 236 break 237 } 238 // Check slice item type struct/map type. 239 switch mapItemValue.Index(0).Kind() { 240 case reflect.Struct, reflect.Map: 241 mapItemJsonBytes, err := json.Marshal(v) 242 if err != nil { 243 // Do not eat any error. 244 intlog.Error(context.TODO(), err) 245 } 246 convertedMap[k] = mapItemJsonBytes 247 } 248 } 249 } 250 return convertedMap 251 } 252 253 // MapOrStructToMapDeep converts `value` to map type recursively(if attribute struct is embedded). 254 // The parameter `value` should be type of *map/map/*struct/struct. 255 // It supports embedded struct definition for struct. 256 func MapOrStructToMapDeep(value interface{}, omitempty bool) map[string]interface{} { 257 m := gconv.Map(value, gconv.MapOption{ 258 Tags: structTagPriority, 259 OmitEmpty: omitempty, 260 }) 261 for k, v := range m { 262 switch v.(type) { 263 case time.Time, *time.Time, gtime.Time, *gtime.Time, gjson.Json, *gjson.Json: 264 m[k] = v 265 } 266 } 267 return m 268 } 269 270 // doQuoteTableName adds prefix string and quote chars for table name. It handles table string like: 271 // "user", "user u", "user,user_detail", "user u, user_detail ut", "user as u, user_detail as ut", 272 // "user.user u", "`user`.`user` u". 273 // 274 // Note that, this will automatically check the table prefix whether already added, if true it does 275 // nothing to the table name, or else adds the prefix to the table name and returns new table name with prefix. 276 func doQuoteTableName(table, prefix, charLeft, charRight string) string { 277 var ( 278 index int 279 chars = charLeft + charRight 280 array1 = gstr.SplitAndTrim(table, ",") 281 ) 282 for k1, v1 := range array1 { 283 array2 := gstr.SplitAndTrim(v1, " ") 284 // Trim the security chars. 285 array2[0] = gstr.Trim(array2[0], chars) 286 // Check whether it has database name. 287 array3 := gstr.Split(gstr.Trim(array2[0]), ".") 288 for k, v := range array3 { 289 array3[k] = gstr.Trim(v, chars) 290 } 291 index = len(array3) - 1 292 // If the table name already has the prefix, skips the prefix adding. 293 if len(array3[index]) <= len(prefix) || array3[index][:len(prefix)] != prefix { 294 array3[index] = prefix + array3[index] 295 } 296 array2[0] = gstr.Join(array3, ".") 297 // Add the security chars. 298 array2[0] = doQuoteString(array2[0], charLeft, charRight) 299 array1[k1] = gstr.Join(array2, " ") 300 } 301 return gstr.Join(array1, ",") 302 } 303 304 // doQuoteWord checks given string `s` a word, if true quotes it with `charLeft` and `charRight` 305 // and returns the quoted string; or else returns `s` without any change. 306 func doQuoteWord(s, charLeft, charRight string) string { 307 if quoteWordReg.MatchString(s) && !gstr.ContainsAny(s, charLeft+charRight) { 308 return charLeft + s + charRight 309 } 310 return s 311 } 312 313 // doQuoteString quotes string with quote chars. 314 // For example, if quote char is '`': 315 // "null" => "NULL" 316 // "user" => "`user`" 317 // "user u" => "`user` u" 318 // "user,user_detail" => "`user`,`user_detail`" 319 // "user u, user_detail ut" => "`user` u,`user_detail` ut" 320 // "user.user u, user.user_detail ut" => "`user`.`user` u,`user`.`user_detail` ut" 321 // "u.id, u.name, u.age" => "`u`.`id`,`u`.`name`,`u`.`age`" 322 // "u.id asc" => "`u`.`id` asc". 323 func doQuoteString(s, charLeft, charRight string) string { 324 array1 := gstr.SplitAndTrim(s, ",") 325 for k1, v1 := range array1 { 326 array2 := gstr.SplitAndTrim(v1, " ") 327 array3 := gstr.Split(gstr.Trim(array2[0]), ".") 328 if len(array3) == 1 { 329 if strings.EqualFold(array3[0], "NULL") { 330 array3[0] = doQuoteWord(array3[0], "", "") 331 } else { 332 array3[0] = doQuoteWord(array3[0], charLeft, charRight) 333 } 334 } else if len(array3) >= 2 { 335 array3[0] = doQuoteWord(array3[0], charLeft, charRight) 336 // Note: 337 // mysql: u.uid 338 // mssql double dots: Database..Table 339 array3[len(array3)-1] = doQuoteWord(array3[len(array3)-1], charLeft, charRight) 340 } 341 array2[0] = gstr.Join(array3, ".") 342 array1[k1] = gstr.Join(array2, " ") 343 } 344 return gstr.Join(array1, ",") 345 } 346 347 func getFieldsFromStructOrMap(structOrMap interface{}) (fields []string) { 348 fields = []string{} 349 if utils.IsStruct(structOrMap) { 350 structFields, _ := gstructs.Fields(gstructs.FieldsInput{ 351 Pointer: structOrMap, 352 RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag, 353 }) 354 var ormTagValue string 355 for _, structField := range structFields { 356 ormTagValue = structField.Tag(OrmTagForStruct) 357 ormTagValue = gstr.Split(gstr.Trim(ormTagValue), ",")[0] 358 if ormTagValue != "" && gregex.IsMatchString(regularFieldNameRegPattern, ormTagValue) { 359 fields = append(fields, ormTagValue) 360 } else { 361 fields = append(fields, structField.Name()) 362 } 363 } 364 } else { 365 fields = gutil.Keys(structOrMap) 366 } 367 return 368 } 369 370 // GetPrimaryKeyCondition returns a new where condition by primary field name. 371 // The optional parameter `where` is like follows: 372 // 123 => primary=123 373 // []int{1, 2, 3} => primary IN(1,2,3) 374 // "john" => primary='john' 375 // []string{"john", "smith"} => primary IN('john','smith') 376 // g.Map{"id": g.Slice{1,2,3}} => id IN(1,2,3) 377 // g.Map{"id": 1, "name": "john"} => id=1 AND name='john' 378 // etc. 379 // 380 // Note that it returns the given `where` parameter directly if the `primary` is empty 381 // or length of `where` > 1. 382 func GetPrimaryKeyCondition(primary string, where ...interface{}) (newWhereCondition []interface{}) { 383 if len(where) == 0 { 384 return nil 385 } 386 if primary == "" { 387 return where 388 } 389 if len(where) == 1 { 390 var ( 391 rv = reflect.ValueOf(where[0]) 392 kind = rv.Kind() 393 ) 394 if kind == reflect.Ptr { 395 rv = rv.Elem() 396 kind = rv.Kind() 397 } 398 switch kind { 399 case reflect.Map, reflect.Struct: 400 // Ignore the parameter `primary`. 401 break 402 403 default: 404 return []interface{}{map[string]interface{}{ 405 primary: where[0], 406 }} 407 } 408 } 409 return where 410 } 411 412 type formatWhereHolderInput struct { 413 WhereHolder 414 OmitNil bool 415 OmitEmpty bool 416 Schema string 417 Table string // Table is used for fields mapping and filtering internally. 418 } 419 420 func isKeyValueCanBeOmitEmpty(omitEmpty bool, whereType string, key, value interface{}) bool { 421 if !omitEmpty { 422 return false 423 } 424 // Eg: 425 // Where("id", []int{}).All() -> SELECT xxx FROM xxx WHERE 0=1 426 // Where("name", "").All() -> SELECT xxx FROM xxx WHERE `name`='' 427 // OmitEmpty().Where("id", []int{}).All() -> SELECT xxx FROM xxx 428 // OmitEmpty().Where("name", "").All() -> SELECT xxx FROM xxx 429 // OmitEmpty().Where("1").All() -> SELECT xxx FROM xxx WHERE 1 430 switch whereType { 431 case whereHolderTypeNoArgs: 432 return false 433 434 case whereHolderTypeIn: 435 return gutil.IsEmpty(value) 436 437 default: 438 if gstr.Count(gconv.String(key), "?") == 0 && gutil.IsEmpty(value) { 439 return true 440 } 441 } 442 return false 443 } 444 445 // formatWhereHolder formats where statement and its arguments for `Where` and `Having` statements. 446 func formatWhereHolder(ctx context.Context, db DB, in formatWhereHolderInput) (newWhere string, newArgs []interface{}) { 447 var ( 448 buffer = bytes.NewBuffer(nil) 449 reflectInfo = reflection.OriginValueAndKind(in.Where) 450 ) 451 switch reflectInfo.OriginKind { 452 case reflect.Array, reflect.Slice: 453 newArgs = formatWhereInterfaces(db, gconv.Interfaces(in.Where), buffer, newArgs) 454 455 case reflect.Map: 456 for key, value := range MapOrStructToMapDeep(in.Where, true) { 457 if in.OmitNil && empty.IsNil(value) { 458 continue 459 } 460 if in.OmitEmpty && empty.IsEmpty(value) { 461 continue 462 } 463 newArgs = formatWhereKeyValue(formatWhereKeyValueInput{ 464 Db: db, 465 Buffer: buffer, 466 Args: newArgs, 467 Key: key, 468 Value: value, 469 Prefix: in.Prefix, 470 Type: in.Type, 471 }) 472 } 473 474 case reflect.Struct: 475 // If the `where` parameter is `DO` struct, it then adds `OmitNil` option for this condition, 476 // which will filter all nil parameters in `where`. 477 if isDoStruct(in.Where) { 478 in.OmitNil = true 479 } 480 // If `where` struct implements `iIterator` interface, 481 // it then uses its Iterate function to iterate its key-value pairs. 482 // For example, ListMap and TreeMap are ordered map, 483 // which implement `iIterator` interface and are index-friendly for where conditions. 484 if iterator, ok := in.Where.(iIterator); ok { 485 iterator.Iterator(func(key, value interface{}) bool { 486 ketStr := gconv.String(key) 487 if in.OmitNil && empty.IsNil(value) { 488 return true 489 } 490 if in.OmitEmpty && empty.IsEmpty(value) { 491 return true 492 } 493 newArgs = formatWhereKeyValue(formatWhereKeyValueInput{ 494 Db: db, 495 Buffer: buffer, 496 Args: newArgs, 497 Key: ketStr, 498 Value: value, 499 OmitEmpty: in.OmitEmpty, 500 Prefix: in.Prefix, 501 Type: in.Type, 502 }) 503 return true 504 }) 505 break 506 } 507 // Automatically mapping and filtering the struct attribute. 508 var ( 509 reflectType = reflectInfo.OriginValue.Type() 510 structField reflect.StructField 511 data = MapOrStructToMapDeep(in.Where, true) 512 ) 513 // If `Prefix` is given, it checks and retrieves the table name. 514 if in.Prefix != "" { 515 hasTable, _ := db.GetCore().HasTable(in.Prefix) 516 if hasTable { 517 in.Table = in.Prefix 518 } else { 519 ormTagTableName := getTableNameFromOrmTag(in.Where) 520 if ormTagTableName != "" { 521 in.Table = ormTagTableName 522 } 523 } 524 } 525 // Mapping and filtering fields if `Table` is given. 526 if in.Table != "" { 527 data, _ = db.GetCore().mappingAndFilterData(ctx, in.Schema, in.Table, data, true) 528 } 529 // Put the struct attributes in sequence in Where statement. 530 var ormTagValue string 531 for i := 0; i < reflectType.NumField(); i++ { 532 structField = reflectType.Field(i) 533 // Use tag value from `orm` as field name if specified. 534 ormTagValue = structField.Tag.Get(OrmTagForStruct) 535 ormTagValue = gstr.Split(gstr.Trim(ormTagValue), ",")[0] 536 if ormTagValue == "" { 537 ormTagValue = structField.Name 538 } 539 foundKey, foundValue := gutil.MapPossibleItemByKey(data, ormTagValue) 540 if foundKey != "" { 541 if in.OmitNil && empty.IsNil(foundValue) { 542 continue 543 } 544 if in.OmitEmpty && empty.IsEmpty(foundValue) { 545 continue 546 } 547 newArgs = formatWhereKeyValue(formatWhereKeyValueInput{ 548 Db: db, 549 Buffer: buffer, 550 Args: newArgs, 551 Key: foundKey, 552 Value: foundValue, 553 OmitEmpty: in.OmitEmpty, 554 Prefix: in.Prefix, 555 Type: in.Type, 556 }) 557 } 558 } 559 560 default: 561 // Where filter. 562 var omitEmptyCheckValue interface{} 563 if len(in.Args) == 1 { 564 omitEmptyCheckValue = in.Args[0] 565 } else { 566 omitEmptyCheckValue = in.Args 567 } 568 if isKeyValueCanBeOmitEmpty(in.OmitEmpty, in.Type, in.Where, omitEmptyCheckValue) { 569 return 570 } 571 // Usually a string. 572 whereStr := gstr.Trim(gconv.String(in.Where)) 573 // Is `whereStr` a field name which composed as a key-value condition? 574 // Eg: 575 // Where("id", 1) 576 // Where("id", g.Slice{1,2,3}) 577 if gregex.IsMatchString(regularFieldNameWithoutDotRegPattern, whereStr) && len(in.Args) == 1 { 578 newArgs = formatWhereKeyValue(formatWhereKeyValueInput{ 579 Db: db, 580 Buffer: buffer, 581 Args: newArgs, 582 Key: whereStr, 583 Value: in.Args[0], 584 OmitEmpty: in.OmitEmpty, 585 Prefix: in.Prefix, 586 Type: in.Type, 587 }) 588 in.Args = in.Args[:0] 589 break 590 } 591 // If the first part is column name, it automatically adds prefix to the column. 592 if in.Prefix != "" { 593 array := gstr.Split(whereStr, " ") 594 if ok, _ := db.GetCore().HasField(ctx, in.Table, array[0]); ok { 595 whereStr = in.Prefix + "." + whereStr 596 } 597 } 598 // Regular string and parameter place holder handling. 599 // Eg: 600 // Where("id in(?) and name=?", g.Slice{1,2,3}, "john") 601 i := 0 602 for { 603 if i >= len(in.Args) { 604 break 605 } 606 // =============================================================== 607 // Sub query, which is always used along with a string condition. 608 // =============================================================== 609 if subModel, ok := in.Args[i].(*Model); ok { 610 index := -1 611 whereStr, _ = gregex.ReplaceStringFunc(`(\?)`, whereStr, func(s string) string { 612 index++ 613 if i+len(newArgs) == index { 614 sqlWithHolder, holderArgs := subModel.getHolderAndArgsAsSubModel(ctx) 615 in.Args = gutil.SliceInsertAfter(in.Args, i, holderArgs...) 616 // Automatically adding the brackets. 617 return "(" + sqlWithHolder + ")" 618 } 619 return s 620 }) 621 in.Args = gutil.SliceDelete(in.Args, i) 622 continue 623 } 624 i++ 625 } 626 buffer.WriteString(whereStr) 627 } 628 629 if buffer.Len() == 0 { 630 return "", in.Args 631 } 632 if len(in.Args) > 0 { 633 newArgs = append(newArgs, in.Args...) 634 } 635 newWhere = buffer.String() 636 if len(newArgs) > 0 { 637 if gstr.Pos(newWhere, "?") == -1 { 638 if gregex.IsMatchString(lastOperatorRegPattern, newWhere) { 639 // Eg: Where/And/Or("uid>=", 1) 640 newWhere += "?" 641 } else if gregex.IsMatchString(regularFieldNameRegPattern, newWhere) { 642 newWhere = db.GetCore().QuoteString(newWhere) 643 if len(newArgs) > 0 { 644 if utils.IsArray(newArgs[0]) { 645 // Eg: 646 // Where("id", []int{1,2,3}) 647 // Where("user.id", []int{1,2,3}) 648 newWhere += " IN (?)" 649 } else if empty.IsNil(newArgs[0]) { 650 // Eg: 651 // Where("id", nil) 652 // Where("user.id", nil) 653 newWhere += " IS NULL" 654 newArgs = nil 655 } else { 656 // Eg: 657 // Where/And/Or("uid", 1) 658 // Where/And/Or("user.uid", 1) 659 newWhere += "=?" 660 } 661 } 662 } 663 } 664 } 665 return handleSliceAndStructArgsForSql(newWhere, newArgs) 666 } 667 668 // formatWhereInterfaces formats `where` as []interface{}. 669 func formatWhereInterfaces(db DB, where []interface{}, buffer *bytes.Buffer, newArgs []interface{}) []interface{} { 670 if len(where) == 0 { 671 return newArgs 672 } 673 if len(where)%2 != 0 { 674 buffer.WriteString(gstr.Join(gconv.Strings(where), "")) 675 return newArgs 676 } 677 var str string 678 for i := 0; i < len(where); i += 2 { 679 str = gconv.String(where[i]) 680 if buffer.Len() > 0 { 681 buffer.WriteString(" AND " + db.GetCore().QuoteWord(str) + "=?") 682 } else { 683 buffer.WriteString(db.GetCore().QuoteWord(str) + "=?") 684 } 685 if s, ok := where[i+1].(Raw); ok { 686 buffer.WriteString(gconv.String(s)) 687 } else { 688 newArgs = append(newArgs, where[i+1]) 689 } 690 } 691 return newArgs 692 } 693 694 type formatWhereKeyValueInput struct { 695 Db DB // Db is the underlying DB object for current operation. 696 Buffer *bytes.Buffer // Buffer is the sql statement string without Args for current operation. 697 Args []interface{} // Args is the full arguments of current operation. 698 Key string // The field name, eg: "id", "name", etc. 699 Value interface{} // The field value, can be any types. 700 Type string // The value in Where type. 701 OmitEmpty bool // Ignores current condition key if `value` is empty. 702 Prefix string // Field prefix, eg: "user", "order", etc. 703 } 704 705 // formatWhereKeyValue handles each key-value pair of the parameter map. 706 func formatWhereKeyValue(in formatWhereKeyValueInput) (newArgs []interface{}) { 707 var ( 708 quotedKey = in.Db.GetCore().QuoteWord(in.Key) 709 holderCount = gstr.Count(quotedKey, "?") 710 ) 711 if isKeyValueCanBeOmitEmpty(in.OmitEmpty, in.Type, quotedKey, in.Value) { 712 return in.Args 713 } 714 if in.Prefix != "" && !gstr.Contains(quotedKey, ".") { 715 quotedKey = in.Prefix + "." + quotedKey 716 } 717 if in.Buffer.Len() > 0 { 718 in.Buffer.WriteString(" AND ") 719 } 720 // If the value is type of slice, and there's only one '?' holder in 721 // the key string, it automatically adds '?' holder chars according to its arguments count 722 // and converts it to "IN" statement. 723 var ( 724 reflectValue = reflect.ValueOf(in.Value) 725 reflectKind = reflectValue.Kind() 726 ) 727 switch reflectKind { 728 // Slice argument. 729 case reflect.Slice, reflect.Array: 730 if holderCount == 0 { 731 in.Buffer.WriteString(quotedKey + " IN(?)") 732 in.Args = append(in.Args, in.Value) 733 } else { 734 if holderCount != reflectValue.Len() { 735 in.Buffer.WriteString(quotedKey) 736 in.Args = append(in.Args, in.Value) 737 } else { 738 in.Buffer.WriteString(quotedKey) 739 in.Args = append(in.Args, gconv.Interfaces(in.Value)...) 740 } 741 } 742 743 default: 744 if in.Value == nil || empty.IsNil(reflectValue) { 745 if gregex.IsMatchString(regularFieldNameRegPattern, in.Key) { 746 // The key is a single field name. 747 in.Buffer.WriteString(quotedKey + " IS NULL") 748 } else { 749 // The key may have operation chars. 750 in.Buffer.WriteString(quotedKey) 751 } 752 } else { 753 // It also supports "LIKE" statement, which we consider it an operator. 754 quotedKey = gstr.Trim(quotedKey) 755 if gstr.Pos(quotedKey, "?") == -1 { 756 like := " LIKE" 757 if len(quotedKey) > len(like) && gstr.Equal(quotedKey[len(quotedKey)-len(like):], like) { 758 // Eg: Where(g.Map{"name like": "john%"}) 759 in.Buffer.WriteString(quotedKey + " ?") 760 } else if gregex.IsMatchString(lastOperatorRegPattern, quotedKey) { 761 // Eg: Where(g.Map{"age > ": 16}) 762 in.Buffer.WriteString(quotedKey + " ?") 763 } else if gregex.IsMatchString(regularFieldNameRegPattern, in.Key) { 764 // The key is a regular field name. 765 in.Buffer.WriteString(quotedKey + "=?") 766 } else { 767 // The key is not a regular field name. 768 // Eg: Where(g.Map{"age > 16": nil}) 769 // Issue: https://github.com/gogf/gf/issues/765 770 if empty.IsEmpty(in.Value) { 771 in.Buffer.WriteString(quotedKey) 772 break 773 } else { 774 in.Buffer.WriteString(quotedKey + "=?") 775 } 776 } 777 } else { 778 in.Buffer.WriteString(quotedKey) 779 } 780 in.Args = append(in.Args, in.Value) 781 } 782 } 783 return in.Args 784 } 785 786 // handleSliceAndStructArgsForSql is an important function, which handles the sql and all its arguments 787 // before committing them to underlying driver. 788 func handleSliceAndStructArgsForSql( 789 oldSql string, oldArgs []interface{}, 790 ) (newSql string, newArgs []interface{}) { 791 newSql = oldSql 792 if len(oldArgs) == 0 { 793 return 794 } 795 // insertHolderCount is used to calculate the inserting position for the '?' holder. 796 insertHolderCount := 0 797 // Handles the slice and struct type argument item. 798 for index, oldArg := range oldArgs { 799 argReflectInfo := reflection.OriginValueAndKind(oldArg) 800 switch argReflectInfo.OriginKind { 801 case reflect.Slice, reflect.Array: 802 // It does not split the type of []byte. 803 // Eg: table.Where("name = ?", []byte("john")) 804 if _, ok := oldArg.([]byte); ok { 805 newArgs = append(newArgs, oldArg) 806 continue 807 } 808 var ( 809 valueHolderCount = gstr.Count(newSql, "?") 810 argSliceLength = argReflectInfo.OriginValue.Len() 811 ) 812 if argSliceLength == 0 { 813 // Empty slice argument, it converts the sql to a false sql. 814 // Example: 815 // Query("select * from xxx where id in(?)", g.Slice{}) -> select * from xxx where 0=1 816 // Where("id in(?)", g.Slice{}) -> WHERE 0=1 817 if gstr.Contains(newSql, "?") { 818 whereKeyWord := " WHERE " 819 if p := gstr.PosI(newSql, whereKeyWord); p == -1 { 820 return "0=1", []interface{}{} 821 } else { 822 return gstr.SubStr(newSql, 0, p+len(whereKeyWord)) + "0=1", []interface{}{} 823 } 824 } 825 } else { 826 // Example: 827 // Query("SELECT ?+?", g.Slice{1,2}) 828 // WHERE("id=?", g.Slice{1,2}) 829 for i := 0; i < argSliceLength; i++ { 830 newArgs = append(newArgs, argReflectInfo.OriginValue.Index(i).Interface()) 831 } 832 } 833 834 // If the '?' holder count equals the length of the slice, 835 // it does not implement the arguments splitting logic. 836 // Eg: db.Query("SELECT ?+?", g.Slice{1, 2}) 837 if len(oldArgs) == 1 && valueHolderCount == argSliceLength { 838 break 839 } 840 841 // counter is used to finding the inserting position for the '?' holder. 842 var ( 843 counter = 0 844 replaced = false 845 ) 846 newSql, _ = gregex.ReplaceStringFunc(`\?`, newSql, func(s string) string { 847 if replaced { 848 return s 849 } 850 counter++ 851 if counter == index+insertHolderCount+1 { 852 replaced = true 853 insertHolderCount += argSliceLength - 1 854 return "?" + strings.Repeat(",?", argSliceLength-1) 855 } 856 return s 857 }) 858 859 // Special struct handling. 860 case reflect.Struct: 861 switch oldArg.(type) { 862 // The underlying driver supports time.Time/*time.Time types. 863 case time.Time, *time.Time: 864 newArgs = append(newArgs, oldArg) 865 continue 866 867 case gtime.Time: 868 newArgs = append(newArgs, oldArg.(gtime.Time).Time) 869 continue 870 871 case *gtime.Time: 872 newArgs = append(newArgs, oldArg.(*gtime.Time).Time) 873 continue 874 875 default: 876 // It converts the struct to string in default 877 // if it has implemented the String interface. 878 if v, ok := oldArg.(iString); ok { 879 newArgs = append(newArgs, v.String()) 880 continue 881 } 882 } 883 newArgs = append(newArgs, oldArg) 884 885 default: 886 newArgs = append(newArgs, oldArg) 887 } 888 } 889 return 890 } 891 892 // FormatSqlWithArgs binds the arguments to the sql string and returns a complete 893 // sql string, just for debugging. 894 func FormatSqlWithArgs(sql string, args []interface{}) string { 895 index := -1 896 newQuery, _ := gregex.ReplaceStringFunc( 897 `(\?|:v\d+|\$\d+|@p\d+)`, 898 sql, 899 func(s string) string { 900 index++ 901 if len(args) > index { 902 if args[index] == nil { 903 return "null" 904 } 905 // Parameters of type Raw do not require special treatment 906 if v, ok := args[index].(Raw); ok { 907 return gconv.String(v) 908 } 909 reflectInfo := reflection.OriginValueAndKind(args[index]) 910 if reflectInfo.OriginKind == reflect.Ptr && 911 (reflectInfo.OriginValue.IsNil() || !reflectInfo.OriginValue.IsValid()) { 912 return "null" 913 } 914 switch reflectInfo.OriginKind { 915 case reflect.String, reflect.Map, reflect.Slice, reflect.Array: 916 return `'` + gstr.QuoteMeta(gconv.String(args[index]), `'`) + `'` 917 918 case reflect.Struct: 919 if t, ok := args[index].(time.Time); ok { 920 return `'` + t.Format(`2006-01-02 15:04:05`) + `'` 921 } 922 return `'` + gstr.QuoteMeta(gconv.String(args[index]), `'`) + `'` 923 } 924 return gconv.String(args[index]) 925 } 926 return s 927 }) 928 return newQuery 929 } 930 931 // FormatMultiLineSqlToSingle formats sql template string into one line. 932 func FormatMultiLineSqlToSingle(sql string) (string, error) { 933 var err error 934 // format sql template string. 935 sql, err = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(sql)) 936 if err != nil { 937 return "", err 938 } 939 sql, err = gregex.ReplaceString(`\s{2,}`, " ", gstr.Trim(sql)) 940 if err != nil { 941 return "", err 942 } 943 return sql, nil 944 } 945 946 func genTableFieldsCacheKey(group, schema, table string) string { 947 return fmt.Sprintf( 948 `%s%s@%s#%s`, 949 cachePrefixTableFields, 950 group, 951 schema, 952 table, 953 ) 954 } 955 956 func genSelectCacheKey(table, group, schema, name, sql string, args ...interface{}) string { 957 if name == "" { 958 name = fmt.Sprintf( 959 `%s@%s#%s:%d`, 960 table, 961 group, 962 schema, 963 ghash.BKDR64([]byte(sql+", @PARAMS:"+gconv.String(args))), 964 ) 965 } 966 return fmt.Sprintf(`%s%s`, cachePrefixSelectCache, name) 967 }