fix(#4645): shortestPath edge:true ClassCastException + wrong vertex in BOTH direction#4646
Conversation
…in BOTH direction
There was a problem hiding this comment.
Code Review
This pull request addresses a ClassCastException and a traversal direction bug in the shortestPath function when using direction BOTH and edge true. It updates EdgeToVertexIterable to handle non-ResettableIterator instances and fixes EdgeToVertexIterator to correctly return the opposite-end vertex. A regression test is also added. The review feedback highlights a potential issue where silently returning an empty iterator for non-resettable iterators could lead to silent correctness bugs, and suggests throwing an exception if a non-empty, non-resettable iterator is encountered.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| final Iterator<Edge> iter = edges.iterator(); | ||
| if (!(iter instanceof ResettableIterator)) | ||
| return Collections.emptyIterator(); | ||
| return new EdgeToVertexIterator((ResettableIterator<Edge>) iter, direction); |
There was a problem hiding this comment.
Silently returning an empty iterator when iter is not an instance of ResettableIterator can lead to silent correctness bugs if a non-empty, non-resettable iterator is passed (for example, standard Java collections like ArrayList or HashSet).
If the iterator is not a ResettableIterator, we should check if it is empty. If it is empty, returning Collections.emptyIterator() is perfectly fine. If it is not empty, we should throw an exception (or wrap it) to prevent silent data loss or incorrect query results.
final Iterator<Edge> iter = edges.iterator();
if (iter instanceof ResettableIterator) {
return new EdgeToVertexIterator((ResettableIterator<Edge>) iter, direction);
}
if (!iter.hasNext()) {
return Collections.emptyIterator();
}
throw new IllegalArgumentException("The edges iterator must be an instance of ResettableIterator when not empty.");There was a problem hiding this comment.
Resolved in 705af13. EdgeToVertexIterable.iterator() now returns Collections.emptyIterator() only when the non-resettable iterator is empty, and throws IllegalArgumentException otherwise so a non-empty, non-resettable iterator fails loudly instead of silently dropping edges. In practice the only non-resettable case is GraphEngine.EMPTY_EDGE_LIST (always empty); a comment documents that invariant.
Code ReviewOverviewThis PR fixes two real bugs that caused Bug 1 - ClassCastException fix (
|
| Area | Status |
|---|---|
| Bug 1 fix correctness | Correct |
| Bug 2 fix correctness | Correct |
| Regression test present | Yes |
| Edge RID assertion in test | Missing |
| Reverse / IN direction tests | Missing |
| Docs file | Likely not needed |
The core fixes are sound. The main ask before merging is asserting the edge RID in the result and adding the reverse/IN direction test cases to make the regression suite complete.
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Coverage variation | ✅ -7.32% coverage variation |
| Diff coverage | ✅ 88.89% diff coverage |
Coverage variation details
Coverable lines Covered lines Coverage Common ancestor commit (898aebe) 130249 96403 74.01% Head commit (94a0e70) 162109 (+31860) 108115 (+11712) 66.69% (-7.32%) Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch:
<coverage of head commit> - <coverage of common ancestor commit>
Diff coverage details
Coverable lines Covered lines Diff coverage Pull request (#4646) 9 8 88.89% Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified:
<covered lines added or modified>/<coverable lines added or modified> * 100%
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
…dge-RID + reverse + IN tests
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4646 +/- ##
============================================
+ Coverage 64.95% 65.02% +0.06%
Complexity 520 520
============================================
Files 1668 1668
Lines 130249 130306 +57
Branches 27931 27943 +12
============================================
+ Hits 84600 84725 +125
+ Misses 33880 33819 -61
+ Partials 11769 11762 -7 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Code Review (Cycle 2 follow-up)Reviewing HEAD What was addressed from the previous reviewAll four cycle-1 suggestions were applied:
Remaining observations1. One uncovered branch (minor) Codecov flags 1 missing + 1 partial line in 2. // EdgeToVertexIterator.next()
final Vertex.DIRECTION neighborEnd = direction == Vertex.DIRECTION.OUT ? Vertex.DIRECTION.IN : Vertex.DIRECTION.OUT;This ternary runs once per iterated edge. It is a constant derived from an immutable field, so it could be precomputed in the constructor and stored. The JIT will almost certainly hoist it anyway, so this is purely cosmetic - not worth a re-review cycle. 3. Docs file The Summary
The fixes are correct, the regression tests are thorough, and all cycle-1 feedback was addressed. The PR is ready to merge. |
Closes #4645
Summary
This PR fixes two bugs in the
shortestPathfunction'sedge:truemode that together madeshortestPath(..., {'direction':'BOTH','edge':true})always fail:Bug 1 (ClassCastException):
EdgeToVertexIterable.iterator()castedges.iterator()unconditionally toEdgeIterator. Whendirection:BOTH, the recursive call for the side with no edges returnsGraphEngine.EMPTY_EDGE_LISTwhoseiterator()isCollections.emptyIterator()- not anEdgeIterator. Fixed by guarding withinstanceof ResettableIterator(the common base of bothEdgeIteratorandEdgeIteratorFilter).Bug 2 (wrong neighbor returned):
EdgeToVertexIterator.next()callededge.getVertex(direction)which returns the current vertex (the OUT/source end for OUT direction), not the neighbor. The BFS made no progress because every discovered "neighbor" was the vertex itself. Fixed by using the opposite direction:direction == OUT ? IN : OUT.Test plan
SQLFunctionShortestPathTest.edgeTrueDirectionBothWithAsymmetricEdges- new regression test; verifiesshortestPath(a, b, {'direction':'BOTH','edge':true})returns[a-rid, edge-rid, b-rid]for a simple directed graph where 'a' has only an outgoing edgeSQLFunctionShortestPathTesttests pass (no regression)CypherReduceAndShortestPathTest(26 tests) passesmvn test -pl engine -Dtest=SQLFunctionShortestPathTest