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 }