summaryrefslogtreecommitdiffstats
path: root/models/db/index.go
diff options
context:
space:
mode:
authorLunny Xiao <xiaolunwen@gmail.com>2021-09-19 19:49:59 +0800
committerGitHub <noreply@github.com>2021-09-19 19:49:59 +0800
commita4bfef265d9e512830350635a0489c2cdcd6508f (patch)
tree1e3c2ec94276dfcb2f8ba73a2ac075ba39c4a34a /models/db/index.go
parent462306e263db5a809dbe2cdf62e99307aeff28de (diff)
downloadgitea-a4bfef265d9e512830350635a0489c2cdcd6508f.tar.gz
gitea-a4bfef265d9e512830350635a0489c2cdcd6508f.zip
Move db related basic functions to models/db (#17075)
* Move db related basic functions to models/db * Fix lint * Fix lint * Fix test * Fix lint * Fix lint * revert unnecessary change * Fix test * Fix wrong replace string * Use *Context * Correct committer spelling and fix wrong replaced words Co-authored-by: zeripath <art27@cantab.net>
Diffstat (limited to 'models/db/index.go')
-rw-r--r--models/db/index.go110
1 files changed, 110 insertions, 0 deletions
diff --git a/models/db/index.go b/models/db/index.go
new file mode 100644
index 0000000000..873289db54
--- /dev/null
+++ b/models/db/index.go
@@ -0,0 +1,110 @@
+// 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 db
+
+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:"pk"`
+ MaxIndex int64 `xorm:"index"`
+}
+
+// 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
+}