diff --git a/api/src/org/labkey/api/data/Selector.java b/api/src/org/labkey/api/data/Selector.java index 3ac7d2256e3..f4ba4f30c5d 100644 --- a/api/src/org/labkey/api/data/Selector.java +++ b/api/src/org/labkey/api/data/Selector.java @@ -77,7 +77,7 @@ public interface Selector /** * Returns a sequential Stream of objects or records representing rows from the database. Converts each result row - * into an object the specified {@code Class}. The Stream is backed by a cached data structure (ResultSet and + * into an object of the specified {@code Class}. The Stream is backed by a cached data structure (ResultSet and * Connection are closed before returning the stream), so no need to close or fully exhaust this stream. Cached * streams are more convenient to use than uncached streams and should perform well in low-volume situations. */ diff --git a/study/api-src/org/labkey/api/study/QueryHelper.java b/study/api-src/org/labkey/api/study/QueryHelper.java index 50fa0186059..2816726a9f5 100644 --- a/study/api-src/org/labkey/api/study/QueryHelper.java +++ b/study/api-src/org/labkey/api/study/QueryHelper.java @@ -29,12 +29,14 @@ import org.labkey.api.data.TableInfoGetter; import org.labkey.api.data.TableSelector; import org.labkey.api.security.User; +import org.labkey.api.study.QueryHelper.StudyCacheCollections; import java.util.Collection; import java.util.Collections; import java.util.Map; +import java.util.stream.Stream; -public class QueryHelper, SC extends QueryHelper.StudyCacheCollections> +public class QueryHelper, SC extends StudyCacheCollections> { private final BlockingCache _cache; private final Class _objectClass; @@ -52,12 +54,18 @@ public QueryHelper(TableInfoGetter tableInfoGetter, Class objectClass, @Nulla _objectClass = objectClass; _defaultSortString = defaultSortString; TableInfo tableInfo = _tableInfoGetter.getTableInfo(); - _cache = DatabaseCache.get(tableInfo.getSchema().getScope(), tableInfo.getCacheSize(), "StudyCache: " + tableInfo.getName(), (key, argument) -> - createCollections( - getTableSelector(key).stream(_objectClass) + _cache = DatabaseCache.get(tableInfo.getSchema().getScope(), tableInfo.getCacheSize(), "StudyCache: " + tableInfo.getName(), (key, _) -> + { + final Map map; + try (Stream stream = getTableSelector(key).uncachedStream(_objectClass)) + { + map = Collections.unmodifiableMap(stream .peek(StudyCachable::lock) - .toList() - )); + .collect(LabKeyCollectors.toLinkedMap(StudyCachable::getPrimaryKey, v -> v)) + ); + } + return createCollections(map); + }); } /** @@ -83,9 +91,10 @@ protected SC getCollections(Container c) return _cache.get(c, null); } - protected SC createCollections(Collection collection) + // map is an unmodifiable, linked map of pk -> locked object + protected SC createCollections(Map map) { - return (SC) new QueryHelper.StudyCacheCollections<>(collection); + return (SC) new StudyCacheCollections<>(map); } public T create(User user, T obj) @@ -133,11 +142,10 @@ public static class StudyCacheCollections> { private final Map _map; - // Receives a collection of locked T objects - public StudyCacheCollections(Collection collection) + // map should be an unmodifiable, linked map of pk -> locked objects + protected StudyCacheCollections(Map map) { - _map = Collections.unmodifiableMap(collection.stream() - .collect(LabKeyCollectors.toLinkedMap(StudyCachable::getPrimaryKey, v -> v))); + _map = map; } public @Nullable V get(K key) diff --git a/study/src/org/labkey/study/model/StudyManager.java b/study/src/org/labkey/study/model/StudyManager.java index 677d8135613..bdf0424e68c 100644 --- a/study/src/org/labkey/study/model/StudyManager.java +++ b/study/src/org/labkey/study/model/StudyManager.java @@ -354,9 +354,9 @@ private Collection getCollection(Container c, Order order) } @Override - protected VisitCollections createCollections(Collection collection) + protected VisitCollections createCollections(Map map) { - return new VisitCollections(collection); + return new VisitCollections(map); } private static class VisitCollections extends StudyCacheCollections @@ -364,9 +364,11 @@ private static class VisitCollections extends StudyCacheCollections _sequenceNumVisits; private final Collection _chronologicalVisits; - private VisitCollections(Collection collection) + private VisitCollections(Map map) { - super(collection); + super(map); + + Collection collection = getCollection(); // I'd prefer to push comparators into Visit.Order, but Visit (in API) doesn't know about the display // order field. @@ -427,9 +429,9 @@ public void clearCache(Container c) } @Override - protected DatasetCollections createCollections(Collection collection) + protected DatasetCollections createCollections(Map map) { - return new DatasetCollections(collection); + return new DatasetCollections(map); } protected DatasetCollections getCollections(Study study) @@ -447,9 +449,11 @@ private static class DatasetCollections extends StudyCacheCollections> _cohortMap; private final List _nullCohortDatasets; - private DatasetCollections(Collection collection) + private DatasetCollections(Map map) { - super(collection); + super(map); + + Collection collection = getCollection(); // study.Dataset has constraints on LOWER(Name) and LOWER(Label), so this code path should never attempt // to put duplicates into these maps. Use asserts to verify this.