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