2021-09-19 19:49:59 +08:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2021-09-19 19:49:59 +08:00
package db
import (
2021-09-23 16:45:36 +01:00
"context"
2021-11-21 23:41:00 +08:00
"database/sql"
2024-10-28 06:48:07 +08:00
"errors"
"runtime"
"slices"
"sync"
"code.gitea.io/gitea/modules/setting"
2021-09-23 16:45:36 +01:00
2023-02-13 13:11:41 +08:00
"xorm.io/builder"
2022-11-13 04:18:50 +08:00
"xorm.io/xorm"
2021-09-19 19:49:59 +08:00
)
2021-09-23 16:45:36 +01:00
// DefaultContext is the default context to run xorm queries in
// will be overwritten by Init with HammerContext
var DefaultContext context . Context
2024-10-28 06:48:07 +08:00
type engineContextKeyType struct { }
2021-09-23 16:45:36 +01:00
2024-10-28 06:48:07 +08:00
var engineContextKey = engineContextKeyType { }
2021-09-23 16:45:36 +01:00
2021-09-19 19:49:59 +08:00
// Context represents a db context
type Context struct {
2021-09-23 16:45:36 +01:00
context . Context
2024-10-28 06:48:07 +08:00
engine Engine
2022-06-20 20:38:58 +08:00
}
2024-10-28 06:48:07 +08:00
func newContext ( ctx context . Context , e Engine ) * Context {
return & Context { Context : ctx , engine : e }
2021-09-19 19:49:59 +08:00
}
2021-09-23 16:45:36 +01:00
// Value shadows Value for context.Context but allows us to get ourselves and an Engined object
2023-07-04 20:36:08 +02:00
func ( ctx * Context ) Value ( key any ) any {
2024-10-28 06:48:07 +08:00
if key == engineContextKey {
2021-09-23 16:45:36 +01:00
return ctx
}
return ctx . Context . Value ( key )
}
2022-01-19 23:26:57 +00:00
// WithContext returns this engine tied to this context
func ( ctx * Context ) WithContext ( other context . Context ) * Context {
2024-10-28 06:48:07 +08:00
return newContext ( ctx , ctx . engine . Context ( other ) )
2022-01-19 23:26:57 +00:00
}
2024-10-28 06:48:07 +08:00
var (
contextSafetyOnce sync . Once
contextSafetyDeniedFuncPCs [ ] uintptr
)
func contextSafetyCheck ( e Engine ) {
if setting . IsProd && ! setting . IsInTesting {
return
}
if e == nil {
return
}
// Only do this check for non-end-users. If the problem could be fixed in the future, this code could be removed.
contextSafetyOnce . Do ( func ( ) {
// try to figure out the bad functions to deny
type m struct { }
_ = e . SQL ( "SELECT 1" ) . Iterate ( & m { } , func ( int , any ) error {
callers := make ( [ ] uintptr , 32 )
callerNum := runtime . Callers ( 1 , callers )
for i := 0 ; i < callerNum ; i ++ {
if funcName := runtime . FuncForPC ( callers [ i ] ) . Name ( ) ; funcName == "xorm.io/xorm.(*Session).Iterate" {
contextSafetyDeniedFuncPCs = append ( contextSafetyDeniedFuncPCs , callers [ i ] )
}
}
return nil
} )
if len ( contextSafetyDeniedFuncPCs ) != 1 {
panic ( errors . New ( "unable to determine the functions to deny" ) )
}
} )
// it should be very fast: xxxx ns/op
callers := make ( [ ] uintptr , 32 )
callerNum := runtime . Callers ( 3 , callers ) // skip 3: runtime.Callers, contextSafetyCheck, GetEngine
for i := 0 ; i < callerNum ; i ++ {
if slices . Contains ( contextSafetyDeniedFuncPCs , callers [ i ] ) {
panic ( errors . New ( "using database context in an iterator would cause corrupted results" ) )
}
}
2021-09-23 16:45:36 +01:00
}
2024-10-28 06:48:07 +08:00
// GetEngine gets an existing db Engine/Statement or creates a new Session
2021-09-23 16:45:36 +01:00
func GetEngine ( ctx context . Context ) Engine {
2024-10-28 06:48:07 +08:00
if e := getExistingEngine ( ctx ) ; e != nil {
2023-01-08 09:34:58 +08:00
return e
}
return x . Context ( ctx )
}
2024-10-28 06:48:07 +08:00
// getExistingEngine gets an existing db Engine/Statement from this context or returns nil
func getExistingEngine ( ctx context . Context ) ( e Engine ) {
defer func ( ) { contextSafetyCheck ( e ) } ( )
if engined , ok := ctx . ( * Context ) ; ok {
return engined . engine
2021-09-23 16:45:36 +01:00
}
2024-10-28 06:48:07 +08:00
if engined , ok := ctx . Value ( engineContextKey ) . ( * Context ) ; ok {
return engined . engine
2021-09-23 16:45:36 +01:00
}
2023-01-08 09:34:58 +08:00
return nil
2021-09-23 16:45:36 +01:00
}
2021-09-19 19:49:59 +08:00
// Committer represents an interface to Commit or Close the Context
type Committer interface {
Commit ( ) error
Close ( ) error
}
2023-01-08 09:34:58 +08:00
// halfCommitter is a wrapper of Committer.
// It can be closed early, but can't be committed early, it is useful for reusing a transaction.
type halfCommitter struct {
2023-01-10 01:19:19 +08:00
committer Committer
committed bool
2023-01-08 09:34:58 +08:00
}
2023-01-10 01:19:19 +08:00
func ( c * halfCommitter ) Commit ( ) error {
c . committed = true
// should do nothing, and the parent committer will commit later
2023-01-08 09:34:58 +08:00
return nil
}
2023-01-10 01:19:19 +08:00
func ( c * halfCommitter ) Close ( ) error {
if c . committed {
// it's "commit and close", should do nothing, and the parent committer will commit later
return nil
}
// it's "rollback and close", let the parent committer rollback right now
return c . committer . Close ( )
}
2023-01-08 09:34:58 +08:00
// TxContext represents a transaction Context,
// it will reuse the existing transaction in the parent context or create a new one.
2024-03-25 15:00:16 +08:00
// Some tips to use:
//
// 1 It's always recommended to use `WithTx` in new code instead of `TxContext`, since `WithTx` will handle the transaction automatically.
// 2. To maintain the old code which uses `TxContext`:
// a. Always call `Close()` before returning regardless of whether `Commit()` has been called.
// b. Always call `Commit()` before returning if there are no errors, even if the code did not change any data.
// c. Remember the `Committer` will be a halfCommitter when a transaction is being reused.
// So calling `Commit()` will do nothing, but calling `Close()` without calling `Commit()` will rollback the transaction.
// And all operations submitted by the caller stack will be rollbacked as well, not only the operations in the current function.
// d. It doesn't mean rollback is forbidden, but always do it only when there is an error, and you do want to rollback.
2022-11-13 04:18:50 +08:00
func TxContext ( parentCtx context . Context ) ( * Context , Committer , error ) {
2023-01-08 09:34:58 +08:00
if sess , ok := inTransaction ( parentCtx ) ; ok {
2024-10-28 06:48:07 +08:00
return newContext ( parentCtx , sess ) , & halfCommitter { committer : sess } , nil
2022-11-13 04:18:50 +08:00
}
2021-09-19 19:49:59 +08:00
sess := x . NewSession ( )
if err := sess . Begin ( ) ; err != nil {
2024-10-28 06:48:07 +08:00
_ = sess . Close ( )
2021-09-19 19:49:59 +08:00
return nil , nil , err
}
2024-10-28 06:48:07 +08:00
return newContext ( DefaultContext , sess ) , sess , nil
2021-09-19 19:49:59 +08:00
}
2023-01-08 09:34:58 +08:00
// WithTx represents executing database operations on a transaction, if the transaction exist,
2022-11-13 04:18:50 +08:00
// this function will reuse it otherwise will create a new one and close it when finished.
2023-01-08 09:34:58 +08:00
func WithTx ( parentCtx context . Context , f func ( ctx context . Context ) error ) error {
if sess , ok := inTransaction ( parentCtx ) ; ok {
2024-10-28 06:48:07 +08:00
err := f ( newContext ( parentCtx , sess ) )
2023-01-10 01:19:19 +08:00
if err != nil {
// rollback immediately, in case the caller ignores returned error and tries to commit the transaction.
_ = sess . Close ( )
}
return err
2022-05-03 21:46:28 +02:00
}
2022-11-13 04:18:50 +08:00
return txWithNoCheck ( parentCtx , f )
}
2022-05-03 21:46:28 +02:00
2022-11-13 04:18:50 +08:00
func txWithNoCheck ( parentCtx context . Context , f func ( ctx context . Context ) error ) error {
2021-09-19 19:49:59 +08:00
sess := x . NewSession ( )
defer sess . Close ( )
if err := sess . Begin ( ) ; err != nil {
return err
}
2024-10-28 06:48:07 +08:00
if err := f ( newContext ( parentCtx , sess ) ) ; err != nil {
2021-09-19 19:49:59 +08:00
return err
}
return sess . Commit ( )
}
// Insert inserts records into database
2023-07-04 20:36:08 +02:00
func Insert ( ctx context . Context , beans ... any ) error {
2021-09-23 16:45:36 +01:00
_ , err := GetEngine ( ctx ) . Insert ( beans ... )
2021-09-19 19:49:59 +08:00
return err
}
2021-11-21 23:41:00 +08:00
// Exec executes a sql with args
2023-07-04 20:36:08 +02:00
func Exec ( ctx context . Context , sqlAndArgs ... any ) ( sql . Result , error ) {
2021-11-21 23:41:00 +08:00
return GetEngine ( ctx ) . Exec ( sqlAndArgs ... )
}
2023-12-07 15:27:36 +08:00
func Get [ T any ] ( ctx context . Context , cond builder . Cond ) ( object * T , exist bool , err error ) {
if ! cond . IsValid ( ) {
2023-12-25 21:25:29 +01:00
panic ( "cond is invalid in db.Get(ctx, cond). This should not be possible." )
2023-12-07 15:27:36 +08:00
}
var bean T
has , err := GetEngine ( ctx ) . Where ( cond ) . NoAutoCondition ( ) . Get ( & bean )
if err != nil {
return nil , false , err
} else if ! has {
return nil , false , nil
}
return & bean , true , nil
}
func GetByID [ T any ] ( ctx context . Context , id int64 ) ( object * T , exist bool , err error ) {
var bean T
has , err := GetEngine ( ctx ) . ID ( id ) . NoAutoCondition ( ) . Get ( & bean )
if err != nil {
return nil , false , err
} else if ! has {
return nil , false , nil
}
return & bean , true , nil
}
func Exist [ T any ] ( ctx context . Context , cond builder . Cond ) ( bool , error ) {
if ! cond . IsValid ( ) {
2023-12-25 21:25:29 +01:00
panic ( "cond is invalid in db.Exist(ctx, cond). This should not be possible." )
2023-12-07 15:27:36 +08:00
}
var bean T
return GetEngine ( ctx ) . Where ( cond ) . NoAutoCondition ( ) . Exist ( & bean )
}
func ExistByID [ T any ] ( ctx context . Context , id int64 ) ( bool , error ) {
var bean T
return GetEngine ( ctx ) . ID ( id ) . NoAutoCondition ( ) . Exist ( & bean )
2021-11-21 23:41:00 +08:00
}
2023-12-25 21:25:29 +01:00
// DeleteByID deletes the given bean with the given ID
func DeleteByID [ T any ] ( ctx context . Context , id int64 ) ( int64 , error ) {
var bean T
return GetEngine ( ctx ) . ID ( id ) . NoAutoCondition ( ) . NoAutoTime ( ) . Delete ( & bean )
}
func DeleteByIDs [ T any ] ( ctx context . Context , ids ... int64 ) error {
if len ( ids ) == 0 {
return nil
}
var bean T
_ , err := GetEngine ( ctx ) . In ( "id" , ids ) . NoAutoCondition ( ) . NoAutoTime ( ) . Delete ( & bean )
return err
}
func Delete [ T any ] ( ctx context . Context , opts FindOptions ) ( int64 , error ) {
if opts == nil || ! opts . ToConds ( ) . IsValid ( ) {
panic ( "opts are empty or invalid in db.Delete(ctx, opts). This should not be possible." )
}
var bean T
return GetEngine ( ctx ) . Where ( opts . ToConds ( ) ) . NoAutoCondition ( ) . NoAutoTime ( ) . Delete ( & bean )
}
2021-11-21 23:41:00 +08:00
// DeleteByBean deletes all records according non-empty fields of the bean as conditions.
2023-07-04 20:36:08 +02:00
func DeleteByBean ( ctx context . Context , bean any ) ( int64 , error ) {
2021-11-21 23:41:00 +08:00
return GetEngine ( ctx ) . Delete ( bean )
}
2023-02-13 13:11:41 +08:00
// FindIDs finds the IDs for the given table name satisfying the given condition
// By passing a different value than "id" for "idCol", you can query for foreign IDs, i.e. the repo IDs which satisfy the condition
func FindIDs ( ctx context . Context , tableName , idCol string , cond builder . Cond ) ( [ ] int64 , error ) {
ids := make ( [ ] int64 , 0 , 10 )
if err := GetEngine ( ctx ) . Table ( tableName ) .
Cols ( idCol ) .
Where ( cond ) .
Find ( & ids ) ; err != nil {
return nil , err
}
return ids , nil
}
// DecrByIDs decreases the given column for entities of the "bean" type with one of the given ids by one
// Timestamps of the entities won't be updated
2023-07-04 20:36:08 +02:00
func DecrByIDs ( ctx context . Context , ids [ ] int64 , decrCol string , bean any ) error {
2023-02-13 13:11:41 +08:00
_ , err := GetEngine ( ctx ) . Decr ( decrCol ) . In ( "id" , ids ) . NoAutoCondition ( ) . NoAutoTime ( ) . Update ( bean )
return err
}
2023-02-23 15:11:56 +01:00
// DeleteBeans deletes all given beans, beans must contain delete conditions.
2023-07-04 20:36:08 +02:00
func DeleteBeans ( ctx context . Context , beans ... any ) ( err error ) {
2022-02-17 16:37:48 +08:00
e := GetEngine ( ctx )
for i := range beans {
if _ , err = e . Delete ( beans [ i ] ) ; err != nil {
return err
}
}
return nil
}
2023-02-23 15:11:56 +01:00
// TruncateBeans deletes all given beans, beans may contain delete conditions.
2023-07-04 20:36:08 +02:00
func TruncateBeans ( ctx context . Context , beans ... any ) ( err error ) {
2023-02-23 15:11:56 +01:00
e := GetEngine ( ctx )
for i := range beans {
if _ , err = e . Truncate ( beans [ i ] ) ; err != nil {
return err
}
}
return nil
}
2021-11-21 23:41:00 +08:00
// CountByBean counts the number of database records according non-empty fields of the bean as conditions.
2023-07-04 20:36:08 +02:00
func CountByBean ( ctx context . Context , bean any ) ( int64 , error ) {
2021-11-21 23:41:00 +08:00
return GetEngine ( ctx ) . Count ( bean )
}
// TableName returns the table name according a bean object
2023-07-04 20:36:08 +02:00
func TableName ( bean any ) string {
2021-11-21 23:41:00 +08:00
return x . TableName ( bean )
}
2022-05-21 19:50:50 +01:00
2022-11-13 04:18:50 +08:00
// InTransaction returns true if the engine is in a transaction otherwise return false
func InTransaction ( ctx context . Context ) bool {
2023-01-08 09:34:58 +08:00
_ , ok := inTransaction ( ctx )
return ok
}
func inTransaction ( ctx context . Context ) ( * xorm . Session , bool ) {
2024-10-28 06:48:07 +08:00
e := getExistingEngine ( ctx )
2022-11-13 04:18:50 +08:00
if e == nil {
2023-01-08 09:34:58 +08:00
return nil , false
2022-11-13 04:18:50 +08:00
}
switch t := e . ( type ) {
case * xorm . Engine :
2023-01-08 09:34:58 +08:00
return nil , false
2022-11-13 04:18:50 +08:00
case * xorm . Session :
2023-01-08 09:34:58 +08:00
if t . IsInTx ( ) {
return t , true
}
return nil , false
2022-11-13 04:18:50 +08:00
default :
2023-01-08 09:34:58 +08:00
return nil , false
2022-11-13 04:18:50 +08:00
}
}