From 74675c58009af4a141d5d51218308a2aea0c661f Mon Sep 17 00:00:00 2001 From: Neel Shah Date: Wed, 1 Jul 2026 17:22:48 +0000 Subject: [PATCH] Fix doFinalReduction to remove keys when post-barrier steps produce nothing --- CHANGELOG.asciidoc | 1 + .../process/traversal/step/Grouping.java | 8 ++++++- .../test/features/sideEffect/Group.feature | 22 +++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 340aa2b2b22..a5ecd33da38 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -31,6 +31,7 @@ This release also includes changes from prior 3.7.x releases. * Bumped to Groovy 4.0.32 which adds support for parsing Java 25 bytecode. * Bumped Hadoop to 3.4.3 (and Kerby to 2.0.3) to enable `hadoop-gremlin` to build and run on Java 25. * Add missing `Configuring` interface to `GraphStepPlaceholder` and `VertexStepPlaceholder` +* Fixed bug in `group()` value traversal where keys were retained with stale barrier state instead of being filtered when steps following a `Barrier` in the second `by()` produced no output (e.g. `by(values("age").fold().unfold())` or `by(__.out().fold().count(local).is(P.gt(0)))` for vertices with no out-edges). [[release-3-8-1]] === TinkerPop 3.8.1 (Release Date: April 1, 2026) diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/Grouping.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/Grouping.java index 62cc816e8c6..18f53097376 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/Grouping.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/Grouping.java @@ -31,6 +31,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.GroupSideEffectStep; import org.apache.tinkerpop.gremlin.process.traversal.step.util.ProfileStep; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -106,12 +107,17 @@ public default Traversal.Admin convertValueTraversal(final Traversal.Admin public default Map doFinalReduction(final Map map, final Traversal.Admin valueTraversal) { final Barrier barrierStep = determineBarrierStep(valueTraversal); if (barrierStep != null) { + final List keysToRemove = new ArrayList<>(); for (final K key : map.keySet()) { valueTraversal.reset(); barrierStep.addBarrier(map.get(key)); - if (valueTraversal.hasNext()) + if (valueTraversal.hasNext()) { map.put(key, valueTraversal.next()); + } else { + keysToRemove.add(key); + } } + keysToRemove.forEach(map::remove); } return (Map) map; } diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/Group.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/Group.feature index bdcd871c10d..c20af59cb52 100644 --- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/Group.feature +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/Group.feature @@ -259,6 +259,28 @@ Feature: Step - group() | result | | m[{"v[peter]":"v[lop]"}] | + Scenario: g_V_group_byXvaluesXnameXX_byXvaluesXageX_fold_unfoldX + Given the modern graph + And the traversal of + """ + g.V().group().by(values("name")).by(values("age").fold().unfold()) + """ + When iterated to list + Then the result should be unordered + | result | + | m[{"marko":"d[29].i", "vadas":"d[27].i", "josh":"d[32].i", "peter":"d[35].i"}] | + + Scenario: g_V_group_byXvaluesXnameXX_byXout_fold_countXlocalX_isXgtX0XXX + Given the modern graph + And the traversal of + """ + g.V().group().by(values("name")).by(__.out().fold().count(local).is(P.gt(0))) + """ + When iterated to list + Then the result should be unordered + | result | + | m[{"marko":"d[3].l", "josh":"d[2].l", "peter":"d[1].l"}] | + Scenario: g_V_hasXperson_name_withinXvadas_peterXX_group_by_byXout_order_countX Given the modern graph And the traversal of