github.com/wangyougui/gf/v2@v2.6.5/database/gdb/gdb_core_transaction.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 package gdb 8 9 import ( 10 "context" 11 "database/sql" 12 "reflect" 13 14 "github.com/wangyougui/gf/v2/container/gtype" 15 "github.com/wangyougui/gf/v2/errors/gcode" 16 "github.com/wangyougui/gf/v2/errors/gerror" 17 "github.com/wangyougui/gf/v2/internal/reflection" 18 "github.com/wangyougui/gf/v2/text/gregex" 19 "github.com/wangyougui/gf/v2/util/gconv" 20 ) 21 22 // TXCore is the struct for transaction management. 23 type TXCore struct { 24 db DB // db is the current gdb database manager. 25 tx *sql.Tx // tx is the raw and underlying transaction manager. 26 ctx context.Context // ctx is the context for this transaction only. 27 master *sql.DB // master is the raw and underlying database manager. 28 transactionId string // transactionId is a unique id generated by this object for this transaction. 29 transactionCount int // transactionCount marks the times that Begins. 30 isClosed bool // isClosed marks this transaction has already been committed or rolled back. 31 } 32 33 const ( 34 transactionPointerPrefix = "transaction" 35 contextTransactionKeyPrefix = "TransactionObjectForGroup_" 36 transactionIdForLoggerCtx = "TransactionId" 37 ) 38 39 var transactionIdGenerator = gtype.NewUint64() 40 41 // Begin starts and returns the transaction object. 42 // You should call Commit or Rollback functions of the transaction object 43 // if you no longer use the transaction. Commit or Rollback functions will also 44 // close the transaction automatically. 45 func (c *Core) Begin(ctx context.Context) (tx TX, err error) { 46 return c.doBeginCtx(ctx) 47 } 48 49 func (c *Core) doBeginCtx(ctx context.Context) (TX, error) { 50 master, err := c.db.Master() 51 if err != nil { 52 return nil, err 53 } 54 var out DoCommitOutput 55 out, err = c.db.DoCommit(ctx, DoCommitInput{ 56 Db: master, 57 Sql: "BEGIN", 58 Type: SqlTypeBegin, 59 IsTransaction: true, 60 }) 61 return out.Tx, err 62 } 63 64 // Transaction wraps the transaction logic using function `f`. 65 // It rollbacks the transaction and returns the error from function `f` if 66 // it returns non-nil error. It commits the transaction and returns nil if 67 // function `f` returns nil. 68 // 69 // Note that, you should not Commit or Rollback the transaction in function `f` 70 // as it is automatically handled by this function. 71 func (c *Core) Transaction(ctx context.Context, f func(ctx context.Context, tx TX) error) (err error) { 72 if ctx == nil { 73 ctx = c.db.GetCtx() 74 } 75 ctx = c.InjectInternalCtxData(ctx) 76 // Check transaction object from context. 77 var tx TX 78 tx = TXFromCtx(ctx, c.db.GetGroup()) 79 if tx != nil { 80 return tx.Transaction(ctx, f) 81 } 82 tx, err = c.doBeginCtx(ctx) 83 if err != nil { 84 return err 85 } 86 // Inject transaction object into context. 87 tx = tx.Ctx(WithTX(tx.GetCtx(), tx)) 88 defer func() { 89 if err == nil { 90 if exception := recover(); exception != nil { 91 if v, ok := exception.(error); ok && gerror.HasStack(v) { 92 err = v 93 } else { 94 err = gerror.NewCodef(gcode.CodeInternalPanic, "%+v", exception) 95 } 96 } 97 } 98 if err != nil { 99 if e := tx.Rollback(); e != nil { 100 err = e 101 } 102 } else { 103 if e := tx.Commit(); e != nil { 104 err = e 105 } 106 } 107 }() 108 err = f(tx.GetCtx(), tx) 109 return 110 } 111 112 // WithTX injects given transaction object into context and returns a new context. 113 func WithTX(ctx context.Context, tx TX) context.Context { 114 if tx == nil { 115 return ctx 116 } 117 // Check repeat injection from given. 118 group := tx.GetDB().GetGroup() 119 if ctxTx := TXFromCtx(ctx, group); ctxTx != nil && ctxTx.GetDB().GetGroup() == group { 120 return ctx 121 } 122 dbCtx := tx.GetDB().GetCtx() 123 if ctxTx := TXFromCtx(dbCtx, group); ctxTx != nil && ctxTx.GetDB().GetGroup() == group { 124 return dbCtx 125 } 126 // Inject transaction object and id into context. 127 ctx = context.WithValue(ctx, transactionKeyForContext(group), tx) 128 return ctx 129 } 130 131 // TXFromCtx retrieves and returns transaction object from context. 132 // It is usually used in nested transaction feature, and it returns nil if it is not set previously. 133 func TXFromCtx(ctx context.Context, group string) TX { 134 if ctx == nil { 135 return nil 136 } 137 v := ctx.Value(transactionKeyForContext(group)) 138 if v != nil { 139 tx := v.(TX) 140 if tx.IsClosed() { 141 return nil 142 } 143 tx = tx.Ctx(ctx) 144 return tx 145 } 146 return nil 147 } 148 149 // transactionKeyForContext forms and returns a string for storing transaction object of certain database group into context. 150 func transactionKeyForContext(group string) string { 151 return contextTransactionKeyPrefix + group 152 } 153 154 // transactionKeyForNestedPoint forms and returns the transaction key at current save point. 155 func (tx *TXCore) transactionKeyForNestedPoint() string { 156 return tx.db.GetCore().QuoteWord(transactionPointerPrefix + gconv.String(tx.transactionCount)) 157 } 158 159 // Ctx sets the context for current transaction. 160 func (tx *TXCore) Ctx(ctx context.Context) TX { 161 tx.ctx = ctx 162 if tx.ctx != nil { 163 tx.ctx = tx.db.GetCore().InjectInternalCtxData(tx.ctx) 164 } 165 return tx 166 } 167 168 // GetCtx returns the context for current transaction. 169 func (tx *TXCore) GetCtx() context.Context { 170 return tx.ctx 171 } 172 173 // GetDB returns the DB for current transaction. 174 func (tx *TXCore) GetDB() DB { 175 return tx.db 176 } 177 178 // GetSqlTX returns the underlying transaction object for current transaction. 179 func (tx *TXCore) GetSqlTX() *sql.Tx { 180 return tx.tx 181 } 182 183 // Commit commits current transaction. 184 // Note that it releases previous saved transaction point if it's in a nested transaction procedure, 185 // or else it commits the hole transaction. 186 func (tx *TXCore) Commit() error { 187 if tx.transactionCount > 0 { 188 tx.transactionCount-- 189 _, err := tx.Exec("RELEASE SAVEPOINT " + tx.transactionKeyForNestedPoint()) 190 return err 191 } 192 _, err := tx.db.DoCommit(tx.ctx, DoCommitInput{ 193 Tx: tx.tx, 194 Sql: "COMMIT", 195 Type: SqlTypeTXCommit, 196 IsTransaction: true, 197 }) 198 if err == nil { 199 tx.isClosed = true 200 } 201 return err 202 } 203 204 // Rollback aborts current transaction. 205 // Note that it aborts current transaction if it's in a nested transaction procedure, 206 // or else it aborts the hole transaction. 207 func (tx *TXCore) Rollback() error { 208 if tx.transactionCount > 0 { 209 tx.transactionCount-- 210 _, err := tx.Exec("ROLLBACK TO SAVEPOINT " + tx.transactionKeyForNestedPoint()) 211 return err 212 } 213 _, err := tx.db.DoCommit(tx.ctx, DoCommitInput{ 214 Tx: tx.tx, 215 Sql: "ROLLBACK", 216 Type: SqlTypeTXRollback, 217 IsTransaction: true, 218 }) 219 if err == nil { 220 tx.isClosed = true 221 } 222 return err 223 } 224 225 // IsClosed checks and returns this transaction has already been committed or rolled back. 226 func (tx *TXCore) IsClosed() bool { 227 return tx.isClosed 228 } 229 230 // Begin starts a nested transaction procedure. 231 func (tx *TXCore) Begin() error { 232 _, err := tx.Exec("SAVEPOINT " + tx.transactionKeyForNestedPoint()) 233 if err != nil { 234 return err 235 } 236 tx.transactionCount++ 237 return nil 238 } 239 240 // SavePoint performs `SAVEPOINT xxx` SQL statement that saves transaction at current point. 241 // The parameter `point` specifies the point name that will be saved to server. 242 func (tx *TXCore) SavePoint(point string) error { 243 _, err := tx.Exec("SAVEPOINT " + tx.db.GetCore().QuoteWord(point)) 244 return err 245 } 246 247 // RollbackTo performs `ROLLBACK TO SAVEPOINT xxx` SQL statement that rollbacks to specified saved transaction. 248 // The parameter `point` specifies the point name that was saved previously. 249 func (tx *TXCore) RollbackTo(point string) error { 250 _, err := tx.Exec("ROLLBACK TO SAVEPOINT " + tx.db.GetCore().QuoteWord(point)) 251 return err 252 } 253 254 // Transaction wraps the transaction logic using function `f`. 255 // It rollbacks the transaction and returns the error from function `f` if 256 // it returns non-nil error. It commits the transaction and returns nil if 257 // function `f` returns nil. 258 // 259 // Note that, you should not Commit or Rollback the transaction in function `f` 260 // as it is automatically handled by this function. 261 func (tx *TXCore) Transaction(ctx context.Context, f func(ctx context.Context, tx TX) error) (err error) { 262 if ctx != nil { 263 tx.ctx = ctx 264 } 265 // Check transaction object from context. 266 if TXFromCtx(tx.ctx, tx.db.GetGroup()) == nil { 267 // Inject transaction object into context. 268 tx.ctx = WithTX(tx.ctx, tx) 269 } 270 err = tx.Begin() 271 if err != nil { 272 return err 273 } 274 defer func() { 275 if err == nil { 276 if exception := recover(); exception != nil { 277 if v, ok := exception.(error); ok && gerror.HasStack(v) { 278 err = v 279 } else { 280 err = gerror.NewCodef(gcode.CodeInternalPanic, "%+v", exception) 281 } 282 } 283 } 284 if err != nil { 285 if e := tx.Rollback(); e != nil { 286 err = e 287 } 288 } else { 289 if e := tx.Commit(); e != nil { 290 err = e 291 } 292 } 293 }() 294 err = f(tx.ctx, tx) 295 return 296 } 297 298 // Query does query operation on transaction. 299 // See Core.Query. 300 func (tx *TXCore) Query(sql string, args ...interface{}) (result Result, err error) { 301 return tx.db.DoQuery(tx.ctx, &txLink{tx.tx}, sql, args...) 302 } 303 304 // Exec does none query operation on transaction. 305 // See Core.Exec. 306 func (tx *TXCore) Exec(sql string, args ...interface{}) (sql.Result, error) { 307 return tx.db.DoExec(tx.ctx, &txLink{tx.tx}, sql, args...) 308 } 309 310 // Prepare creates a prepared statement for later queries or executions. 311 // Multiple queries or executions may be run concurrently from the 312 // returned statement. 313 // The caller must call the statement's Close method 314 // when the statement is no longer needed. 315 func (tx *TXCore) Prepare(sql string) (*Stmt, error) { 316 return tx.db.DoPrepare(tx.ctx, &txLink{tx.tx}, sql) 317 } 318 319 // GetAll queries and returns data records from database. 320 func (tx *TXCore) GetAll(sql string, args ...interface{}) (Result, error) { 321 return tx.Query(sql, args...) 322 } 323 324 // GetOne queries and returns one record from database. 325 func (tx *TXCore) GetOne(sql string, args ...interface{}) (Record, error) { 326 list, err := tx.GetAll(sql, args...) 327 if err != nil { 328 return nil, err 329 } 330 if len(list) > 0 { 331 return list[0], nil 332 } 333 return nil, nil 334 } 335 336 // GetStruct queries one record from database and converts it to given struct. 337 // The parameter `pointer` should be a pointer to struct. 338 func (tx *TXCore) GetStruct(obj interface{}, sql string, args ...interface{}) error { 339 one, err := tx.GetOne(sql, args...) 340 if err != nil { 341 return err 342 } 343 return one.Struct(obj) 344 } 345 346 // GetStructs queries records from database and converts them to given struct. 347 // The parameter `pointer` should be type of struct slice: []struct/[]*struct. 348 func (tx *TXCore) GetStructs(objPointerSlice interface{}, sql string, args ...interface{}) error { 349 all, err := tx.GetAll(sql, args...) 350 if err != nil { 351 return err 352 } 353 return all.Structs(objPointerSlice) 354 } 355 356 // GetScan queries one or more records from database and converts them to given struct or 357 // struct array. 358 // 359 // If parameter `pointer` is type of struct pointer, it calls GetStruct internally for 360 // the conversion. If parameter `pointer` is type of slice, it calls GetStructs internally 361 // for conversion. 362 func (tx *TXCore) GetScan(pointer interface{}, sql string, args ...interface{}) error { 363 reflectInfo := reflection.OriginTypeAndKind(pointer) 364 if reflectInfo.InputKind != reflect.Ptr { 365 return gerror.NewCodef( 366 gcode.CodeInvalidParameter, 367 "params should be type of pointer, but got: %v", 368 reflectInfo.InputKind, 369 ) 370 } 371 switch reflectInfo.OriginKind { 372 case reflect.Array, reflect.Slice: 373 return tx.GetStructs(pointer, sql, args...) 374 375 case reflect.Struct: 376 return tx.GetStruct(pointer, sql, args...) 377 } 378 return gerror.NewCodef( 379 gcode.CodeInvalidParameter, 380 `in valid parameter type "%v", of which element type should be type of struct/slice`, 381 reflectInfo.InputType, 382 ) 383 } 384 385 // GetValue queries and returns the field value from database. 386 // The sql should query only one field from database, or else it returns only one 387 // field of the result. 388 func (tx *TXCore) GetValue(sql string, args ...interface{}) (Value, error) { 389 one, err := tx.GetOne(sql, args...) 390 if err != nil { 391 return nil, err 392 } 393 for _, v := range one { 394 return v, nil 395 } 396 return nil, nil 397 } 398 399 // GetCount queries and returns the count from database. 400 func (tx *TXCore) GetCount(sql string, args ...interface{}) (int64, error) { 401 if !gregex.IsMatchString(`(?i)SELECT\s+COUNT\(.+\)\s+FROM`, sql) { 402 sql, _ = gregex.ReplaceString(`(?i)(SELECT)\s+(.+)\s+(FROM)`, `$1 COUNT($2) $3`, sql) 403 } 404 value, err := tx.GetValue(sql, args...) 405 if err != nil { 406 return 0, err 407 } 408 return value.Int64(), nil 409 } 410 411 // Insert does "INSERT INTO ..." statement for the table. 412 // If there's already one unique record of the data in the table, it returns error. 413 // 414 // The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc. 415 // Eg: 416 // Data(g.Map{"uid": 10000, "name":"john"}) 417 // Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) 418 // 419 // The parameter `batch` specifies the batch operation count when given data is slice. 420 func (tx *TXCore) Insert(table string, data interface{}, batch ...int) (sql.Result, error) { 421 if len(batch) > 0 { 422 return tx.Model(table).Ctx(tx.ctx).Data(data).Batch(batch[0]).Insert() 423 } 424 return tx.Model(table).Ctx(tx.ctx).Data(data).Insert() 425 } 426 427 // InsertIgnore does "INSERT IGNORE INTO ..." statement for the table. 428 // If there's already one unique record of the data in the table, it ignores the inserting. 429 // 430 // The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc. 431 // Eg: 432 // Data(g.Map{"uid": 10000, "name":"john"}) 433 // Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) 434 // 435 // The parameter `batch` specifies the batch operation count when given data is slice. 436 func (tx *TXCore) InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error) { 437 if len(batch) > 0 { 438 return tx.Model(table).Ctx(tx.ctx).Data(data).Batch(batch[0]).InsertIgnore() 439 } 440 return tx.Model(table).Ctx(tx.ctx).Data(data).InsertIgnore() 441 } 442 443 // InsertAndGetId performs action Insert and returns the last insert id that automatically generated. 444 func (tx *TXCore) InsertAndGetId(table string, data interface{}, batch ...int) (int64, error) { 445 if len(batch) > 0 { 446 return tx.Model(table).Ctx(tx.ctx).Data(data).Batch(batch[0]).InsertAndGetId() 447 } 448 return tx.Model(table).Ctx(tx.ctx).Data(data).InsertAndGetId() 449 } 450 451 // Replace does "REPLACE INTO ..." statement for the table. 452 // If there's already one unique record of the data in the table, it deletes the record 453 // and inserts a new one. 454 // 455 // The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc. 456 // Eg: 457 // Data(g.Map{"uid": 10000, "name":"john"}) 458 // Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) 459 // 460 // The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc. 461 // If given data is type of slice, it then does batch replacing, and the optional parameter 462 // `batch` specifies the batch operation count. 463 func (tx *TXCore) Replace(table string, data interface{}, batch ...int) (sql.Result, error) { 464 if len(batch) > 0 { 465 return tx.Model(table).Ctx(tx.ctx).Data(data).Batch(batch[0]).Replace() 466 } 467 return tx.Model(table).Ctx(tx.ctx).Data(data).Replace() 468 } 469 470 // Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the table. 471 // It updates the record if there's primary or unique index in the saving data, 472 // or else it inserts a new record into the table. 473 // 474 // The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc. 475 // Eg: 476 // Data(g.Map{"uid": 10000, "name":"john"}) 477 // Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) 478 // 479 // If given data is type of slice, it then does batch saving, and the optional parameter 480 // `batch` specifies the batch operation count. 481 func (tx *TXCore) Save(table string, data interface{}, batch ...int) (sql.Result, error) { 482 if len(batch) > 0 { 483 return tx.Model(table).Ctx(tx.ctx).Data(data).Batch(batch[0]).Save() 484 } 485 return tx.Model(table).Ctx(tx.ctx).Data(data).Save() 486 } 487 488 // Update does "UPDATE ... " statement for the table. 489 // 490 // The parameter `data` can be type of string/map/gmap/struct/*struct, etc. 491 // Eg: "uid=10000", "uid", 10000, g.Map{"uid": 10000, "name":"john"} 492 // 493 // The parameter `condition` can be type of string/map/gmap/slice/struct/*struct, etc. 494 // It is commonly used with parameter `args`. 495 // Eg: 496 // "uid=10000", 497 // "uid", 10000 498 // "money>? AND name like ?", 99999, "vip_%" 499 // "status IN (?)", g.Slice{1,2,3} 500 // "age IN(?,?)", 18, 50 501 // User{ Id : 1, UserName : "john"}. 502 func (tx *TXCore) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) { 503 return tx.Model(table).Ctx(tx.ctx).Data(data).Where(condition, args...).Update() 504 } 505 506 // Delete does "DELETE FROM ... " statement for the table. 507 // 508 // The parameter `condition` can be type of string/map/gmap/slice/struct/*struct, etc. 509 // It is commonly used with parameter `args`. 510 // Eg: 511 // "uid=10000", 512 // "uid", 10000 513 // "money>? AND name like ?", 99999, "vip_%" 514 // "status IN (?)", g.Slice{1,2,3} 515 // "age IN(?,?)", 18, 50 516 // User{ Id : 1, UserName : "john"}. 517 func (tx *TXCore) Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) { 518 return tx.Model(table).Ctx(tx.ctx).Where(condition, args...).Delete() 519 } 520 521 // QueryContext implements interface function Link.QueryContext. 522 func (tx *TXCore) QueryContext(ctx context.Context, sql string, args ...interface{}) (*sql.Rows, error) { 523 return tx.tx.QueryContext(ctx, sql, args...) 524 } 525 526 // ExecContext implements interface function Link.ExecContext. 527 func (tx *TXCore) ExecContext(ctx context.Context, sql string, args ...interface{}) (sql.Result, error) { 528 return tx.tx.ExecContext(ctx, sql, args...) 529 } 530 531 // PrepareContext implements interface function Link.PrepareContext. 532 func (tx *TXCore) PrepareContext(ctx context.Context, sql string) (*sql.Stmt, error) { 533 return tx.tx.PrepareContext(ctx, sql) 534 } 535 536 // IsOnMaster implements interface function Link.IsOnMaster. 537 func (tx *TXCore) IsOnMaster() bool { 538 return true 539 } 540 541 // IsTransaction implements interface function Link.IsTransaction. 542 func (tx *TXCore) IsTransaction() bool { 543 return true 544 }