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  }