6f261fdf47
There is a flaw in #16820 where it was missed that although xorm will not add a primary key to a table during syncing, it will remove an unique constraint. Users upgrading from 1.15.0 to 1.15.1 will therefore lose the unique constraint that makes this table work unless they run `gitea doctor recreate-table issue_index`. Postgres helpfully warns about this situation but MySQL does not. Main/1.16-dev is not affected by this issue as there is a migration that does the above recreation by default. Users moving directly to 1.15.1 from 1.14.x or lower are also not affected. Whilst we could force all users who ran 1.15.0 to do the above recreate-table call, this PR proposes an alternative: Just add the unique constraint back in for 1.15.x. This won't have any long term effects - just some wasted space for the unnecessary index. Fix #16936 Signed-off-by: Andrew Thornton <art27@cantab.net>
113 lines
3.5 KiB
Go
113 lines
3.5 KiB
Go
// 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:"pk 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
|
|
}
|