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