2021-09-19 11:49:59 +00:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 18:20:29 +00:00
// SPDX-License-Identifier: MIT
2021-09-19 11:49:59 +00:00
package db
import (
2021-09-23 15:45:36 +00:00
"context"
2021-11-21 15:41:00 +00:00
"database/sql"
2021-09-23 15:45:36 +00:00
2023-02-13 05:11:41 +00:00
"xorm.io/builder"
2022-11-12 20:18:50 +00:00
"xorm.io/xorm"
2022-05-21 18:50:50 +00:00
"xorm.io/xorm/schemas"
2021-09-19 11:49:59 +00:00
)
2021-09-23 15:45:36 +00:00
// DefaultContext is the default context to run xorm queries in
// will be overwritten by Init with HammerContext
var DefaultContext context . Context
// contextKey is a value for use with context.WithValue.
type contextKey struct {
name string
}
2022-06-20 12:38:58 +00:00
// enginedContextKey is a context key. It is used with context.Value() to get the current Engined for the context
2022-12-08 08:21:37 +00:00
var (
enginedContextKey = & contextKey { "engined" }
_ Engined = & Context { }
)
2021-09-23 15:45:36 +00:00
2021-09-19 11:49:59 +00:00
// Context represents a db context
type Context struct {
2021-09-23 15:45:36 +00:00
context . Context
2022-06-20 12:38:58 +00:00
e Engine
transaction bool
2021-09-19 11:49:59 +00:00
}
2022-06-20 12:38:58 +00:00
func newContext ( ctx context . Context , e Engine , transaction bool ) * Context {
2021-10-13 19:47:02 +00:00
return & Context {
2022-06-20 12:38:58 +00:00
Context : ctx ,
e : e ,
transaction : transaction ,
2021-10-13 19:47:02 +00:00
}
}
2022-06-20 12:38:58 +00:00
// InTransaction if context is in a transaction
func ( ctx * Context ) InTransaction ( ) bool {
return ctx . transaction
}
2021-09-19 11:49:59 +00:00
// Engine returns db engine
func ( ctx * Context ) Engine ( ) Engine {
return ctx . e
}
2021-09-23 15:45:36 +00:00
// Value shadows Value for context.Context but allows us to get ourselves and an Engined object
func ( ctx * Context ) Value ( key interface { } ) interface { } {
2022-06-20 12:38:58 +00:00
if key == enginedContextKey {
2021-09-23 15:45:36 +00: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 {
2022-06-20 12:38:58 +00:00
return newContext ( ctx , ctx . e . Context ( other ) , ctx . transaction )
2022-01-19 23:26:57 +00:00
}
2021-09-23 15:45:36 +00:00
// Engined structs provide an Engine
type Engined interface {
Engine ( ) Engine
}
// GetEngine will get a db Engine from this context or return an Engine restricted to this context
func GetEngine ( ctx context . Context ) Engine {
2023-01-08 01:34:58 +00:00
if e := getEngine ( ctx ) ; e != nil {
return e
}
return x . Context ( ctx )
}
// getEngine will get a db Engine from this context or return nil
func getEngine ( ctx context . Context ) Engine {
2021-09-23 15:45:36 +00:00
if engined , ok := ctx . ( Engined ) ; ok {
return engined . Engine ( )
}
2022-06-20 12:38:58 +00:00
enginedInterface := ctx . Value ( enginedContextKey )
2021-09-23 15:45:36 +00:00
if enginedInterface != nil {
return enginedInterface . ( Engined ) . Engine ( )
}
2023-01-08 01:34:58 +00:00
return nil
2021-09-23 15:45:36 +00:00
}
2021-09-19 11:49:59 +00:00
// Committer represents an interface to Commit or Close the Context
type Committer interface {
Commit ( ) error
Close ( ) error
}
2023-01-08 01:34:58 +00: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-09 17:19:19 +00:00
committer Committer
committed bool
2023-01-08 01:34:58 +00:00
}
2023-01-09 17:19:19 +00:00
func ( c * halfCommitter ) Commit ( ) error {
c . committed = true
// should do nothing, and the parent committer will commit later
2023-01-08 01:34:58 +00:00
return nil
}
2023-01-09 17:19:19 +00: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 01:34:58 +00:00
// TxContext represents a transaction Context,
// it will reuse the existing transaction in the parent context or create a new one.
2022-11-12 20:18:50 +00:00
func TxContext ( parentCtx context . Context ) ( * Context , Committer , error ) {
2023-01-08 01:34:58 +00:00
if sess , ok := inTransaction ( parentCtx ) ; ok {
2023-01-09 17:19:19 +00:00
return newContext ( parentCtx , sess , true ) , & halfCommitter { committer : sess } , nil
2022-11-12 20:18:50 +00:00
}
2021-09-19 11:49:59 +00:00
sess := x . NewSession ( )
if err := sess . Begin ( ) ; err != nil {
sess . Close ( )
return nil , nil , err
}
2022-06-20 12:38:58 +00:00
return newContext ( DefaultContext , sess , true ) , sess , nil
2021-09-19 11:49:59 +00:00
}
2023-01-08 01:34:58 +00:00
// WithTx represents executing database operations on a transaction, if the transaction exist,
2022-11-12 20:18:50 +00:00
// this function will reuse it otherwise will create a new one and close it when finished.
2023-01-08 01:34:58 +00:00
func WithTx ( parentCtx context . Context , f func ( ctx context . Context ) error ) error {
if sess , ok := inTransaction ( parentCtx ) ; ok {
2023-01-09 17:19:19 +00:00
err := f ( newContext ( parentCtx , sess , true ) )
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 19:46:28 +00:00
}
2022-11-12 20:18:50 +00:00
return txWithNoCheck ( parentCtx , f )
}
2022-05-03 19:46:28 +00:00
2022-11-12 20:18:50 +00:00
func txWithNoCheck ( parentCtx context . Context , f func ( ctx context . Context ) error ) error {
2021-09-19 11:49:59 +00:00
sess := x . NewSession ( )
defer sess . Close ( )
if err := sess . Begin ( ) ; err != nil {
return err
}
2022-06-20 12:38:58 +00:00
if err := f ( newContext ( parentCtx , sess , true ) ) ; err != nil {
2021-09-19 11:49:59 +00:00
return err
}
return sess . Commit ( )
}
// Insert inserts records into database
2021-09-23 15:45:36 +00:00
func Insert ( ctx context . Context , beans ... interface { } ) error {
_ , err := GetEngine ( ctx ) . Insert ( beans ... )
2021-09-19 11:49:59 +00:00
return err
}
2021-11-21 15:41:00 +00:00
// Exec executes a sql with args
func Exec ( ctx context . Context , sqlAndArgs ... interface { } ) ( sql . Result , error ) {
return GetEngine ( ctx ) . Exec ( sqlAndArgs ... )
}
// GetByBean filled empty fields of the bean according non-empty fields to query in database.
func GetByBean ( ctx context . Context , bean interface { } ) ( bool , error ) {
return GetEngine ( ctx ) . Get ( bean )
}
// DeleteByBean deletes all records according non-empty fields of the bean as conditions.
func DeleteByBean ( ctx context . Context , bean interface { } ) ( int64 , error ) {
return GetEngine ( ctx ) . Delete ( bean )
}
2023-02-13 05:11:41 +00:00
// DeleteByID deletes the given bean with the given ID
func DeleteByID ( ctx context . Context , id int64 , bean interface { } ) ( int64 , error ) {
return GetEngine ( ctx ) . ID ( id ) . NoAutoTime ( ) . Delete ( bean )
}
// 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
func DecrByIDs ( ctx context . Context , ids [ ] int64 , decrCol string , bean interface { } ) error {
_ , err := GetEngine ( ctx ) . Decr ( decrCol ) . In ( "id" , ids ) . NoAutoCondition ( ) . NoAutoTime ( ) . Update ( bean )
return err
}
2022-02-17 08:37:48 +00:00
// DeleteBeans deletes all given beans, beans should contain delete conditions.
func DeleteBeans ( ctx context . Context , beans ... interface { } ) ( err error ) {
e := GetEngine ( ctx )
for i := range beans {
if _ , err = e . Delete ( beans [ i ] ) ; err != nil {
return err
}
}
return nil
}
2021-11-21 15:41:00 +00:00
// CountByBean counts the number of database records according non-empty fields of the bean as conditions.
func CountByBean ( ctx context . Context , bean interface { } ) ( int64 , error ) {
return GetEngine ( ctx ) . Count ( bean )
}
// TableName returns the table name according a bean object
func TableName ( bean interface { } ) string {
return x . TableName ( bean )
}
2022-05-21 18:50:50 +00:00
// EstimateCount returns an estimate of total number of rows in table
func EstimateCount ( ctx context . Context , bean interface { } ) ( int64 , error ) {
e := GetEngine ( ctx )
e . Context ( ctx )
var rows int64
var err error
tablename := TableName ( bean )
switch x . Dialect ( ) . URI ( ) . DBType {
case schemas . MYSQL :
_ , err = e . Context ( ctx ) . SQL ( "SELECT table_rows FROM information_schema.tables WHERE tables.table_name = ? AND tables.table_schema = ?;" , tablename , x . Dialect ( ) . URI ( ) . DBName ) . Get ( & rows )
case schemas . POSTGRES :
2022-12-30 18:25:58 +00:00
// the table can live in multiple schemas of a postgres database
// See https://wiki.postgresql.org/wiki/Count_estimate
tablename = x . TableName ( bean , true )
_ , err = e . Context ( ctx ) . SQL ( "SELECT reltuples::bigint AS estimate FROM pg_class WHERE oid = ?::regclass;" , tablename ) . Get ( & rows )
2022-05-21 18:50:50 +00:00
case schemas . MSSQL :
_ , err = e . Context ( ctx ) . SQL ( "sp_spaceused ?;" , tablename ) . Get ( & rows )
default :
return e . Context ( ctx ) . Count ( tablename )
}
return rows , err
}
2022-11-12 20:18:50 +00:00
// InTransaction returns true if the engine is in a transaction otherwise return false
func InTransaction ( ctx context . Context ) bool {
2023-01-08 01:34:58 +00:00
_ , ok := inTransaction ( ctx )
return ok
}
func inTransaction ( ctx context . Context ) ( * xorm . Session , bool ) {
e := getEngine ( ctx )
2022-11-12 20:18:50 +00:00
if e == nil {
2023-01-08 01:34:58 +00:00
return nil , false
2022-11-12 20:18:50 +00:00
}
switch t := e . ( type ) {
case * xorm . Engine :
2023-01-08 01:34:58 +00:00
return nil , false
2022-11-12 20:18:50 +00:00
case * xorm . Session :
2023-01-08 01:34:58 +00:00
if t . IsInTx ( ) {
return t , true
}
return nil , false
2022-11-12 20:18:50 +00:00
default :
2023-01-08 01:34:58 +00:00
return nil , false
2022-11-12 20:18:50 +00:00
}
}