code.gitea.io/gitea@v1.21.7/models/db/context.go (about)

     1  // Copyright 2019 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package db
     5  
     6  import (
     7  	"context"
     8  	"database/sql"
     9  
    10  	"xorm.io/builder"
    11  	"xorm.io/xorm"
    12  )
    13  
    14  // DefaultContext is the default context to run xorm queries in
    15  // will be overwritten by Init with HammerContext
    16  var DefaultContext context.Context
    17  
    18  // contextKey is a value for use with context.WithValue.
    19  type contextKey struct {
    20  	name string
    21  }
    22  
    23  // enginedContextKey is a context key. It is used with context.Value() to get the current Engined for the context
    24  var (
    25  	enginedContextKey         = &contextKey{"engined"}
    26  	_                 Engined = &Context{}
    27  )
    28  
    29  // Context represents a db context
    30  type Context struct {
    31  	context.Context
    32  	e           Engine
    33  	transaction bool
    34  }
    35  
    36  func newContext(ctx context.Context, e Engine, transaction bool) *Context {
    37  	return &Context{
    38  		Context:     ctx,
    39  		e:           e,
    40  		transaction: transaction,
    41  	}
    42  }
    43  
    44  // InTransaction if context is in a transaction
    45  func (ctx *Context) InTransaction() bool {
    46  	return ctx.transaction
    47  }
    48  
    49  // Engine returns db engine
    50  func (ctx *Context) Engine() Engine {
    51  	return ctx.e
    52  }
    53  
    54  // Value shadows Value for context.Context but allows us to get ourselves and an Engined object
    55  func (ctx *Context) Value(key any) any {
    56  	if key == enginedContextKey {
    57  		return ctx
    58  	}
    59  	return ctx.Context.Value(key)
    60  }
    61  
    62  // WithContext returns this engine tied to this context
    63  func (ctx *Context) WithContext(other context.Context) *Context {
    64  	return newContext(ctx, ctx.e.Context(other), ctx.transaction)
    65  }
    66  
    67  // Engined structs provide an Engine
    68  type Engined interface {
    69  	Engine() Engine
    70  }
    71  
    72  // GetEngine will get a db Engine from this context or return an Engine restricted to this context
    73  func GetEngine(ctx context.Context) Engine {
    74  	if e := getEngine(ctx); e != nil {
    75  		return e
    76  	}
    77  	return x.Context(ctx)
    78  }
    79  
    80  // getEngine will get a db Engine from this context or return nil
    81  func getEngine(ctx context.Context) Engine {
    82  	if engined, ok := ctx.(Engined); ok {
    83  		return engined.Engine()
    84  	}
    85  	enginedInterface := ctx.Value(enginedContextKey)
    86  	if enginedInterface != nil {
    87  		return enginedInterface.(Engined).Engine()
    88  	}
    89  	return nil
    90  }
    91  
    92  // Committer represents an interface to Commit or Close the Context
    93  type Committer interface {
    94  	Commit() error
    95  	Close() error
    96  }
    97  
    98  // halfCommitter is a wrapper of Committer.
    99  // It can be closed early, but can't be committed early, it is useful for reusing a transaction.
   100  type halfCommitter struct {
   101  	committer Committer
   102  	committed bool
   103  }
   104  
   105  func (c *halfCommitter) Commit() error {
   106  	c.committed = true
   107  	// should do nothing, and the parent committer will commit later
   108  	return nil
   109  }
   110  
   111  func (c *halfCommitter) Close() error {
   112  	if c.committed {
   113  		// it's "commit and close", should do nothing, and the parent committer will commit later
   114  		return nil
   115  	}
   116  
   117  	// it's "rollback and close", let the parent committer rollback right now
   118  	return c.committer.Close()
   119  }
   120  
   121  // TxContext represents a transaction Context,
   122  // it will reuse the existing transaction in the parent context or create a new one.
   123  func TxContext(parentCtx context.Context) (*Context, Committer, error) {
   124  	if sess, ok := inTransaction(parentCtx); ok {
   125  		return newContext(parentCtx, sess, true), &halfCommitter{committer: sess}, nil
   126  	}
   127  
   128  	sess := x.NewSession()
   129  	if err := sess.Begin(); err != nil {
   130  		sess.Close()
   131  		return nil, nil, err
   132  	}
   133  
   134  	return newContext(DefaultContext, sess, true), sess, nil
   135  }
   136  
   137  // WithTx represents executing database operations on a transaction, if the transaction exist,
   138  // this function will reuse it otherwise will create a new one and close it when finished.
   139  func WithTx(parentCtx context.Context, f func(ctx context.Context) error) error {
   140  	if sess, ok := inTransaction(parentCtx); ok {
   141  		err := f(newContext(parentCtx, sess, true))
   142  		if err != nil {
   143  			// rollback immediately, in case the caller ignores returned error and tries to commit the transaction.
   144  			_ = sess.Close()
   145  		}
   146  		return err
   147  	}
   148  	return txWithNoCheck(parentCtx, f)
   149  }
   150  
   151  func txWithNoCheck(parentCtx context.Context, f func(ctx context.Context) error) error {
   152  	sess := x.NewSession()
   153  	defer sess.Close()
   154  	if err := sess.Begin(); err != nil {
   155  		return err
   156  	}
   157  
   158  	if err := f(newContext(parentCtx, sess, true)); err != nil {
   159  		return err
   160  	}
   161  
   162  	return sess.Commit()
   163  }
   164  
   165  // Insert inserts records into database
   166  func Insert(ctx context.Context, beans ...any) error {
   167  	_, err := GetEngine(ctx).Insert(beans...)
   168  	return err
   169  }
   170  
   171  // Exec executes a sql with args
   172  func Exec(ctx context.Context, sqlAndArgs ...any) (sql.Result, error) {
   173  	return GetEngine(ctx).Exec(sqlAndArgs...)
   174  }
   175  
   176  // GetByBean filled empty fields of the bean according non-empty fields to query in database.
   177  func GetByBean(ctx context.Context, bean any) (bool, error) {
   178  	return GetEngine(ctx).Get(bean)
   179  }
   180  
   181  func Exist[T any](ctx context.Context, cond builder.Cond) (bool, error) {
   182  	if !cond.IsValid() {
   183  		return false, ErrConditionRequired{}
   184  	}
   185  
   186  	var bean T
   187  	return GetEngine(ctx).Where(cond).NoAutoCondition().Exist(&bean)
   188  }
   189  
   190  // DeleteByBean deletes all records according non-empty fields of the bean as conditions.
   191  func DeleteByBean(ctx context.Context, bean any) (int64, error) {
   192  	return GetEngine(ctx).Delete(bean)
   193  }
   194  
   195  // DeleteByID deletes the given bean with the given ID
   196  func DeleteByID(ctx context.Context, id int64, bean any) (int64, error) {
   197  	return GetEngine(ctx).ID(id).NoAutoCondition().NoAutoTime().Delete(bean)
   198  }
   199  
   200  // FindIDs finds the IDs for the given table name satisfying the given condition
   201  // By passing a different value than "id" for "idCol", you can query for foreign IDs, i.e. the repo IDs which satisfy the condition
   202  func FindIDs(ctx context.Context, tableName, idCol string, cond builder.Cond) ([]int64, error) {
   203  	ids := make([]int64, 0, 10)
   204  	if err := GetEngine(ctx).Table(tableName).
   205  		Cols(idCol).
   206  		Where(cond).
   207  		Find(&ids); err != nil {
   208  		return nil, err
   209  	}
   210  	return ids, nil
   211  }
   212  
   213  // DecrByIDs decreases the given column for entities of the "bean" type with one of the given ids by one
   214  // Timestamps of the entities won't be updated
   215  func DecrByIDs(ctx context.Context, ids []int64, decrCol string, bean any) error {
   216  	_, err := GetEngine(ctx).Decr(decrCol).In("id", ids).NoAutoCondition().NoAutoTime().Update(bean)
   217  	return err
   218  }
   219  
   220  // DeleteBeans deletes all given beans, beans must contain delete conditions.
   221  func DeleteBeans(ctx context.Context, beans ...any) (err error) {
   222  	e := GetEngine(ctx)
   223  	for i := range beans {
   224  		if _, err = e.Delete(beans[i]); err != nil {
   225  			return err
   226  		}
   227  	}
   228  	return nil
   229  }
   230  
   231  // TruncateBeans deletes all given beans, beans may contain delete conditions.
   232  func TruncateBeans(ctx context.Context, beans ...any) (err error) {
   233  	e := GetEngine(ctx)
   234  	for i := range beans {
   235  		if _, err = e.Truncate(beans[i]); err != nil {
   236  			return err
   237  		}
   238  	}
   239  	return nil
   240  }
   241  
   242  // CountByBean counts the number of database records according non-empty fields of the bean as conditions.
   243  func CountByBean(ctx context.Context, bean any) (int64, error) {
   244  	return GetEngine(ctx).Count(bean)
   245  }
   246  
   247  // TableName returns the table name according a bean object
   248  func TableName(bean any) string {
   249  	return x.TableName(bean)
   250  }
   251  
   252  // InTransaction returns true if the engine is in a transaction otherwise return false
   253  func InTransaction(ctx context.Context) bool {
   254  	_, ok := inTransaction(ctx)
   255  	return ok
   256  }
   257  
   258  func inTransaction(ctx context.Context) (*xorm.Session, bool) {
   259  	e := getEngine(ctx)
   260  	if e == nil {
   261  		return nil, false
   262  	}
   263  
   264  	switch t := e.(type) {
   265  	case *xorm.Engine:
   266  		return nil, false
   267  	case *xorm.Session:
   268  		if t.IsInTx() {
   269  			return t, true
   270  		}
   271  		return nil, false
   272  	default:
   273  		return nil, false
   274  	}
   275  }