This is an automated email from the ASF dual-hosted git repository. xiazcy pushed a commit to branch steps-taking-traversal-poc in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit fe7f83bd0c0e7e7ffe0bb8a0572ff680c8705359 Author: Yang Xia <[email protected]> AuthorDate: Wed May 20 16:54:19 2026 -0700 allow V()/E() start steps to take traversals by generating a false traverser like mergeV/mergeE --- .../grammar/TraversalSourceSpawnMethodVisitor.java | 22 ++++++++++------------ .../traversal/dsl/graph/GraphTraversalSource.java | 10 ++++++---- .../process/traversal/step/map/GraphStep.java | 15 ++++++++++----- gremlin-language/src/main/antlr4/Gremlin.g4 | 2 ++ .../gremlin/test/features/map/VETraversal.feature | 10 ++++++++-- 5 files changed, 36 insertions(+), 23 deletions(-) diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnMethodVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnMethodVisitor.java index f2ca8b7299..bed196c29e 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnMethodVisitor.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnMethodVisitor.java @@ -99,13 +99,12 @@ public class TraversalSourceSpawnMethodVisitor extends DefaultGremlinBaseVisitor */ @Override public GraphTraversal visitTraversalSourceSpawnMethod_E(final GremlinParser.TraversalSourceSpawnMethod_EContext ctx) { + if (ctx.nestedTraversal() != null) { + return this.traversalSource.E((Traversal<?, ?>) anonymousVisitor.visitNestedTraversal(ctx.nestedTraversal())); + } final Object[] args = antlr.argumentVisitor.parseObjectVarargs(ctx.genericArgumentVarargs()); - for (final Object arg : args) { - if (arg instanceof Traversal) { - throw new IllegalArgumentException( - "E(traversal) cannot be used as a start step because there is no Traverser context " + - "available to evaluate the child traversal. Use E(traversal) as a mid-traversal step instead."); - } + if (args.length == 1 && args[0] instanceof Traversal) { + return this.traversalSource.E((Traversal<?, ?>) args[0]); } return this.traversalSource.E(args); } @@ -115,13 +114,12 @@ public class TraversalSourceSpawnMethodVisitor extends DefaultGremlinBaseVisitor */ @Override public GraphTraversal visitTraversalSourceSpawnMethod_V(final GremlinParser.TraversalSourceSpawnMethod_VContext ctx) { + if (ctx.nestedTraversal() != null) { + return this.traversalSource.V((Traversal<?, ?>) anonymousVisitor.visitNestedTraversal(ctx.nestedTraversal())); + } final Object[] args = antlr.argumentVisitor.parseObjectVarargs(ctx.genericArgumentVarargs()); - for (final Object arg : args) { - if (arg instanceof Traversal) { - throw new IllegalArgumentException( - "V(traversal) cannot be used as a start step because there is no Traverser context " + - "available to evaluate the child traversal. Use V(traversal) as a mid-traversal step instead."); - } + if (args.length == 1 && args[0] instanceof Traversal) { + return this.traversalSource.V((Traversal<?, ?>) args[0]); } return this.traversalSource.V(args); } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java index 78ab16e68e..b0991a67b9 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java @@ -539,8 +539,9 @@ public class GraphTraversalSource implements TraversalSource { /** * Spawns a {@link GraphTraversal} starting with vertices whose IDs are resolved from a child traversal. - * This form will throw an {@link IllegalStateException} at runtime because there is no traverser context - * available to evaluate the child traversal as a start step. + * As a start step, a synthetic traverser is generated to seed the child traversal evaluation, + * consistent with how {@code mergeV(traversal)} handles start steps. The child traversal should + * be self-contained (e.g., {@code __.V(1).id()}) rather than dependent on an incoming traverser. * * @param traversal the child traversal that produces vertex IDs * @since 4.0.0 @@ -576,8 +577,9 @@ public class GraphTraversalSource implements TraversalSource { /** * Spawns a {@link GraphTraversal} starting with edges whose IDs are resolved from a child traversal. - * This form will throw an {@link IllegalStateException} at runtime because there is no traverser context - * available to evaluate the child traversal as a start step. + * As a start step, a synthetic traverser is generated to seed the child traversal evaluation, + * consistent with how {@code mergeE(traversal)} handles start steps. The child traversal should + * be self-contained (e.g., {@code __.V(1).outE().id()}) rather than dependent on an incoming traverser. * * @param traversal the child traversal that produces edge IDs * @since 4.0.0 diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/GraphStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/GraphStep.java index 5abc13ecc9..043c99c99f 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/GraphStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/GraphStep.java @@ -199,12 +199,17 @@ public class GraphStep<S, E extends Element> extends AbstractStep<S, E> implemen if (this.done) throw FastNoSuchElementException.instance(); if (this.idTraversal != null) { - throw new IllegalStateException( - "V(traversal)/E(traversal) cannot be used as a start step: " + - "no Traverser context available to evaluate the child traversal"); + // Start step with idTraversal: generate a synthetic traverser to seed + // the child traversal, consistent with how mergeV/addV handle start steps. + this.done = true; + final Traverser.Admin<S> syntheticTraverser = (Traverser.Admin<S>) + this.getTraversal().getTraverserGenerator().generate(false, (Step) this, 1L); + final Object[] resolvedIds = resolveTraversalIds(syntheticTraverser); + this.iterator = lookupElements(resolvedIds); + } else { + this.done = true; + this.iterator = null == this.iteratorSupplier ? EmptyIterator.instance() : this.iteratorSupplier.get(); } - this.done = true; - this.iterator = null == this.iteratorSupplier ? EmptyIterator.instance() : this.iteratorSupplier.get(); } else { this.head = this.starts.next(); if (this.idTraversal != null) { diff --git a/gremlin-language/src/main/antlr4/Gremlin.g4 b/gremlin-language/src/main/antlr4/Gremlin.g4 index b161a271d3..822cb59481 100644 --- a/gremlin-language/src/main/antlr4/Gremlin.g4 +++ b/gremlin-language/src/main/antlr4/Gremlin.g4 @@ -123,10 +123,12 @@ traversalSourceSpawnMethod_addV traversalSourceSpawnMethod_E : K_E LPAREN genericArgumentVarargs RPAREN + | K_E LPAREN nestedTraversal RPAREN ; traversalSourceSpawnMethod_V : K_V LPAREN genericArgumentVarargs RPAREN + | K_V LPAREN nestedTraversal RPAREN ; traversalSourceSpawnMethod_inject diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/VETraversal.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/VETraversal.feature index 7bddadb221..c8535d6a74 100644 --- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/VETraversal.feature +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/VETraversal.feature @@ -130,7 +130,9 @@ Feature: Step - V(traversal) and E(traversal) g.V(__.V(vid1).id()).values("name") """ When iterated to list - Then the traversal will raise an error + Then the result should be unordered + | result | + | marko | Scenario: g_EXVXvid1X_outE_idX Given the modern graph @@ -140,4 +142,8 @@ Feature: Step - V(traversal) and E(traversal) g.E(__.V(vid1).outE().id()) """ When iterated to list - Then the traversal will raise an error + Then the result should be unordered + | result | + | e[marko-created->lop] | + | e[marko-knows->josh] | + | e[marko-knows->vadas] |
