Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
* Added explicit transaction support to all non-Java GLVs (gremlin-python, gremlin-go, gremlin-javascript, gremlin-dotnet).
* Changed default transaction close behavior from commit to rollback across all GLVs to align with embedded graph defaults.
* Refactored Go driver connection to block until response headers arrive, enabling synchronous error returns and proper transaction ordering.
* Added child traversal support to `has()`, `hasLabel()`, `V()`, `E()`, `property()`, `is()`, `where(P)`, `P.eq/neq/gt/lt/gte/lte/within/without()`, and `TextP` predicates. Child traversals are resolved per-traverser at runtime, enabling dynamic filtering and lookup patterns.
* Added multi-traversal `P.within(trav1, trav2, ...)` and `P.without(trav1, trav2, ...)` which combine results from multiple child traversals for collection membership testing.
* Added `V(traversal)` and `E(traversal)` as start steps with synthetic traverser seeding, consistent with `mergeV(traversal)` behavior.
* Added `ChildTraversalVerificationStrategy` that blocks mutating steps (`addV`, `addE`, `drop`, etc.) inside child traversals. All child traversals must be read-only.
* Added `hasLabel(Traversal)` overload with grammar support for dynamic label filtering.
* Added traversal-bearing predicate support to `where(P)` - resolves child traversal and tests against current value when `P.hasTraversal()` is true.
* Added rejection of traversal-bearing predicates in `choose(P)` and `choose().option(P, ...)` - the predicate's child traversal cannot be resolved in the branch-selection context.
* Added runtime Map validation for `property(traversal)` - rejects results that are not a `Map` of property key/value pairs.
* Added mixed traversal/literal detection in `P.within()` and `P.without()` - throws `IllegalArgumentException` with guidance to wrap literals in `__.constant()`.
* Removed `uuid` dependency from `gremlin-javascript` in favor of the built-in `globalThis.crypto.randomUUID()`.
* Added streaming HTTP response support to `gremlin-driver` for incremental result deserialization over GraphBinary.
* Connected HTTP streaming response deserialization to the traversal API in `gremlin-javascript`, enabling `next()` to return the first result without waiting for the full response.
Expand Down
95 changes: 95 additions & 0 deletions docs/src/reference/the-traversal.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -1253,6 +1253,9 @@ IMPORTANT: It is important to think of `choose()` as a branching step and not a
intuitively lead to thinking the latter, where no match would mean to remove the traverser from the stream. As shown in
the examples, this is not what happens.

NOTE: The `choose(P)` form and `choose().option(P, ...)` currently do not support traversal-bearing predicates.
Dynamic branching is restricted in 4.0.0.

The `choose()`-step can be used within a `map()` step to apply the branching logic to each element in a collection.

[gremlin-groovy,modern]
Expand Down Expand Up @@ -2074,6 +2077,10 @@ It is possible to filter vertices, edges, and vertex properties based on their p
* `has(key,value)`: Remove the traverser if its element does not have the provided key/value property.
* `has(label, key, value)`: Remove the traverser if its element does not have the specified label and provided key/value property.
* `has(key,predicate)`: Remove the traverser if its element does not have a key value that satisfies the bi-predicate. For more information on predicates, please read <<a-note-on-predicates,A Note on Predicates>>.
* `has(key, traversal)`: Remove the traverser if its element's key value does not equal the first result of the provided traversal.
* `has(T, traversal)`: Remove the traverser if its element's `T`-based value (e.g. `T.id`, `T.label`) does not equal the first result of the provided traversal.
* `has(label, key, traversal)`: Remove the traverser if its element does not have the specified label and its key value does not equal the first result of the provided traversal.
* `hasLabel(traversal)`: Remove the traverser if its element's label does not equal the first result of the provided traversal.
* `hasLabel(labels...)`: Remove the traverser if its element does not have any of the labels.
* `hasId(ids...)`: Remove the traverser if its element does not have any of the ids.
* `hasKey(keys...)`: Remove the `Property` traverser if it does not match one of the provided keys.
Expand Down Expand Up @@ -2112,6 +2119,24 @@ the key,value pairs for those vertices.
<9> Property key is always stored as `String` and therefore an equality check with `null` will produce no result.
<10> An example of using `has()` with regular expression predicate.

The traversal-accepting forms of `has()` allow for dynamic property comparisons. Rather than providing a literal value,
a child traversal is supplied and its first result is used as the comparison value. This follows the same first-result
semantics as `by(traversal)`. The child traversal must be read-only - mutating steps are rejected.

[gremlin-groovy,modern]
----
g.V().has('age', P.gt(__.V(1).values('age'))) <1>
g.V().has('name', __.V(1).values('name')) <2>
g.V().hasLabel(__.V(1).label()) <3>
----

<1> Find all vertices whose age is greater than marko's age using a traversal inside a predicate.
<2> Find all vertices whose name equals marko's name using a traversal as the value argument.
<3> Find all vertices whose label equals the label of vertex 1.

NOTE: When a `Traversal` is provided directly as the value argument (not inside a `P`), it is internally wrapped in
`P.eq(traversal)`. See <<a-note-on-predicates,A Note on Predicates>> for more details on traversal-bearing predicates.

*Additional References*

link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#has(java.lang.String)++[`has(String)`],
Expand Down Expand Up @@ -2467,6 +2492,18 @@ g.V().where(__.in('created').values('age').
<2> Find projects having two or more contributors.
<3> Find projects whose contributors average age is between 30 and 35.

The `is()` step also supports predicates that contain traversal arguments for dynamic threshold comparison.

[gremlin-groovy,modern]
----
g.V().values('age').is(P.gt(__.V(1).values('age'))) <1>
----

<1> Find all age values that are greater than marko's age using a traversal inside the predicate.

NOTE: When `is(traversal)` is used directly (without an explicit predicate), it is internally wrapped in
`P.eq(traversal)`.

*Additional References*

link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#is(java.lang.Object)++[`is(Object)`],
Expand Down Expand Up @@ -3989,6 +4026,21 @@ g.addV().property(set, null)
<7> The label value can be specified as a property only at the time a vertex is added and if one is not specified in the addV()
<8> If you pass a `null` value for the Map this will be treated as a no-op and the input will be returned

The `property()` step also accepts a traversal that produces a `Map` of key-value pairs to set as properties. The
child traversal must be read-only - mutating steps are rejected. If the traversal does not produce a `Map`, the result
is rejected. Each entry in the resulting Map becomes a separate property on the element, allowing multiple properties
to be set in a single step.

[gremlin-groovy,modern]
----
g.V(4).property(__.V(1).project('friendCount','createdSoftware').by(__.out('knows').count()).by(__.out('created').values('name'))) <1>
g.V(4).valueMap('friendCount','createdSoftware')
----

<1> Set two properties on vertex 4 (josh) from a Map produced by a child traversal. The `project()` step builds a Map
with keys "friendCount" and "createdSoftware", whose values are computed from vertex 1's (marko's) relationships -
number of friends and name of software created. Both properties are applied to vertex 4 in one operation.

*Additional References*

link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#property(java.lang.Object,java.lang.Object,java.lang.Object...)++[`property(Object, Object, Object...)`],
Expand Down Expand Up @@ -5187,6 +5239,19 @@ g.V().has('name', within('marko', 'vadas', 'josh')).as('person').
<1> Normally the `V()`-step will iterate over all vertices. However, graph strategies can fold ``HasContainer``'s into a `GraphStep` to allow index lookups.
<2> Whether the graph system provider supports mid-traversal `V()` index lookups or not can easily be determined by inspecting the `toString()` output of the iterated traversal. If `has` conditions were folded into the `V()`-step, an index - if one exists - will be used.

The `V()` step also accepts a traversal argument. The child traversal is evaluated and its results are used as the
vertex identifiers. This works both as a start step and mid-traversal. When used as a start step, a synthetic traverser
is provided to the child traversal.

[gremlin-groovy,modern]
----
g.V(__.V(1).id()).values('name') <1>
g.inject(1).V(__.identity()).values('name') <2>
----

<1> Use a child traversal to dynamically determine the vertex ID. Here the traversal resolves vertex 1's ID.
<2> Mid-traversal usage where the injected value is used via `identity()` as the ID argument to `V()`.

*Additional References*

link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#V(java.lang.Object...)++[`V(Object...)`]
Expand Down Expand Up @@ -5399,6 +5464,18 @@ g.V().as('a').both().both().as('b').
<8> Marko is younger than josh, but josh knows someone equal in age to marko (which is marko).
<9> The "age" property is not <<by-step,productive>> for all vertices and therefore those values are filtered.

The `where()` step also supports predicates that contain traversal arguments. When a predicate contains a child
traversal, the traversal is resolved per-traverser and the result is tested directly against the current value -
bypassing scope-label resolution.

[gremlin-groovy,modern]
----
g.V().values('age').where(P.gt(__.V(1).values('age'))) <1>
----

<1> Find all age values that are greater than marko's age. The child traversal inside `P.gt()` is resolved and the
current traverser's value is compared against the result.

WARNING: The anonymous traversal of `where()` processes the current object "locally". In OLAP, where the atomic unit
of computing is the vertex and its local "star graph," it is important that the anonymous traversal does not leave
the confines of the vertex's star graph. In other words, it can not traverse to an adjacent vertex's properties or
Expand Down Expand Up @@ -5524,6 +5601,24 @@ NOTE: The TinkerPop reference implementation uses the Java `Pattern` and `Matche
engine. Other implementations may decide to use a different regular expression engine. It's a good idea to check
the documentation for the implementation you are using to verify the allowed regular expression syntax.

In addition to literal values, most predicates also accept a `Traversal` argument. When a traversal is provided, it is
evaluated and only its first result is used for comparison - consistent with `by(traversal)` first-result semantics.
The child traversal must be read-only; mutating steps are rejected.

* For `within()` and `without()`, the first result should be a `Collection`. Use `fold()` in the child traversal to
produce one.
* The multi-traversal form `within(trav1, trav2, ...)` takes the first result from each traversal and combines them
into a collection for membership testing.

[gremlin-groovy,modern]
----
g.V().has('age', P.gt(__.V(1).values('age'))) <1>
g.V().has('age', P.within(__.V(1).out('knows').values('age').fold())) <2>
----

<1> Find vertices whose age is greater than marko's age using a traversal inside the predicate.
<2> Find vertices whose age is in the set of ages of marko's friends, using `fold()` to produce a collection.

[gremlin-groovy]
----
eq(2)
Expand Down
41 changes: 41 additions & 0 deletions docs/src/upgrade/release-4.x.x.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,32 @@ GLVs (including the Java driver). This aligns with the embedded graph transactio
partial work is discarded if the user forgets to call `commit()`. In Java (both remote and embedded mode), the behavior
can still be overridden via `tx.onClose(Transaction.CLOSE_BEHAVIOR.COMMIT)`. The non-Java GLVs do not support
configuring close behavior and always rollback.
==== Traversal-Accepting Steps

Steps and predicates that previously only accepted literal values now accept child traversals resolved per-traverser
at runtime. This enables dynamic filtering, lookup, and mutation patterns without `where()`/`select()` workarounds.

Affected steps: `has()`, `hasLabel()`, `V()`, `E()`, `property()`, `is()`, `where(P)`, `P.eq/neq/gt/lt/gte/lte()`,
`P.within/without()`, and all `TextP` predicates.

[source,groovy]
----
// Dynamic property comparison
g.V().has("age", P.gt(__.V(1).values("age")))

// Dynamic vertex lookup: resolve the ids of marko's friends, then look them up by id
g.V(1).V(__.out("knows").id()).values("name")

// Multi-source filtering with within()
g.V().has("name", P.within(__.V(1).out("knows").values("name").fold(), __.constant("peter")))
----

Child traversals take only the first result (consistent with `by(traversal)` semantics). For `within()`/`without()`,
use `fold()` to collect multiple values into a list.

All child traversals must be read-only. Mutating steps (`addV`, `addE`, `drop`, `property`) inside child traversals
are rejected at construction time with `IllegalArgumentException`. A `ChildTraversalVerificationStrategy` provides
additional safety at strategy time.

==== Removed `uuid` Dependency in gremlin-javascript

Expand Down Expand Up @@ -565,6 +591,21 @@ benefiting all driver users transparently.
See <<provider-defined-types>> for full details on annotation usage, field filtering, nested types, and ServiceLoader
registration.

===== Traversal-Accepting Steps - HasContainer Guard

Steps and predicates now accept child traversals resolved at runtime. `HasContainer` instances that hold a child
traversal cannot be folded into index lookups because their value is dynamic (resolved per-traverser). Providers that
fold `HasContainer` predicates into their graph step must guard against this:

[source,java]
----
if (hasContainer.hasTraversal()) continue; // skip - dynamic value, cannot fold into index
----

`GraphStep.processHasContainerIds()` already includes this guard. Providers that implement their own `HasContainer`
folding strategy should add the same check. Without it, the container's value is read before it has been resolved
against a traverser, so an unresolved value would be folded into the index lookup and produce incorrect results.

==== Graph Driver Providers

== TinkerPop 4.0.0-beta.2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,18 @@ protected void notImplemented(final ParseTree ctx) {
* {@inheritDoc}
*/
@Override public T visitTraversalMethod_has_T_P(final GremlinParser.TraversalMethod_has_T_PContext ctx) { notImplemented(ctx); return null; }
/**
* {@inheritDoc}
*/
@Override public T visitTraversalMethod_has_String_Traversal(final GremlinParser.TraversalMethod_has_String_TraversalContext ctx) { notImplemented(ctx); return null; }
/**
* {@inheritDoc}
*/
@Override public T visitTraversalMethod_has_T_Traversal(final GremlinParser.TraversalMethod_has_T_TraversalContext ctx) { notImplemented(ctx); return null; }
/**
* {@inheritDoc}
*/
@Override public T visitTraversalMethod_has_String_String_Traversal(final GremlinParser.TraversalMethod_has_String_String_TraversalContext ctx) { notImplemented(ctx); return null; }
/**
* {@inheritDoc}
*/
Expand All @@ -475,6 +487,10 @@ protected void notImplemented(final ParseTree ctx) {
* {@inheritDoc}
*/
@Override public T visitTraversalMethod_hasLabel_P(final GremlinParser.TraversalMethod_hasLabel_PContext ctx) { notImplemented(ctx); return null; }
/**
* {@inheritDoc}
*/
@Override public T visitTraversalMethod_hasLabel_Traversal(final GremlinParser.TraversalMethod_hasLabel_TraversalContext ctx) { notImplemented(ctx); return null; }
/**
* {@inheritDoc}
*/
Expand Down Expand Up @@ -703,6 +719,18 @@ protected void notImplemented(final ParseTree ctx) {
* {@inheritDoc}
*/
@Override public T visitTraversalMethod_property_Object(final GremlinParser.TraversalMethod_property_ObjectContext ctx) { notImplemented(ctx); return null; }
/**
* {@inheritDoc}
*/
@Override public T visitTraversalMethod_property_Cardinality_Object_Traversal(final GremlinParser.TraversalMethod_property_Cardinality_Object_TraversalContext ctx) { notImplemented(ctx); return null; }
/**
* {@inheritDoc}
*/
@Override public T visitTraversalMethod_property_Object_Traversal(final GremlinParser.TraversalMethod_property_Object_TraversalContext ctx) { notImplemented(ctx); return null; }
/**
* {@inheritDoc}
*/
@Override public T visitTraversalMethod_property_Traversal(final GremlinParser.TraversalMethod_property_TraversalContext ctx) { notImplemented(ctx); return null; }
/**
* {@inheritDoc}
*/
Expand Down
Loading
Loading