gitea源码

index.go 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. // Copyright 2021 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package db
  4. import (
  5. "context"
  6. "errors"
  7. "fmt"
  8. "strconv"
  9. "code.gitea.io/gitea/modules/setting"
  10. )
  11. // ResourceIndex represents a resource index which could be used as issue/release and others
  12. // We can create different tables i.e. issue_index, release_index, etc.
  13. type ResourceIndex struct {
  14. GroupID int64 `xorm:"pk"`
  15. MaxIndex int64 `xorm:"index"`
  16. }
  17. var ErrGetResourceIndexFailed = errors.New("get resource index failed")
  18. // SyncMaxResourceIndex sync the max index with the resource
  19. func SyncMaxResourceIndex(ctx context.Context, tableName string, groupID, maxIndex int64) (err error) {
  20. e := GetEngine(ctx)
  21. // try to update the max_index and acquire the write-lock for the record
  22. res, err := e.Exec(fmt.Sprintf("UPDATE %s SET max_index=? WHERE group_id=? AND max_index<?", tableName), maxIndex, groupID, maxIndex)
  23. if err != nil {
  24. return err
  25. }
  26. affected, err := res.RowsAffected()
  27. if err != nil {
  28. return err
  29. }
  30. if affected == 0 {
  31. // if nothing is updated, the record might not exist or might be larger, it's safe to try to insert it again and then check whether the record exists
  32. _, errIns := e.Exec(fmt.Sprintf("INSERT INTO %s (group_id, max_index) VALUES (?, ?)", tableName), groupID, maxIndex)
  33. var savedIdx int64
  34. has, err := e.SQL(fmt.Sprintf("SELECT max_index FROM %s WHERE group_id=?", tableName), groupID).Get(&savedIdx)
  35. if err != nil {
  36. return err
  37. }
  38. // if the record still doesn't exist, there must be some errors (insert error)
  39. if !has {
  40. if errIns == nil {
  41. return errors.New("impossible error when SyncMaxResourceIndex, insert succeeded but no record is saved")
  42. }
  43. return errIns
  44. }
  45. }
  46. return nil
  47. }
  48. func postgresGetNextResourceIndex(ctx context.Context, tableName string, groupID int64) (int64, error) {
  49. res, err := GetEngine(ctx).Query(fmt.Sprintf("INSERT INTO %s (group_id, max_index) "+
  50. "VALUES (?,1) ON CONFLICT (group_id) DO UPDATE SET max_index = %s.max_index+1 RETURNING max_index",
  51. tableName, tableName), groupID)
  52. if err != nil {
  53. return 0, err
  54. }
  55. if len(res) == 0 {
  56. return 0, ErrGetResourceIndexFailed
  57. }
  58. return strconv.ParseInt(string(res[0]["max_index"]), 10, 64)
  59. }
  60. func mysqlGetNextResourceIndex(ctx context.Context, tableName string, groupID int64) (int64, error) {
  61. if _, err := GetEngine(ctx).Exec(fmt.Sprintf("INSERT INTO %s (group_id, max_index) "+
  62. "VALUES (?,1) ON DUPLICATE KEY UPDATE max_index = max_index+1",
  63. tableName), groupID); err != nil {
  64. return 0, err
  65. }
  66. var idx int64
  67. _, err := GetEngine(ctx).SQL(fmt.Sprintf("SELECT max_index FROM %s WHERE group_id = ?", tableName), groupID).Get(&idx)
  68. if err != nil {
  69. return 0, err
  70. }
  71. if idx == 0 {
  72. return 0, errors.New("cannot get the correct index")
  73. }
  74. return idx, nil
  75. }
  76. func mssqlGetNextResourceIndex(ctx context.Context, tableName string, groupID int64) (int64, error) {
  77. if _, err := GetEngine(ctx).Exec(fmt.Sprintf(`
  78. MERGE INTO %s WITH (HOLDLOCK) AS target
  79. USING (SELECT %d AS group_id) AS source
  80. (group_id)
  81. ON target.group_id = source.group_id
  82. WHEN MATCHED
  83. THEN UPDATE
  84. SET max_index = max_index + 1
  85. WHEN NOT MATCHED
  86. THEN INSERT (group_id, max_index)
  87. VALUES (%d, 1);
  88. `, tableName, groupID, groupID)); err != nil {
  89. return 0, err
  90. }
  91. var idx int64
  92. _, err := GetEngine(ctx).SQL(fmt.Sprintf("SELECT max_index FROM %s WHERE group_id = ?", tableName), groupID).Get(&idx)
  93. if err != nil {
  94. return 0, err
  95. }
  96. if idx == 0 {
  97. return 0, errors.New("cannot get the correct index")
  98. }
  99. return idx, nil
  100. }
  101. // GetNextResourceIndex generates a resource index, it must run in the same transaction where the resource is created
  102. func GetNextResourceIndex(ctx context.Context, tableName string, groupID int64) (int64, error) {
  103. switch {
  104. case setting.Database.Type.IsPostgreSQL():
  105. return postgresGetNextResourceIndex(ctx, tableName, groupID)
  106. case setting.Database.Type.IsMySQL():
  107. return mysqlGetNextResourceIndex(ctx, tableName, groupID)
  108. case setting.Database.Type.IsMSSQL():
  109. return mssqlGetNextResourceIndex(ctx, tableName, groupID)
  110. }
  111. e := GetEngine(ctx)
  112. // try to update the max_index to next value, and acquire the write-lock for the record
  113. res, err := e.Exec(fmt.Sprintf("UPDATE %s SET max_index=max_index+1 WHERE group_id=?", tableName), groupID)
  114. if err != nil {
  115. return 0, err
  116. }
  117. affected, err := res.RowsAffected()
  118. if err != nil {
  119. return 0, err
  120. }
  121. if affected == 0 {
  122. // this slow path is only for the first time of creating a resource index
  123. _, errIns := e.Exec(fmt.Sprintf("INSERT INTO %s (group_id, max_index) VALUES (?, 0)", tableName), groupID)
  124. res, err = e.Exec(fmt.Sprintf("UPDATE %s SET max_index=max_index+1 WHERE group_id=?", tableName), groupID)
  125. if err != nil {
  126. return 0, err
  127. }
  128. affected, err = res.RowsAffected()
  129. if err != nil {
  130. return 0, err
  131. }
  132. // if the update still can not update any records, the record must not exist and there must be some errors (insert error)
  133. if affected == 0 {
  134. if errIns == nil {
  135. return 0, errors.New("impossible error when GetNextResourceIndex, insert and update both succeeded but no record is updated")
  136. }
  137. return 0, errIns
  138. }
  139. }
  140. // now, the new index is in database (protected by the transaction and write-lock)
  141. var newIdx int64
  142. has, err := e.SQL(fmt.Sprintf("SELECT max_index FROM %s WHERE group_id=?", tableName), groupID).Get(&newIdx)
  143. if err != nil {
  144. return 0, err
  145. }
  146. if !has {
  147. return 0, errors.New("impossible error when GetNextResourceIndex, upsert succeeded but no record can be selected")
  148. }
  149. return newIdx, nil
  150. }
  151. // DeleteResourceIndex delete resource index
  152. func DeleteResourceIndex(ctx context.Context, tableName string, groupID int64) error {
  153. _, err := Exec(ctx, fmt.Sprintf("DELETE FROM %s WHERE group_id=?", tableName), groupID)
  154. return err
  155. }