From eb1246fc41a1d6b9ff7309f787c342ee4241d80b Mon Sep 17 00:00:00 2001 From: Gani Georgiev Date: Thu, 26 Jan 2023 00:05:20 +0200 Subject: [PATCH] [#1689] fixed cascade delete condition on rel records with the same id as the main record --- daos/record.go | 6 +++++- daos/record_test.go | 29 ++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/daos/record.go b/daos/record.go index 94c60b8f..4e61870d 100644 --- a/daos/record.go +++ b/daos/record.go @@ -98,6 +98,7 @@ func (dao *Dao) FindRecordsByIds( // Returns an empty slice if no records are found. // // Example: +// // expr1 := dbx.HashExp{"email": "test@example.com"} // expr2 := dbx.NewExp("LOWER(username) = {:username}", dbx.Params{"username": "test"}) // 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+ query := dao.RecordQuery(refCollection). Distinct(true). - AndWhere(dbx.Not(dbx.HashExp{recordTableName + ".id": mainRecord.Id})). InnerJoin(fmt.Sprintf( // 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}}`, prefixedFieldName, prefixedFieldName, prefixedFieldName, uniqueJsonEachAlias, ), 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 batchSize := 4000 rows := make([]dbx.NullStringMap, 0, batchSize) diff --git a/daos/record_test.go b/daos/record_test.go index 61da5f1b..28664db4 100644 --- a/daos/record_test.go +++ b/daos/record_test.go @@ -693,6 +693,12 @@ func TestDeleteRecordBatchProcessing(t *testing.T) { 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 c2Records, err := app.Dao().FindRecordsByExpr("c2", nil) if err != nil || len(c2Records) == 0 { @@ -725,6 +731,16 @@ func createMockBatchProcessingData(dao *daos.Dao) error { Name: "text", 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 { return err @@ -771,15 +787,17 @@ func createMockBatchProcessingData(dao *daos.Dao) error { // insert mock records c1RecordA := models.NewRecord(c1) c1RecordA.Id = "a" + c1RecordA.Set("rel", c1RecordA.Id) // self reference if err := dao.Save(c1RecordA); err != nil { return err } c1RecordB := models.NewRecord(c1) c1RecordB.Id = "b" + c1RecordB.Set("rel", c1RecordA.Id) // rel to another record from the same collection if err := dao.Save(c1RecordB); err != nil { return err } - for i := 0; i < 2400; i++ { + for i := 0; i < 4500; i++ { c2Record := models.NewRecord(c2) c2Record.Set("rel", []string{c1RecordA.Id, c1RecordB.Id}) 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 }