enhancement
major
minor
major
minor
major
#29889
Adding one element to a set reference reads the whole set (quadratic set building)
Found in an application (E-Home, 8.0.0-alpha10): the XML import (ModelBinding.addValue) adds the parameters of an imported KNX application program to an unordered composite set reference one by one. SetStorage.addAttributeValue reads the whole current set to check for a duplicate before adding, so building a set of n elements costs O(n²) element reads: about 2,200 parameters cost about 2.4 million element reads; re-adding some 300 manufacturers to a root on each master-data import read the set each time.
Workaround in E-Home: JoiningModelBinding buffers the additions to unordered multiple references of persistent objects during the import and writes each set with one tUpdate at the end (program-import CPU -47%).
Wanted: adding to a set reference without materializing the whole set (e.g. a duplicate check against the association table/index), or a bulk add API documented for imports.
Analysis
SetStorage.checkAddValue resolves the outgoing association query into a set of wrappers (AbstractWrapper.resolveWrappers): every link's destination key is read, all destinations are bulk-loaded and wrapped into a new HashSet, and only then contains is tested. For a single-select attribute the set is read a second time. removeAttributeValue and checkRemoveValue do the same and additionally load the destination of every link to find the one link to delete. The association cache itself is not the problem: it is a set of links that is maintained incrementally within a transaction.
Lösung
The duplicate check and the removal use the indexed live link query that SetStorage already owns for its live collection (IndexedLinkQuery keyed by the destination item). Its map view answers containsKey / get from the current transaction-local cache without marking the map as shared, so a lookup followed by an add does not copy the map. The local map is built once per transaction, and every created link is added to it in O(1).
- checkAddValue: duplicate check via containsKey(value.tHandle()); the single-select check via isEmpty().
- removeAttributeValue / checkRemoveValue: the link is looked up by its destination and exactly that link is deleted; the mandatory check uses the map size.
- No new API: setAttributeValue(collection) remains the bulk path. Adding n elements one by one is now linear, so a separate bulk add API for imports is not needed.
Not changed: a SetStorage on an ordered attribute still re-sorts over all links per add (no element loads; ordered references use ListStorage).
Verified by TestSetStorage (com.top_logic.element): functional cases (add and remove within and after a transaction, duplicate rejected, removing an absent value rejected, last value of a mandatory set rejected) and a CPU-time guard like the one of TestListStorage that adds 10,000 elements one by one via tAdd and removes them one by one via tRemove, each within 3 s of thread CPU time. With the previous SetStorage the two guarded operations take about 20 s and 28 s of CPU time.