Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
da4ec2a
Add multi-label vertex support: core data model, steps, and grammar
xiazcy Jun 26, 2026
be3e714
Implement multi-label in TinkerGraph reference implementation
xiazcy Jun 26, 2026
f4180a2
Add GLV implementations for multi-label across Go, Python, JS, .NET
xiazcy Jun 26, 2026
be5072e
Document multi-label vertex support
xiazcy Jun 26, 2026
6a3c91e
Reconstruct source-spawned multi-label addV as a single step
Cole-Greer Jun 30, 2026
99f496a
Preserve GValue variables through multi-label addV and simplify the a…
Cole-Greer Jun 30, 2026
687878f
Add label-traversal semantics, addV traversal-varargs, and multi-labe…
Cole-Greer Jul 2, 2026
dfc1827
Address multi-label documentation review comments
Cole-Greer Jul 2, 2026
561723f
Fold LabelsDropVerificationStrategy into StandardVerificationStrategy
Cole-Greer Jul 2, 2026
d6c57e2
cleanup gherkin tags docs
Cole-Greer Jul 2, 2026
1310986
Fix gremlin-core compilation for addLabel(Traversal) type inference
Cole-Greer Jul 2, 2026
f1e678b
Test multi-label single-label-default rendering via one-of assertion
Cole-Greer Jul 2, 2026
102938e
Align addLabel()/dropLabel() traversal-varargs with addV() semantics
Cole-Greer Jul 2, 2026
b88f22e
Simplify addV/addLabel/dropLabel grammar visitors
Cole-Greer Jul 3, 2026
43e0336
Fix multi-label addV codegen in GremlinDslProcessor
Cole-Greer Jul 3, 2026
9ea3df1
Use literal-only grammar for addLabel()/dropLabel() strings
Cole-Greer Jul 3, 2026
2958a30
Unify multi-label representation in addV step hierarchy
Cole-Greer Jul 5, 2026
278b64b
Fix multi-label rendering gaps in ElementMapStep and ComputerGraph
Cole-Greer Jul 6, 2026
d272791
Address PR review nits: verification strategy, event util, label help…
Cole-Greer Jul 6, 2026
0b33a8f
Fix @MultiLabelDefault/@SingleLabelDefault semantics and add coverage
Cole-Greer Jul 6, 2026
2eefc22
Address multi-label test coverage gap review comments
Cole-Greer Jul 6, 2026
083c9a6
Split TinkerGraph empty-graph fixture into single/multi-label variants
Cole-Greer Jul 7, 2026
9db5db5
Reject combined with(multilabel)/with(singlelabel), add elementMap() …
Cole-Greer Jul 7, 2026
7855ccf
Relocate hasLabel() OR-semantics note, fix 'supports mutation' wording
Cole-Greer Jul 7, 2026
db79ef9
Weave multi-label support throughout reference docs, recipes, tutorial
Cole-Greer Jul 7, 2026
d036429
Rewrite spec-style multi-label prose in reference docs; drop redundan…
Cole-Greer Jul 8, 2026
941dd3e
fix mergeV example not using 'existing' graph
Cole-Greer Jul 8, 2026
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
7 changes: 7 additions & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
* Changed `Tree` to no longer extend `HashMap`; it is now a final class with a tree-shaped API (`childAt`, `hasChild`, `contains`, `findSubtree`, `getOrCreateChild`, `getNodesAtDepth`, `getLeafNodes`, `nodeCount`) and is no longer a `Map`.
* Changed `count(local)` on a `Tree` to return the total node count (`Tree.nodeCount()`) instead of the root-entry count.
* Changed Tree class in Java to not extend from HashMap and offered a new tree-shaped API for navigation.
* Added multi-label support for vertices with configurable `LabelCardinality` (`ONE`, `ONE_OR_MORE`, `ZERO_OR_MORE`).
* Added `labels()` step to emit each vertex label as a separate traverser.
* Added `addLabel()` and `dropLabel()`/`dropLabels()` steps for vertex label mutation.
* Added `LabelCardinalityValidator` utility for constraint enforcement separated from the `LabelCardinality` enum.
* Added `with("multilabel")` source configuration for `elementMap()` and `valueMap()` to return labels as a set.
* Added `labels().drop()` rejection to `StandardVerificationStrategy` to prevent accidental usage.
* Added multi-label syntax to `addV()` and `mergeV()` with append-only `onMatch` semantics.
* Added typed numeric wrappers and `preciseNumbers` connection option to `gremlin-javascript` for explicit control over numeric type serialization and deserialization.
* Added `NextN(n)` to `Traversal` in `gremlin-go` for batched result iteration, providing API parity with `next(n)` in the Java, Python, and .NET GLVs, and updated the Go translators in `gremlin-core` and `gremlin-javascript` to emit `NextN(n)` for the batched form.
* Added Provider Defined Types (PDT) support — graph providers can define custom types via `@ProviderDefined` annotation that serialize/deserialize seamlessly across all GLVs without driver-side configuration. Replaces TP3 custom type mechanism.
Expand Down
3 changes: 3 additions & 0 deletions docker/gremlin-server/gremlin-server-integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ graphs: {
graph: {
configuration: conf/tinkergraph-service.properties,
traversalSources: [{name: g}, {name: ggraph}]},
multilabel: {
configuration: conf/tinkergraph-multilabel.properties,
traversalSources: [{name: gmultilabel}]},
immutable: {
configuration: conf/tinkergraph-service.properties,
traversalSources: [{name: gimmutable}]},
Expand Down
9 changes: 9 additions & 0 deletions docs/src/dev/developer/for-committers.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -672,8 +672,17 @@ TinkerGQL support out of the box and runs these scenarios without any additional
* `@InsertionOrderingRequired` - The scenario is reliant on the graph system predictably returning results (vertices,
edges, properties) in the same order in which they were inserted into the graph.
* `@MetaProperties` - The scenario makes use of meta-properties.
* `@MultiLabel` - The scenario requires a graph that supports multiple labels per vertex (i.e.
`ONE_OR_MORE` or `ZERO_OR_MORE` vertex label cardinality). Providers that only support a vertex label cardinality of
`ONE` should exclude these tests.
* `@MultiLabelDefault` - The scenario assumes a graphs unconfigured `valueMap()`/`elementMap()` steps
produce results following `with("multilabel")` semantics. Graphs which default to single-label semantics
should exclude these tests.
* `@MultiProperties` - The scenario makes use of multi-properties.
* `@SupportsChar` - The scenario requires that `char` is completely supported.
* `@SingleLabelDefault` - The scenario assumes a graphs unconfigured `valueMap()`/`elementMap()` steps
produce results following `with("singlelabel")` semantics. Graphs which default to multi-label semantics
should exclude these tests.
* `@SupportsDuration` - The scenario requires that `Duration` is completely supported.
* `@SupportsBinary` - The scenario requires that `Binary` is completely supported.
* `@UserSuppliedVertexIds` - The scenario relies on the vertex IDs specified in the dataset used by the scenario.
Expand Down
169 changes: 169 additions & 0 deletions docs/src/dev/provider/gremlin-semantics.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,84 @@ See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/j
link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStartStep.java[source (start)],
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#addvertex-step[reference]

=== addLabel()
Comment thread
Cole-Greer marked this conversation as resolved.

*Description:* Adds one or more labels to an element.

*Syntax:* `addLabel(String label, String... moreLabels)` | `addLabel(Traversal<?, ?> labelTraversal, Traversal<?, ?>... moreLabelTraversals)`

[width="100%",options="header"]
|=========================================================
|Start Step |Mid Step |Modulated |Domain |Range
|N |Y |N |`Element` |`Element`
|=========================================================

*Arguments:*

* `label` - The label to add.
* `moreLabels` - Additional labels to add.
* `labelTraversal` - The first (or only) traversal that produces a label to add.
* `moreLabelTraversals` - Additional label-producing traversals (may be empty for single-traversal behavior).

*Considerations:*

@spmallette spmallette Jun 29, 2026

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.

The graph must be configured with a LabelCardinality that supports mutation (ONE_OR_MORE or ZERO_OR_MORE).

"supports mutation"? First, that sounds a bit odd...LabelCardinality is not really "mutation" right? Does single imply immutability? is that right?

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've reworded this for clarity, but yes singlelabel does imply immutability, because there is no atomic operation to replace labels, and there labels cannot be added or removed without violating the cardinality. This also keeps LabelCardinality.ONE consistent with the legacy TP3 semantics which I think is nice.


Label add and remove operations require a `LabelCardinality` that permits changing the set of labels on an element
(`ONE_OR_MORE` or `ZERO_OR_MORE`). When `LabelCardinality` is `ONE`, the vertex's label is fixed at creation and
cannot be added to or removed.
Adding a label that already exists on the element is a no-op. The step returns the element (sideEffect semantics).
Each traversal argument is iterated for a single result only. If exactly one traversal is provided and it produces
a `Collection<String>`, the collection will be automatically unfolded into the set of labels added; if more than one
traversal is provided, each must resolve to a single `String` label.

*Exceptions*

* If the graph's `LabelCardinality` does not permit label modification, an `IllegalStateException` will be thrown.
* If any label argument is null or empty, an `IllegalArgumentException` will be thrown.
* If more than one traversal is provided and any of them produces a `Collection`, an `IllegalArgumentException` will
be thrown.

See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddLabelStep.java[source],
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#addlabel-step[reference]

=== dropLabel()

*Description:* Removes one or more specific labels from an element, or removes all labels.

*Syntax:* `dropLabel(String label, String... moreLabels)` | `dropLabel(Traversal<?, String> labelTraversal, Traversal<?, String>... moreLabelTraversals)` | `dropLabels()`

[width="100%",options="header"]
|=========================================================
|Start Step |Mid Step |Modulated |Domain |Range
|N |Y |N |`Element` |`Element`
|=========================================================

*Arguments:*

* `label` - The label to remove.
* `moreLabels` - Additional labels to remove.
* `labelTraversal` - The first (or only) traversal that produces a label to remove.
* `moreLabelTraversals` - Additional label-producing traversals (may be empty for single-traversal behavior).

*Considerations:*

Label add and remove operations require a `LabelCardinality` that permits changing the set of labels on an element
(`ONE_OR_MORE` or `ZERO_OR_MORE`). Dropping a label that does not exist
on the element is a no-op. `dropLabels()` removes all labels (only valid with `ZERO_OR_MORE`). The step returns the
element (sideEffect semantics).
Each traversal argument is iterated for a single result only. If exactly one traversal is provided and it produces
a `Collection<String>`, the collection will be automatically unfolded into the set of labels removed; if more than
one traversal is provided, each must resolve to a single `String` label.

*Exceptions*

* If the graph's `LabelCardinality` does not permit label modification, an `IllegalStateException` will be thrown.
* If `dropLabel()` or `dropLabels()` would violate the minimum label count, an `IllegalStateException` will be thrown.
* If more than one traversal is provided and any of them produces a `Collection`, an `IllegalArgumentException` will
be thrown.

See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/DropLabelsStep.java[source],
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#droplabel-step[reference]

[[all-step]]
=== all()

Expand Down Expand Up @@ -1525,6 +1603,41 @@ None

None

[[elementmap-step]]
=== elementMap()

*Description:* Converts elements to a `Map` representation containing the element's `id`, `label`, and properties.
For an `Edge`, the map also includes the `IN` and `OUT` vertex structures (each a nested map of `id` and `label`).

*Syntax:* `elementMap(String... propertyKeys)`

[width="100%",options="header"]
|=========================================================
|Start Step |Mid Step |Modulated |Domain |Range
|N |Y |N |`Element` |`Map`
|=========================================================

*Arguments:*

* `propertyKeys` - If `elementMap()` is provided a list of propertyKeys, then the result map will only contain
property entries specified by propertyKeys. If the list is empty, then all properties are included.

*Considerations:*

The label format in the map is controlled by source-level `with("multilabel")` and `with("singlelabel")` options, not
by the graph's `LabelCardinality`. When `"multilabel"` is present, the label value is a `Set<String>`; likewise when
`"singlelabel"` is present, it is a single `String`. The two options are mutually exclusive, configuring both
`"multilabel"` and `"singlelabel"` on the same source is rejected with a `VerificationException`. Note that is a
provider choice whether an unconfigured `GraphTraversal` defaults to `multilabel` or `singlelabel` semantics. The
reference implementation defaults to `singlelabel` semantics.

*Exceptions*

None

See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ElementMapStep.java[source],
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#elementmap-step[reference]

[[format-step]]
=== format()

Expand Down Expand Up @@ -1637,6 +1750,49 @@ See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/j
link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/GroupCountSideEffectStep.java[source (sideEffect)],
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#groupcount-step[reference]

[[label-step]]
=== label()

*Description:* Maps an element to its label as a single string value.

*Syntax:* `label()`

[width="100%",options="header"]
|=========================================================
|Start Step |Mid Step |Modulated |Domain |Range
|N |Y |N |`Element` |`String`
|=========================================================

*Considerations:*

The `label()` step returns exactly one label string per element. For graphs that support multiple labels per vertex,
`label()` returns only one of the vertex's labels and the choice is non-deterministic (implementation-dependent
ordering). Use `labels()` to retrieve all labels reliably.

See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LabelStep.java[source],
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#label-step[reference]

[[labels-step]]
=== labels()
Comment thread
Cole-Greer marked this conversation as resolved.

*Description:* Maps an element to its labels, emitting one traverser per label.

*Syntax:* `labels()`

[width="100%",options="header"]
|=========================================================
|Start Step |Mid Step |Modulated |Domain |Range
|N |Y |N |`Element` |`String`
|=========================================================

*Considerations:*

For vertices with a single label, `labels()` emits one traverser (equivalent to `label()`). For multi-label vertices,
it emits one traverser per label. For vertices with zero labels (`ZERO_OR_MORE` mode), no traversers are emitted.

See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LabelsStep.java[source],
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#labels-step[reference]

[[length-step]]
=== length()

Expand Down Expand Up @@ -2564,6 +2720,19 @@ the possible values are:
** 8 for including values (VertexProperty)
** 15 for including all

*Considerations:*

The label format in the map is controlled by source-level `with("multilabel")` and `with("singlelabel")` options, not
by the graph's `LabelCardinality`. When `"multilabel"` is present, the label value is a `Set<String>`; likewise when
`"singlelabel"` is present, it is a single `String`. The two options are mutually exclusive, configuring both
`"multilabel"` and `"singlelabel"` on the same source is rejected with a `VerificationException`. Note that is a
provider choice whether an unconfigured `GraphTraversal` defaults to `multilabel` or `singlelabel` semantics. The
reference implementation defaults to `singlelabel` semantics.

*Exceptions*

None

See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PropertyMapStep.java[source],
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#valuemap-step[reference],
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#propertymap-step[reference]
6 changes: 6 additions & 0 deletions docs/src/recipes/anti-patterns.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,12 @@ g.V().choose(label).
<1> Note, that tokens use a `fold()` reducer by default.
<2> `by("name")` doesn't use a token, but falls into the same category as the String `"name"` is translated into an optimized traversal.

NOTE: The `label` token used above is equivalent to the `label()` step, which on a vertex with multiple labels
returns only one of them, chosen non-deterministically. On graphs configured for multiple labels per vertex, prefer
`by(labels().fold())` in place of `by(label)` so that every label a vertex carries is accounted for. See the
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#by-step[By Step] section of the Reference Documentation for
further details.

[has-traversal]
== has() and Traversal Arguments

Expand Down
31 changes: 31 additions & 0 deletions docs/src/reference/implementations-tinkergraph.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ TinkerGraph has several settings that can be provided on creation via `Configura
|gremlin.tinkergraph.vertexPropertyIdManager |The `IdManager` implementation to use for vertex properties.
|gremlin.tinkergraph.defaultVertexPropertyCardinality |The default `VertexProperty.Cardinality` to use when `Vertex.property(k,v)` is called.
|gremlin.tinkergraph.allowNullPropertyValues |A boolean value that determines whether or not `null` property values are allowed and defaults to `false`.
|gremlin.tinkergraph.vertexLabelCardinality |The `LabelCardinality` for vertices. Options are `ONE` (default, single immutable label, backward-compatible with 3.x), `ONE_OR_MORE` (at least one label, mutable), or `ZERO_OR_MORE` (zero or more labels). See <<tinkergraph-multi-label>>.
|gremlin.tinkergraph.graphLocation |The path and file name for where TinkerGraph should persist the graph data. If a
value is specified here, the `gremlin.tinkergraph.graphFormat` should also be specified. If this value is not
included (default), then the graph will stay in-memory and not be loaded/persisted to disk.
Expand Down Expand Up @@ -219,6 +220,36 @@ g.io("data/tinkerpop-crew.kryo").read().iterate()
g.V().properties()
----

[[tinkergraph-multi-label]]

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.

We're missing a lot of docs updates. TinkerGraph isn't the story here. The story is that TinkerPop now has multi-label support. The entire reference guide, recipes, tutorials, etc. need review. At minimum, we have to conceptually introduce this change to the "structure" at the front end of the reference docs: https://tinkerpop.apache.org/docs/current/reference/#graph-structure We should be looking to explain this feature throughout the docs in a seamless fashion.

=== Multi-Label Support

TinkerGraph supports multiple labels per vertex when `gremlin.tinkergraph.vertexLabelCardinality` is set to
`ONE_OR_MORE` or `ZERO_OR_MORE`. This enables use of the `addLabel()`, `dropLabel()`, `dropLabels()`, and `labels()`
traversal steps.

[gremlin-groovy]
----
conf = new BaseConfiguration()
conf.setProperty("gremlin.tinkergraph.vertexLabelCardinality", "ZERO_OR_MORE")
graph = TinkerGraph.open(conf)
g = traversal().with(graph)
g.addV("person","employee").property("name","marko").iterate()
g.V().has("name","marko").labels() <1>
g.V().has("name","marko").addLabel("manager").labels() <2>
g.V().has("name","marko").dropLabel("employee").labels() <3>
----

<1> The vertex was created with two labels.
<2> A third label is added.
<3> One label is removed.

The `hasLabel()` step works with multi-label vertices using OR semantics: `hasLabel("a","b")` matches any vertex that
has label "a" OR label "b". To match vertices having both labels, chain multiple `hasLabel()` calls:
`hasLabel("a").hasLabel("b")`.

This multi-label support applies to vertices only; an `Edge` must have exactly one label regardless of the
graph's configured `vertexLabelCardinality`.

[[tinkergraph-gremlin-tx]]
=== Transactions

Expand Down
6 changes: 5 additions & 1 deletion docs/src/reference/intro.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ tutorial.

.Primary components of the TinkerPop *structure* API
* `Graph`: maintains a set of vertices and edges, and access to database functions such as transactions.
* `Element`: maintains a collection of properties and a string label denoting the element type.
* `Element`: maintains a collection of properties and one or more string labels denoting the element type.
** `Vertex`: extends Element and maintains a set of incoming and outgoing edges.
** `Edge`: extends Element and maintains an incoming and outgoing vertex.
* `Property<V>`: a string key associated with a `V` value.
Expand Down Expand Up @@ -170,6 +170,10 @@ way for users to think when building applications with TinkerPop. The structure
providers. Those interested in implementing the structure API to make their graph system TinkerPop enabled can learn
more about it in the link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/[Graph Provider] documentation.

Graph providers may also optionally support certain variances on this basic structure, such as a vertex having
multiple properties per key or multiple labels. See <<optional-data-model-variances,Optional Data Model Variances>>
for details.

[[the-graph-process]]
=== The Graph Process

Expand Down
Loading
Loading