You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

context.go 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package db
  4. import (
  5. "context"
  6. "database/sql"
  7. "xorm.io/xorm"
  8. "xorm.io/xorm/schemas"
  9. )
  10. // DefaultContext is the default context to run xorm queries in
  11. // will be overwritten by Init with HammerContext
  12. var DefaultContext context.Context
  13. // contextKey is a value for use with context.WithValue.
  14. type contextKey struct {
  15. name string
  16. }
  17. // enginedContextKey is a context key. It is used with context.Value() to get the current Engined for the context
  18. var enginedContextKey = &contextKey{"engined"}
  19. var _ Engined = &Context{}
  20. // Context represents a db context
  21. type Context struct {
  22. context.Context
  23. e Engine
  24. transaction bool
  25. }
  26. func newContext(ctx context.Context, e Engine, transaction bool) *Context {
  27. return &Context{
  28. Context: ctx,
  29. e: e,
  30. transaction: transaction,
  31. }
  32. }
  33. // InTransaction if context is in a transaction
  34. func (ctx *Context) InTransaction() bool {
  35. return ctx.transaction
  36. }
  37. // Engine returns db engine
  38. func (ctx *Context) Engine() Engine {
  39. return ctx.e
  40. }
  41. // Value shadows Value for context.Context but allows us to get ourselves and an Engined object
  42. func (ctx *Context) Value(key interface{}) interface{} {
  43. if key == enginedContextKey {
  44. return ctx
  45. }
  46. return ctx.Context.Value(key)
  47. }
  48. // WithContext returns this engine tied to this context
  49. func (ctx *Context) WithContext(other context.Context) *Context {
  50. return newContext(ctx, ctx.e.Context(other), ctx.transaction)
  51. }
  52. // Engined structs provide an Engine
  53. type Engined interface {
  54. Engine() Engine
  55. }
  56. // GetEngine will get a db Engine from this context or return an Engine restricted to this context
  57. func GetEngine(ctx context.Context) Engine {
  58. if engined, ok := ctx.(Engined); ok {
  59. return engined.Engine()
  60. }
  61. enginedInterface := ctx.Value(enginedContextKey)
  62. if enginedInterface != nil {
  63. return enginedInterface.(Engined).Engine()
  64. }
  65. return x.Context(ctx)
  66. }
  67. // Committer represents an interface to Commit or Close the Context
  68. type Committer interface {
  69. Commit() error
  70. Close() error
  71. }
  72. // TxContext represents a transaction Context
  73. func TxContext(parentCtx context.Context) (*Context, Committer, error) {
  74. if InTransaction(parentCtx) {
  75. return nil, nil, ErrAlreadyInTransaction
  76. }
  77. sess := x.NewSession()
  78. if err := sess.Begin(); err != nil {
  79. sess.Close()
  80. return nil, nil, err
  81. }
  82. return newContext(DefaultContext, sess, true), sess, nil
  83. }
  84. // WithTx represents executing database operations on a transaction
  85. // This function will always open a new transaction, if a transaction exist in parentCtx return an error.
  86. func WithTx(parentCtx context.Context, f func(ctx context.Context) error) error {
  87. if InTransaction(parentCtx) {
  88. return ErrAlreadyInTransaction
  89. }
  90. return txWithNoCheck(parentCtx, f)
  91. }
  92. // AutoTx represents executing database operations on a transaction, if the transaction exist,
  93. // this function will reuse it otherwise will create a new one and close it when finished.
  94. func AutoTx(parentCtx context.Context, f func(ctx context.Context) error) error {
  95. if InTransaction(parentCtx) {
  96. return f(newContext(parentCtx, GetEngine(parentCtx), true))
  97. }
  98. return txWithNoCheck(parentCtx, f)
  99. }
  100. func txWithNoCheck(parentCtx context.Context, f func(ctx context.Context) error) error {
  101. sess := x.NewSession()
  102. defer sess.Close()
  103. if err := sess.Begin(); err != nil {
  104. return err
  105. }
  106. if err := f(newContext(parentCtx, sess, true)); err != nil {
  107. return err
  108. }
  109. return sess.Commit()
  110. }
  111. // Insert inserts records into database
  112. func Insert(ctx context.Context, beans ...interface{}) error {
  113. _, err := GetEngine(ctx).Insert(beans...)
  114. return err
  115. }
  116. // Exec executes a sql with args
  117. func Exec(ctx context.Context, sqlAndArgs ...interface{}) (sql.Result, error) {
  118. return GetEngine(ctx).Exec(sqlAndArgs...)
  119. }
  120. // GetByBean filled empty fields of the bean according non-empty fields to query in database.
  121. func GetByBean(ctx context.Context, bean interface{}) (bool, error) {
  122. return GetEngine(ctx).Get(bean)
  123. }
  124. // DeleteByBean deletes all records according non-empty fields of the bean as conditions.
  125. func DeleteByBean(ctx context.Context, bean interface{}) (int64, error) {
  126. return GetEngine(ctx).Delete(bean)
  127. }
  128. // DeleteBeans deletes all given beans, beans should contain delete conditions.
  129. func DeleteBeans(ctx context.Context, beans ...interface{}) (err error) {
  130. e := GetEngine(ctx)
  131. for i := range beans {
  132. if _, err = e.Delete(beans[i]); err != nil {
  133. return err
  134. }
  135. }
  136. return nil
  137. }
  138. // CountByBean counts the number of database records according non-empty fields of the bean as conditions.
  139. func CountByBean(ctx context.Context, bean interface{}) (int64, error) {
  140. return GetEngine(ctx).Count(bean)
  141. }
  142. // TableName returns the table name according a bean object
  143. func TableName(bean interface{}) string {
  144. return x.TableName(bean)
  145. }
  146. // EstimateCount returns an estimate of total number of rows in table
  147. func EstimateCount(ctx context.Context, bean interface{}) (int64, error) {
  148. e := GetEngine(ctx)
  149. e.Context(ctx)
  150. var rows int64
  151. var err error
  152. tablename := TableName(bean)
  153. switch x.Dialect().URI().DBType {
  154. case schemas.MYSQL:
  155. _, 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)
  156. case schemas.POSTGRES:
  157. _, err = e.Context(ctx).SQL("SELECT reltuples AS estimate FROM pg_class WHERE relname = ?;", tablename).Get(&rows)
  158. case schemas.MSSQL:
  159. _, err = e.Context(ctx).SQL("sp_spaceused ?;", tablename).Get(&rows)
  160. default:
  161. return e.Context(ctx).Count(tablename)
  162. }
  163. return rows, err
  164. }
  165. // InTransaction returns true if the engine is in a transaction otherwise return false
  166. func InTransaction(ctx context.Context) bool {
  167. var e Engine
  168. if engined, ok := ctx.(Engined); ok {
  169. e = engined.Engine()
  170. } else {
  171. enginedInterface := ctx.Value(enginedContextKey)
  172. if enginedInterface != nil {
  173. e = enginedInterface.(Engined).Engine()
  174. }
  175. }
  176. if e == nil {
  177. return false
  178. }
  179. switch t := e.(type) {
  180. case *xorm.Engine:
  181. return false
  182. case *xorm.Session:
  183. return t.IsInTx()
  184. default:
  185. return false
  186. }
  187. }