[#1138] fixed concurrent cascade update/delete and added fail/retry because of SQLITE_BUSY
This commit is contained in:
parent
2deca759fa
commit
647158f62d
|
@ -4,6 +4,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pocketbase/dbx"
|
"github.com/pocketbase/dbx"
|
||||||
"github.com/pocketbase/pocketbase/models"
|
"github.com/pocketbase/pocketbase/models"
|
||||||
|
@ -347,27 +348,42 @@ func (dao *Dao) SaveRecord(record *models.Record) error {
|
||||||
// The delete operation may fail if the record is part of a required
|
// The delete operation may fail if the record is part of a required
|
||||||
// reference in another record (aka. cannot be deleted or set to NULL).
|
// reference in another record (aka. cannot be deleted or set to NULL).
|
||||||
func (dao *Dao) DeleteRecord(record *models.Record) error {
|
func (dao *Dao) DeleteRecord(record *models.Record) error {
|
||||||
// check for references
|
const maxAttempts = 5
|
||||||
// note: the select is outside of the transaction to prevent SQLITE_LOCKED error when mixing read&write in a single transaction.
|
|
||||||
refs, err := dao.FindCollectionReferences(record.Collection())
|
attempts := 1
|
||||||
if err != nil {
|
|
||||||
return err
|
DeleteRetry:
|
||||||
|
deleteErr := dao.deleteRecord(record)
|
||||||
|
if deleteErr != nil &&
|
||||||
|
attempts <= maxAttempts &&
|
||||||
|
// note: we are checking the error msg so that we can handle both the cgo and noncgo errors
|
||||||
|
strings.Contains(deleteErr.Error(), "database is locked") {
|
||||||
|
time.Sleep(time.Duration(250*attempts) * time.Millisecond)
|
||||||
|
attempts++
|
||||||
|
goto DeleteRetry
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if related records has to be deleted (if `CascadeDelete` is set)
|
return deleteErr
|
||||||
// OR
|
}
|
||||||
// just unset the record id from any relation field values (if they are not required)
|
|
||||||
// -----------------------------------------------------------
|
func (dao *Dao) deleteRecord(record *models.Record) error {
|
||||||
return dao.RunInTransaction(func(txDao *Dao) error {
|
return dao.RunInTransaction(func(txDao *Dao) error {
|
||||||
// delete/update references
|
// check for references
|
||||||
|
refs, err := txDao.FindCollectionReferences(record.Collection())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if related records has to be deleted (if `CascadeDelete` is set)
|
||||||
|
// OR
|
||||||
|
// just unset the record id from any relation field values (if they are not required)
|
||||||
for refCollection, fields := range refs {
|
for refCollection, fields := range refs {
|
||||||
for _, field := range fields {
|
for _, field := range fields {
|
||||||
options, _ := field.Options.(*schema.RelationOptions)
|
options, _ := field.Options.(*schema.RelationOptions)
|
||||||
|
|
||||||
rows := []dbx.NullStringMap{}
|
rows := []dbx.NullStringMap{}
|
||||||
|
|
||||||
// note: the select is not using the transaction dao to prevent SQLITE_LOCKED error when mixing read&write in a single transaction
|
err := txDao.RecordQuery(refCollection).
|
||||||
err := dao.RecordQuery(refCollection).
|
|
||||||
AndWhere(dbx.Not(dbx.HashExp{"id": record.Id})).
|
AndWhere(dbx.Not(dbx.HashExp{"id": record.Id})).
|
||||||
AndWhere(dbx.Like(field.Name, record.Id).Match(true, true)).
|
AndWhere(dbx.Like(field.Name, record.Id).Match(true, true)).
|
||||||
All(&rows)
|
All(&rows)
|
||||||
|
|
Loading…
Reference in New Issue