From d5e94b9be344d6ca5391bb4471b49842b711d101 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Wed, 22 Jan 2025 15:45:41 +0700 Subject: [PATCH] avoid reference cycles between ComplexForms by marking the component as deleted if creating it would result in a reference cycle --- .../Entries/AddEntryComponentChange.cs | 34 ++++++++++++++++--- backend/harmony | 2 +- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/backend/FwLite/LcmCrdt/Changes/Entries/AddEntryComponentChange.cs b/backend/FwLite/LcmCrdt/Changes/Entries/AddEntryComponentChange.cs index f12bb9d85..b6fe66f2f 100644 --- a/backend/FwLite/LcmCrdt/Changes/Entries/AddEntryComponentChange.cs +++ b/backend/FwLite/LcmCrdt/Changes/Entries/AddEntryComponentChange.cs @@ -38,7 +38,10 @@ public override async ValueTask NewEntity(Commit commit, C Sense? componentSense = null; if (ComponentSenseId is not null) componentSense = await context.GetCurrent(ComponentSenseId.Value); - return new ComplexFormComponent + var shouldBeDeleted = (complexFormEntry?.DeletedAt is not null || + componentEntry?.DeletedAt is not null || + (ComponentSenseId.HasValue && componentSense?.DeletedAt is not null)); + var component = new ComplexFormComponent { Id = EntityId, ComplexFormEntryId = ComplexFormEntryId, @@ -46,11 +49,34 @@ public override async ValueTask NewEntity(Commit commit, C ComponentEntryId = ComponentEntryId, ComponentHeadword = componentEntry?.Headword(), ComponentSenseId = ComponentSenseId, - DeletedAt = (complexFormEntry?.DeletedAt is not null || - componentEntry?.DeletedAt is not null || - (ComponentSenseId.HasValue && componentSense?.DeletedAt is not null)) + DeletedAt = shouldBeDeleted ? commit.DateTime : (DateTime?)null, }; + if (component.DeletedAt is null && await HasReferenceCycle(component, context)) component.DeletedAt = commit.DateTime; + return component; + } + + private static async ValueTask HasReferenceCycle(ComplexFormComponent parent, ChangeContext context) + { + if (parent.ComplexFormEntryId == parent.ComponentEntryId) return true; + //used to avoid checking the same ComplexFormComponent multiple times + HashSet visited = [parent.Id]; + Queue queue = new Queue(); + queue.Enqueue(parent); + while (queue.Count > 0) + { + var current = queue.Dequeue(); + if (current.ComplexFormEntryId == parent.ComponentEntryId) return true; + await foreach (var o in context.GetObjectsReferencing(current.ComplexFormEntryId)) + { + if (o is not ComplexFormComponent cfc) continue; + if (visited.Contains(cfc.Id)) continue; + if (cfc.ComplexFormEntryId == parent.ComponentEntryId) return true; + queue.Enqueue(cfc); + visited.Add(cfc.Id); + } + } + return false; } } diff --git a/backend/harmony b/backend/harmony index 8a0bd5d8a..5d62145dc 160000 --- a/backend/harmony +++ b/backend/harmony @@ -1 +1 @@ -Subproject commit 8a0bd5d8a503c6e391950122e22e683e0945bd7c +Subproject commit 5d62145dcd6a1ee574666ae5684e199457db163c