Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
* Modified HTTP API to expect gremlin-lang strings for parameters and update all GLVs to send requests in new format.
* Added string parameter parsing to `GremlinServer` to prevent traversal injection and excessive nesting depths.
* Modified all GLVs to detect unsupported types in `GremlinLang` and throw consistent error for that case.
* Added GraphBinary 4.0 `Graph` (`0x10`) serializer/deserializer to `gremlin-javascript`, `gremlin-dotnet`, and `gremlin-go` so that `subgraph()` results are returned as a detached `Graph` data container.
[[release-4-0-0-beta-2]]
=== TinkerPop 4.0.0-beta.2 (April 1, 2026)
Expand Down
29 changes: 20 additions & 9 deletions docs/src/reference/gremlin-variants.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,12 @@ that can be used to fulfill the `gremlingo.Set` interface if desired.
* Go does not support ordered maps natively as the built-in `map` type does not guarantee iteration order. Traversal
results which contain maps may not preserve original ordering when deserialized into Go's native map types.

* The `subgraph()`-step returns a detached `*Graph` data container exposing
`Vertices map[interface{}]*Vertex` and `Edges map[interface{}]*Edge`. The result is not a live `Graph` instance:
mutating the maps has no effect on the source graph, and it cannot be passed to `traversal().with(...)`. To
re-query subgraph elements against the original graph, extract their `Id` and use `g.V(id)` / `g.E(id)` on the
original `GraphTraversalSource`.

[[gremlin-go-examples]]
=== Application Examples

Expand Down Expand Up @@ -1982,9 +1988,11 @@ doubles. `BigDecimal` is not implemented.
signed range are unsuffixed (Int), integers beyond that up to `Number.MAX_SAFE_INTEGER` use the `L` suffix (Long),
non-integer numbers and integers beyond the safe range use the `D` suffix (Double), and `BigInt` values use the `N`
suffix (BigInteger).
* The `subgraph()`-step is not supported by any variant that is not running on the Java Virtual Machine as there is
no `Graph` instance to deserialize a result into on the client-side. A workaround is to replace the step with
`aggregate(local)` and then convert those results to something the client can use locally.
* The `subgraph()`-step returns a detached `Graph` data container exposing
`vertices: Map<any, Vertex>` and `edges: Map<any, Edge>`. The result is not a live `Graph` instance: mutating the
collections has no effect on the source graph, and it cannot be passed to `traversal().with(...)`. To re-query
subgraph elements against the original graph, extract their `id` and use `g.V(id)` / `g.E(id)` on the original
`GraphTraversalSource`.

[[gremlin-javascript-examples]]
=== Application Examples
Expand Down Expand Up @@ -2398,9 +2406,11 @@ anchor:gremlin-net-limitations[]
[[gremlin-dotnet-limitations]]
=== Limitations

* The `subgraph()`-step is not supported by any variant that is not running on the Java Virtual Machine as there is
no `Graph` instance to deserialize a result into on the client-side. A workaround is to replace the step with
`aggregate(local)` and then convert those results to something the client can use locally.
* The `subgraph()`-step returns a detached `Graph` data container exposing
`Vertices: IDictionary<object, Vertex>` and `Edges: IDictionary<object, Edge>`. The result is not a live `Graph`
instance: mutating the collections has no effect on the source graph, and it cannot be passed to
`traversal().with(...)`. To re-query subgraph elements against the original graph, extract their `Id` and use
`g.V(id)` / `g.E(id)` on the original `GraphTraversalSource`.
* `DateTimeOffset` cannot represent the extreme values of Gremlin's `OffsetDateTime` maximum and minimum,
so offset date-time values at those boundaries will fail to deserialize.
* Gremlin's `Duration` type has a much larger range than C#'s `TimeSpan`, so extreme duration values (such as
Expand Down Expand Up @@ -2963,9 +2973,10 @@ and `timedelta`.
* In Gremlin, 1 isn't equal to the boolean true value and 0 isn't equal to the boolean false value, but they are equal
in Python. This means that in `gremlin-python` if these values are in a `Set`, you will get a different behavior than
what is intended by Gremlin, since it follows Python's behavior.
* The `subgraph()`-step is not supported by any variant that is not running on the Java Virtual Machine as there is
no `Graph` instance to deserialize a result into on the client-side. A workaround is to replace the step with
`aggregate(local)` and then convert those results to something the client can use locally.
* The `subgraph()`-step returns a detached `Graph` data container exposing `vertices: dict` and `edges: dict`
keyed by element id. The result is not a live `Graph` instance: mutating the dicts has no effect on the source
graph, and it cannot be passed to `traversal().with(...)`. To re-query subgraph elements against the original
graph, extract their `id` and use `g.V(id)` / `g.E(id)` on the original `GraphTraversalSource`.
* Use of the aiohttp library in the default transport requires the use of asyncio's event loop to run the async functions.
This can be an issue in situations where the application calling Gremlin-Python is already using an event loop.
Certain types of event loops can be patched using nest-asyncio which allows Gremlin-Python to proceed without an error like
Expand Down
54 changes: 54 additions & 0 deletions docs/src/upgrade/release-4.x.x.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,60 @@ beyond this limit will be rejected with an error.

See: link:https://issues.apache.org/jira/browse/TINKERPOP-3247[TINKERPOP-3247]

==== Subgraph Support in GLVs
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to mention python here too? it's essentially all the GLVs being announced for the first time. when i did python i don't think i added any docs because i knew all these other ones had to land too.

i also think this doesn't read like Upgrade Documentation. It should be doing more to explain the relevance of this feature and less about technical details and minutiae.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed it to mention that all GLVs support subgraphs and took out the technical details. Thanks!


The GraphBinary 4.0 `Graph` type (`0x10`) is now handled by `gremlin-javascript`, `gremlin-dotnet`, and `gremlin-go`,
so a `subgraph()`-step result returned to the client is deserialized into a `Graph` data container populated with the
captured vertices and edges. Reference documentation: <<subgraph-step>>.

The returned `Graph` is a detached snapshot of the captured vertices and edges. Mutating its collections has no
effect on the source graph.

* `gremlin-javascript`: `graph.vertices: Map<any, Vertex>` and `graph.edges: Map<any, Edge>`
* `gremlin-dotnet`: `graph.Vertices: IDictionary<object, Vertex>` and `graph.Edges: IDictionary<object, Edge>`
* `gremlin-go`: `graph.Vertices: map[interface{}]*Vertex` and `graph.Edges: map[interface{}]*Edge`

IMPORTANT: The deserialized `Graph` is a detached data container, not a traversable `Graph` instance. It cannot be
passed to `traversal().with(...)` from a GLV. To re-query elements from the original graph, extract their ids and
call `g.V(id)` or `g.E(id)` against the original `GraphTraversalSource`.

Example (`gremlin-javascript`):

[source,javascript]
----
const sg = await g.E().hasLabel('knows').subgraph('sg').cap('sg').next();
for (const [id, vertex] of sg.vertices) {
console.log(id, vertex.label);
}
for (const [id, edge] of sg.edges) {
console.log(`${edge.outV.id} -${edge.label}-> ${edge.inV.id}`);
}
----

Example (`gremlin-dotnet`):

[source,csharp]
----
var sg = (Graph) await g.E().HasLabel("knows").Subgraph("sg").Cap<object>("sg").Next();
foreach (var entry in sg.Vertices)
Console.WriteLine($"{entry.Key} {entry.Value.Label}");
----

Example (`gremlin-go`):

[source,go]
----
result, _ := g.E().HasLabel("knows").Subgraph("sg").Cap("sg").Next()
sg := result.Data.(*gremlingo.Graph)
for id, v := range sg.Vertices {
fmt.Println(id, v.Label)
}
----

The `Graph` string representation in `gremlin-python`, `gremlin-javascript`, `gremlin-dotnet`, and `gremlin-go` now
renders as `graph[vertices:N edges:M]`, matching the format used by Java's `AbstractTinkerGraph.toString()` for easier
debugging.

=== Upgrading for Providers

==== Graph System Providers
Expand Down
17 changes: 17 additions & 0 deletions gremlin-dotnet/src/Gremlin.Net/Structure/Graph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#endregion

using System;
using System.Collections.Generic;
using Gremlin.Net.Process.Traversal;

namespace Gremlin.Net.Structure
Expand All @@ -32,6 +33,16 @@ namespace Gremlin.Net.Structure
/// </summary>
public class Graph
{
/// <summary>
/// Gets the <see cref="Vertex" /> instances contained in this <see cref="Graph" />, keyed by their id.
/// </summary>
public IDictionary<object, Vertex> Vertices { get; } = new Dictionary<object, Vertex>();

/// <summary>
/// Gets the <see cref="Edge" /> instances contained in this <see cref="Graph" />, keyed by their id.
/// </summary>
public IDictionary<object, Edge> Edges { get; } = new Dictionary<object, Edge>();

/// <summary>
/// Generates a reusable <see cref="GraphTraversalSource" /> instance.
/// </summary>
Expand All @@ -41,5 +52,11 @@ public GraphTraversalSource Traversal()
{
return new GraphTraversalSource();
}

/// <inheritdoc />
public override string ToString()
{
return $"graph[vertices:{Vertices.Count} edges:{Edges.Count}]";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ public class DataType : IEquatable<DataType>
public static readonly DataType Edge = new DataType(0x0D);
public static readonly DataType Path = new DataType(0x0E);
public static readonly DataType Property = new DataType(0x0F);
// Not yet implemented
// public static readonly DataType Graph = new DataType(0x10);
public static readonly DataType Graph = new DataType(0x10);
public static readonly DataType Vertex = new DataType(0x11);
public static readonly DataType VertexProperty = new DataType(0x12);
public static readonly DataType Direction = new DataType(0x18);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public class TypeSerializerRegistry
{typeof(float), SingleTypeSerializers.FloatSerializer},
{typeof(Guid), new UuidSerializer()},
{typeof(Edge), new EdgeSerializer()},
{typeof(Graph), new GraphSerializer()},
{typeof(Path), new PathSerializer()},
{typeof(Property), new PropertySerializer()},
{typeof(Vertex), new VertexSerializer()},
Expand Down Expand Up @@ -80,6 +81,7 @@ public class TypeSerializerRegistry
{DataType.Set, new SetSerializer<HashSet<object?>, object>()},
{DataType.Uuid, new UuidSerializer()},
{DataType.Edge, new EdgeSerializer()},
{DataType.Graph, new GraphSerializer()},
{DataType.Path, new PathSerializer()},
{DataType.Property, new PropertySerializer()},
{DataType.Vertex, new VertexSerializer()},
Expand Down
Loading
Loading