gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  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. "errors"
  8. "runtime"
  9. "slices"
  10. "sync"
  11. "code.gitea.io/gitea/modules/setting"
  12. "xorm.io/builder"
  13. "xorm.io/xorm"
  14. )
  15. type engineContextKeyType struct{}
  16. var engineContextKey = engineContextKeyType{}
  17. func withContextEngine(ctx context.Context, e Engine) context.Context {
  18. return context.WithValue(ctx, engineContextKey, e)
  19. }
  20. var (
  21. contextSafetyOnce sync.Once
  22. contextSafetyDeniedFuncPCs []uintptr
  23. )
  24. func contextSafetyCheck(e Engine) {
  25. if setting.IsProd && !setting.IsInTesting {
  26. return
  27. }
  28. if e == nil {
  29. return
  30. }
  31. // Only do this check for non-end-users. If the problem could be fixed in the future, this code could be removed.
  32. contextSafetyOnce.Do(func() {
  33. // try to figure out the bad functions to deny
  34. type m struct{}
  35. _ = e.SQL("SELECT 1").Iterate(&m{}, func(int, any) error {
  36. callers := make([]uintptr, 32)
  37. callerNum := runtime.Callers(1, callers)
  38. for i := range callerNum {
  39. if funcName := runtime.FuncForPC(callers[i]).Name(); funcName == "xorm.io/xorm.(*Session).Iterate" {
  40. contextSafetyDeniedFuncPCs = append(contextSafetyDeniedFuncPCs, callers[i])
  41. }
  42. }
  43. return nil
  44. })
  45. if len(contextSafetyDeniedFuncPCs) != 1 {
  46. panic(errors.New("unable to determine the functions to deny"))
  47. }
  48. })
  49. // it should be very fast: xxxx ns/op
  50. callers := make([]uintptr, 32)
  51. callerNum := runtime.Callers(3, callers) // skip 3: runtime.Callers, contextSafetyCheck, GetEngine
  52. for i := range callerNum {
  53. if slices.Contains(contextSafetyDeniedFuncPCs, callers[i]) {
  54. panic(errors.New("using session context in an iterator would cause corrupted results"))
  55. }
  56. }
  57. }
  58. // GetEngine gets an existing db Engine/Statement or creates a new Session
  59. func GetEngine(ctx context.Context) Engine {
  60. if engine, ok := ctx.Value(engineContextKey).(Engine); ok {
  61. // if reusing the existing session, need to do "contextSafetyCheck" because the Iterate creates a "autoResetStatement=false" session
  62. contextSafetyCheck(engine)
  63. return engine
  64. }
  65. // no need to do "contextSafetyCheck" because it's a new Session
  66. return xormEngine.Context(ctx)
  67. }
  68. func GetXORMEngineForTesting() *xorm.Engine {
  69. return xormEngine
  70. }
  71. // Committer represents an interface to Commit or Close the Context
  72. type Committer interface {
  73. Commit() error
  74. Close() error
  75. }
  76. // halfCommitter is a wrapper of Committer.
  77. // It can be closed early, but can't be committed early, it is useful for reusing a transaction.
  78. type halfCommitter struct {
  79. committer Committer
  80. committed bool
  81. }
  82. func (c *halfCommitter) Commit() error {
  83. c.committed = true
  84. // should do nothing, and the parent committer will commit later
  85. return nil
  86. }
  87. func (c *halfCommitter) Close() error {
  88. if c.committed {
  89. // it's "commit and close", should do nothing, and the parent committer will commit later
  90. return nil
  91. }
  92. // it's "rollback and close", let the parent committer rollback right now
  93. return c.committer.Close()
  94. }
  95. // TxContext represents a transaction Context,
  96. // it will reuse the existing transaction in the parent context or create a new one.
  97. // Some tips to use:
  98. //
  99. // 1 It's always recommended to use `WithTx` in new code instead of `TxContext`, since `WithTx` will handle the transaction automatically.
  100. // 2. To maintain the old code which uses `TxContext`:
  101. // a. Always call `Close()` before returning regardless of whether `Commit()` has been called.
  102. // b. Always call `Commit()` before returning if there are no errors, even if the code did not change any data.
  103. // c. Remember the `Committer` will be a halfCommitter when a transaction is being reused.
  104. // So calling `Commit()` will do nothing, but calling `Close()` without calling `Commit()` will rollback the transaction.
  105. // And all operations submitted by the caller stack will be rollbacked as well, not only the operations in the current function.
  106. // d. It doesn't mean rollback is forbidden, but always do it only when there is an error, and you do want to rollback.
  107. func TxContext(parentCtx context.Context) (context.Context, Committer, error) {
  108. if sess := getTransactionSession(parentCtx); sess != nil {
  109. return withContextEngine(parentCtx, sess), &halfCommitter{committer: sess}, nil
  110. }
  111. sess := xormEngine.NewSession()
  112. if err := sess.Begin(); err != nil {
  113. _ = sess.Close()
  114. return nil, nil, err
  115. }
  116. return withContextEngine(parentCtx, sess), sess, nil
  117. }
  118. // WithTx represents executing database operations on a transaction, if the transaction exist,
  119. // this function will reuse it otherwise will create a new one and close it when finished.
  120. func WithTx(parentCtx context.Context, f func(ctx context.Context) error) error {
  121. if sess := getTransactionSession(parentCtx); sess != nil {
  122. err := f(withContextEngine(parentCtx, sess))
  123. if err != nil {
  124. // rollback immediately, in case the caller ignores returned error and tries to commit the transaction.
  125. _ = sess.Close()
  126. }
  127. return err
  128. }
  129. return txWithNoCheck(parentCtx, f)
  130. }
  131. // WithTx2 is similar to WithTx, but it has two return values: result and error.
  132. func WithTx2[T any](parentCtx context.Context, f func(ctx context.Context) (T, error)) (ret T, errRet error) {
  133. errRet = WithTx(parentCtx, func(ctx context.Context) (errInner error) {
  134. ret, errInner = f(ctx)
  135. return errInner
  136. })
  137. return ret, errRet
  138. }
  139. func txWithNoCheck(parentCtx context.Context, f func(ctx context.Context) error) error {
  140. sess := xormEngine.NewSession()
  141. defer sess.Close()
  142. if err := sess.Begin(); err != nil {
  143. return err
  144. }
  145. if err := f(withContextEngine(parentCtx, sess)); err != nil {
  146. return err
  147. }
  148. return sess.Commit()
  149. }
  150. // Insert inserts records into database
  151. func Insert(ctx context.Context, beans ...any) error {
  152. _, err := GetEngine(ctx).Insert(beans...)
  153. return err
  154. }
  155. // Exec executes a sql with args
  156. func Exec(ctx context.Context, sqlAndArgs ...any) (sql.Result, error) {
  157. return GetEngine(ctx).Exec(sqlAndArgs...)
  158. }
  159. func Get[T any](ctx context.Context, cond builder.Cond) (object *T, exist bool, err error) {
  160. if !cond.IsValid() {
  161. panic("cond is invalid in db.Get(ctx, cond). This should not be possible.")
  162. }
  163. var bean T
  164. has, err := GetEngine(ctx).Where(cond).NoAutoCondition().Get(&bean)
  165. if err != nil {
  166. return nil, false, err
  167. } else if !has {
  168. return nil, false, nil
  169. }
  170. return &bean, true, nil
  171. }
  172. func GetByID[T any](ctx context.Context, id int64) (object *T, exist bool, err error) {
  173. var bean T
  174. has, err := GetEngine(ctx).ID(id).NoAutoCondition().Get(&bean)
  175. if err != nil {
  176. return nil, false, err
  177. } else if !has {
  178. return nil, false, nil
  179. }
  180. return &bean, true, nil
  181. }
  182. func Exist[T any](ctx context.Context, cond builder.Cond) (bool, error) {
  183. if !cond.IsValid() {
  184. panic("cond is invalid in db.Exist(ctx, cond). This should not be possible.")
  185. }
  186. var bean T
  187. return GetEngine(ctx).Where(cond).NoAutoCondition().Exist(&bean)
  188. }
  189. func ExistByID[T any](ctx context.Context, id int64) (bool, error) {
  190. var bean T
  191. return GetEngine(ctx).ID(id).NoAutoCondition().Exist(&bean)
  192. }
  193. // DeleteByID deletes the given bean with the given ID
  194. func DeleteByID[T any](ctx context.Context, id int64) (int64, error) {
  195. var bean T
  196. return GetEngine(ctx).ID(id).NoAutoCondition().NoAutoTime().Delete(&bean)
  197. }
  198. func DeleteByIDs[T any](ctx context.Context, ids ...int64) error {
  199. if len(ids) == 0 {
  200. return nil
  201. }
  202. var bean T
  203. _, err := GetEngine(ctx).In("id", ids).NoAutoCondition().NoAutoTime().Delete(&bean)
  204. return err
  205. }
  206. func Delete[T any](ctx context.Context, opts FindOptions) (int64, error) {
  207. if opts == nil || !opts.ToConds().IsValid() {
  208. panic("opts are empty or invalid in db.Delete(ctx, opts). This should not be possible.")
  209. }
  210. var bean T
  211. return GetEngine(ctx).Where(opts.ToConds()).NoAutoCondition().NoAutoTime().Delete(&bean)
  212. }
  213. // DeleteByBean deletes all records according non-empty fields of the bean as conditions.
  214. func DeleteByBean(ctx context.Context, bean any) (int64, error) {
  215. return GetEngine(ctx).Delete(bean)
  216. }
  217. // FindIDs finds the IDs for the given table name satisfying the given condition
  218. // By passing a different value than "id" for "idCol", you can query for foreign IDs, i.e. the repo IDs which satisfy the condition
  219. func FindIDs(ctx context.Context, tableName, idCol string, cond builder.Cond) ([]int64, error) {
  220. ids := make([]int64, 0, 10)
  221. if err := GetEngine(ctx).Table(tableName).
  222. Cols(idCol).
  223. Where(cond).
  224. Find(&ids); err != nil {
  225. return nil, err
  226. }
  227. return ids, nil
  228. }
  229. // DecrByIDs decreases the given column for entities of the "bean" type with one of the given ids by one
  230. // Timestamps of the entities won't be updated
  231. func DecrByIDs(ctx context.Context, ids []int64, decrCol string, bean any) error {
  232. if len(ids) == 0 {
  233. return nil
  234. }
  235. _, err := GetEngine(ctx).Decr(decrCol).In("id", ids).NoAutoCondition().NoAutoTime().Update(bean)
  236. return err
  237. }
  238. // DeleteBeans deletes all given beans, beans must contain delete conditions.
  239. func DeleteBeans(ctx context.Context, beans ...any) (err error) {
  240. e := GetEngine(ctx)
  241. for i := range beans {
  242. if _, err = e.Delete(beans[i]); err != nil {
  243. return err
  244. }
  245. }
  246. return nil
  247. }
  248. // TruncateBeans deletes all given beans, beans may contain delete conditions.
  249. func TruncateBeans(ctx context.Context, beans ...any) (err error) {
  250. e := GetEngine(ctx)
  251. for i := range beans {
  252. if _, err = e.Truncate(beans[i]); err != nil {
  253. return err
  254. }
  255. }
  256. return nil
  257. }
  258. // CountByBean counts the number of database records according non-empty fields of the bean as conditions.
  259. func CountByBean(ctx context.Context, bean any) (int64, error) {
  260. return GetEngine(ctx).Count(bean)
  261. }
  262. // InTransaction returns true if the engine is in a transaction otherwise return false
  263. func InTransaction(ctx context.Context) bool {
  264. return getTransactionSession(ctx) != nil
  265. }
  266. func getTransactionSession(ctx context.Context) *xorm.Session {
  267. e, _ := ctx.Value(engineContextKey).(Engine)
  268. if sess, ok := e.(*xorm.Session); ok && sess.IsInTx() {
  269. return sess
  270. }
  271. return nil
  272. }