github.com/wangyougui/gf/v2@v2.6.5/database/gdb/gdb_core.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 8 package gdb 9 10 import ( 11 "context" 12 "database/sql" 13 "fmt" 14 "reflect" 15 "strings" 16 17 "github.com/wangyougui/gf/v2/container/gmap" 18 "github.com/wangyougui/gf/v2/container/gset" 19 "github.com/wangyougui/gf/v2/container/gvar" 20 "github.com/wangyougui/gf/v2/errors/gcode" 21 "github.com/wangyougui/gf/v2/errors/gerror" 22 "github.com/wangyougui/gf/v2/internal/intlog" 23 "github.com/wangyougui/gf/v2/internal/reflection" 24 "github.com/wangyougui/gf/v2/internal/utils" 25 "github.com/wangyougui/gf/v2/text/gregex" 26 "github.com/wangyougui/gf/v2/text/gstr" 27 "github.com/wangyougui/gf/v2/util/gconv" 28 "github.com/wangyougui/gf/v2/util/gutil" 29 ) 30 31 // GetCore returns the underlying *Core object. 32 func (c *Core) GetCore() *Core { 33 return c 34 } 35 36 // Ctx is a chaining function, which creates and returns a new DB that is a shallow copy 37 // of current DB object and with given context in it. 38 // Note that this returned DB object can be used only once, so do not assign it to 39 // a global or package variable for long using. 40 func (c *Core) Ctx(ctx context.Context) DB { 41 if ctx == nil { 42 return c.db 43 } 44 // It makes a shallow copy of current db and changes its context for next chaining operation. 45 var ( 46 err error 47 newCore = &Core{} 48 configNode = c.db.GetConfig() 49 ) 50 *newCore = *c 51 // It creates a new DB object(NOT NEW CONNECTION), which is commonly a wrapper for object `Core`. 52 newCore.db, err = driverMap[configNode.Type].New(newCore, configNode) 53 if err != nil { 54 // It is really a serious error here. 55 // Do not let it continue. 56 panic(err) 57 } 58 newCore.ctx = WithDB(ctx, newCore.db) 59 newCore.ctx = c.InjectInternalCtxData(newCore.ctx) 60 return newCore.db 61 } 62 63 // GetCtx returns the context for current DB. 64 // It returns `context.Background()` is there's no context previously set. 65 func (c *Core) GetCtx() context.Context { 66 ctx := c.ctx 67 if ctx == nil { 68 ctx = context.TODO() 69 } 70 return c.InjectInternalCtxData(ctx) 71 } 72 73 // GetCtxTimeout returns the context and cancel function for specified timeout type. 74 func (c *Core) GetCtxTimeout(ctx context.Context, timeoutType int) (context.Context, context.CancelFunc) { 75 if ctx == nil { 76 ctx = c.db.GetCtx() 77 } else { 78 ctx = context.WithValue(ctx, "WrappedByGetCtxTimeout", nil) 79 } 80 switch timeoutType { 81 case ctxTimeoutTypeExec: 82 if c.db.GetConfig().ExecTimeout > 0 { 83 return context.WithTimeout(ctx, c.db.GetConfig().ExecTimeout) 84 } 85 case ctxTimeoutTypeQuery: 86 if c.db.GetConfig().QueryTimeout > 0 { 87 return context.WithTimeout(ctx, c.db.GetConfig().QueryTimeout) 88 } 89 case ctxTimeoutTypePrepare: 90 if c.db.GetConfig().PrepareTimeout > 0 { 91 return context.WithTimeout(ctx, c.db.GetConfig().PrepareTimeout) 92 } 93 default: 94 panic(gerror.NewCodef(gcode.CodeInvalidParameter, "invalid context timeout type: %d", timeoutType)) 95 } 96 return ctx, func() {} 97 } 98 99 // Close closes the database and prevents new queries from starting. 100 // Close then waits for all queries that have started processing on the server 101 // to finish. 102 // 103 // It is rare to Close a DB, as the DB handle is meant to be 104 // long-lived and shared between many goroutines. 105 func (c *Core) Close(ctx context.Context) (err error) { 106 if err = c.cache.Close(ctx); err != nil { 107 return err 108 } 109 c.links.LockFunc(func(m map[string]interface{}) { 110 for k, v := range m { 111 if db, ok := v.(*sql.DB); ok { 112 err = db.Close() 113 if err != nil { 114 err = gerror.WrapCode(gcode.CodeDbOperationError, err, `db.Close failed`) 115 } 116 intlog.Printf(ctx, `close link: %s, err: %v`, k, err) 117 if err != nil { 118 return 119 } 120 delete(m, k) 121 } 122 } 123 }) 124 return 125 } 126 127 // Master creates and returns a connection from master node if master-slave configured. 128 // It returns the default connection if master-slave not configured. 129 func (c *Core) Master(schema ...string) (*sql.DB, error) { 130 var ( 131 usedSchema = gutil.GetOrDefaultStr(c.schema, schema...) 132 charL, charR = c.db.GetChars() 133 ) 134 return c.getSqlDb(true, gstr.Trim(usedSchema, charL+charR)) 135 } 136 137 // Slave creates and returns a connection from slave node if master-slave configured. 138 // It returns the default connection if master-slave not configured. 139 func (c *Core) Slave(schema ...string) (*sql.DB, error) { 140 var ( 141 usedSchema = gutil.GetOrDefaultStr(c.schema, schema...) 142 charL, charR = c.db.GetChars() 143 ) 144 return c.getSqlDb(false, gstr.Trim(usedSchema, charL+charR)) 145 } 146 147 // GetAll queries and returns data records from database. 148 func (c *Core) GetAll(ctx context.Context, sql string, args ...interface{}) (Result, error) { 149 return c.db.DoSelect(ctx, nil, sql, args...) 150 } 151 152 // DoSelect queries and returns data records from database. 153 func (c *Core) DoSelect(ctx context.Context, link Link, sql string, args ...interface{}) (result Result, err error) { 154 return c.db.DoQuery(ctx, link, sql, args...) 155 } 156 157 // GetOne queries and returns one record from database. 158 func (c *Core) GetOne(ctx context.Context, sql string, args ...interface{}) (Record, error) { 159 list, err := c.db.GetAll(ctx, sql, args...) 160 if err != nil { 161 return nil, err 162 } 163 if len(list) > 0 { 164 return list[0], nil 165 } 166 return nil, nil 167 } 168 169 // GetArray queries and returns data values as slice from database. 170 // Note that if there are multiple columns in the result, it returns just one column values randomly. 171 func (c *Core) GetArray(ctx context.Context, sql string, args ...interface{}) ([]Value, error) { 172 all, err := c.db.DoSelect(ctx, nil, sql, args...) 173 if err != nil { 174 return nil, err 175 } 176 return all.Array(), nil 177 } 178 179 // doGetStruct queries one record from database and converts it to given struct. 180 // The parameter `pointer` should be a pointer to struct. 181 func (c *Core) doGetStruct(ctx context.Context, pointer interface{}, sql string, args ...interface{}) error { 182 one, err := c.db.GetOne(ctx, sql, args...) 183 if err != nil { 184 return err 185 } 186 return one.Struct(pointer) 187 } 188 189 // doGetStructs queries records from database and converts them to given struct. 190 // The parameter `pointer` should be type of struct slice: []struct/[]*struct. 191 func (c *Core) doGetStructs(ctx context.Context, pointer interface{}, sql string, args ...interface{}) error { 192 all, err := c.db.GetAll(ctx, sql, args...) 193 if err != nil { 194 return err 195 } 196 return all.Structs(pointer) 197 } 198 199 // GetScan queries one or more records from database and converts them to given struct or 200 // struct array. 201 // 202 // If parameter `pointer` is type of struct pointer, it calls GetStruct internally for 203 // the conversion. If parameter `pointer` is type of slice, it calls GetStructs internally 204 // for conversion. 205 func (c *Core) GetScan(ctx context.Context, pointer interface{}, sql string, args ...interface{}) error { 206 reflectInfo := reflection.OriginTypeAndKind(pointer) 207 if reflectInfo.InputKind != reflect.Ptr { 208 return gerror.NewCodef( 209 gcode.CodeInvalidParameter, 210 "params should be type of pointer, but got: %v", 211 reflectInfo.InputKind, 212 ) 213 } 214 switch reflectInfo.OriginKind { 215 case reflect.Array, reflect.Slice: 216 return c.db.GetCore().doGetStructs(ctx, pointer, sql, args...) 217 218 case reflect.Struct: 219 return c.db.GetCore().doGetStruct(ctx, pointer, sql, args...) 220 } 221 return gerror.NewCodef( 222 gcode.CodeInvalidParameter, 223 `in valid parameter type "%v", of which element type should be type of struct/slice`, 224 reflectInfo.InputType, 225 ) 226 } 227 228 // GetValue queries and returns the field value from database. 229 // The sql should query only one field from database, or else it returns only one 230 // field of the result. 231 func (c *Core) GetValue(ctx context.Context, sql string, args ...interface{}) (Value, error) { 232 one, err := c.db.GetOne(ctx, sql, args...) 233 if err != nil { 234 return gvar.New(nil), err 235 } 236 for _, v := range one { 237 return v, nil 238 } 239 return gvar.New(nil), nil 240 } 241 242 // GetCount queries and returns the count from database. 243 func (c *Core) GetCount(ctx context.Context, sql string, args ...interface{}) (int, error) { 244 // If the query fields do not contain function "COUNT", 245 // it replaces the sql string and adds the "COUNT" function to the fields. 246 if !gregex.IsMatchString(`(?i)SELECT\s+COUNT\(.+\)\s+FROM`, sql) { 247 sql, _ = gregex.ReplaceString(`(?i)(SELECT)\s+(.+)\s+(FROM)`, `$1 COUNT($2) $3`, sql) 248 } 249 value, err := c.db.GetValue(ctx, sql, args...) 250 if err != nil { 251 return 0, err 252 } 253 return value.Int(), nil 254 } 255 256 // Union does "(SELECT xxx FROM xxx) UNION (SELECT xxx FROM xxx) ..." statement. 257 func (c *Core) Union(unions ...*Model) *Model { 258 var ctx = c.db.GetCtx() 259 return c.doUnion(ctx, unionTypeNormal, unions...) 260 } 261 262 // UnionAll does "(SELECT xxx FROM xxx) UNION ALL (SELECT xxx FROM xxx) ..." statement. 263 func (c *Core) UnionAll(unions ...*Model) *Model { 264 var ctx = c.db.GetCtx() 265 return c.doUnion(ctx, unionTypeAll, unions...) 266 } 267 268 func (c *Core) doUnion(ctx context.Context, unionType int, unions ...*Model) *Model { 269 var ( 270 unionTypeStr string 271 composedSqlStr string 272 composedArgs = make([]interface{}, 0) 273 ) 274 if unionType == unionTypeAll { 275 unionTypeStr = "UNION ALL" 276 } else { 277 unionTypeStr = "UNION" 278 } 279 for _, v := range unions { 280 sqlWithHolder, holderArgs := v.getFormattedSqlAndArgs(ctx, queryTypeNormal, false) 281 if composedSqlStr == "" { 282 composedSqlStr += fmt.Sprintf(`(%s)`, sqlWithHolder) 283 } else { 284 composedSqlStr += fmt.Sprintf(` %s (%s)`, unionTypeStr, sqlWithHolder) 285 } 286 composedArgs = append(composedArgs, holderArgs...) 287 } 288 return c.db.Raw(composedSqlStr, composedArgs...) 289 } 290 291 // PingMaster pings the master node to check authentication or keeps the connection alive. 292 func (c *Core) PingMaster() error { 293 var ctx = c.db.GetCtx() 294 if master, err := c.db.Master(); err != nil { 295 return err 296 } else { 297 if err = master.PingContext(ctx); err != nil { 298 err = gerror.WrapCode(gcode.CodeDbOperationError, err, `master.Ping failed`) 299 } 300 return err 301 } 302 } 303 304 // PingSlave pings the slave node to check authentication or keeps the connection alive. 305 func (c *Core) PingSlave() error { 306 var ctx = c.db.GetCtx() 307 if slave, err := c.db.Slave(); err != nil { 308 return err 309 } else { 310 if err = slave.PingContext(ctx); err != nil { 311 err = gerror.WrapCode(gcode.CodeDbOperationError, err, `slave.Ping failed`) 312 } 313 return err 314 } 315 } 316 317 // Insert does "INSERT INTO ..." statement for the table. 318 // If there's already one unique record of the data in the table, it returns error. 319 // 320 // The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc. 321 // Eg: 322 // Data(g.Map{"uid": 10000, "name":"john"}) 323 // Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) 324 // 325 // The parameter `batch` specifies the batch operation count when given data is slice. 326 func (c *Core) Insert(ctx context.Context, table string, data interface{}, batch ...int) (sql.Result, error) { 327 if len(batch) > 0 { 328 return c.Model(table).Ctx(ctx).Data(data).Batch(batch[0]).Insert() 329 } 330 return c.Model(table).Ctx(ctx).Data(data).Insert() 331 } 332 333 // InsertIgnore does "INSERT IGNORE INTO ..." statement for the table. 334 // If there's already one unique record of the data in the table, it ignores the inserting. 335 // 336 // The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc. 337 // Eg: 338 // Data(g.Map{"uid": 10000, "name":"john"}) 339 // Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) 340 // 341 // The parameter `batch` specifies the batch operation count when given data is slice. 342 func (c *Core) InsertIgnore(ctx context.Context, table string, data interface{}, batch ...int) (sql.Result, error) { 343 if len(batch) > 0 { 344 return c.Model(table).Ctx(ctx).Data(data).Batch(batch[0]).InsertIgnore() 345 } 346 return c.Model(table).Ctx(ctx).Data(data).InsertIgnore() 347 } 348 349 // InsertAndGetId performs action Insert and returns the last insert id that automatically generated. 350 func (c *Core) InsertAndGetId(ctx context.Context, table string, data interface{}, batch ...int) (int64, error) { 351 if len(batch) > 0 { 352 return c.Model(table).Ctx(ctx).Data(data).Batch(batch[0]).InsertAndGetId() 353 } 354 return c.Model(table).Ctx(ctx).Data(data).InsertAndGetId() 355 } 356 357 // Replace does "REPLACE INTO ..." statement for the table. 358 // If there's already one unique record of the data in the table, it deletes the record 359 // and inserts a new one. 360 // 361 // The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc. 362 // Eg: 363 // Data(g.Map{"uid": 10000, "name":"john"}) 364 // Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) 365 // 366 // The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc. 367 // If given data is type of slice, it then does batch replacing, and the optional parameter 368 // `batch` specifies the batch operation count. 369 func (c *Core) Replace(ctx context.Context, table string, data interface{}, batch ...int) (sql.Result, error) { 370 if len(batch) > 0 { 371 return c.Model(table).Ctx(ctx).Data(data).Batch(batch[0]).Replace() 372 } 373 return c.Model(table).Ctx(ctx).Data(data).Replace() 374 } 375 376 // Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the table. 377 // It updates the record if there's primary or unique index in the saving data, 378 // or else it inserts a new record into the table. 379 // 380 // The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc. 381 // Eg: 382 // Data(g.Map{"uid": 10000, "name":"john"}) 383 // Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) 384 // 385 // If given data is type of slice, it then does batch saving, and the optional parameter 386 // `batch` specifies the batch operation count. 387 func (c *Core) Save(ctx context.Context, table string, data interface{}, batch ...int) (sql.Result, error) { 388 if len(batch) > 0 { 389 return c.Model(table).Ctx(ctx).Data(data).Batch(batch[0]).Save() 390 } 391 return c.Model(table).Ctx(ctx).Data(data).Save() 392 } 393 394 func (c *Core) fieldsToSequence(ctx context.Context, table string, fields []string) ([]string, error) { 395 var ( 396 fieldSet = gset.NewStrSetFrom(fields) 397 fieldsResultInSequence = make([]string, 0) 398 tableFields, err = c.db.TableFields(ctx, table) 399 ) 400 if err != nil { 401 return nil, err 402 } 403 // Sort the fields in order. 404 var fieldsOfTableInSequence = make([]string, len(tableFields)) 405 for _, field := range tableFields { 406 fieldsOfTableInSequence[field.Index] = field.Name 407 } 408 // Sort the input fields. 409 for _, fieldName := range fieldsOfTableInSequence { 410 if fieldSet.Contains(fieldName) { 411 fieldsResultInSequence = append(fieldsResultInSequence, fieldName) 412 } 413 } 414 return fieldsResultInSequence, nil 415 } 416 417 // DoInsert inserts or updates data for given table. 418 // This function is usually used for custom interface definition, you do not need call it manually. 419 // The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc. 420 // Eg: 421 // Data(g.Map{"uid": 10000, "name":"john"}) 422 // Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) 423 // 424 // The parameter `option` values are as follows: 425 // InsertOptionDefault: just insert, if there's unique/primary key in the data, it returns error; 426 // InsertOptionReplace: if there's unique/primary key in the data, it deletes it from table and inserts a new one; 427 // InsertOptionSave: if there's unique/primary key in the data, it updates it or else inserts a new one; 428 // InsertOptionIgnore: if there's unique/primary key in the data, it ignores the inserting; 429 func (c *Core) DoInsert(ctx context.Context, link Link, table string, list List, option DoInsertOption) (result sql.Result, err error) { 430 var ( 431 keys []string // Field names. 432 values []string // Value holder string array, like: (?,?,?) 433 params []interface{} // Values that will be committed to underlying database driver. 434 onDuplicateStr string // onDuplicateStr is used in "ON DUPLICATE KEY UPDATE" statement. 435 ) 436 // ============================================================================================ 437 // Group the list by fields. Different fields to different list. 438 // It here uses ListMap to keep sequence for data inserting. 439 // ============================================================================================ 440 var keyListMap = gmap.NewListMap() 441 for _, item := range list { 442 var ( 443 tmpKeys = make([]string, 0) 444 tmpKeysInSequenceStr string 445 ) 446 for k := range item { 447 tmpKeys = append(tmpKeys, k) 448 } 449 keys, err = c.fieldsToSequence(ctx, table, tmpKeys) 450 if err != nil { 451 return nil, err 452 } 453 tmpKeysInSequenceStr = gstr.Join(keys, ",") 454 if !keyListMap.Contains(tmpKeysInSequenceStr) { 455 keyListMap.Set(tmpKeysInSequenceStr, make(List, 0)) 456 } 457 tmpKeysInSequenceList := keyListMap.Get(tmpKeysInSequenceStr).(List) 458 tmpKeysInSequenceList = append(tmpKeysInSequenceList, item) 459 keyListMap.Set(tmpKeysInSequenceStr, tmpKeysInSequenceList) 460 } 461 if keyListMap.Size() > 1 { 462 var ( 463 tmpResult sql.Result 464 sqlResult SqlResult 465 rowsAffected int64 466 ) 467 keyListMap.Iterator(func(key, value interface{}) bool { 468 tmpResult, err = c.DoInsert(ctx, link, table, value.(List), option) 469 if err != nil { 470 return false 471 } 472 rowsAffected, err = tmpResult.RowsAffected() 473 if err != nil { 474 return false 475 } 476 sqlResult.Result = tmpResult 477 sqlResult.Affected += rowsAffected 478 return true 479 }) 480 return &sqlResult, err 481 } 482 483 // Prepare the batch result pointer. 484 var ( 485 charL, charR = c.db.GetChars() 486 batchResult = new(SqlResult) 487 keysStr = charL + strings.Join(keys, charR+","+charL) + charR 488 operation = GetInsertOperationByOption(option.InsertOption) 489 ) 490 // Upsert clause only takes effect on Save operation. 491 if option.InsertOption == InsertOptionSave { 492 onDuplicateStr, err = c.db.FormatUpsert(keys, list, option) 493 if err != nil { 494 return nil, err 495 } 496 } 497 var ( 498 listLength = len(list) 499 valueHolders = make([]string, 0) 500 ) 501 for i := 0; i < listLength; i++ { 502 values = values[:0] 503 // Note that the map type is unordered, 504 // so it should use slice+key to retrieve the value. 505 for _, k := range keys { 506 if s, ok := list[i][k].(Raw); ok { 507 values = append(values, gconv.String(s)) 508 } else { 509 values = append(values, "?") 510 params = append(params, list[i][k]) 511 } 512 } 513 valueHolders = append(valueHolders, "("+gstr.Join(values, ",")+")") 514 // Batch package checks: It meets the batch number, or it is the last element. 515 if len(valueHolders) == option.BatchCount || (i == listLength-1 && len(valueHolders) > 0) { 516 var ( 517 stdSqlResult sql.Result 518 affectedRows int64 519 ) 520 stdSqlResult, err = c.db.DoExec(ctx, link, fmt.Sprintf( 521 "%s INTO %s(%s) VALUES%s %s", 522 operation, c.QuotePrefixTableName(table), keysStr, 523 gstr.Join(valueHolders, ","), 524 onDuplicateStr, 525 ), params...) 526 if err != nil { 527 return stdSqlResult, err 528 } 529 if affectedRows, err = stdSqlResult.RowsAffected(); err != nil { 530 err = gerror.WrapCode(gcode.CodeDbOperationError, err, `sql.Result.RowsAffected failed`) 531 return stdSqlResult, err 532 } else { 533 batchResult.Result = stdSqlResult 534 batchResult.Affected += affectedRows 535 } 536 params = params[:0] 537 valueHolders = valueHolders[:0] 538 } 539 } 540 return batchResult, nil 541 } 542 543 // Update does "UPDATE ... " statement for the table. 544 // 545 // The parameter `data` can be type of string/map/gmap/struct/*struct, etc. 546 // Eg: "uid=10000", "uid", 10000, g.Map{"uid": 10000, "name":"john"} 547 // 548 // The parameter `condition` can be type of string/map/gmap/slice/struct/*struct, etc. 549 // It is commonly used with parameter `args`. 550 // Eg: 551 // "uid=10000", 552 // "uid", 10000 553 // "money>? AND name like ?", 99999, "vip_%" 554 // "status IN (?)", g.Slice{1,2,3} 555 // "age IN(?,?)", 18, 50 556 // User{ Id : 1, UserName : "john"}. 557 func (c *Core) Update(ctx context.Context, table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) { 558 return c.Model(table).Ctx(ctx).Data(data).Where(condition, args...).Update() 559 } 560 561 // DoUpdate does "UPDATE ... " statement for the table. 562 // This function is usually used for custom interface definition, you do not need to call it manually. 563 func (c *Core) DoUpdate(ctx context.Context, link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) { 564 table = c.QuotePrefixTableName(table) 565 var ( 566 rv = reflect.ValueOf(data) 567 kind = rv.Kind() 568 ) 569 if kind == reflect.Ptr { 570 rv = rv.Elem() 571 kind = rv.Kind() 572 } 573 var ( 574 params []interface{} 575 updates string 576 ) 577 switch kind { 578 case reflect.Map, reflect.Struct: 579 var ( 580 fields []string 581 dataMap map[string]interface{} 582 counterHandler = func(column string, counter Counter) { 583 if counter.Value != 0 { 584 column = c.QuoteWord(column) 585 var ( 586 columnRef = c.QuoteWord(counter.Field) 587 columnVal = counter.Value 588 operator = "+" 589 ) 590 if columnVal < 0 { 591 operator = "-" 592 columnVal = -columnVal 593 } 594 fields = append(fields, fmt.Sprintf("%s=%s%s?", column, columnRef, operator)) 595 params = append(params, columnVal) 596 } 597 } 598 ) 599 dataMap, err = c.ConvertDataForRecord(ctx, data, table) 600 if err != nil { 601 return nil, err 602 } 603 // Sort the data keys in sequence of table fields. 604 var ( 605 dataKeys = make([]string, 0) 606 keysInSequence = make([]string, 0) 607 ) 608 for k := range dataMap { 609 dataKeys = append(dataKeys, k) 610 } 611 keysInSequence, err = c.fieldsToSequence(ctx, table, dataKeys) 612 if err != nil { 613 return nil, err 614 } 615 for _, k := range keysInSequence { 616 v := dataMap[k] 617 switch value := v.(type) { 618 case *Counter: 619 counterHandler(k, *value) 620 621 case Counter: 622 counterHandler(k, value) 623 624 default: 625 if s, ok := v.(Raw); ok { 626 fields = append(fields, c.QuoteWord(k)+"="+gconv.String(s)) 627 } else { 628 fields = append(fields, c.QuoteWord(k)+"=?") 629 params = append(params, v) 630 } 631 } 632 } 633 updates = strings.Join(fields, ",") 634 635 default: 636 updates = gconv.String(data) 637 } 638 if len(updates) == 0 { 639 return nil, gerror.NewCode(gcode.CodeMissingParameter, "data cannot be empty") 640 } 641 if len(params) > 0 { 642 args = append(params, args...) 643 } 644 // If no link passed, it then uses the master link. 645 if link == nil { 646 if link, err = c.MasterLink(); err != nil { 647 return nil, err 648 } 649 } 650 return c.db.DoExec(ctx, link, fmt.Sprintf( 651 "UPDATE %s SET %s%s", 652 table, updates, condition, 653 ), 654 args..., 655 ) 656 } 657 658 // Delete does "DELETE FROM ... " statement for the table. 659 // 660 // The parameter `condition` can be type of string/map/gmap/slice/struct/*struct, etc. 661 // It is commonly used with parameter `args`. 662 // Eg: 663 // "uid=10000", 664 // "uid", 10000 665 // "money>? AND name like ?", 99999, "vip_%" 666 // "status IN (?)", g.Slice{1,2,3} 667 // "age IN(?,?)", 18, 50 668 // User{ Id : 1, UserName : "john"}. 669 func (c *Core) Delete(ctx context.Context, table string, condition interface{}, args ...interface{}) (result sql.Result, err error) { 670 return c.Model(table).Ctx(ctx).Where(condition, args...).Delete() 671 } 672 673 // DoDelete does "DELETE FROM ... " statement for the table. 674 // This function is usually used for custom interface definition, you do not need call it manually. 675 func (c *Core) DoDelete(ctx context.Context, link Link, table string, condition string, args ...interface{}) (result sql.Result, err error) { 676 if link == nil { 677 if link, err = c.MasterLink(); err != nil { 678 return nil, err 679 } 680 } 681 table = c.QuotePrefixTableName(table) 682 return c.db.DoExec(ctx, link, fmt.Sprintf("DELETE FROM %s%s", table, condition), args...) 683 } 684 685 // FilteredLink retrieves and returns filtered `linkInfo` that can be using for 686 // logging or tracing purpose. 687 func (c *Core) FilteredLink() string { 688 return fmt.Sprintf( 689 `%s@%s(%s:%s)/%s`, 690 c.config.User, c.config.Protocol, c.config.Host, c.config.Port, c.config.Name, 691 ) 692 } 693 694 // MarshalJSON implements the interface MarshalJSON for json.Marshal. 695 // It just returns the pointer address. 696 // 697 // Note that this interface implements mainly for workaround for a json infinite loop bug 698 // of Golang version < v1.14. 699 func (c Core) MarshalJSON() ([]byte, error) { 700 return []byte(fmt.Sprintf(`%+v`, c)), nil 701 } 702 703 // writeSqlToLogger outputs the Sql object to logger. 704 // It is enabled only if configuration "debug" is true. 705 func (c *Core) writeSqlToLogger(ctx context.Context, sql *Sql) { 706 var transactionIdStr string 707 if sql.IsTransaction { 708 if v := ctx.Value(transactionIdForLoggerCtx); v != nil { 709 transactionIdStr = fmt.Sprintf(`[txid:%d] `, v.(uint64)) 710 } 711 } 712 s := fmt.Sprintf( 713 "[%3d ms] [%s] [%s] [rows:%-3d] %s%s", 714 sql.End-sql.Start, sql.Group, sql.Schema, sql.RowsAffected, transactionIdStr, sql.Format, 715 ) 716 if sql.Error != nil { 717 s += "\nError: " + sql.Error.Error() 718 c.logger.Error(ctx, s) 719 } else { 720 c.logger.Debug(ctx, s) 721 } 722 } 723 724 // HasTable determine whether the table name exists in the database. 725 func (c *Core) HasTable(name string) (bool, error) { 726 tables, err := c.GetTablesWithCache() 727 if err != nil { 728 return false, err 729 } 730 charL, charR := c.db.GetChars() 731 name = gstr.Trim(name, charL+charR) 732 for _, table := range tables { 733 if table == name { 734 return true, nil 735 } 736 } 737 return false, nil 738 } 739 740 // GetTablesWithCache retrieves and returns the table names of current database with cache. 741 func (c *Core) GetTablesWithCache() ([]string, error) { 742 var ( 743 ctx = c.db.GetCtx() 744 cacheKey = fmt.Sprintf(`Tables: %s`, c.db.GetGroup()) 745 ) 746 result, err := c.GetCache().GetOrSetFuncLock( 747 ctx, cacheKey, func(ctx context.Context) (interface{}, error) { 748 tableList, err := c.db.Tables(ctx) 749 if err != nil { 750 return false, err 751 } 752 return tableList, nil 753 }, 0, 754 ) 755 if err != nil { 756 return nil, err 757 } 758 return result.Strings(), nil 759 } 760 761 // IsSoftCreatedFieldName checks and returns whether given field name is an automatic-filled created time. 762 func (c *Core) IsSoftCreatedFieldName(fieldName string) bool { 763 if fieldName == "" { 764 return false 765 } 766 if config := c.db.GetConfig(); config.CreatedAt != "" { 767 if utils.EqualFoldWithoutChars(fieldName, config.CreatedAt) { 768 return true 769 } 770 return gstr.InArray(append([]string{config.CreatedAt}, createdFieldNames...), fieldName) 771 } 772 for _, v := range createdFieldNames { 773 if utils.EqualFoldWithoutChars(fieldName, v) { 774 return true 775 } 776 } 777 return false 778 } 779 780 // FormatSqlBeforeExecuting formats the sql string and its arguments before executing. 781 // The internal handleArguments function might be called twice during the SQL procedure, 782 // but do not worry about it, it's safe and efficient. 783 func (c *Core) FormatSqlBeforeExecuting(sql string, args []interface{}) (newSql string, newArgs []interface{}) { 784 // DO NOT do this as there may be multiple lines and comments in the sql. 785 // sql = gstr.Trim(sql) 786 // sql = gstr.Replace(sql, "\n", " ") 787 // sql, _ = gregex.ReplaceString(`\s{2,}`, ` `, sql) 788 return handleSliceAndStructArgsForSql(sql, args) 789 }