diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/serializer/BinaryEntryIterator.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/serializer/BinaryEntryIterator.java index 0eb22f6d50..1095742681 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/serializer/BinaryEntryIterator.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/serializer/BinaryEntryIterator.java @@ -68,23 +68,33 @@ protected final boolean fetch() { while (this.results.hasNext()) { Elem elem = this.results.next(); + if (this.current != null && + this.sizeOf(this.current) >= INLINE_BATCH_SIZE) { + /* + * The current entry already holds a full batch: start the + * next entry with this record instead of appending it. + * results.position() has already advanced to this record and + * a page restarts inclusively from position(), so it must + * never point at a record that has been emitted (a page + * limit ending exactly at a batch boundary would otherwise + * re-emit it as the first record of the next page). + */ + assert this.next == null; + this.next = this.merger.apply(null, elem); + break; + } BackendEntry merged = this.merger.apply(this.current, elem); E.checkState(merged != null, "Error when merging entry"); if (this.current == null) { // The first time to read this.current = merged; - } else if (merged == this.current) { - // The next entry belongs to the current entry - assert this.current != null; - if (this.sizeOf(this.current) >= INLINE_BATCH_SIZE) { - break; - } - } else { + } else if (merged != this.current) { // New entry assert this.next == null; this.next = merged; break; } + // Else the record belongs to the current entry // When limit exceed, stop fetching if (this.reachLimit(this.fetched() - 1)) { diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java index fccb495131..9ea6b0e919 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java @@ -5380,6 +5380,43 @@ public void testQueryInEdgesOfVertexInPaging() { Assert.assertEquals(2, count); } + @Test + public void testQueryOutEdgesOfVertexInPagingAtBatchBoundary() { + HugeGraph graph = graph(); + Assume.assumeTrue("Not support paging", + storeFeatures().supportsQueryByPage()); + // More edges than BackendEntryIterator.INLINE_BATCH_SIZE (500) + int total = 1200; + Vertex louise = graph.addVertex(T.label, "person", "name", "Louise", + "city", "Beijing", "age", 21); + Vertex java1 = graph.addVertex(T.label, "book", "name", "java-1"); + for (int i = 0; i < total; i++) { + louise.addEdge("look", java1, "time", String.format("2017-%04d", i)); + } + graph.tx().commit(); + + // A page limit ending exactly at a batch boundary (500, 1000) used to + // re-emit the last edge of a page as the first edge of the next page + for (int limit : new int[]{400, 500, 600, 1000}) { + Set ids = new HashSet<>(); + int count = 0; + String page = PageInfo.PAGE_NONE; + while (page != null) { + GraphTraversal iterator = graph.traversal() + .V(louise).outE("look") + .has("~page", page) + .limit(limit); + while (iterator.hasNext()) { + ids.add(iterator.next().id()); + count++; + } + page = TraversalUtil.page(iterator); + } + Assert.assertEquals("limit " + limit, total, count); + Assert.assertEquals("limit " + limit, total, ids.size()); + } + } + @Test public void testQueryCount() { HugeGraph graph = graph();