2021-06-14 05:22:55 +03:00
// Copyright 2021 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2021-06-14 05:22:55 +03:00
2021-09-19 14:49:59 +03:00
package db
2021-06-14 05:22:55 +03:00
import (
2022-05-20 17:08:52 +03:00
"context"
2021-06-14 05:22:55 +03:00
"errors"
"fmt"
2022-12-02 06:15:36 +03:00
"strconv"
"code.gitea.io/gitea/modules/setting"
2021-06-14 05:22:55 +03:00
)
// ResourceIndex represents a resource index which could be used as issue/release and others
2022-10-16 13:44:16 +03:00
// We can create different tables i.e. issue_index, release_index, etc.
2021-06-14 05:22:55 +03:00
type ResourceIndex struct {
2021-08-25 11:42:51 +03:00
GroupID int64 ` xorm:"pk" `
2021-06-14 05:22:55 +03:00
MaxIndex int64 ` xorm:"index" `
}
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" )
)
2022-10-16 13:44:16 +03:00
// SyncMaxResourceIndex sync the max index with the resource
func SyncMaxResourceIndex ( ctx context . Context , tableName string , groupID , maxIndex int64 ) ( err error ) {
e := GetEngine ( ctx )
// try to update the max_index and acquire the write-lock for the record
res , err := e . Exec ( fmt . Sprintf ( "UPDATE %s SET max_index=? WHERE group_id=? AND max_index<?" , tableName ) , maxIndex , groupID , maxIndex )
if err != nil {
return err
}
affected , err := res . RowsAffected ( )
if err != nil {
return err
}
if affected == 0 {
// 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
_ , errIns := e . Exec ( fmt . Sprintf ( "INSERT INTO %s (group_id, max_index) VALUES (?, ?)" , tableName ) , groupID , maxIndex )
var savedIdx int64
has , err := e . SQL ( fmt . Sprintf ( "SELECT max_index FROM %s WHERE group_id=?" , tableName ) , groupID ) . Get ( & savedIdx )
2021-06-14 05:22:55 +03:00
if err != nil {
2022-10-16 13:44:16 +03:00
return err
}
// if the record still doesn't exist, there must be some errors (insert error)
if ! has {
if errIns == nil {
return errors . New ( "impossible error when SyncMaxResourceIndex, insert succeeded but no record is saved" )
}
return errIns
2021-06-14 05:22:55 +03:00
}
}
2022-10-16 13:44:16 +03:00
return nil
2021-06-14 05:22:55 +03:00
}
2022-12-02 06:15:36 +03:00
func postgresGetNextResourceIndex ( ctx context . Context , tableName string , groupID int64 ) ( int64 , error ) {
res , err := GetEngine ( ctx ) . Query ( fmt . Sprintf ( "INSERT INTO %s (group_id, max_index) " +
"VALUES (?,1) ON CONFLICT (group_id) DO UPDATE SET max_index = %s.max_index+1 RETURNING max_index" ,
tableName , tableName ) , groupID )
if err != nil {
return 0 , err
}
if len ( res ) == 0 {
return 0 , ErrGetResourceIndexFailed
}
return strconv . ParseInt ( string ( res [ 0 ] [ "max_index" ] ) , 10 , 64 )
}
2022-10-16 13:44:16 +03:00
// GetNextResourceIndex generates a resource index, it must run in the same transaction where the resource is created
func GetNextResourceIndex ( ctx context . Context , tableName string , groupID int64 ) ( int64 , error ) {
2023-03-07 13:51:06 +03:00
if setting . Database . Type . IsPostgreSQL ( ) {
2022-12-02 06:15:36 +03:00
return postgresGetNextResourceIndex ( ctx , tableName , groupID )
}
2022-10-16 13:44:16 +03:00
e := GetEngine ( ctx )
2021-06-14 05:22:55 +03:00
2022-10-16 13:44:16 +03:00
// try to update the max_index to next value, and acquire the write-lock for the record
res , err := e . Exec ( fmt . Sprintf ( "UPDATE %s SET max_index=max_index+1 WHERE group_id=?" , tableName ) , groupID )
2022-06-13 12:37:59 +03:00
if err != nil {
2021-06-14 05:22:55 +03:00
return 0 , err
}
2022-10-16 13:44:16 +03:00
affected , err := res . RowsAffected ( )
if err != nil {
2021-06-14 05:22:55 +03:00
return 0 , err
}
2022-10-16 13:44:16 +03:00
if affected == 0 {
// this slow path is only for the first time of creating a resource index
_ , errIns := e . Exec ( fmt . Sprintf ( "INSERT INTO %s (group_id, max_index) VALUES (?, 0)" , tableName ) , groupID )
res , err = e . Exec ( fmt . Sprintf ( "UPDATE %s SET max_index=max_index+1 WHERE group_id=?" , tableName ) , groupID )
if err != nil {
return 0 , err
}
affected , err = res . RowsAffected ( )
if err != nil {
return 0 , err
}
// if the update still can not update any records, the record must not exist and there must be some errors (insert error)
if affected == 0 {
if errIns == nil {
return 0 , errors . New ( "impossible error when GetNextResourceIndex, insert and update both succeeded but no record is updated" )
}
return 0 , errIns
}
2021-06-14 05:22:55 +03:00
}
2022-10-16 13:44:16 +03:00
// now, the new index is in database (protected by the transaction and write-lock)
var newIdx int64
has , err := e . SQL ( fmt . Sprintf ( "SELECT max_index FROM %s WHERE group_id=?" , tableName ) , groupID ) . Get ( & newIdx )
2021-06-14 05:22:55 +03:00
if err != nil {
return 0 , err
}
if ! has {
2022-10-16 13:44:16 +03:00
return 0 , errors . New ( "impossible error when GetNextResourceIndex, upsert succeeded but no record can be selected" )
2021-06-14 05:22:55 +03:00
}
2022-10-16 13:44:16 +03:00
return newIdx , nil
}
// DeleteResourceIndex delete resource index
func DeleteResourceIndex ( ctx context . Context , tableName string , groupID int64 ) error {
_ , err := Exec ( ctx , fmt . Sprintf ( "DELETE FROM %s WHERE group_id=?" , tableName ) , groupID )
return err
2021-06-14 05:22:55 +03:00
}