sync with master

This commit is contained in:
Gani Georgiev 2023-01-26 09:23:59 +02:00
commit 536707bfe7
3 changed files with 39 additions and 2 deletions

View File

@ -56,6 +56,12 @@
store.GetAll() map[string]T store.GetAll() map[string]T
``` ```
## v0.11.4
- Fixed cascade delete for rel records with the same id as the main record ([#1689](https://github.com/pocketbase/pocketbase/issues/1689)).
## v0.11.3 ## v0.11.3
- Fix realtime API panic on concurrent clients iteration ([#1628](https://github.com/pocketbase/pocketbase/issues/1628)) - Fix realtime API panic on concurrent clients iteration ([#1628](https://github.com/pocketbase/pocketbase/issues/1628))

View File

@ -98,6 +98,7 @@ func (dao *Dao) FindRecordsByIds(
// Returns an empty slice if no records are found. // Returns an empty slice if no records are found.
// //
// Example: // Example:
//
// expr1 := dbx.HashExp{"email": "test@example.com"} // expr1 := dbx.HashExp{"email": "test@example.com"}
// expr2 := dbx.NewExp("LOWER(username) = {:username}", dbx.Params{"username": "test"}) // expr2 := dbx.NewExp("LOWER(username) = {:username}", dbx.Params{"username": "test"})
// dao.FindRecordsByExpr("example", expr1, expr2) // dao.FindRecordsByExpr("example", expr1, expr2)
@ -402,13 +403,16 @@ func (dao *Dao) cascadeRecordDelete(mainRecord *models.Record, refs map[*models.
// @todo optimize single relation lookup in v0.12+ // @todo optimize single relation lookup in v0.12+
query := dao.RecordQuery(refCollection). query := dao.RecordQuery(refCollection).
Distinct(true). Distinct(true).
AndWhere(dbx.Not(dbx.HashExp{recordTableName + ".id": mainRecord.Id})).
InnerJoin(fmt.Sprintf( InnerJoin(fmt.Sprintf(
// note: the case is used to normalize the value access // note: the case is used to normalize the value access
`json_each(CASE WHEN json_valid([[%s]]) THEN [[%s]] ELSE json_array([[%s]]) END) as {{%s}}`, `json_each(CASE WHEN json_valid([[%s]]) THEN [[%s]] ELSE json_array([[%s]]) END) as {{%s}}`,
prefixedFieldName, prefixedFieldName, prefixedFieldName, uniqueJsonEachAlias, prefixedFieldName, prefixedFieldName, prefixedFieldName, uniqueJsonEachAlias,
), dbx.HashExp{uniqueJsonEachAlias + ".value": mainRecord.Id}) ), dbx.HashExp{uniqueJsonEachAlias + ".value": mainRecord.Id})
if refCollection.Id == mainRecord.Collection().Id {
query.AndWhere(dbx.Not(dbx.HashExp{recordTableName + ".id": mainRecord.Id}))
}
// trigger cascade for each batchSize rel items until there is none // trigger cascade for each batchSize rel items until there is none
batchSize := 4000 batchSize := 4000
rows := make([]dbx.NullStringMap, 0, batchSize) rows := make([]dbx.NullStringMap, 0, batchSize)

View File

@ -693,6 +693,12 @@ func TestDeleteRecordBatchProcessing(t *testing.T) {
t.Fatal("The main record wasn't deleted") t.Fatal("The main record wasn't deleted")
} }
// check if the c1 b rel field were updated
c1RecordB, err := app.Dao().FindRecordById("c1", "b")
if err != nil || c1RecordB.GetString("rel") != "" {
t.Fatalf("Expected c1RecordB.rel to be nil, got %v", c1RecordB.GetString("rel"))
}
// check if the c2 rel fields were updated // check if the c2 rel fields were updated
c2Records, err := app.Dao().FindRecordsByExpr("c2", nil) c2Records, err := app.Dao().FindRecordsByExpr("c2", nil)
if err != nil || len(c2Records) == 0 { if err != nil || len(c2Records) == 0 {
@ -725,6 +731,16 @@ func createMockBatchProcessingData(dao *daos.Dao) error {
Name: "text", Name: "text",
Type: schema.FieldTypeText, Type: schema.FieldTypeText,
}, },
// self reference
&schema.SchemaField{
Name: "rel",
Type: schema.FieldTypeRelation,
Options: &schema.RelationOptions{
MaxSelect: types.Pointer(1),
CollectionId: "c1",
CascadeDelete: false, // should unset all rel fields
},
},
) )
if err := dao.SaveCollection(c1); err != nil { if err := dao.SaveCollection(c1); err != nil {
return err return err
@ -771,15 +787,17 @@ func createMockBatchProcessingData(dao *daos.Dao) error {
// insert mock records // insert mock records
c1RecordA := models.NewRecord(c1) c1RecordA := models.NewRecord(c1)
c1RecordA.Id = "a" c1RecordA.Id = "a"
c1RecordA.Set("rel", c1RecordA.Id) // self reference
if err := dao.Save(c1RecordA); err != nil { if err := dao.Save(c1RecordA); err != nil {
return err return err
} }
c1RecordB := models.NewRecord(c1) c1RecordB := models.NewRecord(c1)
c1RecordB.Id = "b" c1RecordB.Id = "b"
c1RecordB.Set("rel", c1RecordA.Id) // rel to another record from the same collection
if err := dao.Save(c1RecordB); err != nil { if err := dao.Save(c1RecordB); err != nil {
return err return err
} }
for i := 0; i < 2400; i++ { for i := 0; i < 4500; i++ {
c2Record := models.NewRecord(c2) c2Record := models.NewRecord(c2)
c2Record.Set("rel", []string{c1RecordA.Id, c1RecordB.Id}) c2Record.Set("rel", []string{c1RecordA.Id, c1RecordB.Id})
if err := dao.Save(c2Record); err != nil { if err := dao.Save(c2Record); err != nil {
@ -793,5 +811,14 @@ func createMockBatchProcessingData(dao *daos.Dao) error {
} }
} }
// set the same id as the relation for at least 1 record
// to check whether the correct condition will be added
c3Record := models.NewRecord(c3)
c3Record.Set("rel", c1RecordA.Id)
c3Record.Id = c1RecordA.Id
if err := dao.Save(c3Record); err != nil {
return err
}
return nil return nil
} }