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  }