From ad7b4783c3f960cf8c5b1f5b86b0c3bbd251b831 Mon Sep 17 00:00:00 2001 From: Mike Bryant Date: Thu, 21 May 2026 16:29:29 +0100 Subject: [PATCH 1/3] Fix flipped time-based event filtering --- .../ehri/project/api/impl/EventsApiImpl.java | 4 +-- .../eu/ehri/project/api/EventsApiTest.java | 28 +++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/ehri-core/src/main/java/eu/ehri/project/api/impl/EventsApiImpl.java b/ehri-core/src/main/java/eu/ehri/project/api/impl/EventsApiImpl.java index 6da313187..bfc88bcb2 100644 --- a/ehri-core/src/main/java/eu/ehri/project/api/impl/EventsApiImpl.java +++ b/ehri-core/src/main/java/eu/ehri/project/api/impl/EventsApiImpl.java @@ -429,14 +429,14 @@ private GremlinPipeline filterEvents( if (from != null) { pipe = pipe.filter(event -> { String timestamp = event.getTimestamp(); - return from.compareTo(timestamp) >= 0; + return timestamp.compareTo(from) >= 0; }); } if (to != null) { pipe = pipe.filter(event -> { String timestamp = event.getTimestamp(); - return to.compareTo(timestamp) <= 0; + return timestamp.compareTo(to) <= 0; }); } diff --git a/ehri-core/src/test/java/eu/ehri/project/api/EventsApiTest.java b/ehri-core/src/test/java/eu/ehri/project/api/EventsApiTest.java index f960642cf..7dfa7c934 100644 --- a/ehri-core/src/test/java/eu/ehri/project/api/EventsApiTest.java +++ b/ehri-core/src/test/java/eu/ehri/project/api/EventsApiTest.java @@ -87,13 +87,13 @@ public void testList() throws Exception { .to(timestamp) .list()); assertEquals(1, Iterables.size(toList)); - assertEquals(doc2, toList.get(0).getFirstSubject()); + assertEquals(doc1, toList.get(0).getFirstSubject()); List fromList = Lists.newArrayList(events(user1) .from(timestamp) .list()); assertEquals(1, Iterables.size(fromList)); - assertEquals(doc1, fromList.get(0).getFirstSubject()); + assertEquals(doc2, fromList.get(0).getFirstSubject()); // Test ID filter List idList = Lists.newArrayList(events(user1) @@ -122,6 +122,30 @@ public void testList() throws Exception { assertEquals(doc1, eventPage.get(0).getFirstSubject()); } + @Test + public void testFromAndToFilterByTimestampRange() throws Exception { + // doc1 is created before `midpoint`, doc2 after, so: + // - .from(midpoint) should keep only the doc2 event (timestamp >= midpoint) + // - .to(midpoint) should keep only the doc1 event (timestamp <= midpoint) + DocumentaryUnit doc1 = createItemWithIdentifier("foo", user1); + Thread.sleep(10); + String midpoint = ActionManager.getTimestamp(); + Thread.sleep(10); + DocumentaryUnit doc2 = createItemWithIdentifier("bar", user1); + + List fromList = Lists.newArrayList(events(user1) + .from(midpoint) + .list()); + assertEquals(1, fromList.size()); + assertEquals(doc2, fromList.get(0).getFirstSubject()); + + List toList = Lists.newArrayList(events(user1) + .to(midpoint) + .list()); + assertEquals(1, toList.size()); + assertEquals(doc1, toList.get(0).getFirstSubject()); + } + @Test public void testListAsUserFollowing() throws Exception { createItemWithIdentifier("foo", user1); From dbcd6421e179d390406bc1ca17099fc322a384e1 Mon Sep 17 00:00:00 2001 From: Mike Bryant Date: Thu, 21 May 2026 16:41:35 +0100 Subject: [PATCH 2/3] Fix a mutable members issue on clone --- .../java/eu/ehri/project/api/impl/EventsApiImpl.java | 10 +++++----- .../test/java/eu/ehri/project/api/EventsApiTest.java | 11 +++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/ehri-core/src/main/java/eu/ehri/project/api/impl/EventsApiImpl.java b/ehri-core/src/main/java/eu/ehri/project/api/impl/EventsApiImpl.java index bfc88bcb2..907ee3b18 100644 --- a/ehri-core/src/main/java/eu/ehri/project/api/impl/EventsApiImpl.java +++ b/ehri-core/src/main/java/eu/ehri/project/api/impl/EventsApiImpl.java @@ -111,13 +111,13 @@ private Builder(EventsApiImpl eventsApi) { this.accessor = eventsApi.accessor; this.offset = eventsApi.offset; this.limit = eventsApi.limit; - this.users = eventsApi.users; - this.ids = eventsApi.ids; - this.entityTypes = eventsApi.entityTypes; - this.eventTypes = eventsApi.eventTypes; + this.users = Sets.newHashSet(eventsApi.users); + this.ids = Sets.newHashSet(eventsApi.ids); + this.entityTypes = Sets.newHashSet(eventsApi.entityTypes); + this.eventTypes = Sets.newHashSet(eventsApi.eventTypes); this.from = eventsApi.from; this.to = eventsApi.to; - this.showType = eventsApi.showType; + this.showType = Sets.newHashSet(eventsApi.showType); this.aggregation = eventsApi.aggregation; } diff --git a/ehri-core/src/test/java/eu/ehri/project/api/EventsApiTest.java b/ehri-core/src/test/java/eu/ehri/project/api/EventsApiTest.java index 7dfa7c934..e54a5ed3b 100644 --- a/ehri-core/src/test/java/eu/ehri/project/api/EventsApiTest.java +++ b/ehri-core/src/test/java/eu/ehri/project/api/EventsApiTest.java @@ -122,6 +122,17 @@ public void testList() throws Exception { assertEquals(doc1, eventPage.get(0).getFirstSubject()); } + @Test + public void testWithFiltersDoesNotMutateSource() throws Exception { + createItemWithIdentifier("foo", user1); + createItemWithIdentifier("bar", user1); + + EventsApi base = events(user1); + // Deriving a filtered view must not bleed back into `base`. + base.withUsers(user2.getId()).list(); + assertEquals(2, Iterables.size(base.list())); + } + @Test public void testFromAndToFilterByTimestampRange() throws Exception { // doc1 is created before `midpoint`, doc2 after, so: From 1f121737305f2cd9ad8002547e59ab257e9cde54 Mon Sep 17 00:00:00 2001 From: Mike Bryant Date: Thu, 21 May 2026 16:41:51 +0100 Subject: [PATCH 3/3] Fix a potential resource leak --- .../main/java/eu/ehri/project/api/impl/QueryApiImpl.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ehri-core/src/main/java/eu/ehri/project/api/impl/QueryApiImpl.java b/ehri-core/src/main/java/eu/ehri/project/api/impl/QueryApiImpl.java index 5f83ec66e..9a8df784c 100644 --- a/ehri-core/src/main/java/eu/ehri/project/api/impl/QueryApiImpl.java +++ b/ehri-core/src/main/java/eu/ehri/project/api/impl/QueryApiImpl.java @@ -258,8 +258,7 @@ public Page page(Iterable entities, Class // can't re-use the iterator for counting and streaming. ArrayList userVerts = Lists.newArrayList(setFilters(pipeline).iterator()); Iterable iterable = graph.frameVertices( - setPipelineRange(setOrder(new GremlinPipeline<>( - userVerts))), cls); + setPipelineRange(setOrder(new GremlinPipeline<>(userVerts))), cls); return new Page<>(iterable, offset, limit, userVerts.size()); } } @@ -293,7 +292,9 @@ public long count(Iterable vertices) { @Override public long count(EntityClass type) { - return count(manager.getVertices(type)); + try (final CloseableIterable vertices = manager.getVertices(type)) { + return count(vertices); + } } // Helpers