summaryrefslogtreecommitdiffstats
path: root/models/index.go
diff options
context:
space:
mode:
authorLunny Xiao <xiaolunwen@gmail.com>2021-06-14 10:22:55 +0800
committerGitHub <noreply@github.com>2021-06-14 10:22:55 +0800
commit0393a57511dc863bea4783fbb4b57cd11c41688a (patch)
treef479085c97ffe419e6fe02c88f6a2730a673fcb8 /models/index.go
parenta005265718b840d34858a3b705f8133e1b4dbdbf (diff)
downloadgitea-0393a57511dc863bea4783fbb4b57cd11c41688a.tar.gz
gitea-0393a57511dc863bea4783fbb4b57cd11c41688a.zip
Add a new table issue_index to store the max issue index so that issue could be deleted with no duplicated index (#15599)
* Add a new table issue_index to store the max issue index so that issue could be deleted with no duplicated index * Fix pull index * Add tests for concurrent creating issues * Fix lint * Fix tests * Fix postgres test * Add test for migration v180 * Rename wrong test file name Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: Lauris BH <lauris@nix.lv>
Diffstat (limited to 'models/index.go')
-rw-r--r--models/index.go113
1 files changed, 113 insertions, 0 deletions
diff --git a/models/index.go b/models/index.go
new file mode 100644
index 0000000000..18db13c490
--- /dev/null
+++ b/models/index.go
@@ -0,0 +1,113 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package models
+
+import (
+ "errors"
+ "fmt"
+
+ "code.gitea.io/gitea/modules/setting"
+)
+
+// ResourceIndex represents a resource index which could be used as issue/release and others
+// We can create different tables i.e. issue_index, release_index and etc.
+type ResourceIndex struct {
+ GroupID int64 `xorm:"unique"`
+ MaxIndex int64 `xorm:"index"`
+}
+
+// IssueIndex represents the issue index table
+type IssueIndex ResourceIndex
+
+// upsertResourceIndex the function will not return until it acquires the lock or receives an error.
+func upsertResourceIndex(e Engine, tableName string, groupID int64) (err error) {
+ // An atomic UPSERT operation (INSERT/UPDATE) is the only operation
+ // that ensures that the key is actually locked.
+ switch {
+ case setting.Database.UseSQLite3 || setting.Database.UsePostgreSQL:
+ _, err = e.Exec(fmt.Sprintf("INSERT INTO %s (group_id, max_index) "+
+ "VALUES (?,1) ON CONFLICT (group_id) DO UPDATE SET max_index = %s.max_index+1",
+ tableName, tableName), groupID)
+ case setting.Database.UseMySQL:
+ _, err = e.Exec(fmt.Sprintf("INSERT INTO %s (group_id, max_index) "+
+ "VALUES (?,1) ON DUPLICATE KEY UPDATE max_index = max_index+1", tableName),
+ groupID)
+ case setting.Database.UseMSSQL:
+ // https://weblogs.sqlteam.com/dang/2009/01/31/upsert-race-condition-with-merge/
+ _, err = e.Exec(fmt.Sprintf("MERGE %s WITH (HOLDLOCK) as target "+
+ "USING (SELECT ? AS group_id) AS src "+
+ "ON src.group_id = target.group_id "+
+ "WHEN MATCHED THEN UPDATE SET target.max_index = target.max_index+1 "+
+ "WHEN NOT MATCHED THEN INSERT (group_id, max_index) "+
+ "VALUES (src.group_id, 1);", tableName),
+ groupID)
+ default:
+ return fmt.Errorf("database type not supported")
+ }
+ return
+}
+
+var (
+ // ErrResouceOutdated represents an error when request resource outdated
+ ErrResouceOutdated = errors.New("resource outdated")
+ // ErrGetResourceIndexFailed represents an error when resource index retries 3 times
+ ErrGetResourceIndexFailed = errors.New("get resource index failed")
+)
+
+const (
+ maxDupIndexAttempts = 3
+)
+
+// GetNextResourceIndex retried 3 times to generate a resource index
+func GetNextResourceIndex(tableName string, groupID int64) (int64, error) {
+ for i := 0; i < maxDupIndexAttempts; i++ {
+ idx, err := getNextResourceIndex(tableName, groupID)
+ if err == ErrResouceOutdated {
+ continue
+ }
+ if err != nil {
+ return 0, err
+ }
+ return idx, nil
+ }
+ return 0, ErrGetResourceIndexFailed
+}
+
+// deleteResouceIndex delete resource index
+func deleteResouceIndex(e Engine, tableName string, groupID int64) error {
+ _, err := e.Exec(fmt.Sprintf("DELETE FROM %s WHERE group_id=?", tableName), groupID)
+ return err
+}
+
+// getNextResourceIndex return the next index
+func getNextResourceIndex(tableName string, groupID int64) (int64, error) {
+ sess := x.NewSession()
+ defer sess.Close()
+ if err := sess.Begin(); err != nil {
+ return 0, err
+ }
+ var preIdx int64
+ _, err := sess.SQL(fmt.Sprintf("SELECT max_index FROM %s WHERE group_id = ?", tableName), groupID).Get(&preIdx)
+ if err != nil {
+ return 0, err
+ }
+
+ if err := upsertResourceIndex(sess, tableName, groupID); err != nil {
+ return 0, err
+ }
+
+ var curIdx int64
+ has, err := sess.SQL(fmt.Sprintf("SELECT max_index FROM %s WHERE group_id = ? AND max_index=?", tableName), groupID, preIdx+1).Get(&curIdx)
+ if err != nil {
+ return 0, err
+ }
+ if !has {
+ return 0, ErrResouceOutdated
+ }
+ if err := sess.Commit(); err != nil {
+ return 0, err
+ }
+ return curIdx, nil
+}