You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by sp...@apache.org on 2026/06/30 19:16:11 UTC
(tinkerpop) 01/06: Add Tiny Gremlin — local in-process traversal for gremlin-javascript
This is an automated email from the ASF dual-hosted git repository.
spmallette pushed a commit to branch jsprocess
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit fb51129928845af9984923218788d01cc8a89297
Author: Stephen Mallette <st...@amazon.com>
AuthorDate: Tue Jun 9 12:42:19 2026 -0400
Add Tiny Gremlin — local in-process traversal for gremlin-javascript
Tiny Gremlin is a defined 33-step subset of Gremlin that executes locally
against a detached Graph object without a server. Primary use case: call
subgraph() on a remote graph and navigate/filter the result in-process.
Secondary use case: build small graphs manually with addV()/addE().
Implementation (gremlin-javascript):
- LocalGraphConnection: RemoteConnection adapter that accepts a Graph via
with_(graph), parses GremlinLang with the ANTLR parser, and executes locally
- ExecuteVisitor: walks the ANTLR parse tree into a StepDescriptor pipeline
- LocalExecutor: chains lazy generators per step, with optimization passes
(validation, hasId folding) and modulator folding (from/to into addE,
by into order)
- steps.ts: full Gremlin Orderability and Comparability semantics
- passes.ts: validation and hasId-folding optimization passes
- Graph.ts: outEdges/inEdges adjacency maps for O(1) neighbor lookup
- Traversal: forEach() and reduce() terminal methods
- @TinyGremlin Gherkin tag on existing scenarios for cross-language coverage
Documentation:
- docs/src/dev/provider/tiny-gremlin.asciidoc: full Tiny Gremlin semantics
- docs/src/reference/the-traversal.asciidoc: Tiny Gremlin reference section
and Tiny Gremlin Semantics links in Additional References for all 22
covered step sections
- docs/src/dev/provider/gremlin-semantics.asciidoc: placeholder sections
for steps referenced by Tiny Gremlin
Assisted-by: Claude Code:claude-sonnet-4-6
---
CHANGELOG.asciidoc | 1 +
docs/src/dev/provider/gremlin-semantics.asciidoc | 140 ++++
docs/src/dev/provider/index.asciidoc | 2 +
docs/src/dev/provider/tiny-gremlin.asciidoc | 385 +++++++++++
docs/src/reference/the-traversal.asciidoc | 134 +++-
docs/static/images/tiny-gremlin.png | Bin 0 -> 85492 bytes
.../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs | 5 +
gremlin-go/driver/cucumber/gremlin.go | 5 +
.../lib/driver/local-graph-connection.ts | 120 ++++
gremlin-js/gremlin-javascript/lib/index.ts | 4 +
.../lib/language/executor/ExecuteVisitor.ts | 708 ++++++++++++++++++++
.../lib/process/anonymous-traversal.ts | 13 +-
.../lib/process/local/LocalExecutionError.ts | 29 +
.../lib/process/local/LocalExecutor.ts | 248 +++++++
.../gremlin-javascript/lib/process/local/passes.ts | 87 +++
.../gremlin-javascript/lib/process/local/steps.ts | 730 +++++++++++++++++++++
.../gremlin-javascript/lib/process/local/types.ts | 57 ++
.../gremlin-javascript/lib/process/traversal.ts | 28 +
.../gremlin-javascript/lib/structure/graph.ts | 16 +
.../io/binary/internals/GraphSerializer.js | 1 +
gremlin-js/gremlin-javascript/package.json | 1 +
.../gremlin-javascript/test/cucumber/gremlin.js | 7 +-
.../test/cucumber/local-fixtures.ts | 231 +++++++
.../test/cucumber/local-world.ts | 84 +++
.../src/main/python/tests/feature/gremlin.py | 5 +
.../gremlin/language/translator/translations.json | 85 +++
.../gremlin/test/features/filter/Has.feature | 42 +-
.../gremlin/test/features/filter/HasId.feature | 12 +
.../gremlin/test/features/filter/HasLabel.feature | 15 +-
.../gremlin/test/features/filter/HasNot.feature | 17 +-
.../gremlin/test/features/filter/Range.feature | 14 +
.../gremlin/test/features/filter/Tail.feature | 4 +
.../gremlin/test/features/map/AddEdge.feature | 5 +-
.../gremlin/test/features/map/AddVertex.feature | 33 +-
.../gremlin/test/features/map/Edge.feature | 24 +-
.../gremlin/test/features/map/ElementMap.feature | 5 +-
.../gremlin/test/features/map/Order.feature | 59 +-
.../gremlin/test/features/map/Path.feature | 17 +-
.../gremlin/test/features/map/Properties.feature | 1 +
.../gremlin/test/features/map/ValueMap.feature | 6 +
.../gremlin/test/features/map/Vertex.feature | 31 +-
.../test/features/semantics/Orderability.feature | 1 +
42 files changed, 3365 insertions(+), 47 deletions(-)
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 963ad54f5c..b5c70c2d21 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -130,6 +130,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
* Added the `gql-gremlin` module containing the TinkerGQL engine, a `MATCH`-pattern executor implementing a deliberate minimal subset of ISO GQL, usable by any graph provider.
* Added `Graph.countVerticesByLabel(String)`, `Graph.countEdgesByLabel(String)`, and the `Graph.Index` nested interface as performance-hint extension points for the TinkerGQL engine.
* Added GQL support to TinkerGraph with `gql-gremlin`,
+* Added Tiny Gremlin local execution to `gremlin-javascript`: `traversal().with_(graph)` now executes a defined subset of Gremlin steps (navigation, filtering, value extraction, path tracking, range, ordering, and local mutation) against an in-memory `Graph` without a remote server. The `Graph` adjacency index (`outEdges`, `inEdges`) enables O(1) navigation. New `forEach(fn)` and `reduce(fn, seed)` terminal methods on `Traversal` support result processing with native JavaScript functions [...]
[[release-4-0-0-beta-2]]
=== TinkerPop 4.0.0-beta.2 (April 1, 2026)
diff --git a/docs/src/dev/provider/gremlin-semantics.asciidoc b/docs/src/dev/provider/gremlin-semantics.asciidoc
index 8b25408ff2..f551caf060 100644
--- a/docs/src/dev/provider/gremlin-semantics.asciidoc
+++ b/docs/src/dev/provider/gremlin-semantics.asciidoc
@@ -504,6 +504,146 @@ and the corresponding reference documentation. When a step has no entries for `A
the field is still present with `None` as its body. When adding a new step or revising an existing one, follow the
pattern from a neighboring entry rather than introducing a new structure.
+[[e-step]]
+=== E()
+
+To be written...
+
+[[v-step]]
+=== V()
+
+To be written...
+
+[[elementmap-step]]
+=== elementMap()
+
+To be written...
+
+[[has-step]]
+=== has()
+
+To be written...
+
+[[hasid-step]]
+=== hasId()
+
+To be written...
+
+[[haskey-step]]
+=== hasKey()
+
+To be written...
+
+[[haslabel-step]]
+=== hasLabel()
+
+To be written...
+
+[[hasnot-step]]
+=== hasNot()
+
+To be written...
+
+[[hasvalue-step]]
+=== hasValue()
+
+To be written...
+
+[[id-step]]
+=== id()
+
+To be written...
+
+[[ine-step]]
+=== inE()
+
+To be written...
+
+[[inv-step]]
+=== inV()
+
+To be written...
+
+[[in-step]]
+=== in()
+
+To be written...
+
+[[key-step]]
+=== key()
+
+To be written...
+
+[[label-step]]
+=== label()
+
+To be written...
+
+[[limit-step]]
+=== limit()
+
+To be written...
+
+[[order-step]]
+=== order()
+
+To be written...
+
+[[otherv-step]]
+=== otherV()
+
+To be written...
+
+[[out-step]]
+=== out()
+
+To be written...
+
+[[oute-step]]
+=== outE()
+
+To be written...
+
+[[outv-step]]
+=== outV()
+
+To be written...
+
+[[path-step]]
+=== path()
+
+To be written...
+
+[[property-step]]
+=== property()
+
+To be written...
+
+[[range-step]]
+=== range()
+
+To be written...
+
+[[skip-step]]
+=== skip()
+
+To be written...
+
+[[tail-step]]
+=== tail()
+
+To be written...
+
+[[value-step]]
+=== value()
+
+To be written...
+
+[[values-step]]
+=== values()
+
+To be written...
+
[[adde-step]]
=== addE()
diff --git a/docs/src/dev/provider/index.asciidoc b/docs/src/dev/provider/index.asciidoc
index a09c67b915..c55bdc0033 100644
--- a/docs/src/dev/provider/index.asciidoc
+++ b/docs/src/dev/provider/index.asciidoc
@@ -1903,4 +1903,6 @@ in use with the Gremlin Console plugin host. Simply instantiate and return a `R
include::gremlin-semantics.asciidoc[]
+include::tiny-gremlin.asciidoc[]
+
include::policies.asciidoc[]
\ No newline at end of file
diff --git a/docs/src/dev/provider/tiny-gremlin.asciidoc b/docs/src/dev/provider/tiny-gremlin.asciidoc
new file mode 100644
index 0000000000..2dd151598a
--- /dev/null
+++ b/docs/src/dev/provider/tiny-gremlin.asciidoc
@@ -0,0 +1,385 @@
+////
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+////
+
+[[tiny-gremlin]]
+= Tiny Gremlin
+
+image:tiny-gremlin.png[]
+
+== Introduction
+
+*Tiny Gremlin* is a defined subset of the Gremlin traversal language intended for local, in-process graph traversal
+against small, detached `Graph` instances. A common use case would be to take the result of a `subgraph()` call and then
+locally traverse it with Tiny Gremlin. It specifies which steps must be supported, the precise semantics of each step
+within the local execution model, and any deviations from the full Gremlin standard that implementors must be aware of.
+
+=== Motivation
+
+The primary use case for Tiny Gremlin is post-processing of subgraphs received from a remote graph server without
+requiring an additional network round-trip. A user retrieves a subgraph, holds it in memory as a `Graph` object, and
+then navigates and filters it locally using familiar Gremlin syntax. A secondary use case is constructing small graphs
+entirely in process for lightweight analysis or testing, using `addV()`, `addE()`, and `property()`.
+
+Because the graph is small and entirely in memory, features that are expensive or architecture-dependent in a full
+implementation, such as `repeat()`, distributed OLAP steps, aggregation barriers, and strategy-based optimizations,
+are deliberately excluded. The result is a small, self-contained, predictable execution model.
+
+=== Supported Steps
+
+Tiny Gremlin supports exactly the 33 steps listed below, grouped by category. No other step may be used in a Tiny
+Gremlin traversal; doing so results in an error before any graph data is accessed.
+
+[width="100%",options="header"]
+|===
+|Category |Steps
+|Source |`V()`, `E()`
+|Navigation |`out()`, `in()`, `both()`, `outE()`, `inE()`, `bothE()`, `outV()`, `inV()`, `otherV()`
+|Filter |`has()`, `hasId()`, `hasLabel()`, `hasNot()`
+|Value Extraction |`id()`, `label()`, `values()`, `valueMap()`, `elementMap()`, `value()`, `key()`
+|Path |`path()`
+|Range |`limit()`, `range()`, `skip()`, `tail()`
+|Ordering |`order()`
+|Mutation |`addV()`, `addE()`, `property()`
+|Modulators |`from()`, `to()`
+|===
+
+=== Terminal Methods
+
+Tiny Gremlin must support the standard `toList()`, `next()`, and `iterate()` terminal methods, but should offer
+language-specific terminals designed to help with stream processing ergonomics. For example, in Javascript, TinkerPop
+offers:
+
+* `forEach(fn)` — calls `fn` for each result element; returns `Promise<void>`.
+* `reduce(fn, seed)` — folds all results into a single value using `fn` and an initial `seed`; returns `Promise<T>`.
+
+Since Tiny Gremlin lacks steps needed for data transformation and filtering, these added steps will allow users to
+express post-processing functions on the result set.
+
+== Step Semantics
+
+The following sub-sections describe each supported step, its relationship to the full Gremlin Semantics definition,
+and any limitations or behavioral differences that apply specifically to Tiny Gremlin.
+
+=== addE()
+
+*Gremlin Semantics reference:* <<adde-step,addE()>>
+
+Creates a new edge in the local `Graph` between vertices specified by `from()` and `to()` modulators.
+
+*Tiny Gremlin limitations:*
+
+* Only the `addE(String edgeLabel)` form is supported. The `addE(Traversal)` label-traversal form is not supported
+ because the traversal would need to reference prior labeled elements via `as()` and `select()`, neither of which
+ is in the Tiny Gremlin step set.
+* `from()` and `to()` must use the anonymous traversal form (e.g. `from(__.V(id))`). The step-label form
+ (`from("stepLabel")`) is not supported because `as()` is not in the Tiny Gremlin step set. The GLV convenience
+ sugar `from_(vertexObject)` does not work because the GLV converts the `Vertex` to its raw ID before
+ serialization, producing `from(id)` which is not a valid grammar form and results in a parse error.
+* The vertex referenced by `from()` or `to()` must exist in the local `Graph` at execution time. If the ID is not
+ found, an error is raised. No vertex is created implicitly.
+
+=== addV()
+
+*Gremlin Semantics reference:* <<addv-step,addV()>>
+
+Creates a new vertex in the local `Graph`.
+
+*Tiny Gremlin limitations:*
+
+* No cardinality-aware property form (`property(Cardinality, key, value)`) is supported; only the simple
+ `property(key, value)` form applies to vertices created by `addV()`.
+* Changes are local only; the remote graph is not modified.
+
+=== both()
+
+*Gremlin Semantics reference:* <<both-step,both()>>
+
+Maps a vertex to its adjacent vertices, traversing both incoming and outgoing edges.
+
+=== bothE()
+
+*Gremlin Semantics reference:* <<bothe-step,bothE()>>
+
+Maps a vertex to its incident edges, both incoming and outgoing.
+
+=== E()
+
+*Gremlin Semantics reference:* <<e-step,E()>>
+
+Produces edges from the local graph. When called with no arguments, all edges are produced. When called with one or
+more IDs, only matching edges are produced. A single list argument is also accepted.
+
+=== elementMap()
+
+*Gremlin Semantics reference:* <<elementmap-step,elementMap()>>
+
+Maps an element to a `Map` containing its `T.id`, `T.label`, and property key-value pairs. For edges,
+`Direction.OUT` and `Direction.IN` keys are included as stub maps containing `T.id` and `T.label` of the adjacent
+vertices.
+
+*Tiny Gremlin limitations:*
+
+* `by()` modulation is not supported.
+
+=== has()
+
+*Gremlin Semantics reference:* <<has-step,has()>>
+
+Filters elements by property existence, equality, or predicate.
+
+Supported forms:
+
+* `has(key)` — element has the property.
+* `has(key, value)` — element has the property with the given value.
+* `has(key, P)` — element has the property satisfying the predicate.
+* `has(label, key, value)` — element has the given label and the property with the given value.
+* `has(label, key, P)` — element has the given label and the property satisfying the predicate.
+
+Supported `P` predicates: `eq`, `neq`, `lt`, `lte`, `gt`, `gte`, `between`, `inside`, `outside`, `within`,
+`without`, `not`. Supported `TextP` predicates: `containing`, `notContaining`, `startingWith`, `notStartingWith`,
+`endingWith`, `notEndingWith`, `regex`, `notRegex`.
+
+The ordering predicates (`lt`, `lte`, `gt`, `gte`, `between`, `inside`, `outside`) follow the
+<<gremlin-semantics-concepts,Gremlin Comparability semantics>>: cross-type comparisons and comparisons involving
+`NaN` return `false` (the comparability ERROR result).
+
+*Tiny Gremlin limitations:*
+
+* Only `T.id` and `T.label` are supported as key tokens. `T.key` and `T.value` are not supported because
+ `properties()` is not in the Tiny Gremlin step set and there is no other way to produce `Property` traversers.
+
+=== hasId()
+
+*Gremlin Semantics reference:* <<hasid-step,hasId()>>
+
+Filters elements by their ID.
+
+*Tiny Gremlin limitations:*
+
+* `hasId([1, 2, 3])` — a single list argument — is unrolled and treated as `hasId(1, 2, 3)`, consistent with
+ `V([1,2,3])` semantics.
+* When more than one argument is provided, each argument is treated as a distinct ID value. A list in that position
+ is compared as-is and will not match any scalar ID (e.g. `hasId(1, [2, 3])` matches only id `1`). This
+ intentionally differs from the standard `hasId()` implementation which unrolls lists in all positions (see
+ TINKERPOP-2863).
+
+=== hasKey()
+
+*Gremlin Semantics reference:* <<haskey-step,hasKey()>>
+
+Filters `Property` traversers by key. Operates on the output of `properties()`.
+
+=== hasLabel()
+
+*Gremlin Semantics reference:* <<haslabel-step,hasLabel()>>
+
+Filters elements by label.
+
+=== hasNot()
+
+*Gremlin Semantics reference:* <<hasnot-step,hasNot()>>
+
+Filters elements by the absence of a property.
+
+=== hasValue()
+
+*Gremlin Semantics reference:* <<hasvalue-step,hasValue()>>
+
+Filters `Property` traversers by value. Operates on the output of `properties()`.
+
+=== id()
+
+*Gremlin Semantics reference:* <<id-step,id()>>
+
+Maps an element to its ID.
+
+=== in()
+
+*Gremlin Semantics reference:* <<in-step,in()>>
+
+Maps a vertex to its adjacent incoming vertices.
+
+=== inE()
+
+*Gremlin Semantics reference:* <<ine-step,inE()>>
+
+Maps a vertex to its incoming edges.
+
+=== inV()
+
+*Gremlin Semantics reference:* <<inv-step,inV()>>
+
+Maps an edge to its incoming vertex (the head of the arrow).
+
+=== key()
+
+*Gremlin Semantics reference:* <<key-step,key()>>
+
+Maps a `Property` traverser to its key.
+
+=== label()
+
+*Gremlin Semantics reference:* <<label-step,label()>>
+
+Maps an element to its label.
+
+=== limit()
+
+*Gremlin Semantics reference:* <<limit-step,limit()>>
+
+Truncates the result stream to at most `n` elements.
+
+*Tiny Gremlin limitations:*
+
+* The `Scope` form is not supported and will raise an error.
+
+=== order()
+
+*Gremlin Semantics reference:* <<order-step,order()>>
+
+Sorts all elements.
+
+Supports a single `by()` modulator with the following forms:
+
+* `by(String key)` — sort by property value ascending.
+* `by(String key, Order)` — sort by property value with explicit direction (`Order.asc` or `Order.desc`).
+* `by(T.id)` / `by(T.id, Order)` and `by(T.label)` / `by(T.label, Order)` — sort by the element's ID or label
+ with optional direction.
+* `by(Order)` — sort by natural element value with explicit direction.
+* `by()` — natural ascending sort (same as omitting `by()`).
+
+*Tiny Gremlin limitations:*
+
+* Only a single `by()` modulator is supported per `order()`.
+* Ordering follows the <<gremlin-semantics-orderability,Gremlin Orderability semantics>>, providing a total order
+ across all types: `null` < `Boolean` < `Number` < `Date` < `String` < `Vertex` < `Edge` < `VertexProperty`
+ < `Property` < `Path` < `Set` < `List` < `Map` < `Unknown`. `NaN` is ordered after `+Infinity` within the
+ numeric type space.
+* The `Scope` form (`order(Scope)`) is not supported and will raise an error.
+* `by(Traversal)`, `by(Traversal, Order)`, and `by(Function)` are not supported and will raise an error.
+
+=== otherV()
+
+*Gremlin Semantics reference:* <<otherv-step,otherV()>>
+
+Maps an edge to the vertex that was not the vertex traversed from.
+
+=== out()
+
+*Gremlin Semantics reference:* <<out-step,out()>>
+
+Maps a vertex to its adjacent outgoing vertices.
+
+=== outE()
+
+*Gremlin Semantics reference:* <<oute-step,outE()>>
+
+Maps a vertex to its outgoing edges.
+
+=== outV()
+
+*Gremlin Semantics reference:* <<outv-step,outV()>>
+
+Maps an edge to its outgoing vertex (the tail of the arrow).
+
+=== path()
+
+*Gremlin Semantics reference:* <<path-step,path()>>
+
+Maps the current traverser to the `Path` of elements it has visited.
+
+*Tiny Gremlin limitations:*
+
+* `by()` modulation is not supported. Path objects always contain the raw elements visited.
+* `from()` and `to()` selection within a path is not supported because `as()` step labels are excluded from Tiny
+ Gremlin.
+
+=== property()
+
+*Gremlin Semantics reference:* <<property-step,property()>>
+
+Sets a property on the current element.
+
+*Tiny Gremlin limitations:*
+
+* Only the `property(key, value)` two-argument form is supported. The cardinality form
+ `property(Cardinality, key, value)` and the map form `property(map)` are not supported.
+* For vertices, an existing property with the same key is replaced (single cardinality semantics regardless of graph
+ configuration).
+
+=== range()
+
+*Gremlin Semantics reference:* <<range-step,range()>>
+
+Emits elements at positions `[low, high)` in the stream, zero-indexed.
+
+*Tiny Gremlin limitations:*
+
+* The `Scope` form is not supported and will raise an error.
+
+=== skip()
+
+*Gremlin Semantics reference:* <<skip-step,skip()>>
+
+Discards the first `n` elements of the stream.
+
+*Tiny Gremlin limitations:*
+
+* The `Scope` form is not supported and will raise an error.
+
+=== tail()
+
+*Gremlin Semantics reference:* <<tail-step,tail()>>
+
+Emits the last `n` elements of the stream. This is a barrier step.
+
+*Tiny Gremlin limitations:*
+
+* The `Scope` form is not supported and will raise an error.
+
+=== V()
+
+*Gremlin Semantics reference:* <<v-step,V()>>
+
+Produces vertices from the local graph. When called with no arguments, all vertices are produced. When called with one
+or more IDs, only matching vertices are produced. A single list argument is also accepted.
+
+=== value()
+
+*Gremlin Semantics reference:* <<value-step,value()>>
+
+Maps a `Property` traverser to its value.
+
+=== valueMap()
+
+*Gremlin Semantics reference:* <<valueMap-step,valueMap()>>
+
+Maps an element to a `Map` of property keys to lists of property values.
+
+*Tiny Gremlin limitations:*
+
+* `by()` modulation is not supported.
+* `with(WithOptions.tokens)` configuration is not supported; use `valueMap(true)` instead.
+
+=== values()
+
+*Gremlin Semantics reference:* <<values-step,values()>>
+
+Maps an element to the values of its properties.
+
diff --git a/docs/src/reference/the-traversal.asciidoc b/docs/src/reference/the-traversal.asciidoc
index 5db65c45db..8465103f13 100644
--- a/docs/src/reference/the-traversal.asciidoc
+++ b/docs/src/reference/the-traversal.asciidoc
@@ -638,7 +638,8 @@ g.addE('knows').from(__.V(1)).to(__.constant(6)) <7>
*Additional References*
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#addE(java.lang.String)++[`addE(String)`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#addE(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`addE(Traversal)`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#addE(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`addE(Traversal)`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
[[addvertex-step]]
=== AddV Step
@@ -659,7 +660,8 @@ g.V().has('name','nothing').bothE()
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#addV()++[`addV()`],
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#addV(java.lang.String)++[`addV(String)`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#addV(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`addV(Traversal)`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#addV(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`addV(Traversal)`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
[[aggregate-step]]
=== [[store-step]]Aggregate Step
@@ -1808,7 +1810,8 @@ g.inject(1).coalesce(E().hasLabel("knows"), addE("knows").from(V().has("name","j
*Additional References*
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#E(java.lang.Object...)++[`E(Object...)`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#E(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`E(Traversal)`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#E(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`E(Traversal)`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
[[element-step]]
=== Element Step
@@ -1859,7 +1862,8 @@ as the `id` is the only available data to the star graph.
*Additional References*
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#elementMap(java.lang.String...)++[`elementMap(String...)`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#elementMap(java.lang.String...)++[`elementMap(String...)`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
[[emit-step]]
=== Emit Step
@@ -2059,7 +2063,8 @@ The term `from` is a reserved word in Python, and therefore must be referred to
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#from(java.lang.String)++[`from(String)`],
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#from(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`from(Traversal)`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#from(org.apache.tinkerpop.gremlin.structure.Vertex)++[`from(Vertex)`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#from(org.apache.tinkerpop.gremlin.structure.Vertex)++[`from(Vertex)`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
[[group-step]]
=== Group Step
@@ -2229,7 +2234,8 @@ link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gre
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#hasValue(org.apache.tinkerpop.gremlin.process.traversal.P)++[`hasValue(P)`],
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/P.html++[`P`],
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/TextP.html++[`TextP`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/structure/T.html++[`T`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/structure/T.html++[`T`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
[[id-step]]
=== Id Step
@@ -2246,7 +2252,8 @@ g.V(1).properties().id()
*Additional References*
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#id()++[`id()`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#id()++[`id()`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
[[identity-step]]
=== Identity Step
@@ -2588,7 +2595,8 @@ g.V(1).properties().properties().key()
*Additional References*
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#key()++[`key()`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#key()++[`key()`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
[[label-step]]
=== Label Step
@@ -2604,7 +2612,8 @@ g.V(1).properties().label()
*Additional References*
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#label()++[`label()`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#label()++[`label()`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
[[length-step]]
=== Length Step
@@ -2656,8 +2665,9 @@ g.V().valueMap().select('location').limit(local, 1).unfold() <4>
*Additional References*
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#limit(long)++[`limit(long)`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#limit(org.apache.tinkerpop.gremlin.process.traversal.Scope,long)++[`limit(Scope,long)`]
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Scope.html++[`Scope`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#limit(org.apache.tinkerpop.gremlin.process.traversal.Scope,long)++[`limit(Scope,long)`],
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Scope.html++[`Scope`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
[[local-step]]
=== Local Step
@@ -3810,7 +3820,8 @@ order. Those tokens were deprecated and eventually removed in 3.5.0.
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#order()++[`order()`],
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#order(org.apache.tinkerpop.gremlin.process.traversal.Scope)++[`order(Scope)`],
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Scope.html++[`Scope`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Order.html++[`Order`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Order.html++[`Order`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
[[pagerank-step]]
=== PageRank Step
@@ -3951,7 +3962,8 @@ path.d == path.e
*Additional References*
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#path()++[`path()`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#path()++[`path()`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
[[peerpressure-step]]
=== PeerPressure Step
@@ -4223,7 +4235,8 @@ mutating steps (`IllegalArgumentException`) and must produce a `Map` (`IllegalSt
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...)`],
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#property(org.apache.tinkerpop.gremlin.structure.VertexProperty.Cardinality,java.lang.Object,java.lang.Object,java.lang.Object...)++[`property(Cardinality, Object, Object, Object...)`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/structure/VertexProperty.Cardinality.html++[`Cardinality`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/structure/VertexProperty.Cardinality.html++[`Cardinality`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
[[propertymap-step]]
=== PropertyMap Step
@@ -4286,7 +4299,8 @@ g.V().valueMap().select('location').range(local, 1, 2).unfold() <3>
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#range(long,long)++[`range(long,long)`],
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#range(org.apache.tinkerpop.gremlin.process.traversal.Scope,long,long)++[`range(Scope,long,long)`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Scope.html++[`Scope`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Scope.html++[`Scope`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
[[read-step]]
=== Read Step
@@ -4922,7 +4936,8 @@ g.V().hasLabel('person').filter(outE('created')).as('p'). <1>
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#skip(long)++[`skip(long)`],
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#skip(org.apache.tinkerpop.gremlin.process.traversal.Scope,long)++[`skip(Scope,long)`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Scope.html++[`Scope`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Scope.html++[`Scope`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
[[split-step]]
=== Split Step
@@ -5141,9 +5156,10 @@ g.V().elementMap().tail(local) <6>
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#tail()++[`tail()`],
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#tail(long)++[`tail(long)`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#tail(org.apache.tinkerpop.gremlin.process.traversal.Scope)++[`tail(Scope)`]
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#tail(org.apache.tinkerpop.gremlin.process.traversal.Scope,long)++[`tail(Scope,long)`]
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Scope.html++[`Scope`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#tail(org.apache.tinkerpop.gremlin.process.traversal.Scope)++[`tail(Scope)`],
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#tail(org.apache.tinkerpop.gremlin.process.traversal.Scope,long)++[`tail(Scope,long)`],
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Scope.html++[`Scope`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
[[timelimit-step]]
=== TimeLimit Step
@@ -5205,7 +5221,8 @@ link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gre
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#to(org.apache.tinkerpop.gremlin.structure.Vertex)++[`to(Vertex)`],
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#toE(org.apache.tinkerpop.gremlin.structure.Direction,java.lang.String...)++[`toE(Direction,String)`],
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#toV(org.apache.tinkerpop.gremlin.structure.Direction)++[`toV(Direction)`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/structure/Direction.html++[`Direction`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/structure/Direction.html++[`Direction`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
[[toLower-step]]
=== ToLower Step
@@ -5430,7 +5447,8 @@ g.V().has('name', within('marko', 'vadas', 'josh')).as('person').
*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...)`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#V(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`V(Traversal)`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#V(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`V(Traversal)`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
[[value-step]]
=== Value Step
@@ -5445,7 +5463,8 @@ g.V(1).properties().properties().value()
*Additional References*
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#value()++[`value()`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#value()++[`value()`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
[[valuemap-step]]
=== ValueMap Step
@@ -5496,7 +5515,8 @@ g.V().hasLabel('person').properties('location').valueMap().with(WithOptions.toke
*Additional References*
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#valueMap(java.lang.String...)++[`valueMap(String...)`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#valueMap(java.lang.String...)++[`valueMap(String...)`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
[[values-step]]
=== Values Step
@@ -5512,7 +5532,8 @@ g.V(1).properties('location').values()
*Additional References*
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#values(java.lang.String...)++[`values(String...)`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#values(java.lang.String...)++[`values(String...)`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
[[vertex-steps]]
=== Vertex Steps
@@ -5581,7 +5602,8 @@ link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gre
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#otherV()++[`otherV()`],
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#out(java.lang.String...)++[`out(String...)`],
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#outE(java.lang.String...)++[`outE(String...)`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#outV()++[`outV()`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#outV()++[`outV()`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
[[where-step]]
=== Where Step
@@ -6839,3 +6861,65 @@ t = g.V().has_label('person')
script = t.gremlin_lang.get_gremlin()
# Returns: "g.V().hasLabel('person')"
----
+
+[[tiny-gremlin-traversal]]
+== Tiny Gremlin
+
+*Tiny Gremlin* is a defined subset of the Gremlin traversal language that executes locally and in-process against a
+small, detached `Graph` instance. The most common use case is to call `subgraph()` on a remote graph, receive a `Graph`
+object, and then navigate and filter it locally using familiar Gremlin syntax without an additional network round-trip.
+A secondary use case is constructing small graphs entirely in-process using `addV()`, `addE()`, and `property()`.
+
+Full details on semantics, supported forms, and limitations are covered in the
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics] section of the
+Provider Documentation. Tiny Gremlin is currently only supported in `gremlin-javascript`.
+
+=== Supported Steps
+
+Tiny Gremlin supports exactly 33 steps. No other step may be used in a Tiny Gremlin traversal; attempting to do so
+results in an error before any graph data is accessed.
+
+[width="100%",options="header"]
+|===
+|Category |Steps
+|Source |<<v-step,`V()`>>, <<e-step,`E()`>>
+|Navigation |<<vertex-steps,`out()`>>, <<vertex-steps,`in()`>>, <<vertex-steps,`both()`>>, <<vertex-steps,`outE()`>>, <<vertex-steps,`inE()`>>, <<vertex-steps,`bothE()`>>, <<vertex-steps,`outV()`>>, <<vertex-steps,`inV()`>>, <<vertex-steps,`otherV()`>>
+|Filter |<<has-step,`has()`>>, <<has-step,`hasId()`>>, <<has-step,`hasLabel()`>>, <<has-step,`hasNot()`>>
+|Value Extraction |<<id-step,`id()`>>, <<label-step,`label()`>>, <<values-step,`values()`>>, <<valuemap-step,`valueMap()`>>, <<elementmap-step,`elementMap()`>>, <<value-step,`value()`>>, <<key-step,`key()`>>
+|Path |<<path-step,`path()`>>
+|Range |<<limit-step,`limit()`>>, <<range-step,`range()`>>, <<skip-step,`skip()`>>, <<tail-step,`tail()`>>
+|Ordering |<<order-step,`order()`>>
+|Mutation |<<addvertex-step,`addV()`>>, <<addedge-step,`addE()`>>, <<property-step,`property()`>>
+|Modulators |<<from-step,`from()`>>, <<to-step,`to()`>>
+|===
+
+=== Language Support
+
+Tiny Gremlin is currently only implemented in `gremlin-javascript`. The `with_(graph)` API accepts a local `Graph`
+object in place of a remote connection, transparently enabling local traversal using the same Gremlin API:
+
+[source,javascript]
+----
+const { graph: { Graph }, process: { anonymousTraversal: { traversal } } } = require('gremlin');
+
+const graph = new Graph();
+const g = traversal().with_(graph);
+
+// Build a small graph and query it locally
+const names = await g.addV('person').property('name', 'alice')
+ .V().values('name').toList();
+// Returns: ['alice']
+----
+
+The primary use case of processing a subgraph received from a remote server requires no code changes other than
+passing the returned `Graph` object to `with_()`:
+
+[source,javascript]
+----
+// Retrieve a subgraph from a remote server
+const subgraph = await gRemote.V().hasLabel('person').subgraph('sg').cap('sg').next();
+
+// Query it locally with Tiny Gremlin — no additional network calls
+const g = traversal().with_(subgraph);
+const paths = await g.V().out('knows').in('knows').path().toList();
+----
diff --git a/docs/static/images/tiny-gremlin.png b/docs/static/images/tiny-gremlin.png
new file mode 100644
index 0000000000..67a53f8984
Binary files /dev/null and b/docs/static/images/tiny-gremlin.png differ
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index e6238de9a6..0d610f47d9 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -563,6 +563,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
{"g_E_hasXlabel_nullX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.E().Has(T.Label, (object) null)}},
{"g_V_properties_hasLabelXnullX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Properties<object>().HasLabel((string) null)}},
{"g_V_hasNotXageX_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasNot("age").Values<object>("name")}},
+ {"g_V_hasNotXlangX_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasNot("lang").Values<object>("name")}},
{"g_V_properties_hasValueXnullX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Properties<object>().HasValue((object) null)}},
{"g_V_properties_hasValueXnull_nullX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Properties<object>().HasValue(null, null)}},
{"g_V_properties_hasValueXnull_joshX_value", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Properties<object>().HasValue(null, "josh").Value<object>()}},
@@ -1645,6 +1646,9 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
{"g_VX1X_elementMap_orderXlocalX_byXkeys_descXunfold", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).ElementMap<object>().Order(Scope.Local).By(Column.Keys, Order.Desc).Unfold<object>()}},
{"g_VX1X_elementMap_orderXlocalX_byXkeys_ascXunfold", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).ElementMap<object>().Order(Scope.Local).By(Column.Keys, Order.Asc).Unfold<object>()}},
{"g_VX1X_valuesXageX_orderXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).Values<object>("age").Order(Scope.Local)}},
+ {"g_V_order_byXTidX_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Order().By(T.Id).Values<object>("name")}},
+ {"g_V_order_byXTid_descX_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Order().By(T.Id, Order.Desc).Values<object>("name")}},
+ {"g_V_order_byXTlabelX_label", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Order().By(T.Label).Label()}},
{"g_V_pageRank_hasXpageRankX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().PageRank().Has("gremlin.pageRankVertexProgram.pageRank")}},
{"g_V_outXcreatedX_pageRank_withXedges_bothEX_withXpropertyName_projectRankX_withXtimes_0X_valueMapXname_projectRankX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Out("created").PageRank().With("~tinkerpop.pageRank.edges", __.BothE()).With("~tinkerpop.pageRank.propertyName", "projectRank").With("~tinkerpop.pageRank.times", 0).ValueMap<object, object>("name", "projectRank")}},
{"g_V_pageRank_order_byXpageRank_descX_byXnameX_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().PageRank().Order().By("gremlin.pageRankVertexProgram.pageRank", Order.Desc).By("name").Values<object>("name")}},
@@ -1665,6 +1669,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
{"g_withStrategiesXProductiveByStrategyX_VX1X_out_path_byXageX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.WithStrategies(new ProductiveByStrategy()).V(p["vid1"]).Out().Path().By("age")}},
{"g_injectX1_null_nullX_path", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(1, null, null).Path()}},
{"g_injectX1_null_nullX_path_dedup", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(1, null, null).Path().Dedup()}},
+ {"g_VX1X_out_out_path", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).Out().Out().Path()}},
{"g_V_peerPressure_hasXclusterX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().PeerPressure().Has("gremlin.peerPressureVertexProgram.cluster")}},
{"g_V_peerPressure_withXpropertyName_clusterX_withXedges_outEXknowsXX_pageRankX1X_byXrankX_withXedges_outEXknowsX_withXtimes_2X_group_byXclusterX_byXrank_sumX_limitX100X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().PeerPressure().With("~tinkerpop.peerPressure.propertyName", "cluster").With("~tinkerpop.peerPressure.edges", __.OutE("knows")).PageRank(1.0).With("~tinkerpop.pageRank.propertyName", "rank").With("~tinkerpop.pageR [...]
{"g_V_hasXname_rippleX_inXcreatedX_peerPressure_withXedges_outEX_withyXpropertyName_clusterX_repeatXunionXidentity__bothX_timesX2X_dedup_valueMapXname_clusterX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("name", "ripple").In("created").PeerPressure().With("~tinkerpop.peerPressure.edges", __.OutE()).With("~tinkerpop.peerPressure.propertyName", "cluster").Repeat(__.Union<object>(__.Identity(), __.Both())).Times(2).Dedup( [...]
diff --git a/gremlin-go/driver/cucumber/gremlin.go b/gremlin-go/driver/cucumber/gremlin.go
index 82f5ff777b..70822b8eba 100644
--- a/gremlin-go/driver/cucumber/gremlin.go
+++ b/gremlin-go/driver/cucumber/gremlin.go
@@ -533,6 +533,7 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[
"g_E_hasXlabel_nullX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.E().Has(gremlingo.T.Label, nil)}},
"g_V_properties_hasLabelXnullX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Properties().HasLabel(nil)}},
"g_V_hasNotXageX_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasNot("age").Values("name")}},
+ "g_V_hasNotXlangX_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasNot("lang").Values("name")}},
"g_V_properties_hasValueXnullX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Properties().HasValue(nil)}},
"g_V_properties_hasValueXnull_nullX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Properties().HasValue(nil, nil)}},
"g_V_properties_hasValueXnull_joshX_value": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Properties().HasValue(nil, "josh").Value()}},
@@ -1615,6 +1616,9 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[
"g_VX1X_elementMap_orderXlocalX_byXkeys_descXunfold": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid1"]).ElementMap().Order(gremlingo.Scope.Local).By(gremlingo.Column.Keys, gremlingo.Order.Desc).Unfold()}},
"g_VX1X_elementMap_orderXlocalX_byXkeys_ascXunfold": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid1"]).ElementMap().Order(gremlingo.Scope.Local).By(gremlingo.Column.Keys, gremlingo.Order.Asc).Unfold()}},
"g_VX1X_valuesXageX_orderXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid1"]).Values("age").Order(gremlingo.Scope.Local)}},
+ "g_V_order_byXTidX_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Order().By(gremlingo.T.Id).Values("name")}},
+ "g_V_order_byXTid_descX_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Order().By(gremlingo.T.Id, gremlingo.Order.Desc).Values("name")}},
+ "g_V_order_byXTlabelX_label": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Order().By(gremlingo.T.Label).Label()}},
"g_V_pageRank_hasXpageRankX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().PageRank().Has("gremlin.pageRankVertexProgram.pageRank")}},
"g_V_outXcreatedX_pageRank_withXedges_bothEX_withXpropertyName_projectRankX_withXtimes_0X_valueMapXname_projectRankX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Out("created").PageRank().With("~tinkerpop.pageRank.edges", gremlingo.T__.BothE()).With("~tinkerpop.pageRank.propertyName", "projectRank").With("~tinkerpop.pageRank.times", 0).ValueMap("name", "projectRank")}},
"g_V_pageRank_order_byXpageRank_descX_byXnameX_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().PageRank().Order().By("gremlin.pageRankVertexProgram.pageRank", gremlingo.Order.Desc).By("name").Values("name")}},
@@ -1635,6 +1639,7 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[
"g_withStrategiesXProductiveByStrategyX_VX1X_out_path_byXageX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.WithStrategies(gremlingo.ProductiveByStrategy()).V(p["vid1"]).Out().Path().By("age")}},
"g_injectX1_null_nullX_path": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(1, nil, nil).Path()}},
"g_injectX1_null_nullX_path_dedup": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(1, nil, nil).Path().Dedup()}},
+ "g_VX1X_out_out_path": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid1"]).Out().Out().Path()}},
"g_V_peerPressure_hasXclusterX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().PeerPressure().Has("gremlin.peerPressureVertexProgram.cluster")}},
"g_V_peerPressure_withXpropertyName_clusterX_withXedges_outEXknowsXX_pageRankX1X_byXrankX_withXedges_outEXknowsX_withXtimes_2X_group_byXclusterX_byXrank_sumX_limitX100X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().PeerPressure().With("~tinkerpop.peerPressure.propertyName", "cluster").With("~tinkerpop.peerPressure.edges", gremlingo.T__.OutE("knows")).PageRank(1.0).With("~tinkerpop.pageRank.propertyName", "rank").With("~ti [...]
"g_V_hasXname_rippleX_inXcreatedX_peerPressure_withXedges_outEX_withyXpropertyName_clusterX_repeatXunionXidentity__bothX_timesX2X_dedup_valueMapXname_clusterX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("name", "ripple").In("created").PeerPressure().With("~tinkerpop.peerPressure.edges", gremlingo.T__.OutE()).With("~tinkerpop.peerPressure.propertyName", "cluster").Repeat(gremlingo.T__.Union(gremlingo.T__.Identity(), [...]
diff --git a/gremlin-js/gremlin-javascript/lib/driver/local-graph-connection.ts b/gremlin-js/gremlin-javascript/lib/driver/local-graph-connection.ts
new file mode 100644
index 0000000000..0828caf8f9
--- /dev/null
+++ b/gremlin-js/gremlin-javascript/lib/driver/local-graph-connection.ts
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {
+ ATNSimulator, BaseErrorListener, CharStream, CommonTokenStream,
+ PredictionMode, Recognizer, RecognitionException,
+} from 'antlr4ng';
+import { GremlinLexer } from '../language/grammar/GremlinLexer.js';
+import { GremlinParser } from '../language/grammar/GremlinParser.js';
+import { ExecuteVisitor } from '../language/executor/ExecuteVisitor.js';
+import { LocalExecutor } from '../process/local/LocalExecutor.js';
+import { RemoteConnection, RemoteTraversal } from './remote-connection.js';
+import { Graph } from '../structure/graph.js';
+import type GremlinLang from '../process/gremlin-lang.js';
+
+class ThrowingErrorListener extends BaseErrorListener {
+ override syntaxError(
+ _recognizer: Recognizer<ATNSimulator>,
+ _offendingSymbol: unknown,
+ line: number,
+ column: number,
+ msg: string,
+ _e: RecognitionException | null,
+ ): void {
+ throw new Error(`Tiny Gremlin parse error at ${line}:${column} — ${msg}`);
+ }
+}
+
+const errorListener = new ThrowingErrorListener();
+
+/**
+ * A RemoteConnection implementation that executes traversals locally against an
+ * in-memory Graph using the Tiny Gremlin subset. No network is involved.
+ *
+ * Usage:
+ * const g = anon.traversal().with_(myGraph);
+ * // or equivalently:
+ * const g = anon.traversal().with_(new LocalGraphConnection(myGraph));
+ */
+export class LocalGraphConnection extends RemoteConnection {
+ private readonly graph: Graph;
+ private readonly executor: LocalExecutor;
+
+ constructor(graph: Graph) {
+ super('local://');
+ this.graph = graph;
+ this.executor = new LocalExecutor();
+ }
+
+ override async open(): Promise<void> {
+ // no-op: no connection to open
+ }
+
+ override get isOpen(): boolean {
+ return true;
+ }
+
+ override async submit(gremlinLang: GremlinLang): Promise<RemoteTraversal> {
+ const query = gremlinLang.getGremlin();
+ const pipeline = this.parse(query);
+ const generator = this.executor.execute(pipeline, this.graph);
+ return new RemoteTraversal(generator);
+ }
+
+ override async commit(): Promise<void> {
+ // no-op: local graph mutations are in-memory only
+ }
+
+ override async rollback(): Promise<void> {
+ // no-op
+ }
+
+ override async close(): Promise<void> {
+ // no-op
+ }
+
+ private parse(query: string) {
+ const chars = CharStream.fromString(query);
+ const lexer = new GremlinLexer(chars);
+ lexer.removeErrorListeners();
+ lexer.addErrorListener(errorListener);
+
+ const tokens = new CommonTokenStream(lexer);
+ const parser = new GremlinParser(tokens);
+ parser.interpreter.predictionMode = PredictionMode.SLL;
+ parser.removeErrorListeners();
+ parser.addErrorListener(errorListener);
+
+ let queryCtx: ReturnType<typeof parser.queryList>;
+ try {
+ queryCtx = parser.queryList();
+ } catch {
+ tokens.seek(0);
+ lexer.reset();
+ parser.reset();
+ parser.interpreter.predictionMode = PredictionMode.LL;
+ queryCtx = parser.queryList();
+ }
+
+ const visitor = new ExecuteVisitor();
+ visitor.visit(queryCtx);
+ return visitor.getPipeline();
+ }
+}
diff --git a/gremlin-js/gremlin-javascript/lib/index.ts b/gremlin-js/gremlin-javascript/lib/index.ts
index a2443b8d1d..3498fa59a3 100644
--- a/gremlin-js/gremlin-javascript/lib/index.ts
+++ b/gremlin-js/gremlin-javascript/lib/index.ts
@@ -36,6 +36,8 @@ import ResultSet from './driver/result-set.js';
import { HttpRequest } from './driver/http-request.js';
import { basic, sigv4 } from './driver/auth.js';
import AnonymousTraversalSource from './process/anonymous-traversal.js';
+import { LocalGraphConnection } from './driver/local-graph-connection.js';
+import { LocalExecutionError } from './process/local/LocalExecutionError.js';
export const driver = {
RemoteConnection: rc.RemoteConnection,
@@ -43,6 +45,7 @@ export const driver = {
RemoteTraversal: rc.RemoteTraversal,
ResponseError,
DriverRemoteConnection,
+ LocalGraphConnection,
Client,
ResultSet,
HttpRequest,
@@ -50,6 +53,7 @@ export const driver = {
basic,
sigv4,
},
+ LocalExecutionError,
};
export const process = {
diff --git a/gremlin-js/gremlin-javascript/lib/language/executor/ExecuteVisitor.ts b/gremlin-js/gremlin-javascript/lib/language/executor/ExecuteVisitor.ts
new file mode 100644
index 0000000000..99957284f2
--- /dev/null
+++ b/gremlin-js/gremlin-javascript/lib/language/executor/ExecuteVisitor.ts
@@ -0,0 +1,708 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { P, TextP } from '../../process/traversal.js';
+import type { Arg, Pipeline, StepDescriptor } from '../../process/local/types.js';
+
+/**
+ * Converts an ANTLR parse tree produced by GremlinParser into a StepDescriptor[]
+ * (Pipeline) suitable for execution by LocalExecutor.
+ *
+ * Uses the same className→visitXxx dispatch mechanism as TranslateVisitor but
+ * accumulates typed step descriptors rather than output strings.
+ */
+export class ExecuteVisitor {
+ private readonly steps: StepDescriptor[] = [];
+
+ getPipeline(): Pipeline {
+ return this.steps;
+ }
+
+ // ── Dispatch ──────────────────────────────────────────────────────────────
+
+ visit(tree: any): void {
+ if (tree == null) return;
+ const className: string = tree?.constructor?.name ?? '';
+ if (className.endsWith('Context')) {
+ const methodName = 'visit' + className.slice(0, -'Context'.length);
+ const method = (this as any)[methodName];
+ if (typeof method === 'function') {
+ method.call(this, tree);
+ return;
+ }
+ }
+ // Fall back to visiting children for unhandled intermediate nodes
+ this.visitChildren(tree);
+ }
+
+ private visitChildren(node: any): void {
+ const n = node.getChildCount?.() ?? 0;
+ for (let i = 0; i < n; i++) {
+ this.visit(node.getChild(i));
+ }
+ }
+
+ // ── Top-level structure ───────────────────────────────────────────────────
+
+ visitQueryList(ctx: any): void {
+ // queryList → query → rootTraversal | terminatedTraversal
+ const n = ctx.getChildCount();
+ for (let i = 0; i < n; i++) {
+ const child = ctx.getChild(i);
+ const name = child?.constructor?.name ?? '';
+ if (name === 'QueryContext') {
+ this.visit(child);
+ return;
+ }
+ }
+ this.visitChildren(ctx);
+ }
+
+ visitQuery(ctx: any): void {
+ this.visitChildren(ctx);
+ }
+
+ visitRootTraversal(ctx: any): void {
+ // traversalSource DOT traversalSourceSpawnMethod (DOT chainedTraversal)?
+ this.visit(ctx.traversalSourceSpawnMethod());
+ const chained = ctx.chainedTraversal?.();
+ if (chained) this.visitChainedTraversal(chained);
+ }
+
+ visitTerminatedTraversal(ctx: any): void {
+ this.visit(ctx.rootTraversal?.() ?? ctx.getChild(0));
+ }
+
+ visitChainedTraversal(ctx: any): void {
+ // Grammar is left-recursive: outermost node holds the LAST step, innermost holds the FIRST.
+ // Recurse into the left subtree first, then append the current (rightmost) step.
+ const next = ctx.chainedTraversal?.();
+ if (next) this.visitChainedTraversal(next);
+ this.visit(ctx.traversalMethod());
+ }
+
+ visitTraversalSourceSpawnMethod(ctx: any): void {
+ this.visitChildren(ctx);
+ }
+
+ visitTraversalMethod(ctx: any): void {
+ // Find the specific TraversalMethod_* child and dispatch to it.
+ // If no specific visitor exists, push a placeholder so validation can reject it.
+ const n = ctx.getChildCount?.() ?? 0;
+ for (let i = 0; i < n; i++) {
+ const child = ctx.getChild(i);
+ const name: string = child?.constructor?.name ?? '';
+ if (name.startsWith('TraversalMethod_') && name.endsWith('Context')) {
+ const visitorName = 'visit' + name.slice(0, -'Context'.length);
+ if (typeof (this as any)[visitorName] === 'function') {
+ (this as any)[visitorName](child);
+ } else {
+ // Unknown step — extract name (e.g. 'TraversalMethod_repeatContext' → 'repeat')
+ const raw = name.slice('TraversalMethod_'.length, -'Context'.length).split('_')[0];
+ this.push(raw, []);
+ }
+ return;
+ }
+ }
+ }
+
+ // ── Nested traversal → sub-pipeline ──────────────────────────────────────
+
+ private visitNestedTraversalAsSubPipeline(ctx: any): Pipeline {
+ const sub = new ExecuteVisitor();
+ // nestedTraversal has a chainedTraversal child
+ const chain = ctx.chainedTraversal?.();
+ if (chain) sub.visitChainedTraversal(chain);
+ return sub.getPipeline();
+ }
+
+ // ── Mid-traversal V/E (used inside anonymous traversals, e.g. __.V(1)) ──────
+
+ visitTraversalMethod_V(ctx: any): void {
+ const args = this.recoverGenericVarargs(ctx.genericArgumentVarargs());
+ this.push('V', args);
+ }
+
+ visitTraversalMethod_E(ctx: any): void {
+ const args = this.recoverGenericVarargs(ctx.genericArgumentVarargs());
+ this.push('E', args);
+ }
+
+ // ── Source spawn methods (g.V(), g.E(), g.addV(), g.addE()) ──────────────
+
+ visitTraversalSourceSpawnMethod_V(ctx: any): void {
+ const args = this.recoverGenericVarargs(ctx.genericArgumentVarargs());
+ this.push('V', args);
+ }
+
+ visitTraversalSourceSpawnMethod_E(ctx: any): void {
+ const args = this.recoverGenericVarargs(ctx.genericArgumentVarargs());
+ this.push('E', args);
+ }
+
+ visitTraversalSourceSpawnMethod_addV(ctx: any): void {
+ const strArg = ctx.stringArgument?.();
+ if (strArg) {
+ this.push('addV', [this.recoverStringArg(strArg)]);
+ } else {
+ this.push('addV', []);
+ }
+ }
+
+ visitTraversalSourceSpawnMethod_addE(ctx: any): void {
+ const strArg = ctx.stringArgument?.();
+ if (strArg) {
+ this.push('addE', [this.recoverStringArg(strArg)]);
+ } else {
+ // Traversal label form — not supported in Tiny Gremlin
+ this.push('addE(Traversal)', []);
+ }
+ }
+
+ // ── Navigation steps ──────────────────────────────────────────────────────
+
+ visitTraversalMethod_out(ctx: any): void {
+ this.push('out', this.recoverStringNullableVarargs(ctx.stringNullableArgumentVarargs()));
+ }
+
+ visitTraversalMethod_in(ctx: any): void {
+ this.push('in', this.recoverStringNullableVarargs(ctx.stringNullableArgumentVarargs()));
+ }
+
+ visitTraversalMethod_both(ctx: any): void {
+ this.push('both', this.recoverStringNullableVarargs(ctx.stringNullableArgumentVarargs()));
+ }
+
+ visitTraversalMethod_outE(ctx: any): void {
+ this.push('outE', this.recoverStringNullableVarargs(ctx.stringNullableArgumentVarargs()));
+ }
+
+ visitTraversalMethod_inE(ctx: any): void {
+ this.push('inE', this.recoverStringNullableVarargs(ctx.stringNullableArgumentVarargs()));
+ }
+
+ visitTraversalMethod_bothE(ctx: any): void {
+ this.push('bothE', this.recoverStringNullableVarargs(ctx.stringNullableArgumentVarargs()));
+ }
+
+ visitTraversalMethod_outV(ctx: any): void {
+ this.push('outV', []);
+ }
+
+ visitTraversalMethod_inV(ctx: any): void {
+ this.push('inV', []);
+ }
+
+ visitTraversalMethod_otherV(ctx: any): void {
+ this.push('otherV', []);
+ }
+
+ // ── Filter steps ──────────────────────────────────────────────────────────
+
+ // has(key) — existence check
+ visitTraversalMethod_has_String(ctx: any): void {
+ this.push('has', [this.recoverStringNullableLiteral(ctx.stringNullableLiteral())]);
+ }
+
+ // has(key, value)
+ visitTraversalMethod_has_String_Object(ctx: any): void {
+ this.push('has', [
+ this.recoverStringNullableLiteral(ctx.stringNullableLiteral()),
+ this.recoverGenericArg(ctx.genericArgument()),
+ ]);
+ }
+
+ // has(key, P)
+ visitTraversalMethod_has_String_P(ctx: any): void {
+ this.push('has', [
+ this.recoverStringNullableLiteral(ctx.stringNullableLiteral()),
+ this.recoverPredicate(ctx.traversalPredicate()),
+ ]);
+ }
+
+ // has(label, key, value) — label is stringNullableArgument, key is stringNullableLiteral
+ visitTraversalMethod_has_String_String_Object(ctx: any): void {
+ this.push('has', [
+ this.recoverStringNullableArgument(ctx.stringNullableArgument()),
+ this.recoverStringNullableLiteral(ctx.stringNullableLiteral()),
+ this.recoverGenericArg(ctx.genericArgument()),
+ ]);
+ }
+
+ // has(label, key, P) — label is stringNullableArgument, key is stringNullableLiteral
+ visitTraversalMethod_has_String_String_P(ctx: any): void {
+ this.push('has', [
+ this.recoverStringNullableArgument(ctx.stringNullableArgument()),
+ this.recoverStringNullableLiteral(ctx.stringNullableLiteral()),
+ this.recoverPredicate(ctx.traversalPredicate()),
+ ]);
+ }
+
+ // has(T, value)
+ visitTraversalMethod_has_T_Object(ctx: any): void {
+ this.push('has', [
+ ctx.traversalT().getText(),
+ this.recoverGenericArg(ctx.genericArgument()),
+ ]);
+ }
+
+ // has(T, P)
+ visitTraversalMethod_has_T_P(ctx: any): void {
+ this.push('has', [
+ ctx.traversalT().getText(),
+ this.recoverPredicate(ctx.traversalPredicate()),
+ ]);
+ }
+
+ visitTraversalMethod_hasId_Object_Object(ctx: any): void {
+ const first = this.recoverGenericArg(ctx.genericArgument());
+ const rest = this.recoverGenericVarargs(ctx.genericArgumentVarargs?.());
+ this.push('hasId', [first, ...rest]);
+ }
+
+ visitTraversalMethod_hasId_P(ctx: any): void {
+ this.push('hasId', [this.recoverPredicate(ctx.traversalPredicate())]);
+ }
+
+ visitTraversalMethod_hasLabel_String_String(ctx: any): void {
+ const first = this.recoverStringNullableArgument(ctx.stringNullableArgument());
+ const rest = this.recoverStringNullableVarargs(ctx.stringNullableArgumentVarargs?.());
+ this.push('hasLabel', [first, ...rest]);
+ }
+
+ visitTraversalMethod_hasLabel_P(ctx: any): void {
+ this.push('hasLabel', [this.recoverPredicate(ctx.traversalPredicate())]);
+ }
+
+ visitTraversalMethod_hasNot(ctx: any): void {
+ this.push('hasNot', [this.recoverStringNullableLiteral(ctx.stringNullableLiteral())]);
+ }
+
+ visitTraversalMethod_hasKey_String_String(ctx: any): void {
+ const first = this.recoverStringNullableArgument(ctx.stringNullableArgument());
+ const rest = this.recoverStringNullableVarargs(ctx.stringNullableArgumentVarargs?.());
+ this.push('hasKey', [first, ...rest]);
+ }
+
+ visitTraversalMethod_hasKey_P(ctx: any): void {
+ this.push('hasKey', [this.recoverPredicate(ctx.traversalPredicate())]);
+ }
+
+ visitTraversalMethod_hasValue_Object_Object(ctx: any): void {
+ const first = this.recoverGenericArg(ctx.genericArgument());
+ const rest = this.recoverGenericVarargs(ctx.genericArgumentVarargs?.());
+ this.push('hasValue', [first, ...rest]);
+ }
+
+ visitTraversalMethod_hasValue_P(ctx: any): void {
+ this.push('hasValue', [this.recoverPredicate(ctx.traversalPredicate())]);
+ }
+
+ // ── Value extraction steps ────────────────────────────────────────────────
+
+ visitTraversalMethod_id(ctx: any): void {
+ this.push('id', []);
+ }
+
+ visitTraversalMethod_label(ctx: any): void {
+ this.push('label', []);
+ }
+
+ visitTraversalMethod_key(ctx: any): void {
+ this.push('key', []);
+ }
+
+ visitTraversalMethod_value(ctx: any): void {
+ this.push('value', []);
+ }
+
+ visitTraversalMethod_values(ctx: any): void {
+ this.push('values', this.recoverStringNullableLiteralVarargs(ctx.stringNullableLiteralVarargs()));
+ }
+
+ visitTraversalMethod_valueMap(ctx: any): void {
+ // valueMap() or valueMap(key, key...)
+ const varargs = ctx.stringNullableArgumentVarargs?.();
+ this.push('valueMap', varargs ? this.recoverStringNullableVarargs(varargs) : []);
+ }
+
+ visitTraversalMethod_valueMap_String(ctx: any): void {
+ this.push('valueMap', this.recoverStringNullableLiteralVarargs(ctx.stringNullableLiteralVarargs()));
+ }
+
+ visitTraversalMethod_valueMap_boolean_String(ctx: any): void {
+ const includeTokens = ctx.booleanLiteral().getText() === 'true';
+ const keys = this.recoverStringNullableLiteralVarargs(ctx.stringNullableLiteralVarargs?.());
+ // True sentinel at position 0 signals token inclusion to stepValueMap
+ this.push('valueMap', includeTokens ? [true, ...keys] : keys);
+ }
+
+ visitTraversalMethod_elementMap(ctx: any): void {
+ this.push('elementMap', this.recoverStringNullableLiteralVarargs(ctx.stringNullableLiteralVarargs()));
+ }
+
+ // ── Path step ─────────────────────────────────────────────────────────────
+
+ visitTraversalMethod_path(ctx: any): void {
+ this.push('path', []);
+ }
+
+ // ── Range steps ───────────────────────────────────────────────────────────
+
+ visitTraversalMethod_limit_long(ctx: any): void {
+ this.push('limit', [this.recoverIntegerArg(ctx.integerArgument())]);
+ }
+
+ visitTraversalMethod_limit_Scope_long(_ctx: any): void {
+ this.push('limit(Scope)', []);
+ }
+
+ visitTraversalMethod_range_long_long(ctx: any): void {
+ const args = ctx.integerArgument();
+ this.push('range', [this.recoverIntegerArg(args[0]), this.recoverIntegerArg(args[1])]);
+ }
+
+ visitTraversalMethod_range_Scope_long_long(_ctx: any): void {
+ this.push('range(Scope)', []);
+ }
+
+ visitTraversalMethod_skip_long(ctx: any): void {
+ this.push('skip', [this.recoverIntegerArg(ctx.integerArgument())]);
+ }
+
+ visitTraversalMethod_skip_Scope_long(_ctx: any): void {
+ this.push('skip(Scope)', []);
+ }
+
+ visitTraversalMethod_tail_Empty(ctx: any): void {
+ this.push('tail', [1]);
+ }
+
+ visitTraversalMethod_tail_long(ctx: any): void {
+ this.push('tail', [this.recoverIntegerArg(ctx.integerArgument())]);
+ }
+
+ visitTraversalMethod_tail_Scope(_ctx: any): void {
+ this.push('tail(Scope)', []);
+ }
+
+ visitTraversalMethod_tail_Scope_long(_ctx: any): void {
+ this.push('tail(Scope)', []);
+ }
+
+ // ── Ordering step and by() modulator ─────────────────────────────────────
+
+ visitTraversalMethod_order_Empty(ctx: any): void {
+ this.push('order', []);
+ }
+
+ visitTraversalMethod_order_Scope(_ctx: any): void {
+ this.push('order(Scope)', []);
+ }
+
+ /** by(String key) — sort by property value ascending */
+ visitTraversalMethod_by_String(ctx: any): void {
+ this.push('by', [this.recoverStringLiteral(ctx.stringLiteral()), 'asc']);
+ }
+
+ /** by(String key, Order) — sort by property value with direction */
+ visitTraversalMethod_by_String_Comparator(ctx: any): void {
+ const dir = ctx.traversalComparator().traversalOrder().getText();
+ this.push('by', [this.recoverStringLiteral(ctx.stringLiteral()), dir]);
+ }
+
+ /** by(T token) — sort by T.id or T.label */
+ visitTraversalMethod_by_T(ctx: any): void {
+ this.push('by', [ctx.traversalT().getText(), 'asc']);
+ }
+
+ /** by() — natural ascending sort */
+ visitTraversalMethod_by_Empty(_ctx: any): void {
+ this.push('by', [null, 'asc']);
+ }
+
+ /** by(Order) — natural sort with explicit direction */
+ visitTraversalMethod_by_Comparator(ctx: any): void {
+ const dir = ctx.traversalComparator().traversalOrder().getText();
+ this.push('by', [null, dir]);
+ }
+
+ /** by(Order) using traversalOrder directly */
+ visitTraversalMethod_by_Order(ctx: any): void {
+ const dir = ctx.traversalOrder().getText();
+ this.push('by', [null, dir]);
+ }
+
+ visitTraversalMethod_by_Traversal(_ctx: any): void { this.push('by(Traversal)', []); }
+ visitTraversalMethod_by_Traversal_Comparator(_ctx: any): void { this.push('by(Traversal)', []); }
+
+ /** by(T, Order) — T.id, T.label etc. with a direction, routed through traversalFunction */
+ visitTraversalMethod_by_Function(ctx: any): void {
+ const tCtx = ctx.traversalFunction().traversalT?.();
+ if (tCtx) {
+ this.push('by', [tCtx.getText(), 'asc']);
+ } else {
+ this.push('by(Function)', []);
+ }
+ }
+
+ visitTraversalMethod_by_Function_Comparator(ctx: any): void {
+ const tCtx = ctx.traversalFunction().traversalT?.();
+ if (tCtx) {
+ const dir = ctx.traversalComparator().traversalOrder().getText();
+ this.push('by', [tCtx.getText(), dir]);
+ } else {
+ this.push('by(Function)', []);
+ }
+ }
+
+ // ── Mutation steps ────────────────────────────────────────────────────────
+
+ visitTraversalMethod_addV_Empty(ctx: any): void {
+ this.push('addV', []);
+ }
+
+ visitTraversalMethod_addV_String(ctx: any): void {
+ this.push('addV', [this.recoverStringArg(ctx.stringArgument())]);
+ }
+
+ visitTraversalMethod_addV_Traversal(ctx: any): void {
+ this.push('addV', [this.visitNestedTraversalAsSubPipeline(ctx.nestedTraversal())]);
+ }
+
+ visitTraversalMethod_addE_String(ctx: any): void {
+ this.push('addE', [this.recoverStringArg(ctx.stringArgument())]);
+ }
+
+ visitTraversalMethod_addE_Traversal(_ctx: any): void {
+ // addE(Traversal) is not supported in Tiny Gremlin — push an unsupported step name
+ // so the validation pass rejects it with a clear LocalExecutionError.
+ this.push('addE(Traversal)', []);
+ }
+
+ // property(key, value) — the supported form
+ visitTraversalMethod_property_Object_Object_Object(ctx: any): void {
+ this.push('property', [
+ this.recoverGenericLiteral(ctx.genericLiteral()),
+ this.recoverGenericArg(ctx.genericArgument()),
+ ]);
+ }
+
+ // from(traversal) modulator
+ visitTraversalMethod_from_Traversal(ctx: any): void {
+ this.push('from', [this.visitNestedTraversalAsSubPipeline(ctx.nestedTraversal())]);
+ }
+
+ // from(string) modulator — label reference, treated as-is
+ visitTraversalMethod_from_String(ctx: any): void {
+ this.push('from', [this.recoverStringLiteral(ctx.stringLiteral())]);
+ }
+
+ // to(traversal) modulator
+ visitTraversalMethod_to_Traversal(ctx: any): void {
+ this.push('to', [this.visitNestedTraversalAsSubPipeline(ctx.nestedTraversal())]);
+ }
+
+ // to(string) modulator
+ visitTraversalMethod_to_String(ctx: any): void {
+ this.push('to', [this.recoverStringLiteral(ctx.stringLiteral())]);
+ }
+
+ // ── Argument recovery helpers ─────────────────────────────────────────────
+
+ private recoverStringLiteral(ctx: any): string {
+ if (ctx == null) return '';
+ const text: string = ctx.getText();
+ // Strip surrounding quotes (' or ") and unescape escaped quotes
+ if ((text.startsWith("'") && text.endsWith("'")) ||
+ (text.startsWith('"') && text.endsWith('"'))) {
+ return text.slice(1, -1)
+ .replace(/\\'/g, "'")
+ .replace(/\\"/g, '"')
+ .replace(/\\\\/g, '\\');
+ }
+ return text;
+ }
+
+ private recoverStringNullableLiteral(ctx: any): string | null {
+ if (ctx == null) return null;
+ const text: string = ctx.getText();
+ if (text === 'null') return null;
+ return this.recoverStringLiteral(ctx.stringLiteral?.() ?? ctx);
+ }
+
+ private recoverStringArg(ctx: any): string {
+ if (ctx == null) return '';
+ const lit = ctx.stringLiteral?.();
+ return lit ? this.recoverStringLiteral(lit) : ctx.getText();
+ }
+
+ private recoverStringNullableArgument(ctx: any): string | null {
+ if (ctx == null) return null;
+ const lit = ctx.stringNullableLiteral?.();
+ return lit ? this.recoverStringNullableLiteral(lit) : ctx.getText();
+ }
+
+ private recoverStringNullableVarargs(ctx: any): Arg[] {
+ if (ctx == null) return [];
+ const items = ctx.stringNullableArgument?.() ?? [];
+ return (Array.isArray(items) ? items : [items])
+ .map((a: any) => this.recoverStringNullableArgument(a));
+ }
+
+ /** Recover args from a stringNullableLiteralVarargs context (used by values, valueMap, elementMap). */
+ private recoverStringNullableLiteralVarargs(ctx: any): Arg[] {
+ if (ctx == null) return [];
+ const items = ctx.stringNullableLiteral?.() ?? [];
+ return (Array.isArray(items) ? items : [items])
+ .map((a: any) => this.recoverStringNullableLiteral(a));
+ }
+
+ private recoverIntegerArg(ctx: any): number {
+ if (ctx == null) return 0;
+ const lit = ctx.integerLiteral?.();
+ if (lit) return this.recoverIntegerLiteral(lit);
+ return parseInt(ctx.getText(), 10);
+ }
+
+ private recoverIntegerLiteral(ctx: any): number {
+ const text: string = ctx.getText().toLowerCase();
+ // Strip type suffix: L, S, B
+ const stripped = text.replace(/[lsb]$/, '');
+ return parseInt(stripped, 10);
+ }
+
+ private recoverFloatLiteral(ctx: any): number {
+ const text: string = ctx.getText().toLowerCase();
+ if (text === '+infinity') return Infinity;
+ if (text === '-infinity') return -Infinity;
+ if (text === 'nan') return NaN;
+ return parseFloat(text.replace(/[fd]$/, ''));
+ }
+
+ private recoverGenericLiteral(ctx: any): Arg {
+ if (ctx == null) return null;
+ const numeric = ctx.numericLiteral?.();
+ if (numeric) {
+ const int = numeric.integerLiteral?.();
+ if (int) return this.recoverIntegerLiteral(int);
+ const float = numeric.floatLiteral?.();
+ if (float) return this.recoverFloatLiteral(float);
+ }
+ const bool = ctx.booleanLiteral?.();
+ if (bool) return bool.getText() === 'true';
+ const str = ctx.stringLiteral?.();
+ if (str) return this.recoverStringLiteral(str);
+ const nil = ctx.nullLiteral?.();
+ if (nil) return null;
+ // Nested traversal as a generic literal
+ const nested = ctx.nestedTraversal?.();
+ if (nested) return this.visitNestedTraversalAsSubPipeline(nested);
+ // Collection literal [a, b, c]
+ const coll = ctx.genericCollectionLiteral?.();
+ if (coll) return coll.genericLiteral().map((gl: any) => this.recoverGenericLiteral(gl));
+ return ctx.getText();
+ }
+
+ private recoverGenericArg(ctx: any): Arg {
+ if (ctx == null) return null;
+ const lit = ctx.genericLiteral?.();
+ if (lit) return this.recoverGenericLiteral(lit);
+ return ctx.getText();
+ }
+
+ private recoverGenericVarargs(ctx: any): Arg[] {
+ if (ctx == null) return [];
+ const items = ctx.genericArgument?.() ?? [];
+ return (Array.isArray(items) ? items : [items])
+ .map((a: any) => this.recoverGenericArg(a));
+ }
+
+ private recoverPredicate(ctx: any): P | TextP {
+ if (ctx == null) return P.eq(null);
+ const name: string = ctx.constructor?.name ?? '';
+
+ // Compound predicates: predicate DOT and/or LPAREN predicate RPAREN (6 children)
+ if (ctx.getChildCount?.() === 6) {
+ const op = ctx.getChild(2)?.getText();
+ if (op === 'and' || op === 'or') {
+ const left = this.recoverPredicate(ctx.getChild(0));
+ const right = this.recoverPredicate(ctx.getChild(4));
+ return op === 'and' ? left.and(right) : left.or(right);
+ }
+ }
+
+ // TextP string predicates — checked BEFORE 'not' to avoid prefix collision
+ if (name.includes('Predicate_notContaining'))
+ return TextP.notContaining(this.recoverStringArg(ctx.stringArgument()) as string);
+ if (name.includes('Predicate_notStartingWith'))
+ return TextP.notStartingWith(this.recoverStringArg(ctx.stringArgument()) as string);
+ if (name.includes('Predicate_notEndingWith'))
+ return TextP.notEndingWith(this.recoverStringArg(ctx.stringArgument()) as string);
+ if (name.includes('Predicate_notRegex'))
+ return (TextP as any).notRegex(this.recoverStringArg(ctx.stringArgument()) as string);
+ if (name.includes('Predicate_containing'))
+ return TextP.containing(this.recoverStringArg(ctx.stringArgument()) as string);
+ if (name.includes('Predicate_startingWith'))
+ return TextP.startingWith(this.recoverStringArg(ctx.stringArgument()) as string);
+ if (name.includes('Predicate_endingWith'))
+ return TextP.endingWith(this.recoverStringArg(ctx.stringArgument()) as string);
+ if (name.includes('Predicate_regex'))
+ return (TextP as any).regex(this.recoverStringArg(ctx.stringArgument()) as string);
+ // P numeric predicates
+ if (name.includes('Predicate_eq')) return P.eq(this.recoverGenericArg(ctx.genericArgument()));
+ if (name.includes('Predicate_neq')) return P.neq(this.recoverGenericArg(ctx.genericArgument()));
+ if (name.includes('Predicate_lte')) return P.lte(this.recoverGenericArg(ctx.genericArgument()));
+ if (name.includes('Predicate_lt')) return P.lt(this.recoverGenericArg(ctx.genericArgument()));
+ if (name.includes('Predicate_gte')) return P.gte(this.recoverGenericArg(ctx.genericArgument()));
+ if (name.includes('Predicate_gt')) return P.gt(this.recoverGenericArg(ctx.genericArgument()));
+ if (name.includes('Predicate_between')) {
+ const args = ctx.genericArgument();
+ return P.between(this.recoverGenericArg(args[0]), this.recoverGenericArg(args[1]));
+ }
+ if (name.includes('Predicate_within')) {
+ const vals = this.recoverGenericVarargs(ctx.genericArgumentVarargs?.());
+ return (P as any).within(...vals);
+ }
+ if (name.includes('Predicate_without')) {
+ const vals = this.recoverGenericVarargs(ctx.genericArgumentVarargs?.());
+ return (P as any).without(...vals);
+ }
+ if (name.includes('Predicate_inside')) {
+ const args = ctx.genericArgument();
+ return (P as any).inside(this.recoverGenericArg(args[0]), this.recoverGenericArg(args[1]));
+ }
+ if (name.includes('Predicate_outside')) {
+ const args = ctx.genericArgument();
+ return (P as any).outside(this.recoverGenericArg(args[0]), this.recoverGenericArg(args[1]));
+ }
+ if (name.includes('Predicate_not')) return P.not(this.recoverPredicate(ctx.traversalPredicate()));
+
+ // Single-child wrapper: visit the child
+ if (ctx.getChildCount?.() === 1) return this.recoverPredicate(ctx.getChild(0));
+
+ return P.eq(null);
+ }
+
+ // ── Step accumulator ──────────────────────────────────────────────────────
+
+ private push(name: string, args: Arg[]): void {
+ this.steps.push({ name, args });
+ }
+}
diff --git a/gremlin-js/gremlin-javascript/lib/process/anonymous-traversal.ts b/gremlin-js/gremlin-javascript/lib/process/anonymous-traversal.ts
index 5920c48dc9..699e2fc03b 100644
--- a/gremlin-js/gremlin-javascript/lib/process/anonymous-traversal.ts
+++ b/gremlin-js/gremlin-javascript/lib/process/anonymous-traversal.ts
@@ -18,6 +18,7 @@
*/
import { RemoteConnection, RemoteStrategy } from '../driver/remote-connection.js';
+import { LocalGraphConnection } from '../driver/local-graph-connection.js';
import { Graph } from '../structure/graph.js';
import { GraphTraversalSource, GraphTraversal } from './graph-traversal.js';
import { TraversalStrategies } from './traversal-strategy.js';
@@ -51,12 +52,16 @@ export default class AnonymousTraversalSource {
}
/**
- * Creates a {@link GraphTraversalSource} binding a {@link RemoteConnection} to a remote {@link Graph} instances as its
- * reference so that traversals spawned from it will execute over that reference.
- * @param {RemoteConnection} connection
+ * Creates a {@link GraphTraversalSource} bound to a connection or a local {@link Graph}.
+ * When passed a {@link Graph} instance, execution runs locally via Tiny Gremlin.
+ * When passed a {@link RemoteConnection}, traversals are submitted to a remote server.
+ * @param {RemoteConnection | Graph} connectionOrGraph
* @return {GraphTraversalSource}
*/
- with_(connection: RemoteConnection) {
+ with_(connectionOrGraph: RemoteConnection | Graph) {
+ const connection = connectionOrGraph instanceof Graph
+ ? new LocalGraphConnection(connectionOrGraph)
+ : connectionOrGraph;
const traversalStrategies = new TraversalStrategies();
traversalStrategies.addStrategy(new RemoteStrategy(connection));
const gl = new GremlinLang();
diff --git a/gremlin-js/gremlin-javascript/lib/process/local/LocalExecutionError.ts b/gremlin-js/gremlin-javascript/lib/process/local/LocalExecutionError.ts
new file mode 100644
index 0000000000..9658c34249
--- /dev/null
+++ b/gremlin-js/gremlin-javascript/lib/process/local/LocalExecutionError.ts
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Thrown when a Gremlin traversal contains a step not supported by the
+ * Tiny Gremlin local execution subset.
+ */
+export class LocalExecutionError extends Error {
+ constructor(message: string) {
+ super(message);
+ this.name = 'LocalExecutionError';
+ }
+}
diff --git a/gremlin-js/gremlin-javascript/lib/process/local/LocalExecutor.ts b/gremlin-js/gremlin-javascript/lib/process/local/LocalExecutor.ts
new file mode 100644
index 0000000000..ee4c04dc63
--- /dev/null
+++ b/gremlin-js/gremlin-javascript/lib/process/local/LocalExecutor.ts
@@ -0,0 +1,248 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import type { Arg, Pipeline, StepDescriptor } from './types.js';
+import { defaultPasses, type OptimizationPass } from './passes.js';
+import { LocalExecutionError } from './LocalExecutionError.js';
+import { Graph, Vertex, Edge } from '../../structure/graph.js';
+import {
+ wrap, getValue, type StreamItem,
+ stepOut, stepIn, stepBoth, stepOutE, stepInE, stepBothE, stepOutV, stepInV, stepOtherV,
+ stepHas, stepHasId, stepHasLabel, stepHasNot,
+ stepId, stepLabel, stepValue, stepKey, stepValues, stepValueMap, stepElementMap,
+ stepPath,
+ stepLimit, stepRange, stepSkip, stepTail, stepOrder,
+ stepAddV, stepAddEWithModulators, stepProperty,
+} from './steps.js';
+
+type StepFn = (source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean) => Generator<StreamItem>;
+
+/**
+ * Executes a Tiny Gremlin Pipeline against a local in-memory Graph, returning
+ * an AsyncGenerator that yields one result element at a time.
+ */
+export class LocalExecutor {
+ private readonly passes: OptimizationPass[];
+
+ constructor(passes: OptimizationPass[] = defaultPasses) {
+ this.passes = passes;
+ }
+
+ execute(pipeline: Pipeline, graph: Graph): AsyncGenerator<any> {
+ const optimized = this.passes.reduce((p, pass) => pass(p), pipeline);
+ const folded = this.foldModulators(optimized);
+ const trackPaths = folded.some(s => s.name === 'path' || s.name === 'otherV');
+ const stream = this.buildChain(folded, graph, trackPaths);
+ return this.toAsync(stream, trackPaths);
+ }
+
+ /**
+ * Folds 'from' and 'to' modulator steps that follow an 'addE' step into a
+ * composite descriptor so the addE executor has all modulator args available.
+ */
+ private foldModulators(pipeline: Pipeline): Pipeline {
+ const result: StepDescriptor[] = [];
+ let i = 0;
+ while (i < pipeline.length) {
+ const step = pipeline[i];
+ if (step.name === 'addE') {
+ let fromArgs: Arg[] | null = null;
+ let toArgs: Arg[] | null = null;
+ let j = i + 1;
+ while (j < pipeline.length && (pipeline[j].name === 'from' || pipeline[j].name === 'to')) {
+ if (pipeline[j].name === 'from') fromArgs = pipeline[j].args[0] as unknown as Arg[];
+ else toArgs = pipeline[j].args[0] as unknown as Arg[];
+ j++;
+ }
+ result.push({ name: 'addE', args: [step.args[0], fromArgs as unknown as Arg, toArgs as unknown as Arg] });
+ i = j;
+ } else if (step.name === 'order') {
+ // Fold a single following by() modulator into the order step args
+ const next = pipeline[i + 1];
+ if (next?.name === 'by') {
+ result.push({ name: 'order', args: next.args }); // args = [key, direction]
+ i += 2;
+ } else {
+ result.push(step);
+ i++;
+ }
+ } else {
+ result.push(step);
+ i++;
+ }
+ }
+ return result;
+ }
+
+ private buildChain(pipeline: Pipeline, graph: Graph, trackPaths: boolean): Iterable<StreamItem> {
+ if (pipeline.length === 0) return [];
+
+ const [source, ...rest] = pipeline;
+ let stream: Iterable<StreamItem> = this.applySourceStep(source, graph, trackPaths);
+
+ for (const step of rest) {
+ stream = this.applyStep(step, stream, graph, trackPaths);
+ }
+ return stream;
+ }
+
+ private applySourceStep(step: StepDescriptor, graph: Graph, trackPaths: boolean): Iterable<StreamItem> {
+ switch (step.name) {
+ case 'V': return sourceV(step.args, graph, trackPaths);
+ case 'E': return sourceE(step.args, graph, trackPaths);
+ case 'addV': return stepAddV([null], step.args, graph, trackPaths);
+ case 'addE': return this.applyAddE([null], step, graph, trackPaths);
+ default:
+ throw new LocalExecutionError(`'${step.name}' cannot be used as a source step`);
+ }
+ }
+
+ private applyStep(step: StepDescriptor, source: Iterable<StreamItem>, graph: Graph, trackPaths: boolean): Iterable<StreamItem> {
+ if (step.name === 'addE') return this.applyAddE(source, step, graph, trackPaths);
+ const fn = STEP_REGISTRY.get(step.name);
+ if (!fn) throw new LocalExecutionError(`No implementation for step '${step.name}'`);
+ return fn(source, step.args, graph, trackPaths);
+ }
+
+ private applyAddE(
+ source: Iterable<StreamItem>,
+ step: StepDescriptor,
+ graph: Graph,
+ trackPaths: boolean,
+ ): Iterable<StreamItem> {
+ const label = step.args[0] as string;
+ const fromPipeline = step.args[1] as Arg[] | null;
+ const toPipeline = step.args[2] as Arg[] | null;
+ return stepAddEWithModulators(
+ source, label, fromPipeline, toPipeline, graph, trackPaths,
+ (subPipeline, g) => this.executeSubPipeline(subPipeline as unknown as Pipeline, g),
+ );
+ }
+
+ private executeSubPipeline(pipeline: Pipeline, graph: Graph): any {
+ const stream = this.buildChain(pipeline, graph, false);
+ for (const item of stream) return item;
+ return null;
+ }
+
+ private async *toAsync(stream: Iterable<StreamItem>, trackPaths: boolean): AsyncGenerator<any> {
+ for (const item of stream) {
+ const value = trackPaths ? getValue(item, true) : item;
+ // Yield a shallow copy of Vertex/Edge so callers that mutate
+ // result objects (e.g. removeProperties) don't corrupt the graph.
+ yield shallowCopyElement(value);
+ }
+ }
+}
+
+// ── Result copying ────────────────────────────────────────────────────────────
+
+/** Shallow-copies Vertex/Edge/Path so callers can mutate results without corrupting the graph. */
+function shallowCopyElement(value: any): any {
+ if (value instanceof Vertex || value instanceof Edge) return Object.assign(Object.create(Object.getPrototypeOf(value)), value);
+ return value;
+}
+
+// ── Source steps ──────────────────────────────────────────────────────────────
+// Mid-traversal V/E: for each incoming traverser, emit matching vertices/edges (cross-product).
+
+function* midSourceV(source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean): Generator<StreamItem> {
+ for (const _item of source) {
+ yield* sourceV(args, graph, trackPaths);
+ }
+}
+
+function* midSourceE(source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean): Generator<StreamItem> {
+ for (const _item of source) {
+ yield* sourceE(args, graph, trackPaths);
+ }
+}
+
+function lookupById<T>(map: Map<any, T>, id: Arg): T | undefined {
+ return map.get(id);
+}
+
+function flattenIds(args: Arg[]): Arg[] {
+ // Single list argument: unroll it, consistent with V([1,2,3]) semantics.
+ // Multiple arguments: treat each as-is — a list is a single ID value, not unrolled.
+ if (args.length === 1 && Array.isArray(args[0])) return args[0] as unknown as Arg[];
+ return args;
+}
+
+function* sourceV(args: Arg[], graph: Graph, trackPaths: boolean): Generator<StreamItem> {
+ const ids = flattenIds(args);
+ if (ids.length === 0) {
+ for (const v of graph.vertices.values()) yield wrap(v, [], trackPaths);
+ } else {
+ for (const id of ids) {
+ if (id === null || id === undefined) continue;
+ const v = lookupById(graph.vertices, id);
+ if (v !== undefined) yield wrap(v, [], trackPaths);
+ }
+ }
+}
+
+function* sourceE(args: Arg[], graph: Graph, trackPaths: boolean): Generator<StreamItem> {
+ const ids = flattenIds(args);
+ if (ids.length === 0) {
+ for (const e of graph.edges.values()) yield wrap(e, [], trackPaths);
+ } else {
+ for (const id of ids) {
+ if (id === null || id === undefined) continue;
+ const e = lookupById(graph.edges, id);
+ if (e !== undefined) yield wrap(e, [], trackPaths);
+ }
+ }
+}
+
+// ── Step dispatch table ───────────────────────────────────────────────────────
+
+const STEP_REGISTRY = new Map<string, StepFn>([
+ // Mid-traversal V/E: for each incoming element produce matching vertices/edges (cross-product)
+ ['V', (source, args, graph, tp) => midSourceV(source, args, graph, tp)],
+ ['E', (source, args, graph, tp) => midSourceE(source, args, graph, tp)],
+ ['out', stepOut],
+ ['in', stepIn],
+ ['both', stepBoth],
+ ['outE', stepOutE],
+ ['inE', stepInE],
+ ['bothE', stepBothE],
+ ['outV', stepOutV],
+ ['inV', stepInV],
+ ['otherV', stepOtherV],
+ ['has', stepHas],
+ ['hasId', stepHasId],
+ ['hasLabel', stepHasLabel],
+ ['hasNot', stepHasNot],
+ ['id', stepId],
+ ['label', stepLabel],
+ ['value', stepValue],
+ ['key', stepKey],
+ ['values', stepValues],
+ ['valueMap', stepValueMap],
+ ['elementMap', stepElementMap],
+ ['path', stepPath],
+ ['limit', stepLimit],
+ ['range', stepRange],
+ ['skip', stepSkip],
+ ['tail', stepTail],
+ ['order', stepOrder],
+ ['addV', stepAddV],
+ ['property', stepProperty],
+]);
diff --git a/gremlin-js/gremlin-javascript/lib/process/local/passes.ts b/gremlin-js/gremlin-javascript/lib/process/local/passes.ts
new file mode 100644
index 0000000000..ce7635263a
--- /dev/null
+++ b/gremlin-js/gremlin-javascript/lib/process/local/passes.ts
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import type { Arg, Pipeline, StepDescriptor } from './types.js';
+import { LocalExecutionError } from './LocalExecutionError.js';
+
+export type OptimizationPass = (pipeline: Pipeline) => Pipeline;
+
+/** Steps supported by the Tiny Gremlin local executor. */
+const SUPPORTED_STEPS = new Set<string>([
+ 'V', 'E',
+ 'out', 'in', 'both', 'outE', 'inE', 'bothE', 'outV', 'inV', 'otherV',
+ 'has', 'hasId', 'hasLabel', 'hasNot',
+ 'id', 'label', 'values', 'valueMap', 'elementMap', 'value', 'key',
+ 'path',
+ 'limit', 'range', 'skip', 'tail',
+ 'order', 'by',
+ 'addV', 'addE', 'property',
+ 'from', 'to',
+]);
+
+/**
+ * Validation pass — runs first. Throws LocalExecutionError for any step not
+ * in the Tiny Gremlin subset, including steps inside nested sub-pipelines.
+ */
+export function validationPass(pipeline: Pipeline): Pipeline {
+ for (const step of pipeline) {
+ if (!SUPPORTED_STEPS.has(step.name)) {
+ throw new LocalExecutionError(
+ `'${step.name}' is not supported for Tiny Gremlin local execution`,
+ );
+ }
+ for (const arg of step.args) {
+ if (Array.isArray(arg) && arg.length > 0 && arg[0] !== null && typeof arg[0] === 'object' && 'name' in arg[0]) {
+ validationPass(arg as Pipeline);
+ }
+ }
+ }
+ return pipeline;
+}
+
+/**
+ * HasId folding pass — collapses V().hasId(id) → V(id) and E().hasId(id) → E(id),
+ * enabling O(1) direct map lookup instead of full iteration.
+ */
+export function hasIdFoldingPass(pipeline: Pipeline): Pipeline {
+ const result: StepDescriptor[] = [];
+ let i = 0;
+ while (i < pipeline.length) {
+ const step = pipeline[i];
+ const next = pipeline[i + 1];
+ if (
+ (step.name === 'V' || step.name === 'E') &&
+ step.args.length === 0 &&
+ next?.name === 'hasId'
+ ) {
+ result.push({ name: step.name, args: next.args });
+ i += 2;
+ } else {
+ result.push(step);
+ i++;
+ }
+ }
+ return result;
+}
+
+/** Default optimization passes applied before execution. */
+export const defaultPasses: OptimizationPass[] = [
+ validationPass,
+ hasIdFoldingPass,
+];
diff --git a/gremlin-js/gremlin-javascript/lib/process/local/steps.ts b/gremlin-js/gremlin-javascript/lib/process/local/steps.ts
new file mode 100644
index 0000000000..7ae0700683
--- /dev/null
+++ b/gremlin-js/gremlin-javascript/lib/process/local/steps.ts
@@ -0,0 +1,730 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { P, TextP, t, direction } from '../traversal.js';
+import { Graph, Vertex, Edge, VertexProperty, Property, Path } from '../../structure/graph.js';
+import type { Arg } from './types.js';
+
+// ── Traverser helpers ─────────────────────────────────────────────────────────
+
+export type StreamItem = any;
+
+export function wrap(value: any, prevPath: PathEntry[], trackPaths: boolean): StreamItem {
+ if (!trackPaths) return value;
+ return { value, path: [...prevPath, { labels: [], object: value }] };
+}
+
+export function getValue(item: StreamItem, trackPaths: boolean): any {
+ return trackPaths ? item.value : item;
+}
+
+export function getPath(item: StreamItem, trackPaths: boolean): PathEntry[] {
+ return trackPaths ? (item.path ?? []) : [];
+}
+
+export interface PathEntry {
+ labels: string[];
+ object: any;
+}
+
+// ── Property lookup helpers ───────────────────────────────────────────────────
+
+/** Returns the property key regardless of whether it's a Property or VertexProperty. */
+function propKey(p: Property | VertexProperty): string {
+ return p instanceof VertexProperty ? p.label : p.key;
+}
+
+/** Returns all properties of an element as a flat list. */
+function allProps(el: Vertex | Edge): Array<Property | VertexProperty> {
+ return (el.properties ?? []) as Array<Property | VertexProperty>;
+}
+
+/** Returns all values for a given key on an element (vertex properties are multi-valued). */
+function getValues(el: Vertex | Edge, key: string): any[] {
+ return allProps(el)
+ .filter(p => propKey(p) === key)
+ .map(p => (p as any).value);
+}
+
+/** Returns true when the element has at least one property with the given key. */
+function hasKey(el: Vertex | Edge, key: string): boolean {
+ return allProps(el).some(p => propKey(p) === key);
+}
+
+// ── Predicate evaluation ──────────────────────────────────────────────────────
+
+export function evaluatePredicate(pred: P | TextP, value: any): boolean {
+ const op = pred.operator;
+ const v = pred.value;
+ const other = pred.other;
+
+ switch (op) {
+ case 'eq': { const c = compareForPredicate(value, v); return c !== null && c === 0; }
+ case 'neq': { const c = compareForPredicate(value, v); return c !== null ? c !== 0 : value !== v; }
+ case 'lt': { const c = compareForPredicate(value, v); return c !== null && c < 0; }
+ case 'lte': { const c = compareForPredicate(value, v); return c !== null && c <= 0; }
+ case 'gt': { const c = compareForPredicate(value, v); return c !== null && c > 0; }
+ case 'gte': { const c = compareForPredicate(value, v); return c !== null && c >= 0; }
+ case 'between': {
+ const clo = compareForPredicate(value, v);
+ const chi = compareForPredicate(value, other);
+ return clo !== null && chi !== null && clo >= 0 && chi < 0;
+ }
+ case 'inside': {
+ const clo = compareForPredicate(value, v);
+ const chi = compareForPredicate(value, other);
+ return clo !== null && chi !== null && clo > 0 && chi < 0;
+ }
+ case 'outside': {
+ const clo = compareForPredicate(value, v);
+ const chi = compareForPredicate(value, other);
+ return (clo !== null && clo < 0) || (chi !== null && chi > 0);
+ }
+ case 'within': return (Array.isArray(v) ? v : [v]).includes(value);
+ case 'without': return !(Array.isArray(v) ? v : [v]).includes(value);
+ case 'not': return !evaluatePredicate(v as P, value);
+ case 'and': return evaluatePredicate(v as P, value) && evaluatePredicate(other as P, value);
+ case 'or': return evaluatePredicate(v as P, value) || evaluatePredicate(other as P, value);
+ case 'containing': return typeof value === 'string' && value.includes(v as string);
+ case 'notContaining': return typeof value === 'string' && !value.includes(v as string);
+ case 'startingWith': return typeof value === 'string' && value.startsWith(v as string);
+ case 'notStartingWith': return typeof value === 'string' && !value.startsWith(v as string);
+ case 'endingWith': return typeof value === 'string' && value.endsWith(v as string);
+ case 'notEndingWith': return typeof value === 'string' && !value.endsWith(v as string);
+ case 'regex': return typeof value === 'string' && new RegExp(v as string, 'u').test(value);
+ case 'notRegex': return typeof value === 'string' && !new RegExp(v as string, 'u').test(value);
+ default: return false;
+ }
+}
+
+// ── Navigation steps ──────────────────────────────────────────────────────────
+
+export function* stepOut(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ const labels = args as string[];
+ for (const item of source) {
+ const v: Vertex = getValue(item, trackPaths);
+ const edges = graph.outEdges.get(v.id) ?? [];
+ for (const e of edges) {
+ if (labels.length === 0 || labels.includes(e.label)) {
+ yield wrap(e.inV, getPath(item, trackPaths), trackPaths);
+ }
+ }
+ }
+}
+
+export function* stepIn(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ const labels = args as string[];
+ for (const item of source) {
+ const v: Vertex = getValue(item, trackPaths);
+ const edges = graph.inEdges.get(v.id) ?? [];
+ for (const e of edges) {
+ if (labels.length === 0 || labels.includes(e.label)) {
+ yield wrap(e.outV, getPath(item, trackPaths), trackPaths);
+ }
+ }
+ }
+}
+
+export function* stepBoth(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ const buf = [...source];
+ yield* stepOut(buf, args, graph, trackPaths);
+ yield* stepIn(buf, args, graph, trackPaths);
+}
+
+export function* stepOutE(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ const labels = args as string[];
+ for (const item of source) {
+ const v: Vertex = getValue(item, trackPaths);
+ const edges = graph.outEdges.get(v.id) ?? [];
+ for (const e of edges) {
+ if (labels.length === 0 || labels.includes(e.label)) {
+ yield wrap(e, getPath(item, trackPaths), trackPaths);
+ }
+ }
+ }
+}
+
+export function* stepInE(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ const labels = args as string[];
+ for (const item of source) {
+ const v: Vertex = getValue(item, trackPaths);
+ const edges = graph.inEdges.get(v.id) ?? [];
+ for (const e of edges) {
+ if (labels.length === 0 || labels.includes(e.label)) {
+ yield wrap(e, getPath(item, trackPaths), trackPaths);
+ }
+ }
+ }
+}
+
+export function* stepBothE(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ const buf = [...source]; // already buffered correctly
+ yield* stepOutE(buf, args, graph, trackPaths);
+ yield* stepInE(buf, args, graph, trackPaths);
+}
+
+export function* stepOutV(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ for (const item of source) {
+ const e: Edge = getValue(item, trackPaths);
+ yield wrap(e.outV, getPath(item, trackPaths), trackPaths);
+ }
+}
+
+export function* stepInV(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ for (const item of source) {
+ const e: Edge = getValue(item, trackPaths);
+ yield wrap(e.inV, getPath(item, trackPaths), trackPaths);
+ }
+}
+
+export function* stepOtherV(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ // trackPaths is always true when otherV is present (forced in LocalExecutor)
+ for (const item of source) {
+ const e: Edge = getValue(item, trackPaths);
+ const path = getPath(item, trackPaths);
+ const prevEntry = path.length >= 2 ? path[path.length - 2] : null;
+ const prevId = prevEntry?.object?.id;
+ const result = prevId === e.outV.id ? e.inV : e.outV;
+ yield wrap(result, path, trackPaths);
+ }
+}
+
+// ── Filter steps ──────────────────────────────────────────────────────────────
+
+export function* stepHas(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ for (const item of source) {
+ const el = getValue(item, trackPaths);
+ if (matchesHas(el, args)) yield item;
+ }
+}
+
+function matchesHas(el: any, args: Arg[]): boolean {
+ if (!(el instanceof Vertex || el instanceof Edge)) return false;
+
+ if (args.length === 1) {
+ const key = args[0] as string;
+ if (key === 'T.id' || key === 'id') return true;
+ if (key === 'T.label' || key === 'label') return true;
+ return hasKey(el, key);
+ }
+
+ if (args.length === 2) {
+ const [key, valOrPred] = args;
+ const keyStr = key as string;
+ let values: any[];
+ if (keyStr === 'T.id' || keyStr === 'id') {
+ values = [el.id];
+ } else if (keyStr === 'T.label' || keyStr === 'label') {
+ values = [el.label];
+ } else {
+ values = getValues(el, keyStr);
+ }
+ if (values.length === 0) return false;
+ if (valOrPred instanceof P || valOrPred instanceof TextP) {
+ return values.some(v => evaluatePredicate(valOrPred, v));
+ }
+ return values.some(v => v === valOrPred);
+ }
+
+ if (args.length === 3) {
+ const [label, key, valOrPred] = args;
+ if (el.label !== label) return false;
+ const values = getValues(el, key as string);
+ if (values.length === 0) return false;
+ if (valOrPred instanceof P || valOrPred instanceof TextP) {
+ return values.some(v => evaluatePredicate(valOrPred as P, v));
+ }
+ return values.some(v => v === valOrPred);
+ }
+
+ return false;
+}
+
+function idMatches(id: any, argId: Arg): boolean {
+ if (argId === null || argId === undefined) return false;
+ return id === argId;
+}
+
+function flattenIdArgs(args: Arg[]): Arg[] {
+ // Single list argument: unroll it, consistent with hasId([1,2,3]) semantics.
+ // Multiple arguments: treat each as-is — a list is a single ID value, not unrolled.
+ if (args.length === 1 && Array.isArray(args[0])) return args[0] as unknown as Arg[];
+ return args;
+}
+
+export function* stepHasId(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ const flatArgs = flattenIdArgs(args);
+ for (const item of source) {
+ const el = getValue(item, trackPaths);
+ const id = el?.id;
+ if (flatArgs.length > 0 && flatArgs[0] instanceof P) {
+ if (evaluatePredicate(flatArgs[0] as P, id)) yield item;
+ } else if (flatArgs.some(a => idMatches(id, a))) {
+ yield item;
+ }
+ }
+}
+
+export function* stepHasLabel(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ for (const item of source) {
+ const el = getValue(item, trackPaths);
+ const label = el?.label;
+ if (args.length > 0 && args[0] instanceof P) {
+ if (evaluatePredicate(args[0] as P, label)) yield item;
+ } else if (args.length === 0 || args.includes(label)) {
+ yield item;
+ }
+ }
+}
+
+export function* stepHasNot(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ const key = args[0] as string;
+ for (const item of source) {
+ const el = getValue(item, trackPaths);
+ if (el instanceof Vertex || el instanceof Edge) {
+ if (!hasKey(el, key)) yield item;
+ }
+ }
+}
+
+export function* stepHasKey(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ for (const item of source) {
+ const p = getValue(item, trackPaths);
+ const k = propKey(p);
+ if (args.length === 0) {
+ yield item;
+ } else if (args[0] instanceof P) {
+ if (evaluatePredicate(args[0] as P, k)) yield item;
+ } else if (args.includes(k)) {
+ yield item;
+ }
+ }
+}
+
+export function* stepHasValue(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ for (const item of source) {
+ const p = getValue(item, trackPaths);
+ const v = p?.value;
+ if (args.length === 0) {
+ yield item;
+ } else if (args[0] instanceof P) {
+ if (evaluatePredicate(args[0] as P, v)) yield item;
+ } else if (args.some(a => a == v)) {
+ yield item;
+ }
+ }
+}
+
+// ── Value extraction steps ────────────────────────────────────────────────────
+
+export function* stepId(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ for (const item of source) {
+ const el = getValue(item, trackPaths);
+ yield wrap(el?.id, getPath(item, trackPaths), trackPaths);
+ }
+}
+
+export function* stepLabel(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ for (const item of source) {
+ const el = getValue(item, trackPaths);
+ yield wrap(el?.label, getPath(item, trackPaths), trackPaths);
+ }
+}
+
+export function* stepValue(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ for (const item of source) {
+ const p = getValue(item, trackPaths);
+ yield wrap(p?.value, getPath(item, trackPaths), trackPaths);
+ }
+}
+
+export function* stepKey(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ for (const item of source) {
+ const p = getValue(item, trackPaths);
+ yield wrap(propKey(p), getPath(item, trackPaths), trackPaths);
+ }
+}
+
+export function* stepValues(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ const keys = args as string[];
+ for (const item of source) {
+ const el = getValue(item, trackPaths);
+ const props = allProps(el);
+ for (const p of props) {
+ if (keys.length === 0 || keys.includes(propKey(p))) {
+ yield wrap((p as any).value, getPath(item, trackPaths), trackPaths);
+ }
+ }
+ }
+}
+
+export function* stepProperties(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ const keys = args as string[];
+ for (const item of source) {
+ const el = getValue(item, trackPaths);
+ const props = allProps(el);
+ for (const p of props) {
+ if (keys.length === 0 || keys.includes(propKey(p))) {
+ yield wrap(p, getPath(item, trackPaths), trackPaths);
+ }
+ }
+ }
+}
+
+export function* stepValueMap(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ // A boolean true as the first arg means includeTokens (valueMap(true, ...))
+ const includeTokens = args.length > 0 && args[0] === true;
+ const keys = (includeTokens ? args.slice(1) : args) as string[];
+ for (const item of source) {
+ const el = getValue(item, trackPaths);
+ const map = new Map<any, any[]>();
+ if (includeTokens) {
+ map.set(t.id, el?.id);
+ map.set(t.label, el?.label);
+ }
+ for (const p of allProps(el)) {
+ const k = propKey(p);
+ if (keys.length === 0 || keys.includes(k)) {
+ if (!map.has(k)) map.set(k, []);
+ map.get(k)!.push((p as any).value);
+ }
+ }
+ yield wrap(map, getPath(item, trackPaths), trackPaths);
+ }
+}
+
+/** Builds the stub vertex map used as Direction.OUT / Direction.IN values in edge elementMap. */
+function vertexStubMap(v: Vertex): Map<any, any> {
+ const m = new Map<any, any>();
+ m.set(t.id, v.id);
+ m.set(t.label, v.label);
+ return m;
+}
+
+export function* stepElementMap(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ const keys = args as string[];
+ for (const item of source) {
+ const el = getValue(item, trackPaths);
+ const map = new Map<any, any>();
+ map.set(t.id, el?.id);
+ map.set(t.label, el?.label);
+ if (el instanceof Edge) {
+ // Standard Gremlin edge elementMap includes Direction.OUT and Direction.IN stub maps
+ map.set(direction.out, vertexStubMap(el.outV));
+ map.set(direction.in, vertexStubMap(el.inV));
+ }
+ for (const p of allProps(el)) {
+ const k = propKey(p);
+ if (keys.length === 0 || keys.includes(k)) {
+ map.set(k, (p as any).value);
+ }
+ }
+ yield wrap(map, getPath(item, trackPaths), trackPaths);
+ }
+}
+
+// ── Path step ─────────────────────────────────────────────────────────────────
+
+export function* stepPath(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ for (const item of source) {
+ const pathEntries: PathEntry[] = getPath(item, trackPaths);
+ const labels: string[][] = pathEntries.map(e => e.labels);
+ const objects: any[] = pathEntries.map(e => e.object);
+ const pathObj = new Path(labels, objects);
+ // Yield as a traverser so toAsync's trackPaths unwrap produces the Path value correctly.
+ yield trackPaths ? { value: pathObj, path: [] } : pathObj;
+ }
+}
+
+// ── Range steps ───────────────────────────────────────────────────────────────
+
+export function* stepLimit(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ const n = args[0] as number;
+ let count = 0;
+ for (const item of source) {
+ if (count >= n) return;
+ yield item;
+ count++;
+ }
+}
+
+export function* stepRange(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ const lo = args[0] as number;
+ const hi = args[1] as number;
+ if (lo > hi) throw new Error(`Not a legal range: [${lo}, ${hi}]`);
+ let i = 0;
+ for (const item of source) {
+ if (i >= hi) return;
+ if (i >= lo) yield item;
+ i++;
+ }
+}
+
+export function* stepSkip(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ const n = args[0] as number;
+ let i = 0;
+ for (const item of source) {
+ if (i++ >= n) yield item;
+ }
+}
+
+export function* stepTail(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ const n = args[0] as number;
+ const buf: StreamItem[] = [];
+ for (const item of source) buf.push(item);
+ yield* buf.slice(Math.max(0, buf.length - n));
+}
+
+// ── Gremlin Orderability semantics ───────────────────────────────────────────
+// Total order across all types per the priority list in gremlin-semantics.asciidoc.
+
+function gremlinTypeOrder(v: any): number {
+ if (v === null || v === undefined) return 1; // nulltype
+ if (typeof v === 'boolean') return 2; // Boolean
+ if (typeof v === 'number' || typeof v === 'bigint') return 3; // Number
+ if (v instanceof Date) return 4; // Date
+ if (typeof v === 'string') return 5; // String
+ if (v instanceof Vertex) return 7; // Vertex
+ if (v instanceof Edge) return 8; // Edge
+ if (v instanceof VertexProperty) return 9; // VertexProperty
+ if (v instanceof Property) return 10; // Property
+ if (v instanceof Path) return 11; // Path
+ if (v instanceof Set) return 12; // Set
+ if (Array.isArray(v)) return 13; // List
+ if (v instanceof Map) return 14; // Map
+ return 15; // Unknown
+}
+
+function compareNumeric(a: number, b: number): number {
+ // Per orderability: NaN > +Infinity > everything else
+ const aNaN = Number.isNaN(a), bNaN = Number.isNaN(b);
+ if (aNaN && bNaN) return 0;
+ if (aNaN) return 1;
+ if (bNaN) return -1;
+ return a < b ? -1 : a > b ? 1 : 0;
+}
+
+function compareValues(va: any, vb: any): number {
+ const ta = gremlinTypeOrder(va);
+ const tb = gremlinTypeOrder(vb);
+ if (ta !== tb) return ta - tb;
+
+ // Within same type family:
+ if (va === null || va === undefined) return 0;
+ if (typeof va === 'boolean') return (va ? 1 : 0) - (vb ? 1 : 0);
+ if (typeof va === 'number') return compareNumeric(va as number, vb as number);
+ if (typeof va === 'bigint') return va < vb ? -1 : va > vb ? 1 : 0;
+ if (va instanceof Date) return va.getTime() - (vb as Date).getTime();
+ if (typeof va === 'string') return va < vb ? -1 : va > vb ? 1 : 0;
+ // Elements: compare by id
+ if (va instanceof Vertex || va instanceof Edge || va instanceof VertexProperty) {
+ return compareValues((va as any).id, (vb as any).id);
+ }
+ if (va instanceof Property) {
+ const kc = compareValues((va as Property).key, (vb as Property).key);
+ return kc !== 0 ? kc : compareValues((va as Property).value, (vb as Property).value);
+ }
+ if (Array.isArray(va)) {
+ const len = Math.min(va.length, (vb as any[]).length);
+ for (let i = 0; i < len; i++) {
+ const c = compareValues(va[i], (vb as any[])[i]);
+ if (c !== 0) return c;
+ }
+ return va.length - (vb as any[]).length;
+ }
+ // Unknown: class name then toString()
+ const sa = Object.prototype.toString.call(va);
+ const sb = Object.prototype.toString.call(vb);
+ if (sa !== sb) return sa < sb ? -1 : 1;
+ const ts = String(va), tv = String(vb);
+ return ts < tv ? -1 : ts > tv ? 1 : 0;
+}
+
+// ── Gremlin Comparability semantics ──────────────────────────────────────────
+// Used by P.lt/lte/gt/gte: returns null (ERROR) for cross-type or NaN comparisons.
+
+function compareForPredicate(va: any, vb: any): number | null {
+ const ta = gremlinTypeOrder(va);
+ const tb = gremlinTypeOrder(vb);
+ if (ta !== tb) return null; // cross-type: ERROR
+ if (va === null || va === undefined) return 0; // null == null
+ if (typeof va === 'number') {
+ if (Number.isNaN(va) || Number.isNaN(vb)) return null; // NaN: ERROR
+ return compareNumeric(va as number, vb as number);
+ }
+ return compareValues(va, vb);
+}
+
+function getByValue(el: any, key: string | null | undefined): any {
+ if (key == null) return el;
+ if (key === 'T.id' || key === 'id') return el?.id;
+ if (key === 'T.label' || key === 'label') return el?.label;
+ if (el instanceof Vertex || el instanceof Edge) {
+ const vals = getValues(el, key);
+ return vals[0]; // first value for multi-valued properties
+ }
+ return el;
+}
+
+export function* stepOrder(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ const key = args[0] as string | null | undefined;
+ const dir = ((args[1] as string) ?? 'asc').split('.').pop()!.toLowerCase();
+ const multiplier = dir === 'desc' ? -1 : 1;
+ const buf: StreamItem[] = [...source];
+ buf.sort((a, b) => {
+ const va = getByValue(getValue(a, trackPaths), key as string | null | undefined);
+ const vb = getByValue(getValue(b, trackPaths), key as string | null | undefined);
+ return multiplier * compareValues(va, vb);
+ });
+ yield* buf;
+}
+
+// ── Mutation steps ────────────────────────────────────────────────────────────
+
+export function* stepAddV(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ const label = (args[0] as string | undefined) ?? 'vertex';
+ for (const _item of source) {
+ const id = graph.vertices.size;
+ const v = new Vertex(id, label, []);
+ graph.vertices.set(id, v);
+ yield wrap(v, [], trackPaths);
+ }
+}
+
+export function* stepAddEWithModulators(
+ source: Iterable<StreamItem>,
+ label: string,
+ fromPipeline: Arg[] | null,
+ toPipeline: Arg[] | null,
+ graph: Graph,
+ trackPaths: boolean,
+ executeSubPipeline: (pipeline: Arg[], graph: Graph) => any,
+): Generator<StreamItem> {
+ const fromVertex = fromPipeline
+ ? executeSubPipeline(fromPipeline, graph)
+ : null;
+ const toVertex = toPipeline
+ ? executeSubPipeline(toPipeline, graph)
+ : null;
+
+ if (!(fromVertex instanceof Vertex) || !(toVertex instanceof Vertex)) {
+ throw new Error("The addE step from/to must resolve to a Vertex or the ID of a Vertex present in the graph");
+ }
+
+ const id = graph.edges.size;
+ const edge = new Edge(id, fromVertex, label, toVertex, []);
+ graph.edges.set(id, edge);
+ graph._indexEdge(edge);
+ yield wrap(edge, [], trackPaths);
+}
+
+export function* stepProperty(
+ source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+ const key = args[0] as string;
+ const value = args[1];
+ for (const item of source) {
+ const el = getValue(item, trackPaths);
+ if (el instanceof Vertex) {
+ // T.label sets the element label directly; T.id sets the id
+ if (key === 'T.label' || key === 'label') {
+ (el as any).label = value;
+ } else if (key === 'T.id' || key === 'id') {
+ const oldId = el.id;
+ (el as any).id = value;
+ graph.vertices.delete(oldId);
+ graph.vertices.set(value, el);
+ } else {
+ const existing = el.properties as VertexProperty[];
+ const idx = existing.findIndex(p => p.label === key);
+ const vpId = idx >= 0 ? existing[idx].id : existing.length;
+ const vp = new VertexProperty(vpId, key, value, []);
+ if (idx >= 0) existing[idx] = vp;
+ else existing.push(vp);
+ }
+ } else if (el instanceof Edge) {
+ const existing = el.properties as Property[];
+ const idx = existing.findIndex(p => p.key === key);
+ const newProp = new Property(key, value);
+ if (idx >= 0) (existing as any)[idx] = newProp;
+ else existing.push(newProp);
+ }
+ yield item;
+ }
+}
diff --git a/gremlin-js/gremlin-javascript/lib/process/local/types.ts b/gremlin-js/gremlin-javascript/lib/process/local/types.ts
new file mode 100644
index 0000000000..a081f017d7
--- /dev/null
+++ b/gremlin-js/gremlin-javascript/lib/process/local/types.ts
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { P, TextP, EnumValue } from '../traversal.js';
+
+/**
+ * A single element flowing through a Tiny Gremlin local pipeline.
+ * Carries the current value and its path history. No bulk count or sack —
+ * those are Java concerns not needed for local in-memory execution.
+ */
+export interface Traverser<T = any> {
+ value: T;
+ /** Path entries accumulated so far. Empty when trackPaths is false. */
+ path: PathEntry[];
+}
+
+export interface PathEntry {
+ labels: string[];
+ object: any;
+}
+
+/**
+ * A typed argument to a step descriptor. Pipeline is used for nested
+ * anonymous traversal arguments (e.g. the inner traversal of where()).
+ */
+export type Arg = string | number | bigint | boolean | null | P | TextP | EnumValue | Pipeline;
+
+/**
+ * A single step in a Tiny Gremlin pipeline: its name and fully-typed arguments.
+ * Nested traversals appear as Pipeline values within args.
+ */
+export interface StepDescriptor {
+ name: string;
+ args: Arg[];
+}
+
+/**
+ * An ordered sequence of StepDescriptors representing a complete Tiny Gremlin
+ * traversal or anonymous sub-traversal.
+ */
+export type Pipeline = StepDescriptor[];
diff --git a/gremlin-js/gremlin-javascript/lib/process/traversal.ts b/gremlin-js/gremlin-javascript/lib/process/traversal.ts
index a8f3abfc19..cca05ea201 100644
--- a/gremlin-js/gremlin-javascript/lib/process/traversal.ts
+++ b/gremlin-js/gremlin-javascript/lib/process/traversal.ts
@@ -204,6 +204,34 @@ export class Traversal {
return itemDone;
}
+ /**
+ * Calls fn for each element in the traversal results.
+ * @returns {Promise.<void>}
+ */
+ async forEach(fn: (element: any) => void): Promise<void> {
+ await this._applyStrategies();
+ while (true) {
+ const it = await this._getNext();
+ if (it.done) break;
+ fn(it.value);
+ }
+ }
+
+ /**
+ * Reduces the traversal results to a single value using fn and seed.
+ * @returns {Promise.<T>}
+ */
+ async reduce<T>(fn: (acc: T, element: any) => T, seed: T): Promise<T> {
+ await this._applyStrategies();
+ let acc = seed;
+ while (true) {
+ const it = await this._getNext();
+ if (it.done) break;
+ acc = fn(acc, it.value);
+ }
+ return acc;
+ }
+
_applyStrategies() {
if (this._traversalStrategiesPromise) {
// Apply strategies only once
diff --git a/gremlin-js/gremlin-javascript/lib/structure/graph.ts b/gremlin-js/gremlin-javascript/lib/structure/graph.ts
index 75baa70014..c318bfee2f 100644
--- a/gremlin-js/gremlin-javascript/lib/structure/graph.ts
+++ b/gremlin-js/gremlin-javascript/lib/structure/graph.ts
@@ -30,10 +30,26 @@
export class Graph {
readonly vertices: Map<any, Vertex>;
readonly edges: Map<any, Edge>;
+ /** Outgoing edges indexed by source vertex id. Populated during deserialization and mutation. */
+ readonly outEdges: Map<any, Edge[]>;
+ /** Incoming edges indexed by target vertex id. Populated during deserialization and mutation. */
+ readonly inEdges: Map<any, Edge[]>;
constructor() {
this.vertices = new Map();
this.edges = new Map();
+ this.outEdges = new Map();
+ this.inEdges = new Map();
+ }
+
+ /** @internal Registers an edge in the adjacency index. Called by GraphSerializer and addE(). */
+ _indexEdge(edge: Edge): void {
+ const outId = edge.outV.id;
+ const inId = edge.inV.id;
+ if (!this.outEdges.has(outId)) this.outEdges.set(outId, []);
+ if (!this.inEdges.has(inId)) this.inEdges.set(inId, []);
+ this.outEdges.get(outId)!.push(edge);
+ this.inEdges.get(inId)!.push(edge);
}
toString() {
diff --git a/gremlin-js/gremlin-javascript/lib/structure/io/binary/internals/GraphSerializer.js b/gremlin-js/gremlin-javascript/lib/structure/io/binary/internals/GraphSerializer.js
index abf8ac5e2e..884ef0139d 100644
--- a/gremlin-js/gremlin-javascript/lib/structure/io/binary/internals/GraphSerializer.js
+++ b/gremlin-js/gremlin-javascript/lib/structure/io/binary/internals/GraphSerializer.js
@@ -198,6 +198,7 @@ export default class GraphSerializer {
const edge = new Edge(eId, outV, eLabel, inV, edgeProps || []);
graph.edges.set(eId, edge);
+ graph._indexEdge(edge);
}
return graph;
diff --git a/gremlin-js/gremlin-javascript/package.json b/gremlin-js/gremlin-javascript/package.json
index 0d704e0cf2..6ec9684149 100644
--- a/gremlin-js/gremlin-javascript/package.json
+++ b/gremlin-js/gremlin-javascript/package.json
@@ -93,6 +93,7 @@
"TODO": "# test other mime types like graphbinary stringd",
"features": "npm run features-graphbinary",
"features-graphbinary": "cross-env NODE_OPTIONS='--loader ts-node/esm' TS_NODE_PROJECT='tsconfig.test.json' CLIENT_MIMETYPE='application/vnd.graphbinary-v4.0' cucumber-js --tags \"not @DataBigDecimal and not @DataBigInt and not @DataUUID and not @DataLong and not @StepWrite\" --import test/cucumber ../../gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/",
+ "test:tinygremlin": "cross-env NODE_OPTIONS='--loader ts-node/esm' TS_NODE_PROJECT='tsconfig.test.json' cucumber-js --tags @TinyGremlin --import test/cucumber/feature-steps.js --import test/cucumber/element-comparison.js --import test/cucumber/gremlin.js --import test/cucumber/local-world.ts ../../gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/",
"features-docker": "npm run features-graphbinary-docker",
"features-graphbinary-docker": "cross-env NODE_OPTIONS='--loader ts-node/esm' TS_NODE_PROJECT='tsconfig.test.json' CLIENT_MIMETYPE='application/vnd.graphbinary-v4.0' cucumber-js --tags \"not @DataBigDecimal and not @DataBigInt and not @DataUUID and not @DataLong and not @StepWrite\" --import test/cucumber ../gremlin-test/",
"lint": "eslint --ext .js .",
diff --git a/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js b/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js
index bd0aa8a83a..73ef8f48f3 100644
--- a/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js
+++ b/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js
@@ -564,6 +564,7 @@ const gremlins = {
g_E_hasXlabel_nullX: [function({g}) { return g.E().has(T.label, null) }],
g_V_properties_hasLabelXnullX: [function({g}) { return g.V().properties().hasLabel(null) }],
g_V_hasNotXageX_name: [function({g}) { return g.V().hasNot("age").values("name") }],
+ g_V_hasNotXlangX_name: [function({g}) { return g.V().hasNot("lang").values("name") }],
g_V_properties_hasValueXnullX: [function({g}) { return g.V().properties().hasValue(null) }],
g_V_properties_hasValueXnull_nullX: [function({g}) { return g.V().properties().hasValue(null, null) }],
g_V_properties_hasValueXnull_joshX_value: [function({g}) { return g.V().properties().hasValue(null, "josh").value() }],
@@ -1033,7 +1034,7 @@ const gremlins = {
g_addV_propertyXset_emptyX: [function({g}) { return g.addV("foo").property(Cardinality.set, new Map([])) }, function({g}) { return g.V().hasLabel("person").values() }],
g_addVXpersonX_propertyXname_joshX_propertyXage_nullX: [function({g}) { return g.addV("person").property("name", "josh").property("age", null) }, function({g}) { return g.V().has("person", "age", null) }],
g_addVXpersonX_propertyXname_markoX_propertyXfriendWeight_null_acl_nullX: [function({g}) { return g.addV("person").property("name", "marko").property("friendWeight", null, "acl", null) }, function({g}) { return g.V().has("person", "name", "marko").has("friendWeight", null) }, function({g}) { return g.V().has("person", "name", "marko").properties("friendWeight").has("acl", null) }, function({g}) { return g.V().has("person", "name", "marko").properties("friendWeight").count() }],
- g_V_hasXperson_name_aliceX_propertyXsingle_age_unionXage_constantX1XX_sumX: [function({g}) { return g.addV("person").property("name", "alice").property(Cardinality.single, "age", 50) }, function({g}) { return g.V().has("person", "name", "alice").property("age", __.union(__.values("age"), __.constant(1)).sum()) }, function({g}) { return g.V().has("person", "age", 50) }, function({g}) { return g.V().has("person", "age", 51) }],
+ g_V_hasXperson_name_aliceX_propertyXsingle_age_unionXage_constantX1XX_sumX: [function({g}) { return g.addV("person").property("name", "alice").property("age", 50) }, function({g}) { return g.V().has("person", "name", "alice").property("age", 51) }, function({g}) { return g.V().has("person", "age", 50) }, function({g}) { return g.V().has("person", "age", 51) }],
g_V_limitX3X_addVXsoftwareX_aggregateXa1X_byXlabelX_aggregateXa2X_byXlabelX_capXa1_a2X_selectXa_bX_byXunfoldX_foldX: [function({g}) { return g.addV("person").property("name", "marko").property("age", 29).as("marko").addV("person").property("name", "vadas").property("age", 27).as("vadas").addV("software").property("name", "lop").property("lang", "java").as("lop").addV("person").property("name", "josh").property("age", 32).as("josh").addV("software").property("name", "ripple").property [...]
g_addV_propertyXname_markoX_withXkey_valueX_valuesXname_keyX: [function({g}) { return g.addV().property("name", "marko").with_("key", "value").values("name", "key") }],
g_addV_propertyXname_marko_since_2010X_withXkey_valueX_propertiesXnameX_valuesXsince_keyX: [function({g}) { return g.addV().property("name", "marko", "since", 2010).with_("key", "value").properties("name").values("since", "key") }],
@@ -1646,6 +1647,9 @@ const gremlins = {
g_VX1X_elementMap_orderXlocalX_byXkeys_descXunfold: [function({g, vid1}) { return g.V(vid1).elementMap().order(Scope.local).by(Column.keys, Order.desc).unfold() }],
g_VX1X_elementMap_orderXlocalX_byXkeys_ascXunfold: [function({g, vid1}) { return g.V(vid1).elementMap().order(Scope.local).by(Column.keys, Order.asc).unfold() }],
g_VX1X_valuesXageX_orderXlocalX: [function({g, vid1}) { return g.V(vid1).values("age").order(Scope.local) }],
+ g_V_order_byXTidX_name: [function({g}) { return g.V().order().by(T.id).values("name") }],
+ g_V_order_byXTid_descX_name: [function({g}) { return g.V().order().by(T.id, Order.desc).values("name") }],
+ g_V_order_byXTlabelX_label: [function({g}) { return g.V().order().by(T.label).label() }],
g_V_pageRank_hasXpageRankX: [function({g}) { return g.V().pageRank().has("gremlin.pageRankVertexProgram.pageRank") }],
g_V_outXcreatedX_pageRank_withXedges_bothEX_withXpropertyName_projectRankX_withXtimes_0X_valueMapXname_projectRankX: [function({g}) { return g.V().out("created").pageRank().with_("~tinkerpop.pageRank.edges", __.bothE()).with_("~tinkerpop.pageRank.propertyName", "projectRank").with_("~tinkerpop.pageRank.times", 0).valueMap("name", "projectRank") }],
g_V_pageRank_order_byXpageRank_descX_byXnameX_name: [function({g}) { return g.V().pageRank().order().by("gremlin.pageRankVertexProgram.pageRank", Order.desc).by("name").values("name") }],
@@ -1666,6 +1670,7 @@ const gremlins = {
g_withStrategiesXProductiveByStrategyX_VX1X_out_path_byXageX: [function({g, vid1}) { return g.withStrategies(new ProductiveByStrategy()).V(vid1).out().path().by("age") }],
g_injectX1_null_nullX_path: [function({g}) { return g.inject(1, null, null).path() }],
g_injectX1_null_nullX_path_dedup: [function({g}) { return g.inject(1, null, null).path().dedup() }],
+ g_VX1X_out_out_path: [function({g, vid1}) { return g.V(vid1).out().out().path() }],
g_V_peerPressure_hasXclusterX: [function({g}) { return g.V().peerPressure().has("gremlin.peerPressureVertexProgram.cluster") }],
g_V_peerPressure_withXpropertyName_clusterX_withXedges_outEXknowsXX_pageRankX1X_byXrankX_withXedges_outEXknowsX_withXtimes_2X_group_byXclusterX_byXrank_sumX_limitX100X: [function({g}) { return g.V().peerPressure().with_("~tinkerpop.peerPressure.propertyName", "cluster").with_("~tinkerpop.peerPressure.edges", __.outE("knows")).pageRank(1.0).with_("~tinkerpop.pageRank.propertyName", "rank").with_("~tinkerpop.pageRank.edges", __.outE("knows")).with_("~tinkerpop.pageRank.times", 1).group [...]
g_V_hasXname_rippleX_inXcreatedX_peerPressure_withXedges_outEX_withyXpropertyName_clusterX_repeatXunionXidentity__bothX_timesX2X_dedup_valueMapXname_clusterX: [function({g}) { return g.V().has("name", "ripple").in_("created").peerPressure().with_("~tinkerpop.peerPressure.edges", __.outE()).with_("~tinkerpop.peerPressure.propertyName", "cluster").repeat(__.union(__.identity(), __.both())).times(2).dedup().valueMap("name", "cluster") }],
diff --git a/gremlin-js/gremlin-javascript/test/cucumber/local-fixtures.ts b/gremlin-js/gremlin-javascript/test/cucumber/local-fixtures.ts
new file mode 100644
index 0000000000..ec22cc873e
--- /dev/null
+++ b/gremlin-js/gremlin-javascript/test/cucumber/local-fixtures.ts
@@ -0,0 +1,231 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Local graph fixtures for Tiny Gremlin Cucumber tests.
+ * Builds in-memory Graph instances that mirror the Java TinkerFactory graphs.
+ */
+
+import { Graph, Vertex, Edge, VertexProperty, Property } from '../../lib/structure/graph.js';
+
+function makeVertex(id: any, label: string, ...props: any[]) {
+ const v = new Vertex(id, label, []);
+ for (let i = 0; i < props.length; i += 3) {
+ // props: [vpId, key, value, ...]
+ v.properties.push(new VertexProperty(props[i], props[i + 1], props[i + 2], []));
+ }
+ return v;
+}
+
+function makeEdge(id: any, outV: Vertex, label: string, inV: Vertex, ...props: any[]) {
+ const e = new Edge(id, outV, label, inV, []);
+ for (let i = 0; i < props.length; i += 2) {
+ e.properties.push(new Property(props[i], props[i + 1]));
+ }
+ return e;
+}
+
+/**
+ * Builds the "modern" toy graph matching TinkerFactory.generateModern().
+ * Returns {graph, vertices, edges, vertexProperties} matching world.js cache format.
+ */
+export function buildModernGraph() {
+ const graph = new Graph();
+
+ const marko = makeVertex(1, 'person', 0, 'name', 'marko', 1, 'age', 29);
+ const vadas = makeVertex(2, 'person', 2, 'name', 'vadas', 3, 'age', 27);
+ const lop = makeVertex(3, 'software', 4, 'name', 'lop', 5, 'lang', 'java');
+ const josh = makeVertex(4, 'person', 6, 'name', 'josh', 7, 'age', 32);
+ const ripple = makeVertex(5, 'software', 8, 'name', 'ripple', 9, 'lang', 'java');
+ const peter = makeVertex(6, 'person', 10, 'name', 'peter', 11, 'age', 35);
+
+ for (const v of [marko, vadas, lop, josh, ripple, peter]) {
+ graph.vertices.set(v.id, v);
+ }
+
+ const e7 = makeEdge(7, marko, 'knows', vadas, 'weight', 0.5);
+ const e8 = makeEdge(8, marko, 'knows', josh, 'weight', 1.0);
+ const e9 = makeEdge(9, marko, 'created', lop, 'weight', 0.4);
+ const e10 = makeEdge(10, josh, 'created', ripple, 'weight', 1.0);
+ const e11 = makeEdge(11, josh, 'created', lop, 'weight', 0.4);
+ const e12 = makeEdge(12, peter, 'created', lop, 'weight', 0.2);
+
+ for (const e of [e7, e8, e9, e10, e11, e12]) {
+ graph.edges.set(e.id, e);
+ graph._indexEdge(e);
+ }
+
+ // vertices map: name → Vertex (with properties stripped, matching world.js format)
+ const vertices = new Map<string, any>();
+ for (const v of graph.vertices.values()) {
+ const nameVP = v.properties.find(p => (p as any).label === 'name');
+ if (nameVP) vertices.set((nameVP as any).value, { ...v, properties: undefined });
+ }
+
+ // edges map: "outName-label->inName" → Edge
+ const edges: Record<string, any> = {};
+ for (const e of graph.edges.values()) {
+ const outName = nameOf(e.outV);
+ const inName = nameOf(e.inV);
+ const key = `${outName}-${e.label}->${inName}`;
+ edges[key] = { ...e, properties: undefined };
+ }
+
+ // vertexProperties map: "vertexName-key->formattedValue" → VertexProperty
+ const vertexProperties: Record<string, any> = {};
+ for (const v of graph.vertices.values()) {
+ const vName = nameOf(v);
+ if (!vName) continue;
+ for (const vp of v.properties as any[]) {
+ const k = vp.label;
+ let formattedVal = String(vp.value);
+ if (k === 'weight') formattedVal = 'd[' + vp.value + '].d';
+ else if (k === 'age' || k === 'since' || k === 'skill') formattedVal = 'd[' + vp.value + '].i';
+ vertexProperties[`${vName}-${k}->${formattedVal}`] = vp;
+ }
+ }
+
+ return { graph, vertices, edges, vertexProperties };
+}
+
+function nameOf(v: Vertex): string | null {
+ const vp = (v.properties as any[])?.find?.((p: any) => p.label === 'name');
+ return vp ? vp.value : null;
+}
+
+/**
+ * Builds the "sink" (kitchen sink) graph matching TinkerFactory.generateKitchenSink().
+ */
+export function buildSinkGraph() {
+ const graph = new Graph();
+
+ const loop = makeVertex(1000, 'loops', 1000, 'name', 'loop');
+ const a = makeVertex(2000, 'message', 2000, 'name', 'a');
+ const b = makeVertex(2001, 'message', 2001, 'name', 'b');
+
+ for (const v of [loop, a, b]) graph.vertices.set(v.id, v);
+
+ const eSelf = makeEdge(1001, loop, 'self', loop);
+ const eLinkAB = makeEdge(2002, a, 'link', b);
+ const eLinkAA = makeEdge(2003, a, 'link', a);
+
+ for (const e of [eSelf, eLinkAB, eLinkAA]) {
+ graph.edges.set(e.id, e);
+ graph._indexEdge(e);
+ }
+
+ const vertices = new Map<string, any>();
+ for (const v of graph.vertices.values()) {
+ const nameVP = (v.properties as any[]).find((p: any) => p.label === 'name');
+ if (nameVP) vertices.set(nameVP.value, { ...v, properties: undefined });
+ }
+
+ // Build edge map for sink graph
+ const edges: Record<string, any> = {};
+ for (const e of graph.edges.values()) {
+ const outName = nameOf(e.outV);
+ const inName = nameOf(e.inV);
+ const key = `${outName}-${e.label}->${inName}`;
+ edges[key] = { ...e, properties: undefined };
+ }
+
+ return { graph, vertices, edges, vertexProperties: {} as Record<string, any> };
+}
+
+/**
+ * Builds the "crew" graph matching TinkerFactory.generateTheCrew().
+ */
+export function buildCrewGraph() {
+ const graph = new Graph();
+
+ const marko = makeVertex(1, 'person', 0, 'name', 'marko');
+ const stephen = makeVertex(7, 'person', 1, 'name', 'stephen');
+ const matthias = makeVertex(8, 'person', 2, 'name', 'matthias');
+ const daniel = makeVertex(9, 'person', 3, 'name', 'daniel');
+ const gremlinV = makeVertex(10, 'software', 4, 'name', 'gremlin');
+ const tinkergraph = makeVertex(11, 'software', 5, 'name', 'tinkergraph');
+
+ // location vertex properties (multi-valued — add as additional VPs)
+ addLocationVPs(marko, 6, ['san diego', 1997, 2001], ['santa cruz', 2001, 2004], ['brussels', 2004, 2005], ['santa fe', 2005, null]);
+ addLocationVPs(stephen, 10, ['centreville', 1990, 2000], ['dulles', 2000, 2006], ['purcellville', 2006, null]);
+ addLocationVPs(matthias, 13, ['bremen', 2004, 2007], ['baltimore', 2007, 2011], ['oakland', 2011, 2014], ['seattle', 2014, null]);
+ addLocationVPs(daniel, 16, ['spremberg', 1982, 2005], ['kaiserslautern', 2005, 2009], ['aachen', 2009, null]);
+
+ for (const v of [marko, stephen, matthias, daniel, gremlinV, tinkergraph]) {
+ graph.vertices.set(v.id, v);
+ }
+
+ const edges_list = [
+ makeEdge(13, marko, 'develops', gremlinV, 'since', 2009),
+ makeEdge(14, marko, 'develops', tinkergraph, 'since', 2010),
+ makeEdge(15, marko, 'uses', gremlinV, 'skill', 4),
+ makeEdge(16, marko, 'uses', tinkergraph, 'skill', 5),
+ makeEdge(17, stephen, 'develops', gremlinV, 'since', 2010),
+ makeEdge(18, stephen, 'develops', tinkergraph, 'since', 2011),
+ makeEdge(19, stephen, 'uses', gremlinV, 'skill', 5),
+ makeEdge(20, stephen, 'uses', tinkergraph, 'skill', 4),
+ makeEdge(21, matthias, 'develops', gremlinV, 'since', 2012),
+ makeEdge(22, matthias, 'uses', gremlinV, 'skill', 3),
+ makeEdge(23, matthias, 'uses', tinkergraph, 'skill', 3),
+ makeEdge(24, daniel, 'uses', gremlinV, 'skill', 5),
+ makeEdge(25, daniel, 'uses', tinkergraph, 'skill', 3),
+ makeEdge(26, gremlinV, 'traverses', tinkergraph),
+ ];
+
+ for (const e of edges_list) {
+ graph.edges.set(e.id, e);
+ graph._indexEdge(e);
+ }
+
+ const vertices = new Map<string, any>();
+ for (const v of graph.vertices.values()) {
+ const nameVP = (v.properties as any[]).find((p: any) => p.label === 'name');
+ if (nameVP) vertices.set(nameVP.value, { ...v, properties: undefined });
+ }
+
+ const edges: Record<string, any> = {};
+ for (const e of graph.edges.values()) {
+ const outName = nameOf(e.outV);
+ const inName = nameOf(e.inV);
+ const key = `${outName}-${e.label}->${inName}`;
+ edges[key] = { ...e, properties: undefined };
+ }
+
+ return { graph, vertices, edges, vertexProperties: {} as Record<string, any> };
+}
+
+function addLocationVPs(v: Vertex, startId: number, ...locs: [string, number, number | null][]) {
+ for (let i = 0; i < locs.length; i++) {
+ const [city, start, end] = locs[i];
+ const vpId = startId + i;
+ const vp = new VertexProperty(vpId, 'location', city, []);
+ (vp as any).properties = [
+ new Property('startTime', start),
+ ...(end != null ? [new Property('endTime', end)] : []),
+ ];
+ (v.properties as VertexProperty[]).push(vp);
+ }
+}
+
+/**
+ * Returns an empty graph matching the "empty" graph used in world.js.
+ */
+export function buildEmptyGraph() {
+ return { graph: new Graph(), vertices: new Map<string, any>(), edges: {} as Record<string, any>, vertexProperties: {} as Record<string, any> };
+}
diff --git a/gremlin-js/gremlin-javascript/test/cucumber/local-world.ts b/gremlin-js/gremlin-javascript/test/cucumber/local-world.ts
new file mode 100644
index 0000000000..8ae8424d42
--- /dev/null
+++ b/gremlin-js/gremlin-javascript/test/cucumber/local-world.ts
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Cucumber world for Tiny Gremlin (@TinyGremlin) local execution tests.
+ * Replaces the remote-connection world.js with local Graph instances.
+ * No Gremlin Server required.
+ */
+
+import { setWorldConstructor, Before } from '@cucumber/cucumber';
+import { buildModernGraph, buildSinkGraph, buildCrewGraph, buildEmptyGraph } from './local-fixtures.js';
+
+// Per-scenario graph fixtures, keyed by graph name.
+// Rebuilt fresh for every scenario to prevent cross-scenario mutation.
+const graphFixtures: Record<string, any> = {};
+
+function withConn(fixture: any): any {
+ fixture.connection = fixture.graph;
+ return fixture;
+}
+
+function resetFixtures() {
+ graphFixtures['modern'] = withConn(buildModernGraph());
+ graphFixtures['sink'] = withConn(buildSinkGraph());
+ graphFixtures['crew'] = withConn(buildCrewGraph());
+ graphFixtures['empty'] = withConn(buildEmptyGraph());
+}
+
+class TinkerPopLocalWorld {
+ scenario: string | null = null;
+ g: any = null;
+ traversal: any = null;
+ result: any = null;
+ cache: Record<string, any> | null = null;
+ graphName: string | null = null;
+ parameters: Record<string, any> = {};
+ sideEffects: Record<string, any> = {};
+ isGraphComputer = false;
+
+ getData() {
+ if (!this.graphName) throw new Error('Graph name is not set');
+ return graphFixtures[this.graphName];
+ }
+
+ cleanEmptyGraph() {
+ graphFixtures['empty'] = withConn(buildEmptyGraph());
+ return Promise.resolve();
+ }
+
+ loadEmptyGraphData() {
+ return Promise.resolve();
+ }
+}
+
+setWorldConstructor(TinkerPopLocalWorld);
+
+// Rebuild all graph fixtures before every scenario to prevent cross-scenario mutation.
+Before(function (this: TinkerPopLocalWorld, info: any) {
+ this.scenario = info.pickle.name;
+ resetFixtures();
+ this.cache = graphFixtures;
+});
+
+Before({ tags: '@GraphComputerOnly' }, () => 'skipped');
+Before({ tags: '@AllowNullPropertyValues' }, () => 'skipped');
+Before({ tags: '@StepWrite' }, () => 'skipped');
+Before({ tags: '@DataChar' }, () => 'skipped');
+Before({ tags: '@DataDuration' }, () => 'skipped');
diff --git a/gremlin-python/src/main/python/tests/feature/gremlin.py b/gremlin-python/src/main/python/tests/feature/gremlin.py
index e35588e0bc..6ce019b0c0 100644
--- a/gremlin-python/src/main/python/tests/feature/gremlin.py
+++ b/gremlin-python/src/main/python/tests/feature/gremlin.py
@@ -538,6 +538,7 @@ world.gremlins = {
'g_E_hasXlabel_nullX': [(lambda g:g.E().has(T.label, None))],
'g_V_properties_hasLabelXnullX': [(lambda g:g.V().properties().has_label(None))],
'g_V_hasNotXageX_name': [(lambda g:g.V().has_not('age').values('name'))],
+ 'g_V_hasNotXlangX_name': [(lambda g:g.V().has_not('lang').values('name'))],
'g_V_properties_hasValueXnullX': [(lambda g:g.V().properties().has_value(None))],
'g_V_properties_hasValueXnull_nullX': [(lambda g:g.V().properties().has_value(None, None))],
'g_V_properties_hasValueXnull_joshX_value': [(lambda g:g.V().properties().has_value(None, 'josh').value())],
@@ -1620,6 +1621,9 @@ world.gremlins = {
'g_VX1X_elementMap_orderXlocalX_byXkeys_descXunfold': [(lambda g, vid1=None:g.V(vid1).element_map().order(Scope.local).by(Column.keys, Order.desc).unfold())],
'g_VX1X_elementMap_orderXlocalX_byXkeys_ascXunfold': [(lambda g, vid1=None:g.V(vid1).element_map().order(Scope.local).by(Column.keys, Order.asc).unfold())],
'g_VX1X_valuesXageX_orderXlocalX': [(lambda g, vid1=None:g.V(vid1).values('age').order(Scope.local))],
+ 'g_V_order_byXTidX_name': [(lambda g:g.V().order().by(T.id_).values('name'))],
+ 'g_V_order_byXTid_descX_name': [(lambda g:g.V().order().by(T.id_, Order.desc).values('name'))],
+ 'g_V_order_byXTlabelX_label': [(lambda g:g.V().order().by(T.label).label())],
'g_V_pageRank_hasXpageRankX': [(lambda g:g.V().page_rank().has('gremlin.pageRankVertexProgram.pageRank'))],
'g_V_outXcreatedX_pageRank_withXedges_bothEX_withXpropertyName_projectRankX_withXtimes_0X_valueMapXname_projectRankX': [(lambda g:g.V().out('created').page_rank().with_('~tinkerpop.pageRank.edges', __.both_e()).with_('~tinkerpop.pageRank.propertyName', 'projectRank').with_('~tinkerpop.pageRank.times', 0).value_map('name', 'projectRank'))],
'g_V_pageRank_order_byXpageRank_descX_byXnameX_name': [(lambda g:g.V().page_rank().order().by('gremlin.pageRankVertexProgram.pageRank', Order.desc).by('name').values('name'))],
@@ -1640,6 +1644,7 @@ world.gremlins = {
'g_withStrategiesXProductiveByStrategyX_VX1X_out_path_byXageX': [(lambda g, vid1=None:g.with_strategies(ProductiveByStrategy()).V(vid1).out().path().by('age'))],
'g_injectX1_null_nullX_path': [(lambda g:g.inject(1, None, None).path())],
'g_injectX1_null_nullX_path_dedup': [(lambda g:g.inject(1, None, None).path().dedup())],
+ 'g_VX1X_out_out_path': [(lambda g, vid1=None:g.V(vid1).out().out().path())],
'g_V_peerPressure_hasXclusterX': [(lambda g:g.V().peer_pressure().has('gremlin.peerPressureVertexProgram.cluster'))],
'g_V_peerPressure_withXpropertyName_clusterX_withXedges_outEXknowsXX_pageRankX1X_byXrankX_withXedges_outEXknowsX_withXtimes_2X_group_byXclusterX_byXrank_sumX_limitX100X': [(lambda g:g.V().peer_pressure().with_('~tinkerpop.peerPressure.propertyName', 'cluster').with_('~tinkerpop.peerPressure.edges', __.out_e('knows')).page_rank(1.0).with_('~tinkerpop.pageRank.propertyName', 'rank').with_('~tinkerpop.pageRank.edges', __.out_e('knows')).with_('~tinkerpop.pageRank.times', 1).group().by(' [...]
'g_V_hasXname_rippleX_inXcreatedX_peerPressure_withXedges_outEX_withyXpropertyName_clusterX_repeatXunionXidentity__bothX_timesX2X_dedup_valueMapXname_clusterX': [(lambda g:g.V().has('name', 'ripple').in_('created').peer_pressure().with_('~tinkerpop.peerPressure.edges', __.out_e()).with_('~tinkerpop.peerPressure.propertyName', 'cluster').repeat(__.union(__.identity(), __.both())).times(2).dedup().value_map('name', 'cluster'))],
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json
index ff0ad07295..d7e90db6b3 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json
@@ -9789,6 +9789,23 @@
}
]
},
+ {
+ "scenario": "g_V_hasNotXlangX_name",
+ "traversals": [
+ {
+ "original": "g.V().hasNot(\"lang\").values(\"name\")",
+ "language": "g.V().hasNot(\"lang\").values(\"name\")",
+ "canonical": "g.V().hasNot(\"lang\").values(\"name\")",
+ "anonymized": "g.V().hasNot(string0).values(string1)",
+ "dotnet": "g.V().HasNot(\"lang\").Values<object>(\"name\")",
+ "go": "g.V().HasNot(\"lang\").Values(\"name\")",
+ "groovy": "g.V().hasNot(\"lang\").values(\"name\")",
+ "java": "g.V().hasNot(\"lang\").values(\"name\")",
+ "javascript": "g.V().hasNot(\"lang\").values(\"name\")",
+ "python": "g.V().has_not('lang').values('name')"
+ }
+ ]
+ },
{
"scenario": "g_V_properties_hasValueXnullX",
"traversals": [
@@ -34867,6 +34884,57 @@
}
]
},
+ {
+ "scenario": "g_V_order_byXTidX_name",
+ "traversals": [
+ {
+ "original": "g.V().order().by(T.id).values(\"name\")",
+ "language": "g.V().order().by(T.id).values(\"name\")",
+ "canonical": "g.V().order().by(T.id).values(\"name\")",
+ "anonymized": "g.V().order().by(T.id).values(string0)",
+ "dotnet": "g.V().Order().By(T.Id).Values<object>(\"name\")",
+ "go": "g.V().Order().By(gremlingo.T.Id).Values(\"name\")",
+ "groovy": "g.V().order().by(T.id).values(\"name\")",
+ "java": "g.V().order().by(T.id).values(\"name\")",
+ "javascript": "g.V().order().by(T.id).values(\"name\")",
+ "python": "g.V().order().by(T.id_).values('name')"
+ }
+ ]
+ },
+ {
+ "scenario": "g_V_order_byXTid_descX_name",
+ "traversals": [
+ {
+ "original": "g.V().order().by(T.id, Order.desc).values(\"name\")",
+ "language": "g.V().order().by(T.id, Order.desc).values(\"name\")",
+ "canonical": "g.V().order().by(T.id, Order.desc).values(\"name\")",
+ "anonymized": "g.V().order().by(T.id, Order.desc).values(string0)",
+ "dotnet": "g.V().Order().By(T.Id, Order.Desc).Values<object>(\"name\")",
+ "go": "g.V().Order().By(gremlingo.T.Id, gremlingo.Order.Desc).Values(\"name\")",
+ "groovy": "g.V().order().by(T.id, Order.desc).values(\"name\")",
+ "java": "g.V().order().by(T.id, Order.desc).values(\"name\")",
+ "javascript": "g.V().order().by(T.id, Order.desc).values(\"name\")",
+ "python": "g.V().order().by(T.id_, Order.desc).values('name')"
+ }
+ ]
+ },
+ {
+ "scenario": "g_V_order_byXTlabelX_label",
+ "traversals": [
+ {
+ "original": "g.V().order().by(T.label).label()",
+ "language": "g.V().order().by(T.label).label()",
+ "canonical": "g.V().order().by(T.label).label()",
+ "anonymized": "g.V().order().by(T.label).label()",
+ "dotnet": "g.V().Order().By(T.Label).Label()",
+ "go": "g.V().Order().By(gremlingo.T.Label).Label()",
+ "groovy": "g.V().order().by(T.label).label()",
+ "java": "g.V().order().by(T.label).label()",
+ "javascript": "g.V().order().by(T.label).label()",
+ "python": "g.V().order().by(T.label).label()"
+ }
+ ]
+ },
{
"scenario": "g_V_pageRank_hasXpageRankX",
"traversals": [
@@ -35207,6 +35275,23 @@
}
]
},
+ {
+ "scenario": "g_VX1X_out_out_path",
+ "traversals": [
+ {
+ "original": "g.V(vid1).out().out().path()",
+ "language": "g.V(vid1).out().out().path()",
+ "canonical": "g.V(vid1).out().out().path()",
+ "anonymized": "g.V(vid1).out().out().path()",
+ "dotnet": "g.V(vid1).Out().Out().Path()",
+ "go": "g.V(vid1).Out().Out().Path()",
+ "groovy": "g.V(vid1).out().out().path()",
+ "java": "g.V(vid1).out().out().path()",
+ "javascript": "g.V(vid1).out().out().path()",
+ "python": "g.V(vid1).out().out().path()"
+ }
+ ]
+ },
{
"scenario": "g_V_peerPressure_hasXclusterX",
"traversals": [
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Has.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Has.feature
index e559148f4b..2397577d0b 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Has.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Has.feature
@@ -18,6 +18,7 @@
@StepClassFilter @StepHas
Feature: Step - has()
+ @TinyGremlin
Scenario: g_VX1X_hasXnameX
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -30,6 +31,7 @@ Feature: Step - has()
| result |
| v[marko] |
+ @TinyGremlin
Scenario: g_VX1X_hasXcircumferenceX
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -40,6 +42,7 @@ Feature: Step - has()
When iterated to list
Then the result should be empty
+ @TinyGremlin
Scenario: g_VX1X_hasXname_markoX
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -52,6 +55,7 @@ Feature: Step - has()
| result |
| v[marko] |
+ @TinyGremlin
Scenario: g_VX1X_hasXname_markovarX
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -65,6 +69,7 @@ Feature: Step - has()
| result |
| v[marko] |
+ @TinyGremlin
Scenario: g_VX2X_hasXname_markoX
Given the modern graph
And using the parameter vid1 defined as "v[vadas].id"
@@ -75,6 +80,7 @@ Feature: Step - has()
When iterated to list
Then the result should be empty
+ @TinyGremlin
Scenario: g_V_hasXname_markoX
Given the modern graph
And the traversal of
@@ -86,6 +92,7 @@ Feature: Step - has()
| result |
| v[marko] |
+ @TinyGremlin
Scenario: g_V_hasXname_blahX
Given the modern graph
And the traversal of
@@ -95,6 +102,7 @@ Feature: Step - has()
When iterated to list
Then the result should be empty
+ @TinyGremlin
Scenario: g_V_hasXage_gt_30X
Given the modern graph
And the traversal of
@@ -107,6 +115,7 @@ Feature: Step - has()
| v[josh] |
| v[peter] |
+ @TinyGremlin
Scenario: g_VX1X_hasXage_gt_30X
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -117,6 +126,7 @@ Feature: Step - has()
When iterated to list
Then the result should be empty
+ @TinyGremlin
Scenario: g_V_hasXpersonvar_age_gt_30X
Given the modern graph
And using the parameter xx1 defined as "person"
@@ -130,6 +140,7 @@ Feature: Step - has()
| v[peter] |
| v[josh] |
+ @TinyGremlin
Scenario: g_VX4X_hasXage_gt_30X
Given the modern graph
And using the parameter vid4 defined as "v[josh].id"
@@ -142,6 +153,7 @@ Feature: Step - has()
| result |
| v[josh] |
+ @TinyGremlin
Scenario: g_VXv1X_hasXage_gt_30X
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -152,6 +164,7 @@ Feature: Step - has()
When iterated to list
Then the result should be empty
+ @TinyGremlin
Scenario: g_VXv4X_hasXage_gt_30X
Given the modern graph
And using the parameter vid4 defined as "v[josh].id"
@@ -164,6 +177,7 @@ Feature: Step - has()
| result |
| v[josh] |
+ @TinyGremlin
Scenario: g_VX1X_out_hasXid_2X
Given the modern graph
And using the parameter vid2 defined as "v[josh].id"
@@ -176,6 +190,7 @@ Feature: Step - has()
| result |
| v[josh] |
+ @TinyGremlin
Scenario: g_V_hasXblahX
Given the modern graph
And the traversal of
@@ -185,6 +200,7 @@ Feature: Step - has()
When iterated to list
Then the result should be empty
+ @TinyGremlin
Scenario: g_V_hasXperson_name_markoX_age
Given the modern graph
And the traversal of
@@ -196,6 +212,7 @@ Feature: Step - has()
| result |
| d[29].i |
+ @TinyGremlin
Scenario: g_V_hasXperson_name_markovarX_age
Given the modern graph
And using the parameter xx1 defined as "marko"
@@ -208,6 +225,7 @@ Feature: Step - has()
| result |
| d[29].i |
+ @TinyGremlin
Scenario: g_V_hasXpersonvar_name_markoX_age
Given the modern graph
And using the parameter xx1 defined as "person"
@@ -220,6 +238,7 @@ Feature: Step - has()
| result |
| d[29].i |
+ @TinyGremlin
Scenario: g_VX1X_outE_hasXweight_inside_0_06X_inV
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -233,6 +252,7 @@ Feature: Step - has()
| v[vadas] |
| v[lop] |
+ @TinyGremlin
Scenario: g_EX11X_outV_outE_hasXid_10X
Given the modern graph
And using the parameter eid11 defined as "e[josh-created->lop].id"
@@ -259,7 +279,7 @@ Feature: Step - has()
| result |
| e[josh-created->ripple] |
- @MultiProperties @MetaProperties
+ @MultiProperties @MetaProperties @TinyGremlin
Scenario: g_V_hasXlocationX
Given the crew graph
And the traversal of
@@ -329,6 +349,7 @@ Feature: Step - has()
| result |
| d[2].l |
+ @TinyGremlin
Scenario: g_V_hasXperson_age_withinX
Given the modern graph
And the traversal of
@@ -338,6 +359,7 @@ Feature: Step - has()
When iterated to list
Then the result should be empty
+ @TinyGremlin
Scenario: g_V_hasXperson_age_withoutX
Given the modern graph
And the traversal of
@@ -352,6 +374,7 @@ Feature: Step - has()
| v[josh] |
| v[peter] |
+ @TinyGremlin
Scenario: g_V_hasXname_containingXarkXX
Given the modern graph
And the traversal of
@@ -363,6 +386,7 @@ Feature: Step - has()
| result |
| v[marko] |
+ @TinyGremlin
Scenario: g_V_hasXname_startingWithXmarXX
Given the modern graph
And the traversal of
@@ -374,6 +398,7 @@ Feature: Step - has()
| result |
| v[marko] |
+ @TinyGremlin
Scenario: g_V_hasXname_endingWithXasXX
Given the modern graph
And the traversal of
@@ -407,6 +432,7 @@ Feature: Step - has()
| result |
| v[marko] |
+ @TinyGremlin
Scenario: g_V_hasXname_not_containingXarkXX
Given the modern graph
And the traversal of
@@ -422,6 +448,7 @@ Feature: Step - has()
| v[ripple] |
| v[peter] |
+ @TinyGremlin
Scenario: g_V_hasXname_not_startingWithXmarXX
Given the modern graph
And the traversal of
@@ -437,6 +464,7 @@ Feature: Step - has()
| v[ripple] |
| v[peter] |
+ @TinyGremlin
Scenario: g_V_hasXname_not_endingWithXasXX
Given the modern graph
And the traversal of
@@ -452,6 +480,7 @@ Feature: Step - has()
| v[ripple] |
| v[peter] |
+ @TinyGremlin
Scenario: g_V_hasXname_regexXrMarXX
Given the modern graph
And the traversal of
@@ -463,6 +492,7 @@ Feature: Step - has()
| result |
| v[marko] |
+ @TinyGremlin
Scenario: g_V_hasXname_notRegexXrMarXX
Given the modern graph
And the traversal of
@@ -478,6 +508,7 @@ Feature: Step - has()
| v[ripple] |
| v[peter] |
+ @TinyGremlin
Scenario: g_V_hasXname_regexXTinkerXX
Given the empty graph
And the graph initializer of
@@ -493,6 +524,7 @@ Feature: Step - has()
| result |
| Apache TinkerPop© |
+ @TinyGremlin
Scenario: g_V_hasXname_regexXTinkerUnicodeXX
Given the empty graph
And the graph initializer of
@@ -508,6 +540,7 @@ Feature: Step - has()
| result |
| Apache TinkerPop© |
+ @TinyGremlin
Scenario: g_V_hasXp_neqXvXX
Given the modern graph
And the traversal of
@@ -541,6 +574,7 @@ Feature: Step - has()
| v[marko] |
| v[vadas] |
+ @TinyGremlin
Scenario: g_V_hasXk_withinXcXX_valuesXkX
Given the empty graph
And the graph initializer of
@@ -558,6 +592,7 @@ Feature: Step - has()
| ✦ |
| ♠ |
+ @TinyGremlin
Scenario: g_V_hasXnullX
Given the modern graph
And the traversal of
@@ -567,6 +602,7 @@ Feature: Step - has()
When iterated to list
Then the result should be empty
+ @TinyGremlin
Scenario: g_V_hasXnull_testnullkeyX
Given the modern graph
And the traversal of
@@ -576,6 +612,7 @@ Feature: Step - has()
When iterated to list
Then the result should be empty
+ @TinyGremlin
Scenario: g_E_hasXnullX
Given the modern graph
And the traversal of
@@ -585,6 +622,7 @@ Feature: Step - has()
When iterated to list
Then the result should be empty
+ @TinyGremlin
Scenario: g_V_hasXlabel_personX
Given the modern graph
And the traversal of
@@ -599,6 +637,7 @@ Feature: Step - has()
| v[josh] |
| v[peter] |
+ @TinyGremlin
Scenario: g_V_hasXlabel_eqXpersonXX
Given the modern graph
And the traversal of
@@ -613,6 +652,7 @@ Feature: Step - has()
| v[josh] |
| v[peter] |
+ @TinyGremlin
Scenario: g_V_hasXname_nullX
Given the modern graph
And the traversal of
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasId.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasId.feature
index 818d55ec33..f942f920f7 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasId.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasId.feature
@@ -63,6 +63,7 @@ Feature: Step - hasId()
| result |
| d[6].l |
+ @TinyGremlin
Scenario: g_V_hasIdXnullX
Given the modern graph
And the traversal of
@@ -72,6 +73,7 @@ Feature: Step - hasId()
When iterated to list
Then the result should be empty
+ @TinyGremlin
Scenario: g_V_hasIdXeqXnullXX
Given the modern graph
And the traversal of
@@ -81,6 +83,7 @@ Feature: Step - hasId()
When iterated to list
Then the result should be empty
+ @TinyGremlin
Scenario: g_V_hasIdX2_nullX
Given the modern graph
And using the parameter vid2 defined as "v[vadas].id"
@@ -93,6 +96,7 @@ Feature: Step - hasId()
| result |
| v[vadas] |
+ @TinyGremlin
Scenario: g_V_hasIdXmarkovar_vadasvarX
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -148,6 +152,7 @@ Feature: Step - hasId()
| v[marko] |
| v[vadas] |
+ @TinyGremlin
Scenario: g_V_hasIdXnull_2X
Given the modern graph
And using the parameter vid2 defined as "v[vadas].id"
@@ -160,6 +165,7 @@ Feature: Step - hasId()
| result |
| v[vadas] |
+ @TinyGremlin
Scenario: g_V_hasIdX1X_hasIdX2X
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -171,6 +177,7 @@ Feature: Step - hasId()
When iterated to list
Then the result should be empty
+ @TinyGremlin
Scenario: g_V_in_hasIdXneqX1XX
Given the modern graph
And using the parameter xx1 defined as "v[marko].id"
@@ -185,6 +192,7 @@ Feature: Step - hasId()
| v[josh] |
| v[peter] |
+ @TinyGremlin
Scenario: g_VX1X_out_hasIdX2X
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -198,6 +206,7 @@ Feature: Step - hasId()
| result |
| v[vadas] |
+ @TinyGremlin
Scenario: g_VX1X_out_hasXid_2_3X
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -241,6 +250,7 @@ Feature: Step - hasId()
| result |
| v[vadas] |
+ @TinyGremlin
Scenario: g_VX1X_out_hasXid_2_3X_inList
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -255,6 +265,7 @@ Feature: Step - hasId()
| v[vadas] |
| v[lop] |
+ @TinyGremlin
Scenario: g_V_hasXid_1_2X
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -269,6 +280,7 @@ Feature: Step - hasId()
| v[marko] |
| v[vadas] |
+ @TinyGremlin
Scenario: g_V_hasXid_1_2X_inList
Given the modern graph
And using the parameter xx1 defined as "l[v[marko].id,v[vadas].id]"
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasLabel.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasLabel.feature
index 54e29938ad..d7eb23c968 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasLabel.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasLabel.feature
@@ -18,6 +18,7 @@
@StepClassFilter @StepHasLabel
Feature: Step - hasLabel()
+ @TinyGremlin
Scenario: g_EX7X_hasLabelXknowsX
Given the modern graph
And using the parameter eid7 defined as "e[marko-knows->vadas].id"
@@ -30,6 +31,7 @@ Feature: Step - hasLabel()
| result |
| e[marko-knows->vadas] |
+ @TinyGremlin
Scenario: g_E_hasLabelXknowsX
Given the modern graph
And the traversal of
@@ -42,7 +44,7 @@ Feature: Step - hasLabel()
| e[marko-knows->vadas] |
| e[marko-knows->josh] |
- @MultiProperties @MetaProperties
+ @MultiProperties @MetaProperties @TinyGremlin
Scenario: g_E_hasLabelXuses_traversesX
Given the crew graph
And the traversal of
@@ -62,6 +64,7 @@ Feature: Step - hasLabel()
| e[matthias-uses->tinkergraph] |
| e[gremlin-traverses->tinkergraph] |
+ @TinyGremlin
Scenario: g_V_hasLabelXperson_software_blahX
Given the modern graph
And the traversal of
@@ -78,6 +81,7 @@ Feature: Step - hasLabel()
| v[lop] |
| v[ripple] |
+ @TinyGremlin
Scenario: g_V_hasLabelXperson_softwarevarX
Given the modern graph
And using the parameter xx1 defined as "software"
@@ -95,6 +99,7 @@ Feature: Step - hasLabel()
| v[lop] |
| v[ripple] |
+ @TinyGremlin
Scenario: g_V_hasLabelXpersonX_hasLabelXsoftwareX
Given the modern graph
And the traversal of
@@ -104,6 +109,7 @@ Feature: Step - hasLabel()
When iterated to list
Then the result should be empty
+ @TinyGremlin
Scenario: g_V_hasLabelXpersonvarX_hasLabelXsoftwareX
Given the modern graph
And using the parameter xx1 defined as "person"
@@ -114,6 +120,7 @@ Feature: Step - hasLabel()
When iterated to list
Then the result should be empty
+ @TinyGremlin
Scenario: g_V_hasLabelXpersonvar_softwarevarX
Given the modern graph
And using the parameter xx1 defined as "person"
@@ -144,6 +151,7 @@ Feature: Step - hasLabel()
| vadas |
| peter |
+ @TinyGremlin
Scenario: g_V_hasLabelXnullX
Given the modern graph
And the traversal of
@@ -153,6 +161,7 @@ Feature: Step - hasLabel()
When iterated to list
Then the result should be empty
+ @TinyGremlin
Scenario: g_V_hasXlabel_nullX
Given the modern graph
And the traversal of
@@ -162,6 +171,7 @@ Feature: Step - hasLabel()
When iterated to list
Then the result should be empty
+ @TinyGremlin
Scenario: g_V_hasLabelXnull_nullX
Given the modern graph
And the traversal of
@@ -171,6 +181,7 @@ Feature: Step - hasLabel()
When iterated to list
Then the result should be empty
+ @TinyGremlin
Scenario: g_V_hasLabelXnull_personX
Given the modern graph
And the traversal of
@@ -185,6 +196,7 @@ Feature: Step - hasLabel()
| v[josh] |
| v[peter] |
+ @TinyGremlin
Scenario: g_E_hasLabelXnullX
Given the modern graph
And the traversal of
@@ -194,6 +206,7 @@ Feature: Step - hasLabel()
When iterated to list
Then the result should be empty
+ @TinyGremlin
Scenario: g_E_hasXlabel_nullX
Given the modern graph
And the traversal of
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasNot.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasNot.feature
index 10a24e50fe..404e6f0130 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasNot.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasNot.feature
@@ -18,6 +18,7 @@
@StepClassFilter @StepHasNot
Feature: Step - hasNot()
+ @TinyGremlin
Scenario: g_V_hasNotXageX_name
Given the modern graph
And the traversal of
@@ -28,4 +29,18 @@ Feature: Step - hasNot()
Then the result should be unordered
| result |
| lop |
- | ripple |
\ No newline at end of file
+ | ripple |
+ @TinyGremlin
+ Scenario: g_V_hasNotXlangX_name
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().hasNot("lang").values("name")
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | marko |
+ | vadas |
+ | josh |
+ | peter |
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Range.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Range.feature
index 3341bc3541..2bfb27dad0 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Range.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Range.feature
@@ -18,6 +18,7 @@
@StepClassFilter @StepRange
Feature: Step - range()
+ @TinyGremlin
Scenario: g_VX1X_out_limitX2X
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -33,6 +34,7 @@ Feature: Step - range()
| v[lop] |
And the result should have a count of 2
+ @TinyGremlin
Scenario: g_VX1X_out_limitX2varX
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -64,6 +66,7 @@ Feature: Step - range()
| v[ripple] |
And the result should have a count of 3
+ @TinyGremlin
Scenario: g_VX1X_outXknowsX_outEXcreatedX_rangeX0_1X_inV
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -78,6 +81,7 @@ Feature: Step - range()
| v[ripple] |
And the result should have a count of 1
+ @TinyGremlin
Scenario: g_VX1X_outXknowsX_outXcreatedX_rangeX0_1X
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -92,6 +96,7 @@ Feature: Step - range()
| v[ripple] |
And the result should have a count of 1
+ @TinyGremlin
Scenario: g_VX1X_outXcreatedX_inXcreatedX_rangeX1_3X
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -107,6 +112,7 @@ Feature: Step - range()
| v[peter] |
And the result should have a count of 2
+ @TinyGremlin
Scenario: g_VX1X_outXcreatedX_inXcreatedX_rangeX1var_3varX
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -124,6 +130,7 @@ Feature: Step - range()
| v[peter] |
And the result should have a count of 2
+ @TinyGremlin
Scenario: g_VX1X_outXcreatedX_inEXcreatedX_rangeX1_3X_outV
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -231,6 +238,7 @@ Feature: Step - range()
| m[{"b":"josh"}] |
| m[{"b":"josh"}] |
+ @TinyGremlin
Scenario: g_V_hasLabelXpersonX_order_byXageX_skipX1X_valuesXnameX
Given the modern graph
And the traversal of
@@ -244,6 +252,7 @@ Feature: Step - range()
| josh |
| peter |
+ @TinyGremlin
Scenario: g_V_hasLabelXpersonX_order_byXageX_skipX1varX_valuesXnameX
Given the modern graph
And using the parameter xx1 defined as "d[1].l"
@@ -293,6 +302,7 @@ Feature: Step - range()
| result |
| l[d[0.4].d,d[0.5].d,d[1.0].d,d[1.0].d] |
+ @TinyGremlin
Scenario: g_V_hasLabelXpersonX_order_byXageX_valuesXnameX_skipX1X
Given the modern graph
And the traversal of
@@ -331,6 +341,7 @@ Feature: Step - range()
| marko |
| marko |
+ @TinyGremlin
Scenario: g_V_rangeX2_1X
Given the modern graph
And the traversal of
@@ -341,6 +352,7 @@ Feature: Step - range()
Then the traversal will raise an error with message containing text of "Not a legal range: [2, 1]"
# iterated next variant of the previous test (which is iterated to list)
+ @TinyGremlin
Scenario: g_V_rangeX3_2X
Given the modern graph
And the traversal of
@@ -436,6 +448,7 @@ Feature: Step - range()
| result |
| marko |
+ @TinyGremlin
Scenario: g_VX5X_limitX1X_in_limitX1X_in_valuesXnameX
Given the modern graph
And using the parameter vid5 defined as "v[ripple].id"
@@ -497,6 +510,7 @@ Feature: Step - range()
| result |
| marko |
+ @TinyGremlin
Scenario: g_VX5X_rangeX0_1X_in_rangeX0_1X_in_valuesXnameX
Given the modern graph
And using the parameter vid5 defined as "v[ripple].id"
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Tail.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Tail.feature
index 4ffc9420ca..b7b8be906c 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Tail.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Tail.feature
@@ -30,6 +30,7 @@ Feature: Step - tail()
| ripple |
| vadas |
+ @TinyGremlin
Scenario: g_V_valuesXnameX_order_tailX2X
Given the modern graph
And the traversal of
@@ -42,6 +43,7 @@ Feature: Step - tail()
| ripple |
| vadas |
+ @TinyGremlin
Scenario: g_V_valuesXnameX_order_tailX2varX
Given the modern graph
And using the parameter xx1 defined as "d[2].l"
@@ -55,6 +57,7 @@ Feature: Step - tail()
| ripple |
| vadas |
+ @TinyGremlin
Scenario: g_V_valuesXnameX_order_tail
Given the modern graph
And the traversal of
@@ -66,6 +69,7 @@ Feature: Step - tail()
| result |
| vadas |
+ @TinyGremlin
Scenario: g_V_valuesXnameX_order_tailX7X
Given the modern graph
And the traversal of
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AddEdge.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AddEdge.feature
index 02c2be3d7b..edb14a836f 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AddEdge.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AddEdge.feature
@@ -450,7 +450,7 @@ Feature: Step - addE()
And the graph should return 3 for count of "g.V(vid1).outE(\"knows\")"
And the graph should return 1 for count of "g.V(vid1).out(\"knows\").has(\"name\",\"peter\")"
- @AllowNullPropertyValues
+ @AllowNullPropertyValues @TinyGremlin
Scenario: g_addEXknowsXpropertyXweight_nullXfromXV_hasXname_markoXX_toXV_hasXname_vadasXX
Given the empty graph
And the graph initializer of
@@ -466,7 +466,7 @@ Feature: Step - addE()
Then the result should have a count of 1
And the graph should return 1 for count of "g.E().has(\"knows\",\"weight\",null)"
- @AllowNullPropertyValues
+ @AllowNullPropertyValues @TinyGremlin
Scenario: g_addEXknowsvarXpropertyXweight_nullXfromXV_hasXname_markoXX_toXV_hasXname_vadasXX
Given the empty graph
And the graph initializer of
@@ -515,6 +515,7 @@ Feature: Step - addE()
| result |
| d[0.5].d |
+ @TinyGremlin
Scenario: g_addEXknowsX_fromXV_hasXname_markoXX_toXV_hasXname_vadasXX_propertyXweight_0_5X_addEXknowsX_fromXV_hasXname_markoXX
Given the empty graph
And the graph initializer of
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AddVertex.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AddVertex.feature
index 99e1492f56..2dd651c493 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AddVertex.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AddVertex.feature
@@ -473,6 +473,7 @@ Feature: Step - addV()
| result |
| name |
+ @TinyGremlin
Scenario: g_addV_propertyXlabel_personX
Given the empty graph
And the traversal of
@@ -605,7 +606,6 @@ Feature: Step - addV()
Then the result should have a count of 1
And the graph should return 0 for count of "g.V().hasLabel(\"foo\").values()"
-
Scenario: g_addV_propertyXset_emptyX
Given the empty graph
And the traversal of
@@ -616,7 +616,7 @@ Feature: Step - addV()
Then the result should have a count of 1
And the graph should return 0 for count of "g.V().hasLabel(\"person\").values()"
- @AllowNullPropertyValues
+ @AllowNullPropertyValues @TinyGremlin
Scenario: g_addVXpersonX_propertyXname_joshX_propertyXage_nullX
Given the empty graph
And the traversal of
@@ -640,6 +640,35 @@ Feature: Step - addV()
And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").properties(\"friendWeight\").has(\"acl\",null)"
And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").properties(\"friendWeight\").count()"
+ @TinyGremlin
+ Scenario: g_V_hasXperson_name_aliceX_propertyXage_51X
+ Given the empty graph
+ And the graph initializer of
+ """
+ g.addV("person").property("name", "alice").property("age", 50)
+ """
+ And the traversal of
+ """
+ g.V().has("person","name","alice").property("age", 51)
+ """
+ When iterated to list
+ Then the result should have a count of 1
+ And the graph should return 0 for count of "g.V().has(\"person\",\"age\",50)"
+ And the graph should return 1 for count of "g.V().has(\"person\",\"age\",51)"
+
+ @TinyGremlin
+ Scenario: g_addVXpersonX_propertyXname_aliceX_propertyXage_30X_query
+ Given the empty graph
+ And the traversal of
+ """
+ g.addV("person").property("name", "alice").property("age", 30)
+ """
+ When iterated to list
+ Then the result should have a count of 1
+ And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"alice\")"
+ And the graph should return 1 for count of "g.V().has(\"person\",\"age\",30)"
+
+
Scenario: g_V_hasXperson_name_aliceX_propertyXsingle_age_unionXage_constantX1XX_sumX
Given the empty graph
And the graph initializer of
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Edge.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Edge.feature
index 670476742a..64ce226b2b 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Edge.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Edge.feature
@@ -18,6 +18,7 @@
@StepClassMap @StepE
Feature: Step - E(), inV(), outV(), bothV(), otherV()
+ @TinyGremlin
Scenario: g_E
Given the modern graph
And the traversal of
@@ -34,6 +35,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
| e[josh-created->lop] |
| e[josh-created->ripple] |
+ @TinyGremlin
Scenario: g_EX11X
Given the modern graph
And using the parameter eid11 defined as "e[josh-created->lop].id"
@@ -58,6 +60,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
| result |
| e[josh-created->lop] |
+ @TinyGremlin
Scenario: g_EXeid7_eid11X
Given the modern graph
And using the parameter eid7 defined as "e[marko-knows->vadas].id"
@@ -72,6 +75,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
| e[marko-knows->vadas] |
| e[josh-created->lop] |
+ @TinyGremlin
Scenario: g_EXlistXeid7_eid11XX
Given the modern graph
And using the parameter xx1 defined as "l[e[marko-knows->vadas].id,e[josh-created->lop].id]"
@@ -85,6 +89,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
| e[marko-knows->vadas] |
| e[josh-created->lop] |
+ @TinyGremlin
Scenario: g_EXnullX
Given the modern graph
And the traversal of
@@ -94,6 +99,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
When iterated to list
Then the result should be empty
+ @TinyGremlin
Scenario: g_EXlistXnullXX
Given the modern graph
And using the parameter xx1 defined as "l[null]"
@@ -104,6 +110,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
When iterated to list
Then the result should be empty
+ @TinyGremlin
Scenario: g_EX11_nullX
Given the modern graph
And using the parameter eid11 defined as "e[josh-created->lop].id"
@@ -116,7 +123,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
| result |
| e[josh-created->lop] |
- @GraphComputerVerificationMidENotSupported
+ @GraphComputerVerificationMidENotSupported @TinyGremlin
Scenario: g_V_EX11X
Given the modern graph
And using the parameter eid11 defined as "e[josh-created->lop].id"
@@ -134,7 +141,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
| e[josh-created->lop] |
| e[josh-created->lop] |
- @GraphComputerVerificationMidENotSupported
+ @GraphComputerVerificationMidENotSupported @TinyGremlin
Scenario: g_EX11X_E
Given the modern graph
And using the parameter eid11 defined as "e[josh-created->lop].id"
@@ -152,7 +159,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
| e[josh-created->lop] |
| e[josh-created->ripple] |
- @GraphComputerVerificationMidENotSupported
+ @GraphComputerVerificationMidENotSupported @TinyGremlin
Scenario: g_V_EXnullX
Given the modern graph
And the traversal of
@@ -162,7 +169,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
When iterated to list
Then the result should be empty
- @GraphComputerVerificationMidENotSupported
+ @GraphComputerVerificationMidENotSupported @TinyGremlin
Scenario: g_V_EXlistXnullXX
Given the modern graph
And using the parameter xx1 defined as "l[null]"
@@ -202,6 +209,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
Then the result should have a count of 1
And the graph should return 1 for count of "g.E().hasLabel(\"tests\")"
+ @TinyGremlin
Scenario: g_VX1X_outE_inV
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -216,6 +224,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
| v[josh] |
| v[lop] |
+ @TinyGremlin
Scenario: g_VX2X_inE_outV
Given the modern graph
And using the parameter vid2 defined as "v[vadas].id"
@@ -228,6 +237,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
| result |
| v[marko] |
+ @TinyGremlin
Scenario: g_V_outE_hasXweight_1X_outV
Given the modern graph
And the traversal of
@@ -240,6 +250,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
| v[marko] |
| v[josh] |
+ @TinyGremlin
Scenario: g_VX1X_outE_otherV
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -254,6 +265,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
| v[josh] |
| v[lop] |
+ @TinyGremlin
Scenario: g_VX4X_bothE_otherV
Given the modern graph
And using the parameter vid4 defined as "v[josh].id"
@@ -268,6 +280,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
| v[ripple] |
| v[lop] |
+ @TinyGremlin
Scenario: g_VX4X_bothE_hasXweight_lt_1X_otherV
Given the modern graph
And using the parameter vid4 defined as "v[josh].id"
@@ -280,6 +293,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
| result |
| v[lop] |
+ @TinyGremlin
Scenario: get_g_VX1X_outE_otherV
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -294,6 +308,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
| v[josh] |
| v[lop] |
+ @TinyGremlin
Scenario: g_VX1X_outEXknowsX_inV
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -307,6 +322,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
| v[vadas] |
| v[josh] |
+ @TinyGremlin
Scenario: g_VX1X_outEXknows_createdX_inV
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/ElementMap.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/ElementMap.feature
index f119a2e964..999805cac0 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/ElementMap.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/ElementMap.feature
@@ -18,6 +18,7 @@
@StepClassMap @StepElementMap
Feature: Step - elementMap()
+ @TinyGremlin
Scenario: g_V_elementMap
Given the modern graph
And the traversal of
@@ -34,6 +35,7 @@ Feature: Step - elementMap()
| m[{"t[id]": "v[lop].id", "t[label]": "software", "name": "lop", "lang": "java"}] |
| m[{"t[id]": "v[ripple].id", "t[label]": "software", "name": "ripple", "lang": "java"}] |
+ @TinyGremlin
Scenario: g_V_elementMapXname_ageX
Given the modern graph
And the traversal of
@@ -50,7 +52,7 @@ Feature: Step - elementMap()
| m[{"t[id]": "v[lop].id", "t[label]": "software", "name": "lop"}] |
| m[{"t[id]": "v[ripple].id", "t[label]": "software", "name": "ripple"}] |
- @GraphComputerVerificationReferenceOnly
+ @GraphComputerVerificationReferenceOnly @TinyGremlin
Scenario: g_EX11X_elementMap
Given the modern graph
And using the parameter eid11 defined as "e[josh-created->lop].id"
@@ -63,6 +65,7 @@ Feature: Step - elementMap()
| result |
| m[{"t[id]": "e[josh-created->lop].id", "t[label]": "created", "weight": "d[0.4].d", "D[OUT]": "m[{\\"t[id]\\": \\"v[josh].id\\", \\"t[label]\\": \\"person\\"}]", "D[IN]": "m[{\\"t[id]\\": \\"v[lop].id\\", \\"t[label]\\": \\"software\\"}]"}] |
+ @TinyGremlin
Scenario: g_V_elementMapXname_age_nullX
Given the modern graph
And the traversal of
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Order.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Order.feature
index c49c6fca14..2780cecb6f 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Order.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Order.feature
@@ -18,6 +18,7 @@
@StepClassMap @StepOrder
Feature: Step - order()
+ @TinyGremlin
Scenario: g_V_name_order
Given the modern graph
And the traversal of
@@ -34,6 +35,7 @@ Feature: Step - order()
| ripple |
| vadas |
+ @TinyGremlin
Scenario: g_V_order_byXname_ascX_name
Given the modern graph
And the traversal of
@@ -50,6 +52,7 @@ Feature: Step - order()
| ripple |
| vadas |
+ @TinyGremlin
Scenario: g_V_order_byXnameX_name
Given the modern graph
And the traversal of
@@ -66,6 +69,7 @@ Feature: Step - order()
| ripple |
| vadas |
+ @TinyGremlin
Scenario: g_V_outE_order_byXweight_descX_weight
Given the modern graph
And the traversal of
@@ -96,6 +100,7 @@ Feature: Step - order()
| m[{"a":"v[josh]","b":"v[ripple]"}] |
| m[{"a":"v[josh]","b":"v[lop]"}] |
+ @TinyGremlin
Scenario: g_V_both_hasLabelXpersonX_order_byXage_descX_limitX5X_name
Given the modern graph
And the traversal of
@@ -212,6 +217,7 @@ Feature: Step - order()
| result |
| l[v[vadas],v[marko],v[josh],v[peter]] |
+ @TinyGremlin
Scenario: g_V_both_hasLabelXpersonX_order_byXage_descX_name
Given the modern graph
And the traversal of
@@ -246,6 +252,7 @@ Feature: Step - order()
| v[ripple] |
| v[vadas] |
+ @TinyGremlin
Scenario: g_V_hasLabelXpersonX_order_byXageX
Given the modern graph
And the traversal of
@@ -548,4 +555,54 @@ Feature: Step - order()
When iterated to list
Then the result should be unordered
| result |
- | d[29].i |
\ No newline at end of file
+ | d[29].i |
+ @TinyGremlin
+ Scenario: g_V_order_byXTidX_name
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().order().by(T.id).values("name")
+ """
+ When iterated to list
+ Then the result should be ordered
+ | result |
+ | marko |
+ | vadas |
+ | lop |
+ | josh |
+ | ripple |
+ | peter |
+
+ @TinyGremlin
+ Scenario: g_V_order_byXTid_descX_name
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().order().by(T.id, Order.desc).values("name")
+ """
+ When iterated to list
+ Then the result should be ordered
+ | result |
+ | peter |
+ | ripple |
+ | josh |
+ | lop |
+ | vadas |
+ | marko |
+
+ @TinyGremlin
+ Scenario: g_V_order_byXTlabelX_label
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().order().by(T.label).label()
+ """
+ When iterated to list
+ Then the result should be ordered
+ | result |
+ | person |
+ | person |
+ | person |
+ | person |
+ | software |
+ | software |
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Path.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Path.feature
index 36722662a1..adfdd7ccb1 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Path.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Path.feature
@@ -18,6 +18,7 @@
@StepClassMap @StepPath
Feature: Step - path()
+ @TinyGremlin
Scenario: g_VX1X_name_path
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -82,6 +83,7 @@ Feature: Step - path()
| result |
| p[v[marko]] |
+ @TinyGremlin
Scenario: g_VX1X_outEXcreatedX_inV_inE_outV_path
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -161,4 +163,17 @@ Feature: Step - path()
Then the result should be unordered
| result |
| p[d[1].i] |
- | p[null] |
\ No newline at end of file
+ | p[null] |
+ @TinyGremlin
+ Scenario: g_VX1X_out_out_path
+ Given the modern graph
+ And using the parameter vid1 defined as "v[marko].id"
+ And the traversal of
+ """
+ g.V(vid1).out().out().path()
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | p[v[marko],v[josh],v[ripple]] |
+ | p[v[marko],v[josh],v[lop]] |
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Properties.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Properties.feature
index b5b40fee17..1be665bda6 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Properties.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Properties.feature
@@ -88,6 +88,7 @@ Feature: Step - properties()
| lop |
| ripple |
+ @TinyGremlin
Scenario: g_V_valuesXname_age_nullX
Given the modern graph
And the traversal of
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/ValueMap.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/ValueMap.feature
index 63f52324d3..62043514fa 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/ValueMap.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/ValueMap.feature
@@ -18,6 +18,7 @@
@StepClassMap @StepValueMap
Feature: Step - valueMap()
+ @TinyGremlin
Scenario: g_V_valueMap
Given the modern graph
And the traversal of
@@ -34,6 +35,7 @@ Feature: Step - valueMap()
| m[{"name": ["lop"], "lang": ["java"]}] |
| m[{"name": ["ripple"], "lang": ["java"]}] |
+ @TinyGremlin
Scenario: g_V_valueMapXtrueX
Given the modern graph
And the traversal of
@@ -66,6 +68,7 @@ Feature: Step - valueMap()
| m[{"t[id]": "v[lop].id", "t[label]": "software", "name": ["lop"], "lang": ["java"]}] |
| m[{"t[id]": "v[ripple].id", "t[label]": "software", "name": ["ripple"], "lang": ["java"]}] |
+ @TinyGremlin
Scenario: g_V_valueMapXname_ageX
Given the modern graph
And the traversal of
@@ -82,6 +85,7 @@ Feature: Step - valueMap()
| m[{"name": ["lop"]}] |
| m[{"name": ["ripple"]}] |
+ @TinyGremlin
Scenario: g_V_valueMapXtrue_name_ageX
Given the modern graph
And the traversal of
@@ -146,6 +150,7 @@ Feature: Step - valueMap()
| m[{"t[id]": "v[lop].id", "name": "lop"}] |
| m[{"t[id]": "v[ripple].id", "name": "ripple"}] |
+ @TinyGremlin
Scenario: g_VX1X_outXcreatedX_valueMap
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -195,6 +200,7 @@ Feature: Step - valueMap()
When iterated next
Then the traversal will raise an error with message containing text of "step can only have one by modulator"
+ @TinyGremlin
Scenario: g_V_valueMapXname_age_nullX
Given the modern graph
And the traversal of
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Vertex.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Vertex.feature
index f9218ee462..91f6f65b06 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Vertex.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Vertex.feature
@@ -18,6 +18,7 @@
@StepClassMap @StepVertex
Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
+ @TinyGremlin
Scenario: g_VXnullX
Given the modern graph
And the traversal of
@@ -27,6 +28,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
When iterated to list
Then the result should be empty
+ @TinyGremlin
Scenario: g_VXlistXnullXX
Given the modern graph
And using the parameter xx1 defined as "l[null]"
@@ -37,6 +39,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
When iterated to list
Then the result should be empty
+ @TinyGremlin
Scenario: g_VX1_nullX
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -49,6 +52,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
| result |
| v[marko] |
+ @TinyGremlin
Scenario: g_VXlistX1_2_3XX_name
Given the modern graph
And using the parameter xx1 defined as "l[v[marko].id,v[vadas].id,v[lop].id]"
@@ -63,6 +67,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
| vadas |
| lop |
+ @TinyGremlin
Scenario: g_VXlistXv1_v2_v3XX_name
Given the modern graph
And using the parameter xx1 defined as "l[v[marko].id,v[vadas].id,v[lop].id]"
@@ -77,6 +82,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
| vadas |
| lop |
+ @TinyGremlin
Scenario: g_V
Given the modern graph
And the traversal of
@@ -93,6 +99,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
| v[ripple] |
| v[peter] |
+ @TinyGremlin
Scenario: g_VXv1X_out
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -107,6 +114,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
| v[lop] |
| v[josh] |
+ @TinyGremlin
Scenario: g_VX1X_out
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -121,6 +129,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
| v[lop] |
| v[josh] |
+ @TinyGremlin
Scenario: g_VX2X_in
Given the modern graph
And using the parameter vid2 defined as "v[vadas].id"
@@ -133,6 +142,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
| result |
| v[marko] |
+ @TinyGremlin
Scenario: g_VX4X_both
Given the modern graph
And using the parameter vid4 defined as "v[josh].id"
@@ -147,6 +157,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
| v[lop] |
| v[ripple] |
+ @TinyGremlin
Scenario: g_VX1X_outE
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -161,6 +172,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
| e[marko-knows->josh] |
| e[marko-knows->vadas] |
+ @TinyGremlin
Scenario: g_VX2X_outE
Given the modern graph
And using the parameter vid2 defined as "v[vadas].id"
@@ -173,6 +185,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
| result |
| e[marko-knows->vadas] |
+ @TinyGremlin
Scenario: g_VX4X_bothEXcreatedX
Given the modern graph
And using the parameter vid4 defined as "v[josh].id"
@@ -186,6 +199,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
| e[josh-created->lop] |
| e[josh-created->ripple] |
+ @TinyGremlin
Scenario: g_VX4X_bothEXcreatedvarX
Given the modern graph
And using the parameter vid4 defined as "v[josh].id"
@@ -200,6 +214,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
| e[josh-created->lop] |
| e[josh-created->ripple] |
+ @TinyGremlin
Scenario: g_VX4X_bothE
Given the modern graph
And using the parameter vid4 defined as "v[josh].id"
@@ -214,6 +229,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
| e[josh-created->ripple] |
| e[marko-knows->josh] |
+ @TinyGremlin
Scenario: g_V_out_outE_inV_inE_inV_both_name
Given the modern graph
And the traversal of
@@ -234,6 +250,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
| peter |
| peter |
+ @TinyGremlin
Scenario: g_VX2X_inE
Given the modern graph
And using the parameter vid2 defined as "v[vadas].id"
@@ -246,6 +263,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
| result |
| e[marko-knows->vadas] |
+ @TinyGremlin
Scenario: g_VX1X_outXknowsX
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -272,6 +290,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
| v[vadas] |
| v[josh] |
+ @TinyGremlin
Scenario: g_VX1X_outXknows_createdX
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -286,6 +305,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
| v[josh] |
| v[lop] |
+ @TinyGremlin
Scenario: g_VX1X_outXknowsvar_createdvarX
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -302,6 +322,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
| v[josh] |
| v[lop] |
+ @TinyGremlin
Scenario: g_V_out_out
Given the modern graph
And the traversal of
@@ -314,6 +335,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
| v[ripple] |
| v[lop] |
+ @TinyGremlin
Scenario: g_VX1X_out_out_out
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -324,6 +346,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
When iterated to list
Then the result should be empty
+ @TinyGremlin
Scenario: g_VX1X_out_name
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -383,7 +406,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
Then the result should be empty
And the graph should return 3 for count of "g.V(vid1, vid2, vid3, vid4)"
- @GraphComputerVerificationMidVNotSupported
+ @GraphComputerVerificationMidVNotSupported @TinyGremlin
Scenario: g_V_hasLabelXpersonX_V_hasLabelXsoftwareX_name
Given the modern graph
And the traversal of
@@ -402,6 +425,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
| ripple |
| ripple |
+ @TinyGremlin
Scenario: g_V_hasLabelXloopsX_bothEXselfX
Given the sink graph
And the traversal of
@@ -414,6 +438,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
| e[loop-self->loop] |
| e[loop-self->loop] |
+ @TinyGremlin
Scenario: g_V_hasLabelXloopsX_bothXselfX
Given the sink graph
And the traversal of
@@ -449,7 +474,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
| result |
| v[marko] |
- @GraphComputerVerificationMidENotSupported
+ @GraphComputerVerificationMidENotSupported @TinyGremlin
Scenario: g_VX1X_V_valuesXnameX
Given the modern graph
And using the parameter vid1 defined as "v[marko].id"
@@ -467,7 +492,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
| ripple |
| peter |
- @GraphComputerVerificationMidENotSupported
+ @GraphComputerVerificationMidENotSupported @TinyGremlin
Scenario: g_V_outXknowsX_V_name
Given the modern graph
And the traversal of
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/semantics/Orderability.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/semantics/Orderability.feature
index 1e1a3c3397..e6601fb2ba 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/semantics/Orderability.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/semantics/Orderability.feature
@@ -18,6 +18,7 @@
@StepClassSemantics
Feature: Orderability
+ @TinyGremlin
Scenario: g_V_values_order
Given the modern graph
And the traversal of