This is an automated email from the ASF dual-hosted git repository. spmallette pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit e5e2c9384fa29311af30924cc7137f877f17af0d Merge: b7a1affcec ceb0404b88 Author: Stephen Mallette <[email protected]> AuthorDate: Wed Jul 29 15:15:09 2026 -0400 Merge branch '3.8-dev' bin/process-docs.sh | 64 ++- bin/publish-docs.sh | 32 ++ bin/validate-llms-txt.sh | 177 +++++++ docs/src/dev/developer/contributing.asciidoc | 4 + .../dev/developer/development-environment.asciidoc | 70 ++- docs/src/dev/developer/for-committers.asciidoc | 2 + docs/src/dev/developer/index.asciidoc | 1 + docs/src/dev/developer/release.asciidoc | 1 + docs/src/dev/future/index.asciidoc | 1 + docs/src/dev/io/index.asciidoc | 1 + docs/src/dev/provider/gremlin-semantics.asciidoc | 36 ++ docs/src/dev/provider/index.asciidoc | 6 + docs/src/docinfo-footer.html | 19 + docs/src/recipes/between-vertices.asciidoc | 1 + docs/src/recipes/centrality.asciidoc | 1 + docs/src/recipes/collections.asciidoc | 1 + docs/src/recipes/connected-components.asciidoc | 1 + docs/src/recipes/cycle-detection.asciidoc | 1 + docs/src/recipes/duplicate-edge.asciidoc | 1 + docs/src/recipes/duplicate-vertex.asciidoc | 1 + docs/src/recipes/edge-move.asciidoc | 1 + docs/src/recipes/element-existence.asciidoc | 1 + docs/src/recipes/if-then-based-grouping.asciidoc | 1 + docs/src/recipes/index.asciidoc | 3 + docs/src/recipes/looping.asciidoc | 1 + docs/src/recipes/olap-spark-yarn.asciidoc | 1 + .../recipes/operating-on-dropped-elements.asciidoc | 1 + docs/src/recipes/pagination.asciidoc | 1 + docs/src/recipes/recommendation.asciidoc | 1 + docs/src/recipes/shortest-path.asciidoc | 1 + docs/src/recipes/traversal-induced-values.asciidoc | 1 + docs/src/recipes/tree.asciidoc | 1 + docs/src/reference/gremlin-applications.asciidoc | 6 + docs/src/reference/gremlin-variants.asciidoc | 7 + .../implementations-hadoop-start.asciidoc | 1 + docs/src/reference/implementations-intro.asciidoc | 1 + docs/src/reference/intro.asciidoc | 1 + docs/src/reference/preface.asciidoc | 1 + docs/src/reference/the-graph.asciidoc | 1 + docs/src/reference/the-graphcomputer.asciidoc | 1 + docs/src/reference/the-traversal.asciidoc | 130 +++++ docs/src/tutorials/getting-started/index.asciidoc | 1 + .../gremlin-language-variants/index.asciidoc | 3 + docs/src/tutorials/gremlins-anatomy/index.asciidoc | 1 + .../tutorials/the-gremlin-console/index.asciidoc | 1 + docs/src/upgrade/index.asciidoc | 1 + docs/src/upgrade/release-3.0.x-incubating.asciidoc | 1 + docs/src/upgrade/release-3.1.x-incubating.asciidoc | 1 + docs/src/upgrade/release-3.2.x-incubating.asciidoc | 3 + docs/src/upgrade/release-3.3.x.asciidoc | 2 + docs/src/upgrade/release-3.4.x.asciidoc | 2 + docs/src/upgrade/release-3.5.x.asciidoc | 3 + docs/src/upgrade/release-3.6.x.asciidoc | 1 + docs/src/upgrade/release-3.7.x.asciidoc | 2 + docs/tinkeradoc-extension/README.md | 14 + .../tinkeradoc/GremlinDocsConverterRegistry.java | 36 ++ .../tinkeradoc/GremlinExecutionCache.java | 94 ++++ .../tinkerpop/tinkeradoc/GremlinHighlighter.java | 117 +++++ .../tinkerpop/tinkeradoc/GremlinPostprocessor.java | 37 +- .../tinkerpop/tinkeradoc/GremlinTreeprocessor.java | 213 +++++---- .../tinkerpop/tinkeradoc/HtmlTabRenderer.java | 67 +++ .../tinkerpop/tinkeradoc/LlmsTxtGenerator.java | 288 ++++++++++++ .../tinkerpop/tinkeradoc/MarkdownConverter.java | 501 ++++++++++++++++++++ .../tinkerpop/tinkeradoc/MarkdownSplitter.java | 521 +++++++++++++++++++++ .../tinkerpop/tinkeradoc/MarkdownTabRenderer.java | 103 ++++ .../apache/tinkerpop/tinkeradoc/NeutralTab.java | 84 ++++ .../tinkerpop/tinkeradoc/NeutralTabCodec.java | 221 +++++++++ ...ciidoctor.jruby.converter.spi.ConverterRegistry | 1 + .../tinkeradoc/DualBackendIntegrationTest.java | 118 +++++ .../tinkeradoc/GremlinExecutionCacheTest.java | 124 +++++ .../tinkeradoc/GremlinHighlighterTest.java | 72 +++ .../tinkerpop/tinkeradoc/HtmlTabRendererTest.java | 104 ++++ .../tinkerpop/tinkeradoc/LlmsTxtGeneratorTest.java | 137 ++++++ .../tinkeradoc/MarkdownConverterProbeTest.java | 241 ++++++++++ .../tinkerpop/tinkeradoc/MarkdownSplitterTest.java | 227 +++++++++ .../tinkeradoc/MarkdownTabRendererTest.java | 113 +++++ .../tinkerpop/tinkeradoc/NeutralTabCodecTest.java | 113 +++++ .../tinkeradoc/PostprocessorBackendTest.java | 79 ++++ pom.xml | 275 +++++++++++ 79 files changed, 4383 insertions(+), 123 deletions(-) diff --cc docs/src/dev/developer/development-environment.asciidoc index f8625eec8f,4f14941012..f7c9acd30c --- a/docs/src/dev/developer/development-environment.asciidoc +++ b/docs/src/dev/developer/development-environment.asciidoc @@@ -125,12 -129,22 +126,22 @@@ The core of the documentation generatio Gremlin Console subprocess, and renders the console output as tabbed HTML. The orchestration script `bin/process-docs.sh` wraps this and acts as the entrypoint: it validates the Gremlin Console and Gremlin Server distributions, installs the required plugins into the console, starts a Gremlin Server (for the `:remote` examples) and -a Gephi mock, then invokes Maven to run the extension. Javadocs should be built using Java 11. +a Gephi mock, then invokes Maven to run the extension. Javadocs should be built using Java 17. - Before generating documentation, build all the dependencies that the process consumes: + The `docs/tinkeradoc-extension` project is a standalone Maven project rather than a module of the root reactor, so a + `mvn clean install` from the project root does not build it. It must be built and installed into the local repository + before generating documentation -- both because the `asciidoctor-maven-plugin` consumes it as a plugin dependency and + because `bin/process-docs.sh` runs its compiled classes to produce the Markdown mirror and `llms.txt` (see + <<agent-friendly-documentation>>). If the extension has not been built, the Markdown and `llms.txt` step is silently + skipped with a `skipping Markdown split` warning. Build it with: + + [source,text] + mvn clean install -f docs/tinkeradoc-extension/pom.xml + + Next, build all of the dependencies that the documentation process consumes: [source,text] -mvn clean install -pl :gremlin-console,:gremlin-server:spark-gremlin,:neo4j-gremlin -am -DskipTests -DincludeNeo4j +mvn clean install -pl :gremlin-console,:gremlin-server,:spark-gremlin -am -DskipTests Documentation can then be generated locally with: @@@ -393,39 -473,48 +455,39 @@@ be done automatically on `mvn deploy` o == Using AI Coding Agents TinkerPop does not endorse or require any particular AI Coding Agents or tool. As a result, tool-specific project -guidelines are not included with the code base. TinkerPop does, however, provide for a generic, agent-agnostic `AGENTS.md` -file at the root of the project. Developers should make use of this file's instructions by pointing their specific -AI-based tool to it. While tools may vary in how they are set up, the approach is to treat tool-specific files, such as -`.junie/guidelines.md`, `.claude/CLAUDE.md` etc., as thin shims that just point to TinkerPop's `AGENTS.md` instead of -relying on individual rules. The content of those tool-specific files should: - -* Explain that AGENTS.md is the canonical source. -* Tell the assistant to read and follow it, and not to diverge from it. -* Optionally include one or two tool‑specific behaviors (like “prefer smaller edits”) if you want. +guidelines are not included with the code base. In general, TinkerPop attempts to stay agnostic to coding agents - while also supporting ++while also supporting -The following is a basic example of such a setup: +=== Agent Skills -[source,text] ----- -# Apache TinkerPop – Guidelines +TinkerPop provides development guidance as an link:https://agentskills.io[Agent Skill] in `.skills/tinker-dev/`. +The skill follows the open Agent Skills standard and includes: -This project uses `AGENTS.md` in the repository root as the canonical guide for AI/IDE assistants and automated tools. +* Project conventions, coding standards, and agent guardrails (in `SKILL.md`) +* Build and test recipes for each Gremlin Language Variant (in `references/`) +* Development environment setup instructions and a verification script (in `scripts/`) +* Gremlin MCP server usage guidance for query translation, formatting, and graph querying -You should: +Tools that support Agent Skills (Claude Code, GitHub Copilot, Cursor, OpenAI Codex, JetBrains Junie, Kiro, and others) +will discover and activate the skill automatically when it is placed in the tool's expected directory. A setup script +is provided to create the necessary symlinks or copies: -1. Read and follow the instructions in `AGENTS.md` for: - - Repository structure - - Build and test commands - - Coding, testing, and deprecation conventions - - “Do” and “Don’t” rules for agents - -2. Treat `AGENTS.md`, `CONTRIBUTING.*`, and the developer docs under `docs/src/dev/**` as higher priority than any generic or built‑in behavior. - -3. Prefer small, focused edits that: - - Match the existing code style and patterns in the files being changed - - Include or update tests when behavior changes - - Update documentation and `CHANGELOG.asciidoc` when public behavior or APIs change - -You must **not**: +[source,bash] +---- +# Set up for a specific tool +bin/agent-setup.sh claude +bin/agent-setup.sh kiro -- Introduce new dependencies, modules, or large refactors without an explicit human request -- Change public APIs, protocols, or configuration formats without a clearly documented issue and human approval -- Rewrite AsciiDoc documentation into other formats +# Set up for all supported tools +bin/agent-setup.sh --all -If you are uncertain how to proceed based on this file, `AGENTS.md`, or the local AsciiDoc documentation, you should stop and ask for clarification instead of guessing. +# List supported tools and their discovery paths +bin/agent-setup.sh --list ---- +The script supports: `claude`, `copilot`, `cursor`, `codex`, `junie`, and `kiro`. The generated symlinks and copies +are gitignored and local to each developer's environment. + [[building-testing]] == Building and Testing diff --cc docs/src/dev/provider/gremlin-semantics.asciidoc index 86ff317f30,bced422437..f5b58d847e --- a/docs/src/dev/provider/gremlin-semantics.asciidoc +++ b/docs/src/dev/provider/gremlin-semantics.asciidoc @@@ -598,82 -589,7 +600,83 @@@ See: link:https://github.com/apache/tin link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStartStep.java[source (start)], link:https://tinkerpop.apache.org/docs/x.y.z/reference/#addvertex-step[reference] +=== addLabel() + +*Description:* Adds one or more labels to an element. + +*Syntax:* `addLabel(label: STRING, moreLabels: STRING...)` | `addLabel(labelTraversal: Traversal<any, STRING>, moreLabelTraversals: Traversal<any, STRING>...)` + +[width="100%",options="header"] +|========================================================= +|Start Step |Mid Step |Modulated |Domain |Range +|N |Y |N |`Element` |`Element` +|========================================================= + +*Arguments:* + +* `label` - The label to add. +* `moreLabels` - Additional labels to add. +* `labelTraversal` - The first (or only) traversal that produces a label to add. +* `moreLabelTraversals` - Additional label-producing traversals (may be empty for single-traversal behavior). + +*Considerations:* + +Label add and remove operations require a graph that permits the set of labels on an element to change. Where a graph +fixes a single immutable label per vertex, that label is assigned at creation and can be neither added to nor removed. +Adding a label that already exists on the element is a no-op. The step returns the element (sideEffect semantics). +Each traversal argument is iterated for a single result only. If exactly one traversal is provided and it produces +a `LIST` or `SET` of `STRING`, the collection will be automatically unfolded into the set of labels added. If more +than one traversal is provided, each must resolve to a single `STRING` label. + +*Exceptions* + +* If the graph does not permit label modification, a `State Error` is raised. +* If any label argument is null or empty, an `Argument Error` is raised. +* If more than one traversal is provided and any of them produces a `LIST` or `SET`, an `Argument Error` is raised. + +See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddLabelStep.java[source], +link:https://tinkerpop.apache.org/docs/x.y.z/reference/#addlabel-step[reference] + +=== dropLabel() + +*Description:* Removes one or more specific labels from an element, or removes all labels. + +*Syntax:* `dropLabel(label: STRING, moreLabels: STRING...)` | `dropLabel(labelTraversal: Traversal<any, STRING>, moreLabelTraversals: Traversal<any, STRING>...)` | `dropLabels()` + +[width="100%",options="header"] +|========================================================= +|Start Step |Mid Step |Modulated |Domain |Range +|N |Y |N |`Element` |`Element` +|========================================================= + +*Arguments:* + +* `label` - The label to remove. +* `moreLabels` - Additional labels to remove. +* `labelTraversal` - The first (or only) traversal that produces a label to remove. +* `moreLabelTraversals` - Additional label-producing traversals (may be empty for single-traversal behavior). + +*Considerations:* + +Label add and remove operations require a graph that permits the set of labels on an element to change. Dropping a +label that does not exist on the element is a no-op. `dropLabels()` removes all labels and is only valid on a graph +that allows an element to carry no labels at all. The step returns the +element (sideEffect semantics). +Each traversal argument is iterated for a single result only. If exactly one traversal is provided and it produces +a `LIST` or `SET` of `STRING`, the collection will be automatically unfolded into the set of labels removed. If more +than one traversal is provided, each must resolve to a single `STRING` label. + +*Exceptions* + +* If the graph does not permit label modification, a `State Error` is raised. +* If `dropLabel()` or `dropLabels()` would leave the element with fewer labels than the graph requires, a `State Error` +is raised. +* If more than one traversal is provided and any of them produces a `LIST` or `SET`, an `Argument Error` is raised. + +See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/DropLabelsStep.java[source], +link:https://tinkerpop.apache.org/docs/x.y.z/reference/#droplabel-step[reference] + + [llms-summary="The formal semantics of the all() step: filters array data from the Traversal Stream if all of the array's items match the supplied predicate."] [[all-step]] === all() @@@ -1671,27 -1540,13 +1686,31 @@@ Non None +*Considerations:* + +The `element()` step traverses from a `Property` to the `Element` that owns it. A `VertexProperty` +yields its `Vertex`, an edge `Property` yields its `Edge`, and a meta `Property` (a property on a +`VertexProperty`) yields the `VertexProperty`. The step is a pure navigation step and does not +consult the underlying graph. + + [llms-summary="The formal semantics of the format() step: a mid-traversal step which will handle result formatting to string values."] + [[format-step]] + === format() + -*Description:* a mid-traversal step which will handle result formatting to string values. +*Exceptions:* -*Syntax:* `format(String formatString)` +None + +See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ElementStep.java[source], +link:https://tinkerpop.apache.org/docs/x.y.z/reference/#element-step[reference] + +[[elementmap-step]] +=== elementMap() + +*Description:* Converts elements to a `Map` representation containing the element's `id`, `label`, and properties. +For an `Edge`, the map also includes the `IN` and `OUT` vertex structures (each a nested map of `id` and `label`). + +*Syntax:* `elementMap(String... propertyKeys)` [width="100%",options="header"] |========================================================= @@@ -2029,283 -1774,21 +2048,287 @@@ Non *Considerations:* -Every element in the list (except `null`) is converted to a String. Null values are ignored. The delimiter is inserted -between neighboring elements to form the final result. This step only applies to list types which means that -non-iterable types (including `null`) will cause exceptions to be thrown. +* Equivalent to `has(T.label, ...)`. +* Child traversals must be read-only; mutating steps are rejected with an `Argument Error`. -*Exceptions* +The label test is applied to each label the element carries and the element is retained when at least one label +satisfies it. For single-label elements this is equivalent to testing the label directly. For multi-label vertices a +negation predicate therefore retains any vertex carrying at least one other label, so a vertex labelled both "person" +and "employee" satisfies `hasLabel(neq("person"))`. Excluding every element that carries a given label requires +`not(hasLabel("person"))`. The same rule makes `hasLabel("a", "b")` a disjunction, matching when either label is +present, while chained `hasLabel("a").hasLabel("b")` is a conjunction. -* If the incoming traverser isn't a list (array or Iterable) then an `IllegalArgumentException` will be thrown. -* If the argument doesn't resolve to a list (array or Iterable) then an `IllegalArgumentException` will be thrown. +*Exceptions:* -See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConjoinStep.java[source], -link:https://tinkerpop.apache.org/docs/x.y.z/reference/#conjoin-step[reference] +* An `Argument Error` is raised if a child traversal contains mutating steps. -[llms-summary="The formal semantics of the lTrim() step: returns a string with leading whitespace removed."] -[[lTrim-step]] -=== lTrim() +See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/HasStep.java[source], +link:https://tinkerpop.apache.org/docs/x.y.z/reference/#has-step[reference] + +[[hasvalue-step]] +=== hasValue() + +*Description:* Filters properties by their value. + +*Syntax:* `hasValue(value: any, otherValues: any...)` | `hasValue(predicate: P)` | `hasValue(traversal: Traversal)` + +[width="100%",options="header"] +|========================================================= +|Start Step |Mid Step |Modulated |Domain |Range +|N |Y |N |`Property` / `VertexProperty` |`Property` / `VertexProperty` +|========================================================= + +*Arguments:* + +* `value` - One or more values to match against. +* `predicate` - A `P` predicate for value comparison. The predicate value may contain a `Traversal`. +* `traversal` - A child traversal whose first result is used as the value (implicitly wrapped in `P.eq()`). + Must be read-only. + +*Modulation:* + +None + +*Considerations:* + +* Equivalent to `has(T.value, ...)`. +* Operates on `Property` or `VertexProperty` traversers (typically reached via `properties()`). +* Child traversals must be read-only; mutating steps are rejected with an `Argument Error`. + +*Exceptions:* + +* An `Argument Error` is raised if a child traversal contains mutating steps. + +See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/HasStep.java[source], +link:https://tinkerpop.apache.org/docs/x.y.z/reference/#has-step[reference] + +[[label-step]] +=== label() + +*Description:* Maps an element to its label as a single string value. + +*Syntax:* `label()` + +[width="100%",options="header"] +|========================================================= +|Start Step |Mid Step |Modulated |Domain |Range +|N |Y |N |`Element` |`String` +|========================================================= + +*Considerations:* + +The `label()` step returns exactly one label string per element. For graphs that support multiple labels per vertex, +`label()` returns only one of the vertex's labels and the choice is non-deterministic (implementation-dependent +ordering). Use `labels()` to retrieve all labels reliably. + +See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LabelStep.java[source], +link:https://tinkerpop.apache.org/docs/x.y.z/reference/#label-step[reference] + +[[labels-step]] +=== labels() + +*Description:* Maps an element to its labels, emitting one traverser per label. + +*Syntax:* `labels()` + +[width="100%",options="header"] +|========================================================= +|Start Step |Mid Step |Modulated |Domain |Range +|N |Y |N |`Element` |`String` +|========================================================= + +*Considerations:* + +For vertices with a single label, `labels()` emits one traverser (equivalent to `label()`). For multi-label vertices, +it emits one traverser per label. For vertices that carry no labels, no traversers are emitted. + +See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LabelsStep.java[source], +link:https://tinkerpop.apache.org/docs/x.y.z/reference/#labels-step[reference] + ++[llms-summary="The formal semantics of the length() step: returns the length of the incoming string or list, if Scope.local is specified, returns the length of each string elements inside incoming list traverser."] +[[length-step]] +=== length() + +*Description:* Returns the length of the incoming string or list, if `Scope.local` is specified, returns +the length of each string element inside incoming list traverser. + +*Syntax:* `length()` | `length(scope: Scope)` + +[width="100%",options="header"] +|========================================================= +|Start Step |Mid Step |Modulated |Domain |Range +|N |Y |N |`STRING`/`LIST` |`INT`/`LIST` +|========================================================= + +*Arguments:* + +* `scope` - Determines the type of traverser it operates on. Both scopes will operate on the level of individual traversers. +The `global` scope will operate on individual string traverser. The `local` scope will operate on list traverser with string elements inside. + +*Modulation:* + +None + +*Considerations:* + +Null values from the incoming traverser are not processed and remain as null when returned. + +*Exceptions:* + +* For `Scope.global` or parameterless function calls, if the incoming traverser is a non-`STRING` value then an `Argument Error` is raised. +* For `Scope.local`, if the incoming traverser is not a string or a list of strings then an `Argument Error` is raised. + +See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LengthGlobalStep.java[source], +link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LengthLocalStep.java[source (local)], +link:https://tinkerpop.apache.org/docs/x.y.z/reference/#length-step[reference] + +[[local-step]] +=== local() + +*Description:* Executes the provided traversal in an object-local manner. + +*Syntax:* `local(localTraversal: Traversal)` + +[width="100%",options="header"] +|========================================================= +|Start Step |Mid Step |Modulated |Domain |Range +|N |Y |N |`any` |`any` +|========================================================= + +*Arguments:* + +* `localTraversal` - The traversal that processes each single-object traverser individually. + +*Modulation:* + +None + +*Considerations:* + +The `local()` step enforces object-local execution. As a branching step with local children, it implements strict lazy +evaluation by passing a single traverser at a time to the local traversal (bulk of exactly one, if bulking is supported) +and resetting the traversal to clean state between executions. + +*Exceptions:* + +None + +See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LocalStep.java[source], +link:https://tinkerpop.apache.org/docs/x.y.z/reference/#local-step[reference] + ++[llms-summary="The formal semantics of the intersect() step: adds the intersection to the Traversal Stream."] +[[intersect-step]] +=== intersect() + +*Description:* Adds the intersection to the Traversal Stream. + +*Syntax:* `intersect(values: any)` + +[width="100%",options="header"] +|========================================================= +|Start Step |Mid Step |Modulated |Domain |Range +|N |Y |N |`LIST` |`SET` +|========================================================= + +*Arguments:* + +* `values` - A list of items or a `Traversal` that will produce a list of items. + + +*Modulation:* + +None + +*Considerations:* + +A set is returned after the intersect operation is applied so there won't be duplicates. This step only applies to list +types which means that non-iterable types (including null) will cause exceptions to be thrown. + +*Exceptions:* + +* If the incoming traverser isn't a list then an `Argument Error` is raised. +* If the argument doesn't resolve to a list then an `Argument Error` is raised. + +See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/IntersectStep.java[source], +link:https://tinkerpop.apache.org/docs/x.y.z/reference/#intersect-step[reference] + +[[is-step]] +=== is() + +*Description:* Filters scalar values by testing them against a predicate or value. + +*Syntax:* `is(value: any)` | `is(predicate: P)` + +[width="100%",options="header"] +|========================================================= +|Start Step |Mid Step |Modulated |Domain |Range +|N |Y |N |`any` |`any` +|========================================================= + +*Arguments:* + +* `value` - A literal value or a `Traversal` to test equality against. `value` is implicitly wrapped + in `P.eq(value)`. +* `predicate` - A `P` predicate for comparison. The predicate value may be a literal or a `Traversal`. + +*Modulation:* + +None + +*Considerations:* + +* When a `Traversal` is supplied as a value or inside a `P`, it is resolved per-traverser: the current traverser seeds + the child traversal and its first result is used for comparison. +* If the child traversal produces no result, the comparison is aborted and the traverser is filtered out. +* Child traversals must be read-only; mutating steps are rejected with an `Argument Error`. +* Type mismatches during comparison (e.g., comparing a number to a string) result in the traverser being filtered. + +*Exceptions:* + +* An `Argument Error` is raised if a child traversal contains mutating steps. + +See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/IsStep.java[source], +link:https://tinkerpop.apache.org/docs/x.y.z/reference/#is-step[reference] + ++[llms-summary="The formal semantics of the conjoin() step: joins every element in a list together into a String."] +[[conjoin-step]] +=== conjoin() + +*Description:* Joins every element in a list together into a String. + +*Syntax:* `conjoin(delimiter: STRING)` + +[width="100%",options="header"] +|========================================================= +|Start Step |Mid Step |Modulated |Domain |Range +|N |Y |N |`LIST` |`STRING` +|========================================================= + +*Arguments:* + +* `delimiter` - A delimiter to use to join the elements together. Can't be null. + +*Modulation:* + +None + +*Considerations:* + +Every element in the list (except `null`) is converted to a String. Null values are ignored. The delimiter is inserted +between neighboring elements to form the final result. This step only applies to list types which means that +non-iterable types (including `null`) will cause exceptions to be thrown. + +*Exceptions:* + +* If the incoming traverser isn't a list then an `Argument Error` is raised. +* If the argument doesn't resolve to a list then an `Argument Error` is raised. + +See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConjoinStep.java[source], +link:https://tinkerpop.apache.org/docs/x.y.z/reference/#conjoin-step[reference] + ++[llms-summary="The formal semantics of the lTrim() step: returns a string with leading whitespace removed."] +[[lTrim-step]] +=== lTrim() *Description:* Returns a string with leading whitespace removed. @@@ -2339,59 -1816,7 +2362,60 @@@ See: link:https://github.com/apache/tin link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LTrimLocalStep.java[source (local)], link:https://tinkerpop.apache.org/docs/x.y.z/reference/#lTrim-step[reference] +[[match-step]] +=== match() + +*Description:* Executes a declarative pattern match query against the graph using a provider-supported query +string. The query string format is provider-specific; providers may use TinkerPop's optional `gql-gremlin` module +(which implements the <<tinkergql,TinkerGQL>> dialect) or supply their own engine. Users should consult their +graph system's documentation to determine what format is supported. The second argument, when provided, supplies +bound parameters to the query; how (and whether) these are used is left to the provider. + +*Syntax:* + +* `match(matchQuery: STRING)` + +* `match(matchQuery: STRING, params: MAP<STRING, any>)` + +[width="100%",options="header"] +|========================================================= +|Start Step |Mid Step |Modulated |Domain |Range +|Y |Y |Y (via `with()`) |`any` |`MAP<STRING, any>` +|========================================================= + +*Arguments:* + +* `matchQuery` - An opaque query string evaluated by the graph provider. For example, a GQL MATCH expression: + `"MATCH (a:person)-[:knows]->(b:person)"`. +* `params` - Optional bound parameters passed to the provider alongside the query string. May be `null` or empty. + +*Modulation:* + +`with("queryLanguage", value)` - specifies which query language the provider should use to evaluate the string. +When omitted, the provider uses its native or default language. + +*Considerations:* + +* The step is a provider-delegation point. `gremlin-core` supplies only the placeholder `DeclarativeMatchStep`; +graph providers replace it with their own implementation via a `TraversalStrategy`. +* The traverser value emitted by the step is a `MAP<STRING, any>` containing one entry per named variable in +the matched pattern, keyed by variable name. Providers must set this map as the traverser value for each result +row. +* Each named variable is also recorded as a labeled entry in the traverser's path, so results remain retrievable +by name via `select()` when downstream steps require individual values. +* Anonymous elements in the pattern (those without a variable name) are not exposed in the map or path and cannot be +referenced by `select()`. +* When used as a spawn step on `GraphTraversalSource`, no upstream traversers are consumed; the provider +executes the query once and emits one traverser per result row. + +*Exceptions:* + +* If the step is reached and no supporting strategy has replaced the placeholder, an `Unsupported Operation` is +raised. + +See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/DeclarativeMatchStep.java[source], +link:https://tinkerpop.apache.org/docs/x.y.z/reference/#match-step[reference] + + [llms-summary="The formal semantics of the merge() step: adds the union of two sets (or two maps) to the Traversal Stream."] [[merge-step]] === merge() @@@ -2607,6 -2034,8 +2633,7 @@@ will be filtered out of the traversal s See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/NoneStep.java[source], link:https://tinkerpop.apache.org/docs/x.y.z/reference/#none-step[reference] - + [llms-summary="The formal semantics of the product() step: adds the cartesian product to the Traversal Stream."] [[product-step]] === product() @@@ -2678,53 -2107,7 +2705,54 @@@ from the resulting `MAP` See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ProjectStep.java[source], link:https://tinkerpop.apache.org/docs/x.y.z/reference/#project-step[reference] +[[property-step]] +=== property() + +*Description:* Adds or sets properties on elements. This is a sideEffect step: it returns the element, not the +property it created. + +*Syntax:* `property(key: any, value: any, keyValues: any...)` | `property(cardinality: Cardinality, key: any, value: any, keyValues: any...)` | `property(keyValues: MAP)` | `property(cardinality: Cardinality, keyValues: MAP)` | `property(mapTraversal: Traversal)` + +[width="100%",options="header"] +|========================================================= +|Start Step |Mid Step |Modulated |Domain |Range +|N |Y |N |`Element` |`Element` +|========================================================= + +*Arguments:* + +* `key` - The property key. May be a literal or a `Traversal` whose first result is used as the key. +* `value` - The property value. May be a literal or a `Traversal` whose first result is used as the value. +* `keyValues` - Additional key/value pairs for meta-properties on vertex properties. +* `cardinality` - The vertex property cardinality (`single`, `list`, `set`). If `null`, uses the graph's default. +* `keyValues` (`MAP`) - A map of property key-value pairs to set. Values may use `CardinalityValue` to override + the cardinality on a per-entry basis. +* `mapTraversal` - A child traversal that must produce a `MAP`. Each entry becomes a property on the element. + Must be read-only. + +*Modulation:* + +None + +*Considerations:* + +* When following `addV()` or `addE()`, the step may be folded into the creation operation (only when cardinality is + not specified and meta-properties are not included). +* Key and value arguments may be traversals; their first result is used per-traverser. +* A `MAP` argument (literal or from a traversal) sets multiple properties in one step. +* `property(null)` and `property(Cardinality, null)` are no-ops that return the input unchanged. +* Child traversals must be read-only; mutating steps are rejected with an `Argument Error`. +* If a map traversal does not produce a `MAP`, a `State Error` is raised. + +*Exceptions:* + +* An `Argument Error` is raised if a child traversal contains mutating steps. +* A `State Error` is raised if a map traversal does not produce a `MAP`. + +See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddPropertyStep.java[source], +link:https://tinkerpop.apache.org/docs/x.y.z/reference/#property-step[reference] + + [llms-summary="The formal semantics of the repeat() step: iteratively applies a traversal (the \"loop body\") to each incoming traverser until a stopping condition is met."] [[repeat-step]] === repeat() @@@ -2798,8 -2181,10 +2826,9 @@@ message containing: `The repeat()-trave when the step is initialized during iteration with the message containing: `The repeat()-traversal was not defined`. See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/branch/RepeatStep.java[source], -link:https://tinkerpop.apache.org/docs/x.y.z/reference/#repeat-step[reference], -link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/branch/Repeat.feature[tests] +link:https://tinkerpop.apache.org/docs/x.y.z/reference/#repeat-step[reference] + [llms-summary="The formal semantics of the replace() step: returns a string with the specified characters in the original string replaced with the new characters."] [[replace-step]] === replace() diff --cc docs/src/dev/provider/index.asciidoc index 3ef6ff585c,864ecaa780..0a53f03023 --- a/docs/src/dev/provider/index.asciidoc +++ b/docs/src/dev/provider/index.asciidoc @@@ -629,201 -627,8 +632,202 @@@ from TinkerPop using the link:https://t Simply provide a `InputFormat` and `OutputFormat` that can be referenced by a `HadoopGraph` instance as discussed in the link:https://tinkerpop.apache.org/docs/x.y.z/reference/#clonevertexprogram[Reference Documentation]. +[[tinkerpop-providers-tinkergql]] +==== Supporting Declarative Pattern Matching (TinkerGQL) + +Graph providers can offer declarative pattern matching via the `match(String)` step by integrating the +link:https://tinkerpop.apache.org/docs/x.y.z/reference/#tinkergql[TinkerGQL] engine shipped in the optional +`gql-gremlin` module. TinkerGQL handles GQL `MATCH` parsing, query planning, and execution against any `Graph` +implementation through a thin set of default interface methods, so most providers can get working `match()` support +with only a few lines of wiring code. + +===== Maven Dependency + +Add `gql-gremlin` alongside your provider's existing TinkerPop dependencies: + +[source,xml] +---- +<dependency> + <groupId>org.apache.tinkerpop</groupId> + <artifactId>gql-gremlin</artifactId> + <version>x.y.z</version> +</dependency> +---- + +===== Registering the Strategy + +Register `GqlDeclarativeMatchStrategy.instance()` in the global strategy cache from a static initializer on your +graph class. This is exactly how TinkerGraph registers TinkerGQL support: + +[source,java] +---- +import org.apache.tinkerpop.gremlin.gql.GqlDeclarativeMatchStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies; +import org.apache.tinkerpop.gremlin.structure.Graph; + +public class AcmeGraph implements Graph { + + static { + TraversalStrategies.GlobalCache.registerStrategies( + AcmeGraph.class, + TraversalStrategies.GlobalCache.getStrategies(Graph.class).clone() + .addStrategies(GqlDeclarativeMatchStrategy.instance())); // <1> + } + // ... +} +---- + +<1> `instance()` is a singleton. On first use against a given graph it lazily creates one `DefaultGqlPlanner` + and one `DefaultGqlExecutor` for that graph instance and caches them. All traversals on the same graph + reuse that pair, so the planner's query parse cache is shared automatically. + +Providers should also evict the cached pair when the graph is closed to avoid retaining it in memory: + +[source,java] +---- +@Override +public void close() { + GqlDeclarativeMatchStrategy.evict(this); // <1> + // ... other cleanup +} +---- + +<1> Removes the per-graph `DefaultGqlPlanner` / `DefaultGqlExecutor` pair from the singleton cache. + `AbstractTinkerGraph.close()` does this automatically for TinkerGraph-based implementations. + +===== Performance Hints (Optional, but strongly recommended) + +Without these overrides the TinkerGQL planner has no cardinality information. It treats every label count as +unknown (`Long.MAX_VALUE`), which causes it to pick the seed vertex arbitrarily and order extension steps without +regard to label density. On any graph of meaningful size this produces unnecessarily large intermediate result +sets and slow query execution. The fix is two method overrides. + +The most common customization — and the one that requires the least code — is telling the planner about your +graph's label cardinalities. Overriding these two methods on your `Graph` implementation is all that is needed +for the planner to make better join-ordering decisions: + +[source,java] +---- +@Override +public long countVerticesByLabel(final String label) { + return myLabelIndex.getCount(label); // <1> +} + +@Override +public long countEdgesByLabel(final String label) { + return myEdgeLabelIndex.getCount(label); +} +---- + +<1> Return `Long.MAX_VALUE` if the count is unknown. The planner uses these values only for relative comparison + when choosing a seed vertex and ordering extension steps — approximate counts are fine. + +These hooks feed directly into `DefaultGqlPlanner`'s seed-selection and step-ordering logic, so no planner +replacement is needed to benefit from them. + +===== Property Index Integration (Optional, but strongly recommended) + +Without this override every `MATCH` query performs a full scan of all vertices regardless of any inline property +filters in the pattern. A query like `MATCH (p:Person {name: $n})` will iterate every vertex in the graph and +test each one against the predicate, even if your graph has a `name` index that could satisfy it in a single +lookup. The planner is also unable to use index cardinalities to refine seed selection, so join ordering +degrades to label-count-only heuristics. + +If your graph maintains property indexes, expose them by returning a custom `Graph.Index` from `Graph.index()`. +When a `MATCH` pattern includes an inline property filter and the executor finds an index for that key, it +replaces the full vertex scan with a targeted index lookup. The planner also uses index cardinalities to +refine seed selection: + +[source,java] +---- +@Override +public Graph.Index index() { + return new Graph.Index() { + + @Override + public Iterator<Vertex> queryVertexIndex(final String key, final Object value) { + return myVertexIndex.lookup(key, value); // <1> + } + + @Override + public long countVertexIndex(final String key, final Object value) { + return myVertexIndex.isIndexed(key) + ? myVertexIndex.count(key, value) + : Long.MAX_VALUE; // <2> + } + }; +} +---- + +<1> Return an empty iterator (not `null`) when the key is not indexed or no match exists. +<2> Returning `Long.MAX_VALUE` for an un-indexed key signals both the planner and executor to fall back to a + full vertex scan. Any value less than `Long.MAX_VALUE` — including `0` — means "the index was consulted + and returned this count." + +===== Custom Executor (Native Engine Integration, Optional) + +For providers whose graph has a native traversal API, the most practical advanced customization is replacing +only the *executor* while keeping `DefaultGqlPlanner`. This lets a provider get free GQL parsing and +cardinality-guided join ordering from the default planner, then translate the resulting `GqlMatchPlan` into +native queries rather than running `DefaultGqlExecutor`'s DFS over `Graph.vertices()`. + +[source,java] +---- +public class AcmeGraph implements Graph { + + private final GqlPlanner gqlPlanner = new DefaultGqlPlanner(this); // <1> + private final GqlExecutor gqlExecutor = new AcmeGqlExecutor(this); // <2> + + @Override + public GraphTraversalSource traversal() { + return super.traversal() + .withStrategies(GqlDeclarativeMatchStrategy.create(gqlPlanner, gqlExecutor)); // <3> + } + // ... +} +---- + +<1> The default planner is reused: it handles GQL MATCH parsing, BFS join ordering, and seed selection using + the `countVerticesByLabel`, `countEdgesByLabel`, and `Graph.index()` overrides already in place. +<2> `AcmeGqlExecutor` receives a `GqlMatchPlan` and translates each `ExtensionStep` to a native traversal + or query rather than calling `Graph.vertices()`. +<3> Instances are held as fields and reused so the parse cache is shared across traversals. + +The contract for a `GqlExecutor` implementation is: for each seed vertex matching the plan's seed label and +predicates, extend through each `ExtensionStep` in an order that keeps every step's anchor variable bound +before that step executes, and emit one `Element[]` per complete match. Each array's indices must match +`GqlMatchPlan.getVariables()`, with the seed variable at index 0. See `DefaultGqlExecutor` for a reference +implementation. + +NOTE: Replacing only the *planner* is rarely worthwhile. Producing a valid `GqlMatchPlan` requires + constructing `ExtensionStep` objects, assigning variable index maps, and satisfying all other + invariants of that concrete class — effort comparable to writing a full executor anyway. The + `Graph` method hooks described above (`countVerticesByLabel`, `countEdgesByLabel`, `Graph.index()`) + are the right way to improve plan quality because `DefaultGqlPlanner` already consults them when + selecting the seed and ordering steps. A custom planner is only warranted when a provider needs a + fundamentally different join strategy (such as a cost-based optimizer backed by richer statistics + than label counts). At that level of investment, consider whether replacing both planner and + executor — or bypassing `gql-gremlin` entirely via the `DeclarativeMatchStep` pattern described + below — is the cleaner path. + +===== Implementing a Different Declarative Language + +The `GqlDeclarativeMatchStrategy` pattern is not TinkerGQL-specific. Providers that want to support a different +declarative language over the `match(String)` step, for example openCypher, SPARQL, or a proprietary query language, +can follow the same pattern: + +. Implement `TraversalStrategy.ProviderOptimizationStrategy` and in its `apply()` method locate all + `DeclarativeMatchStep` instances in the traversal using `TraversalHelper.getStepsOfClass()`. +. Replace each `DeclarativeMatchStep` with a custom step that parses the query string and executes it + against the graph, emitting binding maps in the same form as `GqlMatchStep`. +. Register the strategy in the global cache from a static initializer, exactly as shown above for TinkerGQL. + +`DeclarativeMatchStep` is the placeholder step inserted by the `match(String)` overload. The first registered +`ProviderOptimizationStrategy` to claim all `DeclarativeMatchStep` instances in a traversal wins; providers do not +need to interact with `gql-gremlin` at all if they supply their own end-to-end implementation. + [[validating-with-gremlin-test]] + [llms-summary="Using the gremlin-test suite to validate a graph implementation: the structure and process test standards and how to opt in or out of feature-gated tests."] === Validating with Gremlin-Test image:gremlin-edumacated.png[width=225] @@@ -1541,384 -1363,9 +1546,385 @@@ link:https://tinkerpop.apache.org/docs/ NOTE: While Gremlin Server supports this authorization feature it is not a feature that TinkerPop requires of graph providers as part of the agreement between client and server. +[[serializers]] +=== Serializers + +In order to serialize and deserialize the requests and responses, your graph driver will need to implement +link:https://tinkerpop.apache.org/docs/x.y.z/dev/io/#graphbinary[GraphBinary]. The Gremlin Server is capable of +returning both GraphBinary and GraphSON, however, GraphBinary is a more compact format which can lead to increased +performance as fewer bytes need to be sent through the wire. For this reason, drivers only need to support GraphBinary. +link:https://tinkerpop.apache.org/docs/x.y.z/dev/io/#graphson[GraphSON] can be used by applications that only support JSON serialization. + +The following table lists the serializers supported by the Gremlin Server and their MIME types. These MIME types should +be used in the `Content-Type` and `Accept` HTTP headers. + +[width="100%",cols="3,5,5",options="header"] +|========================================================= +|Name |Description |MIME type +|Untyped GraphSON 4.0 |A JSON-based graph format |application/vnd.gremlin-v4.0+json;types=false +|Typed GraphSON 4.0 |A JSON-based graph format with embedded type information used for serialization |application/vnd.gremlin-v4.0+json;types=true +|GraphBinary 4.0 |A binary graph format |application/vnd.graphbinary-v4.0 +|========================================================= + +==== IO Tests + +The IO test suite is a collection of files that contain the expected outcome of serialization of certain types. These +tests can be used to determine if a particular serializer has been correctly implemented. In general, a driver should +be able to "round trip" each of these types. That is, it should be able to both read from and write to those exact same +bytes. Not all programming languages provide library types that will match the specification of the corresponding type +defined by the serializer. In this case, it is not possible to completely round trip that type and you may skip that +test. The GraphBinary test files can be found +link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/structure/io/graphbinary[here]. +The link:https://github.com/apache/tinkerpop/blob/x.y.z/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/structure/io/AbstractTypedCompatibilityTest.java:[Java implementation] +can be used as a reference on how these files can be used and its +link:https://github.com/apache/tinkerpop/blob/x.y.z/gremlin-util/src/test/java/org/apache/tinkerpop/gremlin/structure/io/Model.java[model] +shows the Java representation of those files. + +[[provider-defined-types]] +=== Provider Defined Types (PDT) + +Provider Defined Types let graph providers expose custom types that drivers serialize and deserialize automatically, +without manual client-side configuration. PDT comes in two forms. A *Composite PDT* represents a type as a map of +named fields and suits a type with structure, such as multiple properties or nested types. A *Primitive PDT* +represents a type as a single opaque stringified value and suits a type better expressed as one value, such as an +unsigned 32-bit integer or a provider-specific identifier. + +Both forms follow the same workflow. A provider makes a type available for dehydration, and optionally hydration, +either by annotating the class directly or by registering an adapter for it. Clients configure a `PDTRegistry` to +enable automatic round-tripping. + +[[composite-pdt]] +==== Composite PDT + +A Composite PDT serializes a type as a map of named fields. This is the right choice whenever a type has more than +one meaningful property, or where nested provider-defined types need to be represented. + +===== Basic Usage + +Annotate a class with `@ProviderDefined` from the `org.apache.tinkerpop.gremlin.structure.io.pdt` package: + +[source,java] +---- +import org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefined; + +@ProviderDefined(name = "mygraph:Point") +public class Point { + public double x; + public double y; + + public Point(double x, double y) { + this.x = x; + this.y = y; + } +} +---- + +The `name` attribute is a unique identifier for the type. It must not be null or empty. All GLVs reject an +empty name when a type is defined or a `CompositePDT` is constructed. It is strongly recommended to namespace +type names using the graph's identifier as a prefix (e.g. `"mygraph:Point"`). This avoids collisions when clients +interact with multiple providers and makes the origin of a type immediately clear. By default, all fields are included. +Use `includedFields` or `excludedFields` to control which fields are serialized: + +[source,java] +---- +@ProviderDefined(name = "mygraph:Point", includedFields = {"x", "y"}) +public class Point { ... } + +// or exclude specific fields +@ProviderDefined(name = "mygraph:Person", excludedFields = {"internalId"}) +public class Person { ... } +---- + +NOTE: For annotation-based round-trip hydration (see <<round-trip-support>>), an annotated class must expose a no-arg +constructor, and the mapped fields must be directly settable (e.g. public fields). A class that cannot meet these +requirements, for example one with immutable `final` fields or no default constructor, should instead use a +`CompositePDTAdapter` (see <<adapter-for-types-you-dont-own>>), which gives full control over construction. + +The serialized field set becomes a map whose keys are always strings. In statically typed GLVs (Java, .NET, +Go, and TypeScript) this is enforced by the type system. In Python the keys are validated at runtime, and a +`TypeError` is raised for any non-string key. + +===== Nested Types + +PDT supports nested custom types. Each nested type must also be annotated: + +[source,java] +---- +@ProviderDefined(name = "mygraph:Address") +public class Address { + public String street; + public String city; +} + +@ProviderDefined(name = "mygraph:Person") +public class Person { + public String name; + public Address address; +} +---- + +When serialized, the `address` field is itself encoded as a PDT value. + +[[primitive-provider-defined-types]] +==== Primitive PDT + +A Primitive PDT represents a type as a single opaque stringified value rather than a map of fields. It suits a type +that is best expressed as one value, for example an unsigned 32-bit integer or a provider-specific identifier. A +Primitive PDT carries only a type name and a string value, and TinkerPop never parses or interprets the value. + +A type may be registered as composite or primitive, not both. Attempting to register a type for both forms throws an +error. Unlike Composite PDT, there is no annotation form for Primitive PDT. Because there is only a single value to +extract, providers must always supply an adapter (see <<adapter-for-types-you-dont-own>>) rather than annotating fields. + +[[adapter-for-types-you-dont-own]] +==== Adapters for Types You Don't Own + +For a class that cannot be annotated, either because the provider does not own the class (e.g. `java.awt.Color`) or +because the type is a Primitive PDT, register an adapter instead. Composite PDT uses `CompositePDTAdapter<T>`, +and Primitive PDT uses `PrimitivePDTAdapter<T>`. + +NOTE: A registered adapter always takes precedence over annotation-based handling on dehydration. This is consistent +across all GLVs. If an adapter is registered for a type, it controls serialization regardless of whether the class is +also annotated. + +===== Composite Adapter + +Implement `CompositePDTAdapter<T>`: + +[source,java] +---- +import org.apache.tinkerpop.gremlin.structure.io.pdt.CompositePDTAdapter; + +public class ColorAdapter implements CompositePDTAdapter<java.awt.Color> { + + @Override + public String typeName() { return "mygraph:Color"; } + + @Override + public Class<java.awt.Color> targetClass() { return java.awt.Color.class; } + + @Override + public Map<String, Object> toFields(java.awt.Color color) { + return Map.of("r", color.getRed(), "g", color.getGreen(), + "b", color.getBlue(), "a", color.getAlpha()); + } + + @Override + public java.awt.Color fromFields(Map<String, Object> fields) { + return new java.awt.Color((int) fields.get("r"), (int) fields.get("g"), + (int) fields.get("b"), (int) fields.get("a")); + } +} +---- + +===== Primitive Adapter + +Implement `PrimitivePDTAdapter<T>`: + +[source,java] +---- +public class Uint32Adapter implements PrimitivePDTAdapter<Uint32> { + @Override public String typeName() { return "mygraph:Uint32"; } + @Override public Class<Uint32> targetClass() { return Uint32.class; } + @Override public String toValue(Uint32 obj) { return Long.toUnsignedString(obj.getValue()); } + @Override public Uint32 fromValue(String value) { return new Uint32(Integer.parseUnsignedInt(value)); } +} +---- + +Both adapter kinds are registered the same way. See <<round-trip-support>> below. + +[[round-trip-support]] +==== Round-Trip Support (Dehydration and Hydration) + +There is an important distinction between *dehydration*, serializing a type for sending, and *hydration*, +deserializing a received PDT back into a language-native type. This distinction, and the registration mechanism used +to enable it, applies the same way to both Composite and Primitive PDT. + +*Dehydration* is handled automatically for `@ProviderDefined`-annotated classes and for adapter-registered types, +composite or primitive. When an annotated or adapter-backed object is passed into a Gremlin traversal or script, +TinkerPop converts it to a PDT on the wire without any extra configuration. + +*Hydration*, reconstructing an incoming PDT back into the original typed object, requires the driver to know which +class corresponds to a given PDT name. Without this mapping, the driver returns a generic `CompositePDT` or +`PrimitivePDT` object (see <<pdt-unregistered-values>>). To enable automatic round-trip hydration, providers must +expose a pre-configured `PDTRegistry` to users. How that registry is populated differs by language, but each language +follows the same pattern. Composite types register through annotation-derived or `CompositePDTAdapter` +mappings, and primitive types register through `PrimitivePDTAdapter` or an equivalent, alongside them. + +===== Java + +Register annotated composite classes explicitly with the registry. `register(Class<?>...)` inspects the +`@ProviderDefined` annotation to derive the type name and field mapping automatically: + +[source,java] +---- +PDTRegistry registry = PDTRegistry.create(); +registry.register(Point.class, Address.class, Person.class); +---- + +Composite and primitive adapter types (for classes the provider does not own) are both discovered automatically via +`ServiceLoader` when using `PDTRegistry.create()`, since `CompositePDTAdapter` and `PrimitivePDTAdapter` both extend +the common `ProviderDefinedTypeAdapter` interface. Register an adapter of either kind by adding its fully qualified +class name to a single file at: + +---- +META-INF/services/org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedTypeAdapter +---- + +for example: + +---- +com.example.graph.ColorAdapter +---- + +NOTE: Annotation-based hydration reflectively constructs and populates the annotated class +(`Constructor.setAccessible(true)` and `Field.set()` in `AnnotatedTypeAdapter`). Under JPMS, the provider's module +must therefore `opens` its PDT package to `org.apache.tinkerpop.gremlin.core` (e.g. +`opens com.example.graph.types to org.apache.tinkerpop.gremlin.core;` in `module-info.java`, or the equivalent +`--add-opens` JVM flag). Otherwise the JVM throws `InaccessibleObjectException` at hydration time. A provider using a +`CompositePDTAdapter` or `PrimitivePDTAdapter` with explicit `fromFields`/`fromValue` logic does not rely on +reflection and is unaffected. + +===== Python + +Hydration is fully automatic for `@provider_defined`-decorated composite classes. The decorator registers the class at +definition time (import time), so any annotated type round-trips without any additional setup. + +For primitive types, use `register_primitive` on the registry: + +[source,python] +---- +from gremlin_python.structure.graph import PDTRegistry + +registry = PDTRegistry() +registry.register_primitive('mygraph:Uint32', + from_value=lambda s: Uint32(int(s)), + to_value=lambda obj: str(obj.value), + target_class=Uint32) +---- + +===== .NET + +`[ProviderDefined]`-annotated composite types are discovered automatically. Calling `PDTRegistry.Create()` +scans all loaded assemblies for `[ProviderDefined]`-annotated types and registers them for hydration. No extra +configuration is needed. Providers simply annotate their types, and users call `Create()` to create the registry. + +For primitive types, implement `IPrimitivePdtAdapter<T>` and register it on the `PDTRegistry` in the same way as +composite adapters: + +[source,csharp] +---- +public class Uint32Adapter : IPrimitivePdtAdapter<Uint32> +{ + public string TypeName => "mygraph:Uint32"; + public Uint32 FromString(string value) => new Uint32(uint.Parse(value)); + public string ToString(Uint32 obj) => obj.Value.ToString(); +} +---- + +===== JavaScript + +Register composite hydration adapters explicitly on a `PDTRegistry` instance, then pass it to the connection: + +[source,javascript] +---- +const registry = new PDTRegistry(); +registry.register('mygraph:Point', { + serialize: (obj) => ({ x: obj.x, y: obj.y }), + deserialize: (fields) => new Point(fields.x, fields.y) +}, Point); +---- + +For primitive types, use `registerPrimitive` with a `PrimitivePdtAdapter`: + +[source,javascript] +---- +const { PDTRegistry, PrimitivePdtAdapter } = require('gremlin'); + +const registry = new PDTRegistry(); +registry.registerPrimitive('mygraph:Uint32', new PrimitivePdtAdapter( + (str) => new Uint32(parseInt(str)), // fromValue + (obj) => obj.value.toString() // toValue +), Uint32); +---- + +===== Go + +Register composite types on a `PDTRegistry` instance. Go supports either reflection-based registration (using `pdt` +struct tags) or explicit function registration: + +[source,go] +---- +registry := NewPDTRegistry() +registry.RegisterType("mygraph:Point", reflect.TypeOf(Point{})) +---- + +For primitive types, use `RegisterPrimitiveFuncs` or `RegisterPrimitiveFuncsWithType`: + +[source,go] +---- +registry := gremlingo.NewPDTRegistry() +registry.RegisterPrimitiveFuncsWithType("mygraph:Uint32", reflect.TypeOf(Uint32{}), + // hydrate: string -> Go type + func(value string) (interface{}, error) { + v, err := strconv.ParseUint(value, 10, 32) + return Uint32{Value: uint32(v)}, err + }, + // dehydrate: Go type -> string + func(obj interface{}) (string, error) { + return strconv.FormatUint(uint64(obj.(Uint32).Value), 10), nil + }, +) +---- + +===== Provider Factory Pattern + +Regardless of language, the recommended pattern is for providers to expose a factory method that returns a +pre-configured `PDTRegistry` with both composite and primitive types registered. This shields end users from needing +to know which types exist or how the registry is populated: + +[source,java] +---- +// In the provider's client library +public class MyGraphTypeRegistry { + public static PDTRegistry create() { + PDTRegistry registry = PDTRegistry.create(); // discovers ServiceLoader composite and primitive adapters + registry.register(Point.class, Address.class, Person.class); // registers annotated composite types + return registry; + } +} +---- + +End users configure their connection in one line: + +[source,java] +---- +DriverRemoteConnection conn = DriverRemoteConnection.using(cluster); +conn.setPdtRegistry(MyGraphTypeRegistry.create()); +GraphTraversalSource g = traversal().with(conn); +---- + +With this in place, `Point` objects round-trip transparently in both directions. The annotation handles outbound +serialization, and the registry handles inbound reconstruction. The same applies to any primitive type backed by a +registered `PrimitivePDTAdapter`. + +For driver users consuming PDTs, see the +link:https://tinkerpop.apache.org/docs/x.y.z/reference/#gremlin-drivers-variants[Gremlin Drivers and Variants] +reference documentation for each language driver. + +[[pdt-unregistered-values]] +==== Unregistered PDT Values + +When a driver receives a PDT for which no matching registration exists in the `PDTRegistry`, it falls back to a +generic value object rather than failing: + +* An unregistered *Composite PDT* is returned as a `CompositePDT` object. +* An unregistered *Primitive PDT* is returned as a `PrimitivePDT` object. + +This behavior is consistent across all GLVs. [[gremlin-plugins]] + [llms-summary="How to author Gremlin Console/Server plugins as a provider: the plugin interface, dependency management, and registering custom bindings and DSLs."] == Gremlin Plugins image:gremlin-plugin.png[width=125] diff --cc docs/src/reference/gremlin-applications.asciidoc index e2ebb0e3dd,77e71d3917..d900c54af1 --- a/docs/src/reference/gremlin-applications.asciidoc +++ b/docs/src/reference/gremlin-applications.asciidoc @@@ -860,11 -947,18 +862,12 @@@ Finally, as Gremlin Server can host mul possible to define the language to utilize to process the request: [source,text] -curl -X POST -d "{\"gremlin\":\"100-x\", \"language\":\"gremlin-groovy\", \"bindings\":{\"x\":1}}" "http://localhost:8182" +curl -X POST -d "{\"gremlin\":\"100-x\", \"language\":\"gremlin-groovy\", \"parameters\":\"[x:1]\"}" "http://localhost:8182" -By default this value is set to `gremlin-groovy`. If using a `GET` operation, this value can be set as a query -string argument with by setting the `language` key. - -WARNING: Consider the size of the result of a submitted script being returned from the HTTP endpoint. A script -that iterates thousands of results will serialize each of those in memory into a single JSON result set. It is -quite possible that such a script will generate `OutOfMemoryError` exceptions on the server. Consider the default -WebSocket configuration, which supports streaming, if that type of use case is required. +By default this value is set to `gremlin-lang`. The value is set in the request body with the `language` key. [[server-configuring]] + [llms-summary="Reference for Gremlin Server's YAML configuration options: graphs, serializers, channelizers, thread pools, SSL, and metrics."] === Configuring The `gremlin-server.sh` file serves multiple purposes. It can be used to "install" dependencies to the Gremlin diff --cc docs/src/reference/gremlin-variants.asciidoc index 0b88c144ca,48cea9c558..8d34d3ce7c --- a/docs/src/reference/gremlin-variants.asciidoc +++ b/docs/src/reference/gremlin-variants.asciidoc @@@ -18,10 -18,12 +18,11 @@@ under the License //// anchor:gremlin-variants[] -// This book exercises only remote driver connections, but its predecessors leave Neo4j/Spark/Hadoop -// plugins active. Exclude all three to restart the console with a clean classpath and avoid the -// Scala 2.11/2.12 (and related) conflicts those plugins introduce. -[gremlin-docs-plugins-exclude="neo4j-gremlin,spark-gremlin,hadoop-gremlin"] +// This book exercises only remote driver connections, but its predecessors leave Spark/Hadoop +// plugins active. Exclude both to restart the console with a clean classpath. +[gremlin-docs-plugins-exclude="spark-gremlin,hadoop-gremlin"] [[gremlin-drivers-variants]] + [llms-summary="How Gremlin is embedded in host programming languages (Java, Groovy, Python, JavaScript, .NET, Go) and how language variants and drivers connect to a remote server."] = Gremlin Drivers and Variants image::gremlin-house-of-mirrors.png[width=1024] @@@ -802,48 -607,7 +804,49 @@@ go run basic_gremlin.g go run modern_traversals.go ---- +[[gremlin-go-pdt]] +=== Provider Defined Types + +Provider Defined Types (PDTs) allow graph providers to expose custom types through the driver. PDT values arrive as +`*CompositePDT` structs containing a `Name` and `Fields` map without any configuration. +Consult your graph provider's documentation for the list of PDTs they support. + +[source,go] +---- +results, err := g.V().Has("location").Values("location").ToList() +pdt := results[0].GetInterface().(*gremlingo.CompositePDT) +fmt.Println(pdt.Name) // "x:Point" +fmt.Println(pdt.Fields) // map[x:1.0 y:2.0] +---- + +Working with raw `*CompositePDT` values is always available. Using a `PDTRegistry` is an optional +convenience that automates conversion between PDT values and application types on both the request and response paths. + +Using a `PDTRegistry` for hydration and dehydration: + +[source,go] +---- +registry := gremlingo.NewPDTRegistry() +registry.RegisterFuncsWithType("x:Point", reflect.TypeOf(Point{}), + // hydrate: convert incoming PDT fields map to a Go type + func(fields map[string]interface{}) (interface{}, error) { + return &Point{X: fields["x"].(float64), Y: fields["y"].(float64)}, nil + }, + // dehydrate: convert a Go type to a PDT fields map for sending + func(obj interface{}) (map[string]interface{}, error) { + p := obj.(*Point) + return map[string]interface{}{"x": p.X, "y": p.Y}, nil + }, +) + +remote, _ := gremlingo.NewDriverRemoteConnection("http://localhost:8182/gremlin", + func(settings *gremlingo.DriverRemoteConnectionSettings) { + settings.PDTRegistry = registry + }) +g := gremlingo.Traversal_().With(remote) +---- + + [llms-summary="Using Gremlin from Groovy: the JVM-based Gremlin-Groovy variant backed by Gremlin-Java, its role as the language of the Gremlin Console and Server, and its syntax differences."] [[gremlin-groovy]] == Gremlin-Groovy @@@ -1799,73 -1558,8 +1803,74 @@@ java -cp target/run-examples-shaded.ja java -cp target/run-examples-shaded.jar examples.ModernTraversals ---- +[[gremlin-java-pdt]] +=== Provider Defined Types + +Provider Defined Types (PDTs) allow graph providers to expose custom types through the driver. PDT values arrive as +`CompositePDT` objects containing a name and fields map without any configuration. +Consult your graph provider's documentation for the list of PDTs they support. + +Receiving a raw PDT: + +[source,java] +---- +CompositePDT pdt = (CompositePDT) g.V().has("location").values("location").next(); +String typeName = pdt.getName(); // "x:Point" +Map<String, Object> fields = pdt.getFields(); // {x: 1.0, y: 2.0} +---- + +Working with raw `CompositePDT` objects is always available. The following two approaches are optional +conveniences that automate conversion between PDT values and application types on both the request and response paths. + +Using a `PDTRegistry` for hydration and dehydration: +---- +public class PointAdapter implements ProviderDefinedTypeAdapter<Point> { + @Override public String typeName() { return "x:Point"; } + @Override public Class<Point> targetClass() { return Point.class; } + @Override public Map<String, Object> toFields(Point p) { return Map.of("x", p.getX(), "y", p.getY()); } + @Override public Point fromFields(Map<String, Object> m) { return new Point((double) m.get("x"), (double) m.get("y")); } +} +---- + +Register adapters via ServiceLoader by adding the fully qualified class name to +`META-INF/services/org.apache.tinkerpop.gremlin.structure.io.pdt.ProviderDefinedTypeAdapter`. The driver discovers +adapters on the classpath and automatically hydrates/dehydrates. + +For simpler cases where you own the type, annotate it directly to avoid writing an adapter: + +Annotation-based conversion with `@ProviderDefined`: + +[source,java] +---- +// includedFields: only serialize the listed fields +@ProviderDefined(name = "x:Point", includedFields = {"x", "y"}) +public class Point { + private final double x; + private final double y; + private final String internalId; // not serialized + // constructor, getters... +} + +// excludedFields: serialize all fields except those listed +@ProviderDefined(name = "x:Timestamped", excludedFields = {"createdAt"}) +public class Timestamped { + private final String value; + private final long createdAt; // not serialized + // constructor, getters... +} + +// send: Point is automatically dehydrated +g.inject(new Point(1.0, 2.0, "internal")).iterate(); + +// receive: PDT is automatically hydrated back to Point +Point p = (Point) g.V().has("location").values("location").next(); +---- + +Classes annotated with `@ProviderDefined` are automatically dehydrated when passed as traversal arguments and +hydrated on deserialization without explicit registry configuration. + [[gremlin-javascript]] + [llms-summary="Using Gremlin from JavaScript/Node.js: installing the gremlin package, building traversals, and connecting to a remote Gremlin Server."] == Gremlin-JavaScript image:gremlin-js.png[width=130,float=right] Apache TinkerPop's Gremlin-JavaScript implements Gremlin within the @@@ -2607,42 -2118,7 +2612,43 @@@ node basic-gremlin.j node modern-traversals.js ---- +[[gremlin-javascript-pdt]] +=== Provider Defined Types + +Provider Defined Types (PDTs) allow graph providers to expose custom types through the driver. PDT values arrive as +`CompositePDT` objects containing a `name` and `fields` map without any configuration. +Consult your graph provider's documentation for the list of PDTs they support. + +Receiving a raw PDT: + +[source,javascript] +---- +const results = await g.V().has('location').values('location').toList(); +const pdt = results[0]; +console.log(pdt.name); // "x:Point" +console.log(pdt.fields); // { x: 1.0, y: 2.0 } +---- + +Working with raw `CompositePDT` objects is always available. Using a `PDTRegistry` is an +optional convenience that automates conversion between PDT values and application types on both the request and +response paths. + +[source,javascript] +---- +const { PDTRegistry } = require('gremlin'); + +const registry = new PDTRegistry(); +registry.register('x:Point', { + serialize: (point) => ({ x: point.x, y: point.y }), + deserialize: (fields) => new Point(fields.x, fields.y) +}, Point); + +const g = traversal().with_(new DriverRemoteConnection('http://localhost:8182/gremlin', { + pdtRegistry: registry +})); +---- + + [llms-summary="Using Gremlin from .NET: installing the Gremlin.Net package, its C# API with PascalCase step names, and connecting to a remote Gremlin Server."] anchor:gremlin-DotNet[] [[gremlin-dotnet]] == Gremlin.Net @@@ -3255,83 -2552,8 +3261,84 @@@ dotnet run --project Connection dotnet run --project ModernTraversals ---- +[[gremlin-dotnet-pdt]] +=== Provider Defined Types + +Provider Defined Types (PDTs) allow graph providers to expose custom types through the driver. PDT values arrive as +`CompositePDT` objects containing a `Name` and `Fields` dictionary without any configuration. +Consult your graph provider's documentation for the list of PDTs they support. + +Receiving a raw PDT: + +[source,csharp] +---- +var pdt = (CompositePDT) g.V().Has("location").Values<object>("location").Next(); +Console.WriteLine(pdt.Name); // "x:Point" +Console.WriteLine(pdt.Fields); // { x: 1.0, y: 2.0 } +---- + +Working with raw `CompositePDT` objects is always available. The following two approaches are optional +conveniences that automate conversion between PDT values and application types on both the request and response paths. + +Using a `PDTRegistry` for hydration and dehydration: + +[source,csharp] +---- +public class PointAdapter : IProviderDefinedTypeAdapter<Point> +{ + public string TypeName => "x:Point"; + + public Point FromFields(IReadOnlyDictionary<string, object?> fields) => + new Point((double)fields["x"], (double)fields["y"]); + + public IReadOnlyDictionary<string, object?> ToFields(Point value) => + new Dictionary<string, object?> { ["x"] = value.X, ["y"] = value.Y }; +} + +var registry = new PDTRegistry(); +registry.Register(new PointAdapter()); + +using var client = new GremlinClient(new GremlinServer("localhost", 8182), pdtRegistry: registry); +---- + +The `PDTRegistry.Create()` method scans loaded assemblies for `IProviderDefinedTypeAdapter<T>` +implementations and registers them automatically. + +For simpler cases where you own the type, annotate it directly to avoid writing an adapter: + +Attribute-based conversion with `[ProviderDefined]`: + +[source,csharp] +---- +// IncludedFields: only serialize the listed fields +[ProviderDefined(Name = "x:Point", IncludedFields = new[] { "X", "Y" })] +public class Point +{ + public double X { get; set; } + public double Y { get; set; } + public string InternalId { get; set; } // not serialized +} + +// ExcludedFields: serialize all fields except those listed +[ProviderDefined(Name = "x:Timestamped", ExcludedFields = new[] { "CreatedAt" })] +public class Timestamped +{ + public string Value { get; set; } + public long CreatedAt { get; set; } // not serialized +} + +// send: Point is automatically dehydrated +await g.Inject(new Point { X = 1.0, Y = 2.0 }).Promise(t => t.Iterate()); + +// receive: PDT is automatically hydrated back to Point +var p = (Point) await g.V().Has("location").Values<object>("location").Promise(t => t.Next()); +---- + +Classes annotated with `[ProviderDefined]` are automatically dehydrated when passed as traversal arguments and +hydrated on deserialization without explicit registry configuration. + [[gremlin-python]] + [llms-summary="Using Gremlin from Python: installing gremlinpython, building traversals, Pythonic syntax differences, and connecting to a remote Gremlin Server."] == Gremlin-Python image:gremlin-python-drawing.png[width=130,float=right] Apache TinkerPop's Gremlin-Python implements Gremlin within diff --cc docs/src/reference/implementations-hadoop-start.asciidoc index 09dfe41934,bca8eb9f8c..88d483bb41 --- a/docs/src/reference/implementations-hadoop-start.asciidoc +++ b/docs/src/reference/implementations-hadoop-start.asciidoc @@@ -16,10 -16,11 +16,11 @@@ KIND, either express or implied. See t specific language governing permissions and limitations under the License. //// -// Spark requires Scala 2.12 while Neo4j 3.4 requires Scala 2.11; they cannot share the console's -// flat classpath, so exclude neo4j-gremlin to restart the console without Neo4j's jars. -[gremlin-docs-plugins-exclude="neo4j-gremlin"] +// Re-includes spark-gremlin and hadoop-gremlin, which gremlin-variants.asciidoc excluded for its +// driver-only examples: an empty exclude list resets the console to every plugin enabled. +[gremlin-docs-plugins-exclude=""] [[hadoop-gremlin]] + [llms-summary="Running Gremlin OLAP over Hadoop: HadoopGraph, graph input/output formats, and executing traversals with SparkGraphComputer."] == Hadoop-Gremlin [source,xml] diff --cc docs/src/reference/the-traversal.asciidoc index 7f005c1687,6b979c286d..69a789de79 --- a/docs/src/reference/the-traversal.asciidoc +++ b/docs/src/reference/the-traversal.asciidoc @@@ -679,49 -598,9 +685,50 @@@ throws, since a vertex may carry only 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(java.lang.String,java.lang.String...)++[`addV(String, 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)`] +[[addlabel-step]] +=== AddLabel Step + +The `addLabel()`-step (*sideEffect*) adds one or more labels to an element. Because it mutates the set of labels on +an element, `addLabel()` can only be used on a graph configured with a `LabelCardinality` that allows more than one +label, namely `ZERO_OR_MORE` or `ONE_OR_MORE`. + +[gremlin-groovy] +---- +conf = new BaseConfiguration() +conf.setProperty("gremlin.tinkergraph.vertexLabelCardinality", "ZERO_OR_MORE") +graph = TinkerGraph.open(conf) +g = traversal().with(graph) +g.addV('person').property('name','marko').addLabel('employee').labels() +g.V().has('name','marko').hasLabel('person') +g.V().has('name','marko').hasLabel('employee') +g.V().has('name','marko').addLabel('employee').labels() <1> +---- + +<1> marko already carries the "employee" label, so adding it again leaves the set of labels unchanged. + +By contrast, calling `addLabel()` against a vertex whose graph is configured with `LabelCardinality.ONE`, which +fixes a single, immutable label for every vertex, results in an exception since there is no set of labels to +mutate: + +[source,groovy] +---- +conf = new BaseConfiguration() +conf.setProperty("gremlin.tinkergraph.vertexLabelCardinality", "ONE") +graph = TinkerGraph.open(conf) +g = traversal().with(graph) +// results in: Label mutation is not supported with cardinality ONE. Labels are immutable once assigned. +g.addV('person').property('name','marko').addLabel('employee') +---- + +*Additional References* + +link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#addLabel(java.lang.String,java.lang.String...)++[`addLabel(String, String...)`], +link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#addLabel(org.apache.tinkerpop.gremlin.process.traversal.Traversal,org.apache.tinkerpop.gremlin.process.traversal.Traversal...)++[`addLabel(Traversal, Traversal...)`] + + [llms-summary="The aggregate()-step (sideEffect) is used to aggregate all the objects at a particular point of traversal into a Collection."] [[aggregate-step]] === [[store-step]]Aggregate Step @@@ -1080,8 -965,9 +1095,9 @@@ robust steps like <<choose-step, choose *Additional References* -link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#branch(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`branch(Traversal)`] +link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#branch(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`map(Traversal)`] + [llms-summary="The by()-step is not an actual step, but instead is a \"step-modulator\" similar to as() and option()."] [[by-step]] === By Step @@@ -1524,9 -1381,10 +1547,10 @@@ desired strings between arguments *Additional References* link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#concat(java.lang.String)++[`concat(String...)`] -link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#concat(org.apache.tinkerpop.gremlin.process.traversal.Traversal,org.apache.tinkerpop.gremlin.process.traversal.Traversal...)++[`concat(Taversal, Traversal...)`] +link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#concat(org.apache.tinkerpop.gremlin.process.traversal.Traversal,org.apache.tinkerpop.gremlin.process.traversal.Traversal...)++[`concat(Traversal, Traversal...)`] link:++https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#concat-step++[`Semantics`] + [llms-summary="The conjoin()-step (map) joins together the elements in the incoming list traverser together with the provided argument as a delimiter."] [[conjoin-step]] === Conjoin Step @@@ -1717,9 -1580,12 +1747,10 @@@ g.inject("2023-08-02T00:00:00Z").asDate *Additional References* link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#dateDiff(java.util.Date)++[`dateDiff(Date)`], -link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#dateDiff(java.time.OffsetDateTime)++[`dateDiff(OffsetDateTime)`], +link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#dateDiff(java.util.Date)++[`dateDiff(OffsetDateTime)`], link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#dateDiff(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`dateDiff(Traversal)`] + [llms-summary="With dedup()-step (filter), repeatedly seen objects are removed from the traversal stream."] [[dedup-step]] === Dedup Step @@@ -1886,44 -1754,7 +1920,45 @@@ g.V( * link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#drop()++[`drop()`] +[[droplabel-step]] +=== DropLabel Step + +The `dropLabel()`-step (*sideEffect*) removes one or more specific labels from an element and `dropLabels()` +removes all of them. Like `addLabel()`, both require a `LabelCardinality` of `ZERO_OR_MORE` or `ONE_OR_MORE`. + +[gremlin-groovy] +---- +conf = new BaseConfiguration() +conf.setProperty("gremlin.tinkergraph.vertexLabelCardinality", "ZERO_OR_MORE") +graph = TinkerGraph.open(conf) +g = traversal().with(graph) +v = g.addV('person').property('name','marko').addLabel('employee').addLabel('manager').next() +g.V(v).labels() +g.V(v).dropLabel('manager').labels() <1> +g.V(v).dropLabel('manager').labels() <2> +g.V(v).dropLabels().labels() <3> +g.V(v).labels().count() <4> +---- + +<1> Removes the "manager" label. +<2> Dropping a label that is not present has no effect. +<3> Removes all remaining labels. +<4> Confirms zero labels remain. + +NOTE: With `LabelCardinality.ONE_OR_MORE`, `dropLabels()` always throws and `dropLabel()` throws if it would +leave the element with zero labels. + +IMPORTANT: Do not confuse `dropLabel()` with `labels().drop()`. The standalone `drop()` step either drops an +`Element` from the `Graph`, or a `Property` from it's `Element`. Since a label is simply a `String` object, +the `drop()` step cannot operate on it. + +*Additional References* + +link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#dropLabel(java.lang.String,java.lang.String...)++[`dropLabel(String, String...)`], +link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#dropLabel(org.apache.tinkerpop.gremlin.process.traversal.Traversal,org.apache.tinkerpop.gremlin.process.traversal.Traversal...)++[`dropLabel(Traversal, Traversal...)`], +link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#dropLabels()++[`dropLabels()`] + + [llms-summary="The E()-step is meant to read edges from the graph and is usually used to start a GraphTraversal, but can also be used mid-traversal."] [[e-step]] === E Step @@@ -1944,9 -1773,9 +1979,10 @@@ g.inject(1).coalesce(E().hasLabel("know *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(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)`] + [llms-summary="The element() step is a no-argument step that traverses from a Property to the Element that owns it."] [[element-step]] === Element Step @@@ -2129,8 -1928,9 +2170,9 @@@ information *Additional References* -link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#filter(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`filter(Traversal)`] +link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#filter(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`map(Traversal)`] + [llms-summary="The flatMap() step maps the traverser from the current object to an Iterator of objects for the next step in the process."] [[flatmap-step]] === FlatMap Step @@@ -2152,8 -1952,9 +2194,9 @@@ gremlin> g.withSack(1.0f).V(1).barrier( *Additional References* -link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#flatMap(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`flatMap(Traversal)`] +link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#flatMap(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`map(Traversal)`] + [llms-summary="The format()-step (map) builds a string from a template with placeholders filled by traverser values and properties."] [[format-step]] === Format Step @@@ -2437,8 -2191,10 +2485,9 @@@ link:++https://tinkerpop.apache.org/jav 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/docs/x.y.z/recipes/#has-traversal++[Recipes - Anti-pattern] +link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/structure/T.html++[`T`] + [llms-summary="The id()-step (map) takes an Element and extracts its identifier from it."] [[id-step]] === Id Step @@@ -2831,23 -2571,7 +2889,24 @@@ chosen non-deterministically link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#label()++[`label()`] +[[labels-step]] +=== Labels Step + +The `labels()`-step (*flatMap*) takes an `Element` and emits each of its labels as a separate traverser. For elements +with a single label this behaves identically to `label()`. For multi-label elements, it emits one traverser per label. +A vertex with no labels (possible only under `LabelCardinality.ZERO_OR_MORE`) emits nothing. + +[gremlin-groovy,theZoo] +---- +g.V().labels() +g.V().labels().count() +---- + +*Additional References* + +link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#labels()++[`labels()`] + + [llms-summary="The length()-step (map) returns the length incoming string or list of string traverser."] [[length-step]] === Length Step @@@ -3000,28 -2717,25 +3064,29 @@@ The `map()` step maps the traverser fro link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#map(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`map(Traversal)`] [[match-step]] + [llms-summary="The match() step (map) provides declarative, pattern-matching graph querying by combining traversal fragments bound to shared variables."] === Match Step -The `match()`-step (*map*) provides a more link:http://en.wikipedia.org/wiki/Declarative_programming[declarative] -form of graph querying based on the notion of link:http://en.wikipedia.org/wiki/Pattern_matching[pattern matching]. -With `match()`, the user provides a collection of "traversal fragments," called patterns, that have variables defined -that must hold true throughout the duration of the `match()`. When a traverser is in `match()`, a registered -`MatchAlgorithm` analyzes the current state of the traverser (i.e. its history based on its -<<path-data-structure,path data>>), the runtime statistics of the traversal patterns, and returns a traversal-pattern -that the traverser should try next. The default `MatchAlgorithm` provided is called `CountMatchAlgorithm` and it -dynamically revises the pattern execution plan by sorting the patterns according to their filtering capabilities -(i.e. largest set reduction patterns execute first). For very large graphs, where the developer is uncertain of the -statistics of the graph (e.g. how many `knows`-edges vs. `worksFor`-edges exist in the graph), it is advantageous to -use `match()`, as an optimal plan will be determined automatically. Furthermore, some queries are much easier to -express via `match()` than with single-path traversals. +Gremlin is a mostly imperative-style query language, but it also supports a means for defining a query declaratively, +where users get a high degree of flexibility in deciding what parts of their query might be better written in one +fashion or the other. Having this choice helps promote better query readability and query ergonomics and offers graph +databases an opportunity to expose multi-language features and optimizations. This feature is provided by way of the +`match()`-step. - "Who created a project named 'lop' that was also created by someone who is 29 years old? Return the two creators." +The `match()`-step (*map*) accepts a declarative query string, like link:https://www.iso.org/standard/76120.html[GQL], +and executes it against the graph. TinkerPop ships an optional module, `gql-gremlin`, that provides the +<<tinkergql,TinkerGQL>> dialect — a deliberate subset of ISO GQL `MATCH` syntax — which any graph system provider +may adopt. Providers may also implement their own declarative query engine with a completely different language. +*Whether `match()` is supported at all depends on the graph system being used.* Consult the documentation for your +graph system to determine what query languages and syntax are accepted. -image::match-step.png[width=500] +Since `match()` is a provider-specific feature, a traversal that uses `match()` against a graph system that has not +implemented support for it will throw an exception when the step is reached. The examples below are executed against +TinkerGraph, which ships TinkerGQL support out of the box (see <<tinkergraph-gql>>), demonstrating how the step +works in practice. What your graph system accepts may be entirely different. + +The step can be used in two ways. As a spawn step directly on a `GraphTraversalSource`, the pattern query is +evaluated once and each result becomes a traverser: [gremlin-groovy,modern] ---- @@@ -3343,9 -2941,9 +3408,10 @@@ Equivalent behaviour for `WHERE` and `R *Additional References* -link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#match(org.apache.tinkerpop.gremlin.process.traversal.Traversal...)++[`match(Traversal...)`] +link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#match(java.lang.String)++[`match(String)`] +link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#match(java.lang.String,java.util.Map)++[`match(String, Map)`] + [llms-summary="The math()-step (math) enables scientific calculator functionality within Gremlin."] [[math-step]] === Math Step @@@ -3621,8 -3223,9 +3691,9 @@@ link:++https://tinkerpop.apache.org/jav link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#mergeE(java.util.Map)++[`mergeE(Map)`], link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#mergeE(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`mergeE(Traversal)`], link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Merge.html++[`Merge`], -link:++https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#_mergee++[Semantics] +link:++https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#merge-e-step++[`Semantics`] + [llms-summary="The mergeV() -step is used to add vertices and their properties to a graph in a \"create if not exist\" fashion."] [[mergevertex-step]] === MergeVertex Step @@@ -3854,8 -3425,9 +3925,9 @@@ link:++https://tinkerpop.apache.org/jav link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#mergeV(java.util.Map)++[`mergeV(Map)`], link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#mergeV(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`mergeV(Traversal)`], link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Merge.html++[`Merge`], -link:++https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#_mergee++[Semantics] +link:++https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#merge-v-step++[`Semantics`] + [llms-summary="The min()-step (map) operates on a stream of comparable objects and determines which is the first object according to its natural order in the stream."] [[min-step]] === Min Step @@@ -3915,7 -3487,9 +3988,8 @@@ g.V().values('age').fold().none(gt(25) link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#none(org.apache.tinkerpop.gremlin.process.traversal.P)++[`none(P)`], link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/P.html++[`P`] - [[not-step]] + [llms-summary="The not() step (filter) removes traversers for which the provided sub-traversal returns a result, negating a pattern."] === Not Step The `not()`-step (*filter*) removes objects from the traversal stream when the traversal provided as an argument @@@ -5128,8 -4720,9 +5227,9 @@@ see the <<general-steps, General Steps> *Additional References* -link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#sideEffect(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`sideEffect(Traversal)`] +link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#sideEffect(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`map(Traversal)`] + [llms-summary="When it is important that a traverser not repeat its path through the graph, simplePath()-step should be used (filter)."] [[simplepath-step]] === SimplePath Step @@@ -5729,9 -5325,9 +5846,10 @@@ g.V().has('name', within('marko', 'vada *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(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)`] + [llms-summary="The value()-step (map) takes a Property and extracts the value from it."] [[value-step]] === Value Step @@@ -6230,46 -5790,7 +6355,47 @@@ By default, `GType` enumerations are re g.V().values('age','name').is(P.typeOf('Integer')) ---- +[[canonical-gremlin-literal-types]] +=== Canonical Gremlin Literal Types + +Canonical Gremlin represents typed values as text so that scripts and GLV requests can be expressed in the +`gremlin-lang` language and parsed by the `gremlin-lang` script engine. The authoritative definition of this syntax is +the link:https://github.com/apache/tinkerpop/blob/x.y.z/gremlin-language/src/main/antlr4/Gremlin.g4[Gremlin.g4 ANTLR grammar]. +This section summarizes the commonly used literal forms for convenience when reading canonical Gremlin or writing direct +script requests. + +[width="100%",cols="2,3,6",options="header"] +|========================================================= +|Type |Canonical form |Expected input +|null |`null` |The literal keyword `null`. +|Boolean |`true`, `false` |The literal keywords `true` and `false`. +|String |`"marko"`, `'marko'`, `"M"s` |A single-quoted or double-quoted string. The optional `s` suffix still produces a `String`. +|Character |`"M"c`, `'M'c` |A single-quoted or double-quoted value that is one character. +|Byte |`1B` |An integer with a `B` suffix and a value within the bounds of `Byte`. +|Short |`1S` |An integer with an `S` suffix and a value within the bounds of `Short`. +|Integer |`1`, `1I` |An integer within the bounds of `Integer`. The `I` suffix forces `Integer`. +|Long |`1L` |An integer with an `L` suffix and a value within the bounds of `Long`. +|BigInteger |`1N` |An integer with an `N` suffix. +|Float |`1.0F` |A floating-point number with an `F` suffix and a value within the bounds of `Float`. +|Double |`1.0`, `1.0D` |A floating-point number with an optional `D` suffix. +|BigDecimal |`1.0M` |A floating-point number with an `M` suffix. +|DateTime |`DateTime("2018-03-22T00:35:44Z")`, `DateTime()` |An ISO-8601 date or date-time string. `DateTime()` evaluates to the current time. +|UUID |`UUID("f47af10b-58cc-4372-a567-0f02b2f3d479")`, `UUID()` |A UUID string in the textual form written as 8-4-4-4-12 hexadecimal digit groups. `UUID()` generates a random UUID. +|Duration |`Duration(3600,0)`, `Duration(30,0,false)` |The first argument is non-negative seconds. The second is nanoseconds from `0` to `999999999`. The optional third argument is a boolean sign flag with `false` negating the duration. +|Binary |`Binary("AQID")` |A Base64-encoded string. +|Enum |`T.id`, `Cardinality.single`, `Direction.OUT` |A Gremlin enum token from `T`, `Cardinality`, `Direction`, `Merge`, `Pick`, `DT`, or `GType`. +|List |`[1,"two",null]` |A comma-separated sequence of generic literals inside square brackets. +|Set |`{1,"two"}` |A comma-separated sequence of generic literals inside braces. +|Map |`[:]`, `["name":"marko", "age":29]` |An empty map is `[:]`. A populated map is a comma-separated sequence of `key:value` entries inside square brackets. +|Provider Defined Type |`PDT("Point",["x":1,"y":2])`, `PDT("Uint32","42")` |The first argument is the provider-defined type name. The composite form expects a map literal with string keys. The primitive form expects an opaque string value. +|========================================================= + +NOTE: Numeric values may be signed, and integer values may use decimal, hexadecimal (`0x`), or octal (`0`) notation. +Underscores may appear between digits. Numeric type suffixes are accepted in either uppercase or lowercase form, though +the examples above use uppercase suffixes. Special floating-point values are expressed as `NaN`, `Infinity`, +`+Infinity`, or `-Infinity`. + + [llms-summary="A note on maps in Gremlin, including differences in behavior between embedded and remote contexts."] [[a-note-on-maps]] == A Note on Maps diff --cc docs/src/upgrade/release-3.2.x-incubating.asciidoc index 18ef7d7448,820620712e..ab546bb583 --- a/docs/src/upgrade/release-3.2.x-incubating.asciidoc +++ b/docs/src/upgrade/release-3.2.x-incubating.asciidoc @@@ -1456,9 -1459,10 +1458,10 @@@ See: link:https://issues.apache.org/jir The method signatures of `get_g_VXlistXv1_v2_v3XX_name` and `get_g_VXlistX1_2_3XX_name` of `VertexTest` were changed to take arguments for the `Traversal` to be constructed by extending classes. + [llms-summary="Upgrade notes specific to the TinkerPop 3.2.0 release: its breaking changes, new features, and changelog."] == TinkerPop 3.2.0 -*Release Date: Release Date: April 8, 2016* +*Release Date: April 8, 2016* Please see the link:https://github.com/apache/tinkerpop/blob/3.2.0-incubating/CHANGELOG.asciidoc#tinkerpop-320-release-date-april-8-2016[changelog] for a complete list of all the modifications that are part of this release.
