This is an automated email from the ASF dual-hosted git repository. spmallette pushed a commit to branch polecat/nux/ti-sgi@mntfr6jh in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit d9e450aae5efc1ccaa3e402181957bd03b4bbf75 Author: nux <[email protected]> AuthorDate: Fri Apr 10 17:50:52 2026 -0400 fix: replace Comparable cast in Traverser.compareTo() with GremlinValueComparator (TINKERPOP-3197) Fixes ClassCastException when order() or order(local) is called on Map.Entry objects (e.g. from unfold() on a groupCount result). Traverser.compareTo() previously cast the traverser's object to Comparable, which fails for types like HashMap$Node that don't implement Comparable. Replaced with GremlinValueComparator.ORDERABILITY.compare() which handles all Gremlin types including Map.Entry. Added Gherkin scenarios and a unit test to cover the fixed behavior. (ti-sgi) --- CHANGELOG.asciidoc | 2 ++ .../gremlin/process/traversal/Traverser.java | 11 +++++----- .../traversal/step/map/OrderGlobalStepTest.java | 14 ++++++++++++ .../gremlin/test/features/map/Order.feature | 25 +++++++++++++++++++++- 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 032033ad5e..4ee4453a98 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -25,6 +25,8 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima [[release-4-0-0]] === TinkerPop 4.0.0 (Release Date: NOT OFFICIALLY RELEASED YET) +* Fixed `ClassCastException` thrown when calling `order()` or `order(local)` on `Map.Entry` objects produced by `unfold()` on grouped traversals. `Traverser.compareTo()` now uses `GremlinValueComparator.ORDERABILITY` to support all Gremlin types rather than requiring `Comparable` (TINKERPOP-3197). + [[release-4-0-0-beta-2]] === TinkerPop 4.0.0-beta.2 (April 1, 2026) diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Traverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Traverser.java index 4c78d55f59..88595f887c 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Traverser.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Traverser.java @@ -20,6 +20,7 @@ package org.apache.tinkerpop.gremlin.process.traversal; import org.apache.tinkerpop.gremlin.process.traversal.step.map.LoopsStep; import org.apache.tinkerpop.gremlin.structure.util.Attachable; +import org.apache.tinkerpop.gremlin.util.GremlinValueComparator; import java.io.Serializable; import java.util.Collections; @@ -132,20 +133,20 @@ public interface Traverser<T> extends Serializable, Comparable<Traverser<T>>, Cl } /** - * If the underlying object of the traverser is comparable, compare it with the other traverser. + * Compares this traverser with another traverser using {@link GremlinValueComparator#ORDERABILITY}, + * which supports all Gremlin types including {@link java.util.Map.Entry}. * - * @param other the other traverser that presumably has a comparable internal object + * @param other the other traverser to compare against * @return the comparison of the two objects of the traversers - * @throws ClassCastException if the object of the traverser is not comparable */ @Override - public default int compareTo(final Traverser<T> other) throws ClassCastException { + public default int compareTo(final Traverser<T> other) { final Object thisObj = this.get(); final Object otherObj = other.get(); if (thisObj == otherObj) return 0; if (null == thisObj) return -1; if (null == otherObj) return 1; - return ((Comparable) thisObj).compareTo(otherObj); + return GremlinValueComparator.ORDERABILITY.compare(thisObj, otherObj); } /** diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/OrderGlobalStepTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/OrderGlobalStepTest.java index 9400e43d14..64958bdd34 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/OrderGlobalStepTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/OrderGlobalStepTest.java @@ -26,9 +26,12 @@ import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.outE; +import static org.junit.Assert.assertEquals; /** * @author Daniel Kuppitz (http://gremlin.guru) @@ -64,4 +67,15 @@ public class OrderGlobalStepTest extends StepTest { __.inject(list).unfold().order().by(__.identity(), Order.shuffle).by().iterate(); } } + + @Test + public void shouldOrderMapEntriesWithoutBy() { + final Map<String, Long> map = new LinkedHashMap<>(); + map.put("software", 2L); + map.put("person", 4L); + final List<Map.Entry> results = __.inject(map).unfold().order().toList(); + assertEquals(2, results.size()); + assertEquals("person", results.get(0).getKey()); + assertEquals("software", results.get(1).getKey()); + } } diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Order.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Order.feature index c49c6fca14..eee8d1ef9a 100644 --- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Order.feature +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Order.feature @@ -548,4 +548,27 @@ Feature: Step - order() When iterated to list Then the result should be unordered | result | - | d[29].i | \ No newline at end of file + | d[29].i | + + Scenario: g_V_groupCount_byXlabelX_unfold_order + Given the modern graph + And the traversal of + """ + g.V().groupCount().by(T.label).unfold().order() + """ + When iterated to list + Then the result should be ordered + | result | + | m[{"person":"d[4].l"}] | + | m[{"software":"d[2].l"}] | + + Scenario: g_V_groupCount_byXlabelX_orderXlocalX + Given the modern graph + And the traversal of + """ + g.V().groupCount().by(T.label).order(Scope.local) + """ + When iterated to list + Then the result should be ordered + | result | + | m[{"person":"d[4].l","software":"d[2].l"}] | \ No newline at end of file
