github.com/gogf/gf@v1.16.9/database/gdb/gdb_core_underlying.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  	"github.com/gogf/gf/errors/gcode"
    14  	"github.com/gogf/gf/errors/gerror"
    15  
    16  	"github.com/gogf/gf/os/gtime"
    17  )
    18  
    19  // Query commits one query SQL to underlying driver and returns the execution result.
    20  // It is most commonly used for data querying.
    21  func (c *Core) Query(sql string, args ...interface{}) (rows *sql.Rows, err error) {
    22  	return c.db.DoQuery(c.GetCtx(), nil, sql, args...)
    23  }
    24  
    25  // DoQuery commits the sql string and its arguments to underlying driver
    26  // through given link object and returns the execution result.
    27  func (c *Core) DoQuery(ctx context.Context, link Link, sql string, args ...interface{}) (rows *sql.Rows, err error) {
    28  	// Transaction checks.
    29  	if link == nil {
    30  		if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
    31  			// Firstly, check and retrieve transaction link from context.
    32  			link = &txLink{tx.tx}
    33  		} else if link, err = c.SlaveLink(); err != nil {
    34  			// Or else it creates one from master node.
    35  			return nil, err
    36  		}
    37  	} else if !link.IsTransaction() {
    38  		// If current link is not transaction link, it checks and retrieves transaction from context.
    39  		if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
    40  			link = &txLink{tx.tx}
    41  		}
    42  	}
    43  
    44  	if c.GetConfig().QueryTimeout > 0 {
    45  		ctx, _ = context.WithTimeout(ctx, c.GetConfig().QueryTimeout)
    46  	}
    47  
    48  	// Link execution.
    49  	sql, args = formatSql(sql, args)
    50  	sql, args, err = c.db.DoCommit(ctx, link, sql, args)
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  	mTime1 := gtime.TimestampMilli()
    55  	rows, err = link.QueryContext(ctx, sql, args...)
    56  	mTime2 := gtime.TimestampMilli()
    57  	sqlObj := &Sql{
    58  		Sql:           sql,
    59  		Type:          "DB.QueryContext",
    60  		Args:          args,
    61  		Format:        FormatSqlWithArgs(sql, args),
    62  		Error:         err,
    63  		Start:         mTime1,
    64  		End:           mTime2,
    65  		Group:         c.db.GetGroup(),
    66  		IsTransaction: link.IsTransaction(),
    67  	}
    68  	// Tracing and logging.
    69  	c.addSqlToTracing(ctx, sqlObj)
    70  	if c.db.GetDebug() {
    71  		c.writeSqlToLogger(ctx, sqlObj)
    72  	}
    73  	if err == nil {
    74  		return rows, nil
    75  	} else {
    76  		err = formatError(err, sql, args...)
    77  	}
    78  	return nil, err
    79  }
    80  
    81  // Exec commits one query SQL to underlying driver and returns the execution result.
    82  // It is most commonly used for data inserting and updating.
    83  func (c *Core) Exec(sql string, args ...interface{}) (result sql.Result, err error) {
    84  	return c.db.DoExec(c.GetCtx(), nil, sql, args...)
    85  }
    86  
    87  // DoExec commits the sql string and its arguments to underlying driver
    88  // through given link object and returns the execution result.
    89  func (c *Core) DoExec(ctx context.Context, link Link, sql string, args ...interface{}) (result sql.Result, err error) {
    90  	// Transaction checks.
    91  	if link == nil {
    92  		if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
    93  			// Firstly, check and retrieve transaction link from context.
    94  			link = &txLink{tx.tx}
    95  		} else if link, err = c.MasterLink(); err != nil {
    96  			// Or else it creates one from master node.
    97  			return nil, err
    98  		}
    99  	} else if !link.IsTransaction() {
   100  		// If current link is not transaction link, it checks and retrieves transaction from context.
   101  		if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
   102  			link = &txLink{tx.tx}
   103  		}
   104  	}
   105  
   106  	if c.GetConfig().ExecTimeout > 0 {
   107  		var cancelFunc context.CancelFunc
   108  		ctx, cancelFunc = context.WithTimeout(ctx, c.GetConfig().ExecTimeout)
   109  		defer cancelFunc()
   110  	}
   111  
   112  	// Link execution.
   113  	sql, args = formatSql(sql, args)
   114  	sql, args, err = c.db.DoCommit(ctx, link, sql, args)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  	mTime1 := gtime.TimestampMilli()
   119  	if !c.db.GetDryRun() {
   120  		result, err = link.ExecContext(ctx, sql, args...)
   121  	} else {
   122  		result = new(SqlResult)
   123  	}
   124  	mTime2 := gtime.TimestampMilli()
   125  	sqlObj := &Sql{
   126  		Sql:           sql,
   127  		Type:          "DB.ExecContext",
   128  		Args:          args,
   129  		Format:        FormatSqlWithArgs(sql, args),
   130  		Error:         err,
   131  		Start:         mTime1,
   132  		End:           mTime2,
   133  		Group:         c.db.GetGroup(),
   134  		IsTransaction: link.IsTransaction(),
   135  	}
   136  	// Tracing and logging.
   137  	c.addSqlToTracing(ctx, sqlObj)
   138  	if c.db.GetDebug() {
   139  		c.writeSqlToLogger(ctx, sqlObj)
   140  	}
   141  	return result, formatError(err, sql, args...)
   142  }
   143  
   144  // DoCommit is a hook function, which deals with the sql string before it's committed to underlying driver.
   145  // The parameter `link` specifies the current database connection operation object. You can modify the sql
   146  // string `sql` and its arguments `args` as you wish before they're committed to driver.
   147  func (c *Core) DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
   148  	if c.db.GetConfig().CtxStrict {
   149  		if v := ctx.Value(ctxStrictKeyName); v == nil {
   150  			return sql, args, gerror.NewCode(gcode.CodeMissingParameter, ctxStrictErrorStr)
   151  		}
   152  	}
   153  	return sql, args, nil
   154  }
   155  
   156  // Prepare creates a prepared statement for later queries or executions.
   157  // Multiple queries or executions may be run concurrently from the
   158  // returned statement.
   159  // The caller must call the statement's Close method
   160  // when the statement is no longer needed.
   161  //
   162  // The parameter `execOnMaster` specifies whether executing the sql on master node,
   163  // or else it executes the sql on slave node if master-slave configured.
   164  func (c *Core) Prepare(sql string, execOnMaster ...bool) (*Stmt, error) {
   165  	var (
   166  		err  error
   167  		link Link
   168  	)
   169  	if len(execOnMaster) > 0 && execOnMaster[0] {
   170  		if link, err = c.MasterLink(); err != nil {
   171  			return nil, err
   172  		}
   173  	} else {
   174  		if link, err = c.SlaveLink(); err != nil {
   175  			return nil, err
   176  		}
   177  	}
   178  	return c.db.DoPrepare(c.GetCtx(), link, sql)
   179  }
   180  
   181  // DoPrepare calls prepare function on given link object and returns the statement object.
   182  func (c *Core) DoPrepare(ctx context.Context, link Link, sql string) (*Stmt, error) {
   183  	// Transaction checks.
   184  	if link == nil {
   185  		if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
   186  			// Firstly, check and retrieve transaction link from context.
   187  			link = &txLink{tx.tx}
   188  		} else {
   189  			// Or else it creates one from master node.
   190  			var err error
   191  			if link, err = c.MasterLink(); err != nil {
   192  				return nil, err
   193  			}
   194  		}
   195  	} else if !link.IsTransaction() {
   196  		// If current link is not transaction link, it checks and retrieves transaction from context.
   197  		if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
   198  			link = &txLink{tx.tx}
   199  		}
   200  	}
   201  
   202  	if c.GetConfig().PrepareTimeout > 0 {
   203  		// DO NOT USE cancel function in prepare statement.
   204  		ctx, _ = context.WithTimeout(ctx, c.GetConfig().PrepareTimeout)
   205  	}
   206  
   207  	if c.db.GetConfig().CtxStrict {
   208  		if v := ctx.Value(ctxStrictKeyName); v == nil {
   209  			return nil, gerror.NewCode(gcode.CodeMissingParameter, ctxStrictErrorStr)
   210  		}
   211  	}
   212  
   213  	var (
   214  		mTime1    = gtime.TimestampMilli()
   215  		stmt, err = link.PrepareContext(ctx, sql)
   216  		mTime2    = gtime.TimestampMilli()
   217  		sqlObj    = &Sql{
   218  			Sql:           sql,
   219  			Type:          "DB.PrepareContext",
   220  			Args:          nil,
   221  			Format:        FormatSqlWithArgs(sql, nil),
   222  			Error:         err,
   223  			Start:         mTime1,
   224  			End:           mTime2,
   225  			Group:         c.db.GetGroup(),
   226  			IsTransaction: link.IsTransaction(),
   227  		}
   228  	)
   229  	// Tracing and logging.
   230  	c.addSqlToTracing(ctx, sqlObj)
   231  	if c.db.GetDebug() {
   232  		c.writeSqlToLogger(ctx, sqlObj)
   233  	}
   234  	return &Stmt{
   235  		Stmt: stmt,
   236  		core: c,
   237  		link: link,
   238  		sql:  sql,
   239  	}, err
   240  }