////
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
////

= TinkerPop 3.6.0

image::gremlin-victorian.png[width=185]

*Tinkerheart*

== TinkerPop 3.6.8

*Release Date: October 23, 2024*

Please see the link:https://github.com/apache/tinkerpop/blob/3.6.8/CHANGELOG.asciidoc#release-3-6-8[changelog] for a
complete list of all the modifications that are part of this release.

=== Upgrading for Users

==== Runtime Updates

`gremlin-python` has upgraded to Python 3.9 as Python 3.8 has passed end of life.
`gremlin-go` has upgraded to Go 1.22 as Go 1.21 has passed end of life.


== TinkerPop 3.6.7

*Release Date: April 8, 2024*

Please see the link:https://github.com/apache/tinkerpop/blob/3.6.7/CHANGELOG.asciidoc#release-3-6-7[changelog] for a
complete list of all the modifications that are part of this release.

=== Upgrading for Users

==== Runtime Version Upgrades
`gremlin-javascript` and `gremlint` have upgraded from Node 16 to Node 18 as Node 16 has passed end of life.
`gremlin-go` has upgraded from Go 1.20 to Go 1.21 as Go 1.20 has passed end of life.

==== Gremlin.Net: Fixed a bug in traversal enumeration on .NET 8

The behavior of `IEnumerable` was changed in .NET 8 when `Current` was accessed on it before starting the enumeration
via `MoveNext()`.
The Gremlin.Net driver unfortunately did exactly that in some cases which led to exceptions
(`InvalidOperationException: Enumeration has not started. Call MoveNext.`) on .NET 8 for some traversals.
Traversal enumeration has been changed for this version of Gremlin.Net to avoid this problem.

Older platforms than .NET 8 are not affected as `IEnumerable.Current` returned `null` there which is what the
Gremlin.Net driver expected.

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


== TinkerPop 3.6.6

*Release Date: November 20, 2023*

Please see the link:https://github.com/apache/tinkerpop/blob/3.6.6/CHANGELOG.asciidoc#release-3-6-6[changelog] for a
complete list of all the modifications that are part of this release.

=== Upgrading for Providers

The `HttpGremlinRequestEncoder` constructor has been deprecated in favor of one with an additional parameter
`boolean userAgentEnabled`. User agent HTTP headers can now be encoded if this flag is enabled.

== TinkerPop 3.6.5

*Release Date: July 31, 2023*

Please see the link:https://github.com/apache/tinkerpop/blob/3.6.5/CHANGELOG.asciidoc#release-3.6.5[changelog] for a
complete list of all the modifications that are part of this release.

=== Upgrading for Users

==== HTTP Plain Text

A `text/plain` MIME type has been added to the HTTP endpoint to return Gremlin Console formatted results in plain text.
This format can be helpful for a variety of reasons. Reading JSON formatted results can be difficult sometimes and
`text/plain` is a more simple, readable representation for when that is helpful.

[source,text]
----
$ curl -H "Accept:text/plain" -X POST -d "{\"gremlin\":\"g.V()\"}" "http://localhost:8182"
==>v[1]
==>v[2]
==>v[3]
==>v[4]
==>v[5]
==>v[6]
----

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

==== Untyped GraphSON

Prior to GraphSON 3.0, there were options to have GraphSON returned with and without types. The latter was helpful
for getting a more simple to process JSON format that relied strictly on the JSON type system. This format was of
course lossy in nature and not conducive for use with the language variants that were developing as the primary
mechanism for working with Gremlin. Of course, simple HTTP never really went away and forcing the type system on
consumers makes that route of connecting harder than it should be.

With HTTP users in mind, this version brings back both versions of untyped GraphSON by adding the following mime types:

* GraphSON 1.0 - `application/vnd.gremlin-v1.0+json;types=false`
* GraphSON 2.0 - `application/vnd.gremlin-v2.0+json;types=false`

Neither is configured by default in Gremlin Server, but can be added if desired by editing the Gremlin Server
configuration file.

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

== TinkerPop 3.6.4

*Release Date: May 12, 2023*

Please see the link:https://github.com/apache/tinkerpop/blob/3.6.4/CHANGELOG.asciidoc#release-3-6-4[changelog] for a
complete list of all the modifications that are part of this release.

== TinkerPop 3.6.3

*Release Date: May 1, 2023*

Please see the link:https://github.com/apache/tinkerpop/blob/3.6.3/CHANGELOG.asciidoc#release-3-6-3[changelog] for a
complete list of all the modifications that are part of this release.

=== Upgrading for Users

==== Deprecation Warning for Go 1.17

Release 3.6.3 will be one of the last versions of 3.6.x to use Go 1.17 for `gremlin-go` as this runtime is no longer
supported. Upcoming releases of `gremlin-go` will attempt to use the latest available version of Go.

=== Upgrading for Providers

==== Graph System Providers

===== Writing and Deleting Extension to Mutating Interface

`Writing` and `Deleting` are two new interfaces that extend `Mutating`. These two interfaces can be used to distinguish
whether a `Step` that implements `Mutating` will cause a write or a delete. A mapping has also been added that maps a
`Bytecode` instruction to possible steps. By using these two changes in conjunction, you can tell if a traversal will
change the underlying graph by calling `BytecodeHelper.findPossibleTraversalSteps()` on each `Bytecode` instruction of
the traversal. Be aware that there are a small number of special steps (e.g. `io()` or `call()`) that aren't marked
with these interfaces even though they could potentially modify the graph as they can't implement the current
`Mutating` interface which brings in the `Event` subsystem.

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

== TinkerPop 3.6.2

*Release Date: January 16, 2023*

Please see the link:https://github.com/apache/tinkerpop/blob/3.6.2/CHANGELOG.asciidoc#release-3-6-2[changelog] for a
complete list of all the modifications that are part of this release.

=== Upgrading for Users

==== Changes to mergeV/E semantics

The `mergeV()` and `mergeE()` step were added at version 3.6.0. Given some feedback on implementation and usage, some
additional changes were needed in order to improve the usability of these steps. These changes could not be made
without a breaking change to existing behavior introduced at 3.6.0. The main changes to consider are:

1. `onCreate` Map will now inherit from main merge argument, and overrides of existence criteria (`T.id/T.label` and `Direction.OUT/IN`) will be prohibited.
2. `Direction.IN/OUT` can be specified by additional options (`Merge.inV/outV`), which can take Map arguments, or a traversal which results in a Map or Vertex.
3. `mergeE()` will no longer accept upstream Vertices as arguments for `Direction.IN/OUT` where not specified in the map arguments. Late binding of those arguments
will come from `Merge.inV/outV` instead.

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

=== Upgrading for Providers

==== Graph System Providers

===== Callbacks for GraphManager

The `GraphManager` class now has several new methods that act as callbacks for various Gremlin Server operations
related to query processing. Overriding these methods in a `GraphManager` implementation can help make it easier for
providers to get notification of a query starting and whether it ends in success or failure. The feature may even
be useful to Gremlin Server users who simply wish to develop more advanced logging capabilities and other custom
features without having to extend more complicated classes within the Gremlin Server structure.

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

===== Gherkin Tests Moved to Resources

The Gherkin feature tests have been moved from `gremlin-test/features` to actual resources on `gremlin-test`. In this
way, these files can be more easily referenced from the classpath. Providers can now configure their `CucumberOptions`
in this fashion (taken from TinkerGraph):

[source,java]
----
@CucumberOptions(
        tags = "not @RemoteOnly and not @GraphComputerOnly and not @AllowNullPropertyValues",
        glue = { "org.apache.tinkerpop.gremlin.features" },
        objectFactory = TinkerGraphFeatureTest.TinkerGraphGuiceFactory.class,
        features = { "classpath:/org/apache/tinkerpop/gremlin/test/features" },
        plugin = {"progress", "junit:target/cucumber.xml"})
----

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

===== Changes to mergeV/E semantics

See above in changes for Users.

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

== TinkerPop 3.6.1

*Release Date: July 18, 2022*

Please see the link:https://github.com/apache/tinkerpop/blob/3.6.1/CHANGELOG.asciidoc#release-3-6-1[changelog] for a
complete list of all the modifications that are part of this release.

=== Upgrading for Users

==== GraphBinary Default Serialization

Python and .NET have support for GraphBinary at least since 3.5.0, but kept GraphSON 3 by default. It now seems safe
to make GraphBinary the default in 3.6.x. With this change, all language variants now have GraphBinary as their default
serialization format.

To continue using the GraphSON, explicitly specify it as the serializer to use in the configuration.

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

== TinkerPop 3.6.0

*Release Date: April 4, 2022*

Please see the link:https://github.com/apache/tinkerpop/blob/3.6.0/CHANGELOG.asciidoc#release-3-6-0[changelog] for a
complete list of all the modifications that are part of this release.

=== Upgrading for Users

==== element() Step

The new `element()` step provides a way to traverse from a `Property` to the `Element` that owns it:

[source,text]
----
gremlin> g = traversal().withEmbedded(TinkerFactory.createTheCrew())
==>graphtraversalsource[tinkergraph[vertices:6 edges:14], standard]

// VertexProperty -> Vertex
gremlin> g.V(1).properties().element().limit(1)
==>v[1]

// (Edge)Property -> Edge
gremlin> g.E(13).properties().element().limit(1)
==>e[13][1-develops->10]

// (Meta)Property -> VertexProperty
gremlin> g.V(1).properties().properties().element().limit(1)
==>vp[location->san diego]
----

See: link:https://issues.apache.org/jira/browse/TINKERPOP-2713[TINKERPOP-2713],
link:https://tinkerpop.apache.org/docs/3.6.0/reference/#element-step[Reference Documentation],
link:https://tinkerpop.apache.org/docs/3.6.0/dev/provider/#_element[Provider Documentation]

==== mergeV() and mergeE()

One of the most commonly used patterns in Gremlin is the use of `fold().coalesce(unfold(), ...)` to perform upsert-like
functionality. While this pattern is quite flexible, it can also be confusing to new users and for certain use cases
challenging to get the pattern correctly implemented. For providers, the pattern is difficult to properly optimize
because it can branch into complexity quite quickly making it hard to identify a section of Gremlin for an upsert and
therefore is not executed as efficiently as it might have been otherwise.

The new `mergeV()` and `mergeE()` steps greatly simplify this pattern and as the pattern is condensed into a single
step it should be straightforward for providers to optimize as part of their implementations. The following example
demonstrates just how much easier implementing a basic upsert of a vertex has gotten:

[source,text]
----
// prior to 3.6.0, use fold().coalesce(unfold(), ...)
gremlin> g.V().
......1>   has('person', 'name', 'vadas').has('age',27).
......2>   fold().
......3>   coalesce(unfold(),
......4>            addV('person').property('name', 'vadas').property('age', 27)).
......5>   elementMap()
==>[id:2,label:person,name:vadas,age:27]

// 3.6.0
gremlin> g.mergeV([(T.label): 'person', name:'vadas', age: 27]).
......1>   elementMap()
==>[id:2,label:person,name:vadas,age:27]
----

In a more complex example below, if the vertex is found, then it is updated with an "age" of "30" otherwise it is
created with an "age" of 27:

[source,text]
----
// prior to 3.6.0, use fold().coalesce(unfold(), ...)
gremlin> g.V().has('person','name','vadas').has('age', 27).
......1>   fold().
......2>   coalesce(unfold().property('age',30),
......3>            addV('person').property('name','vadas').property('age',27)).
......4>   elementMap()
==>[id:2,label:person,name:vadas,age:30]

// 3.6.0
gremlin> g.mergeV([(T.label): 'person', name:'vadas', age: 27]).
......1>     option(onMatch, [age: 30]).
......2>   elementMap()
==>[id:2,label:person,name:vadas,age:30]
----

The pattern was even more complicated for upserting edges, but the following example demonstrates how much easier
`mergeE()` is to follow:

[source,text]
----
// prior to 3.6.0, use a form of coalesce()
gremlin> g.V().has('person','name','vadas').as('v').
......1>            V().has('software','name','ripple').
......2>            coalesce(__.inE('created').where(outV().as('v')),
......3>                     addE('created').from('v').property('weight',0.5)).
......4>   elementMap()
==>[id:0,label:created,IN:[id:5,label:software],OUT:[id:2,label:person],weight:0.5]

// 3.6.0
gremlin> ripple = g.V().has('software','name','ripple').next()
==>v[5]
gremlin> g.V().has('person','name','vadas').
......1>    mergeE([(T.label):'created',(to):ripple, weight: 0.5]).
......2>    elementMap()
==>[id:0,label:created,IN:[id:5,label:software],OUT:[id:2,label:person],weight:0.5]
----

For those currently using the `fold().coalesce(unfold(), ...)` pattern, there is no need to be concerned with
incompatibility as a result of these new steps. That pattern is still perfectly usable and valid Gremlin, but whenever
possible it would be best to migrate away from it as graph providers ramp up on 3.6.0 support and introduce important
write optimizations that will make a big difference in performance.

See: link:https://issues.apache.org/jira/browse/TINKERPOP-2681[TINKERPOP-2681],
link:https://tinkerpop.apache.org/docs/3.6.0/reference/#mergeedge-step[mergeE()-step],
link:https://tinkerpop.apache.org/docs/3.6.0/reference/#mergevertex-step[mergeV()-step]

==== Direction Aliases

Aliases have been added to `Direction` to allow for `OUT` to be referred to as `from` and `IN` can be referred to as
`to`, which is a bit more friendly and matches more closely with existing Gremlin syntax.

==== Moved Pick

`Pick` was formerly a nested class of `TraversalOptionParent`, but has now been promoted to being a class on its own
in `org.apache.tinkerpop.gremlin.process.traversal.Pick`.

==== Consistent by() Behavior

The `by()` modulator is critical to the usage of Gremlin. When used in conjunction with a step that supports it, the
arguments to the `by()` modulator shift the behavior of the internals of the step.  The behavior that `by()` introduces
has not always been consistent with some overloads establishing `null` traversers, others throwing exceptions that are
hard to digest, some filtering, etc.

In 3.6.0, the rules for the `by()` modulator are made straightforward. If the `by()` produces a result then it is
said to be "productive" and its value is propagated to the step for use. If the `by()` does not produce a result then
the traverser to which it was to be applied is filtered.

The following sections demonstrate the behavior in 3.5.x alongside the new 3.6.0 behavior:

*aggregate()*

[source,text]
----
gremlin> g.V().aggregate('a').by('age').cap('a') // 3.5.x
==>[29,27,null,null,32,35]
gremlin> g.V().aggregate('a').by('age').cap('a') // 3.6.0
==>[29,27,32,35]
gremlin> g.V().aggregate('a').by(__.values('age')).cap('a') // 3.6.0
==>[29,27,32,35]
gremlin> g.V().aggregate('a').by(out()).cap('a') // 3.5.x
The provided traverser does not map to a value: v[2]->[VertexStep(OUT,vertex)]
Type ':help' or ':h' for help.
Display stack trace? [yN]n
gremlin> g.V().aggregate('a').by(out()).cap('a') // 3.6.0
==>[v[3],v[3],v[5]]
gremlin> g.V().aggregate('a').by('age') // same for 3.5.x and future
==>v[1]
==>v[2]
==>v[3]
==>v[4]
==>v[5]
==>v[6]
----

*cyclicPath()*

[source,text]
----
gremlin> g.V().has('person','name','marko').both().both().cyclicPath().by('age') // 3.5.x
==>v[1]
java.lang.NullPointerException
Type ':help' or ':h' for help.
Display stack trace? [yN]n
gremlin> g.V().has('person','name','marko').both().both().cyclicPath().by('age') // 3.6.0
==>v[1]
==>v[1]
----

*dedup()*

[source,text]
----
gremlin> g.V().both().dedup().by('age').elementMap() // 3.5.x
==>[id:3,label:software,name:lop,lang:java]
==>[id:2,label:person,name:vadas,age:27]
==>[id:4,label:person,name:josh,age:32]
==>[id:1,label:person,name:marko,age:29]
==>[id:6,label:person,name:peter,age:35]
gremlin> g.V().both().dedup().by('age').elementMap() // 3.6.0
==>[id:2,label:person,name:vadas,age:27]
==>[id:4,label:person,name:josh,age:32]
==>[id:1,label:person,name:marko,age:29]
==>[id:6,label:person,name:peter,age:35]
----

When using `dedup()` over labels all labels must produce or the path will be filtered:

[source,text]
----
gremlin> g.V().as('a').both().as('b').both().as('c').dedup('a','b').by('age').select('a','b','c').by('name')  // 3.5.x
The provided start does not map to a value: v[3]->value(age)
Type ':help' or ':h' for help.
Display stack trace? [yN]n
gremlin> g.V().as('a').both().as('b').both().as('c').dedup('a','b').by('age').select('a','b','c').by('name') // 3.6.0
==>[a:marko,b:vadas,c:marko]
==>[a:marko,b:josh,c:ripple]
==>[a:vadas,b:marko,c:lop]
==>[a:josh,b:marko,c:lop]
----

*group()*

There are two `by()` modulators that can be assigned to `group()``. The first modulator is meant to identify the key to
group on and will filter values without that key out of the resulting `Map`.

[source,text]
----
gremlin> g.V().group().by('age').by('name') // 3.5.x
==>[null:[lop,ripple],32:[josh],35:[peter],27:[vadas],29:[marko]]
gremlin> g.V().group().by('age').by('name') // 3.6.0
==>[32:[josh],35:[peter],27:[vadas],29:[marko]]
----

The second `by()`` is applied to the result as a reducing operation and will filter away entries in the `List` value of
each key.

[source,text]
----
gremlin> g.V().group().by('name').by('age') // 3.5.x
==>[ripple:[null],peter:[35],vadas:[27],josh:[32],lop:[null],marko:[29]]
gremlin> g.V().group().by('name').by('age') // 3.6.0
==>[ripple:[],peter:[35],vadas:[27],josh:[32],lop:[],marko:[29]]
----

*groupCount()*

[source,text]
----
gremlin> g.V().groupCount().by('age') // 3.5.x
==>[null:2,32:1,35:1,27:1,29:1]
gremlin> g.V().groupCount().by('age') // 3.6.0
==>[32:1,35:1,27:1,29:1]
----

*math()*

The `math()` step requires that the result of the `by()` be a `Number`, so a result of `null` will still result in a
runtime exception. Filtering will eliminate such errors, though a runtime error may still be present should the
modulator produce a non-numeric value.

[source,text]
----
gremlin> g.V().math('_+1').by('age') // 3.5.x
==>30.0
==>28.0
The variable _ for math() step must resolve to a Number - it is instead of type null with value null
Type ':help' or ':h' for help.
Display stack trace? [yN]n
gremlin> g.V().math('_+1').by('age') // 3.6.0
==>30.0
==>28.0
==>33.0
==>36.0
----

*order()*

[source,text]
----
gremlin> g.V().both().order().by('age').elementMap() // 3.5.x
==>[id:3,label:software,name:lop,lang:java]
==>[id:3,label:software,name:lop,lang:java]
==>[id:3,label:software,name:lop,lang:java]
==>[id:5,label:software,name:ripple,lang:java]
==>[id:2,label:person,name:vadas,age:27]
==>[id:1,label:person,name:marko,age:29]
==>[id:1,label:person,name:marko,age:29]
==>[id:1,label:person,name:marko,age:29]
==>[id:4,label:person,name:josh,age:32]
==>[id:4,label:person,name:josh,age:32]
==>[id:4,label:person,name:josh,age:32]
==>[id:6,label:person,name:peter,age:35]
gremlin> g.V().both().order().by('age').elementMap() // 3.6.0
==>[id:2,label:person,name:vadas,age:27]
==>[id:1,label:person,name:marko,age:29]
==>[id:1,label:person,name:marko,age:29]
==>[id:1,label:person,name:marko,age:29]
==>[id:4,label:person,name:josh,age:32]
==>[id:4,label:person,name:josh,age:32]
==>[id:4,label:person,name:josh,age:32]
==>[id:6,label:person,name:peter,age:35]
----

*path()*

All `by()` modulators must be productive for the filter to be satisfied.

[source,text]
----
gremlin> g.V().both().path().by('age') // 3.5.x
==>[29,null]
==>[29,27]
==>[29,32]
==>[27,29]
==>[null,29]
==>[null,32]
==>[null,35]
==>[32,null]
==>[32,null]
==>[32,29]
==>[null,32]
==>[35,null]
gremlin> g.V().both().path().by('age') // 3.6.0
==>[29,27]
==>[29,32]
==>[27,29]
==>[32,29]
----

*project()*

The `project()` step will produce an incomplete `Map` by filtering away keys of unproductive `by()` modulators.

[source,text]
----
gremlin> g.V().project('n','a').by('name').by('age') // 3.5.x
==>[n:marko,a:29]
==>[n:vadas,a:27]
==>[n:lop,a:null]
==>[n:josh,a:32]
==>[n:ripple,a:null]
==>[n:peter,a:35]
gremlin> g.V().project('n','a').by('name').by('age') // 3.6.0
==>[n:marko,a:29]
==>[n:vadas,a:27]
==>[n:lop]
==>[n:josh,a:32]
==>[n:ripple]
==>[n:peter,a:35]
----

*propertyMap()*

[source,text]
----
gremlin> g.V().propertyMap().by(is('x')) // 3.5.x
The provided start does not map to a value: [vp[name→marko]]→[IsStep(eq(x))]
Type ':help' or ':h' for help.
Display stack trace? [yN]n
gremlin> g.V().propertyMap().by(is('x')) // 3.6.0
==>[name:[],age:[]]
==>[name:[],age:[]]
==>[name:[],lang:[]]
==>[name:[],age:[]]
==>[name:[],lang:[]]
==>[name:[],age:[]]
----

*sack()*

[source,text]
----
gremlin> g.V().sack(assign).by('age').sack() // 3.5.x
==>29
==>27
==>null
==>32
==>null
==>35
gremlin> g.V().sack(assign).by('age').sack() // 3.6.0
==>29
==>27
==>32
==>35
----

*sample()*

[source,text]
----
gremlin> g.V().both().sample(2).by('age') // 3.5.x
java.lang.NullPointerException
Type ':help' or ':h' for help.
Display stack trace? [yN]n
gremlin> g.V().both().sample(2).by('age') // 3.6.0
==>v[1]
==>v[4]
----

*select()*

All `by()` modulators must be productive for the filter to be satisfied.

[source,text]
----
gremlin> g.V().has('person','name','marko').as('a').both().as('b').select('a','b').by('age') // 3.5.x
==>[a:29,b:null]
==>[a:29,b:27]
==>[a:29,b:32]
gremlin> g.V().has('person','name','marko').as('a').both().as('b').select('a','b').by('age') // 3.6.0
==>[a:29,b:27]
==>[a:29,b:32]
----

*simplePath()*

[source,text]
----
gremlin> g.V().has('person','name','marko').both().both().simplePath().by('age') // 3.5.x
java.lang.NullPointerException
Type ':help' or ':h' for help.
Display stack trace? [yN]n
gremlin> g.V().has('person','name','marko').both().both().simplePath().by('age') // 3.6.0
gremlin>
----

*tree()*

All `by()` modulators must be productive for the filter to be satisfied.

[source,text]
----
gremlin> g.V().out().tree().by('age') // 3.5.x
==>[32:[null:[]],35:[null:[]],29:[null:[],32:[],27:[]]]
gremlin> g.V().out().tree().by('age') // 3.6.0
==>[32:[],35:[],29:[32:[],27:[]]]
----

*valueMap()*

[source,text]
----
gremlin> g.V().valueMap().by(is('x')) // 3.5.x
The provided start does not map to a value: [marko]→[IsStep(eq(x))]
Type ':help' or ':h' for help.
Display stack trace? [yN]n
gremlin> g.V().valueMap().by(is('x')) // 3.6.0
==>[name:[],age:[]]
==>[name:[],age:[]]
==>[name:[],lang:[]]
==>[name:[],age:[]]
==>[name:[],lang:[]]
==>[name:[],age:[]]
----

*where()*

[source,text]
----
gremlin> g.V().as('a').both().both().as('b').where('a',eq('b')).by('age') // 3.5.x
==>v[1]
==>v[1]
==>v[1]
==>v[2]
==>v[3]
==>v[5]
==>v[3]
==>v[3]
==>v[4]
==>v[4]
==>v[4]
==>v[5]
==>v[3]
==>v[6]
gremlin> g.V().as('a').both().both().as('b').where('a',eq('b')).by('age') // 3.6.0
==>v[1]
==>v[1]
==>v[1]
==>v[2]
==>v[4]
==>v[4]
==>v[4]
==>v[6]
----

For the most part, this change largely removes runtime exceptions and since most uses cases are not likely to rely
on those for query execution, existing code should not be broken by this upgrade. However, users who relied on 3.5.x
behavior where `by()` might propagate a `null` would however see a behavioral change. To temporarily restore the old
behavior, simply include `g.withStrategies(ProductiveByStrategy)` in the traversal configuration, which will force the
`null` to be produced. Ultimately, it would be best not to rely on this strategy in the long term however and convert
Gremlin that requires it to behave properly without it.

For example, if in 3.5.x there was a traversal like `g.V().group().by('age')` and "age" is known to not always be a
valid key, the appropriate change would be to propagate `null` explicitly as with:
`g.V().group().by(coalesce(values('age'), constant(null)))`.

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

==== TextP Regex

A number of graph databases have included support for regular expressions text predicates and now TinkerPop includes
a `regex()` option to `TextP`:

[source,text]
----
gremlin> g.V().has('person', 'name', regex('peter')).values('name')
==>peter
gremlin> g.V().has('person', 'name', regex('r')).values('name')
==>marko
==>peter
gremlin> g.V().has('person', 'name', regex('r$')).values('name')
==>peter
gremlin> g.V().has('person', 'name', regex('a[rd]')).values('name')
==>marko
==>vadas
----

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

==== gremlin-annotations

There is a new module called `gremlin-annotations` and it holds the annotations used to construct
link:https://tinkerpop.apache.org/docs/3.6.0/reference/#gremlin-java-dsl[Java-based Gremlin DSLs]. These annotations
were formerly in `gremlin-core` and therefore it will be necessary to modify dependencies accordingly when upgrading
to 3.6.0. Package and class names have remained the same and general usage is unchanged.

[source,xml]
----
<dependency>
    <groupId>org.apache.tinkerpop</groupId>
    <artifactId>gremlin-annotations</artifactId>
    <version>3.6.0</version>
</dependency>
----

It is worth noting that `gremlin-groovy` utilized the DSL annotations to construct the
link:https://tinkerpop.apache.org/docs/3.6.0/reference/#credentials-dsl[Credentials DSL] so the `gremlin-annotations`
package is now explicitly associated to `gremlin-groovy` but as an `<optional>` dependency.

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

==== fail() Step

The new `fail()` step provides a way to immediately terminate a traversal with a runtime exception. In the Gremlin
Console, the exception will be rendered as follows which helps provide some context to the failure:

[source,text]
----
gremlin> g.V().fail("nope!")
fail() Step Triggered
=====================
Message > nope!
Traverser> v[1]
  Bulk   > 1
Traversal> V().fail()
Metadata > {}
=====================
----

See: link:https://issues.apache.org/jira/browse/TINKERPOP-2635[TINKERPOP-2635],
link:https://tinkerpop.apache.org/docs/3.6.0/reference/#fail-step[Reference Documentation]

==== Null for T

In 3.5.x, calling `property()` with a key that is of type `T` and a value that is `null` or calling `addV()` with a
`null` label is processed as a valid traversal and default values are used. That approach allows opportunities for
unexpected problems should a variable being passed as a parameter end up accidentally shifting to `null` without the
caller's knowledge. Starting in 3.6.0, such traversals will generate an exception during construction of the traversal.

It is still possible to call `addV()` with no arguments to assume a default `label` and `id` generation remains
implementation specific with some graphs accepting `id` and others ignoring it to generate their own. Both value of
`T` remain immutable.

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

==== Logging Changes

In Gremlin Server and Gremlin Console distributions, the default logging implementation of log4j 1.2.x has been
replaced by logback 1.2.x given link:https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2019-17571[CVE-2019-17571].
While it was easy to replace log4j for users of the zip distributions, it was a little harder for users to change
our packaged Docker containers which should work more cleanly out of the box.

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

==== Short and Byte

Numeric operations around `short` and `byte` have not behaved quite like `int` and `long`. Here is an example of a
`sum` operation with `sack()`:

[source,text]
----
gremlin> g.withSack((short) 2).inject((short) 1, (int) 2).sack(sum).sack()
==>3
==>4
gremlin> g.withSack((short) 2).inject((short) 1, (int) 2).sack(sum).sack().collect{it.class}
==>class java.lang.Integer
==>class java.lang.Integer
gremlin> g.withSack((short) 2).inject((short) 1, (long) 2).sack(sum).sack().collect{it.class}
==>class java.lang.Integer
==>class java.lang.Long
gremlin> g.withSack((short) 2).inject((short) 1,(byte) 2).sack(sum).sack().collect{it.class}
==>class java.lang.Integer
==>class java.lang.Integer
----

Note that the type returned for the the `sum` should be the largest type encountered in the operation, thus if a
`long + int` would return `long` or a `byte + int` would return `int`. The last example above shows inconsistency in
this rule when dealing with types `short` and `byte` which simply promote them to `int`.

For 3.6.0, that inconsistency is resolved and may be a breaking change should code be relying on the integer promotion.

[source,text]
----
gremlin> g.withSack((short) 2).inject((short) 1,(byte) 2).sack(sum).sack().collect{it.class}
==>class java.lang.Short
==>class java.lang.Short
gremlin> g.withSack((byte) 2).inject((byte) 1,(byte) 2).sack(sum).sack().collect{it.class}
==>class java.lang.Byte
==>class java.lang.Byte
----

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

==== Groovy in gremlin-driver

The `gremlin-driver` module no longer depends on `groovy` or `groovy-json`. It became an `<optional>` dependency in
3.5.0 and general deprecation of the serializers for the `JsonBuilder` class from Groovy (which was the only reason the
dependency existed in the first place) occurred in 3.5.2. As they were made `<optional>` it is likely that users who
required those packages have already adjusted their dependencies to explicitly include them. As for those who still
make use of `JsonBuilder` serialization for some reason, the only recourse is to find the code in TinkerPop and
maintain it independently. The following classes were removed as of this change (links go to their 3.5.1 versions):

* link:https://github.com/apache/tinkerpop/blob/3.5.1/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/JsonBuilderGryoSerializer.java[JsonBuilderGryoSerializer]
* link:https://github.com/apache/tinkerpop/blob/3.5.1/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/AbstractGraphSONMessageSerializerV1d0.java#L170-L184[AbstractGraphSONMessageSerializerV1d0$JsonBuilderJacksonSerializer]
* link:https://github.com/apache/tinkerpop/blob/3.5.1/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/ser/AbstractGraphSONMessageSerializerV2d0.java#L168-L182[AbstractGraphSONMessageSerializerV2d0$JsonBuilderJacksonSerializer]

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

==== Removed Gryo MessageSerializers

Gryo `MessageSerializer` implementations were deprecated at 3.4.3 (GryoLite at 3.2.6) in favor of GraphBinary. As
GraphBinary has been the default implementation for some time now and given that Gryo is a JVM-only format which
reduces its usability within Gremlin Language Variants, it seemed like the right time to remove the Gryo
`MessageSerializer` implementations from the code base. Gryo may still be used for file based applications.

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

==== GroovyTranslator of gremlin-groovy

`GroovyTranslator` has been removed from the `gremlin-groovy` module. Please update any code using that class to
instead use `org.apache.tinkerpop.gremlin.process.traversal.translator.GroovyTranslator` which is found in
`gremlin-core`.

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

==== gremlin-python Step Naming

When gremlin-python was first built, it followed the Gremlin step names perfectly and didn't account well for Python
keywords that those steps conflicted with. As this conflict led to problems in usage, steps that matched keywords were
renamed to have an underscore suffix (e.g. `sum()` to `sum_()`) and the old step names were deprecated.

In 3.6.0, those original conflicting steps names have simply been removed. Please change any of the following steps
that may still be in use to instead prefer the underscore suffixed versions:

* `filter`
* `id`
* `max`
* `min`
* `range`
* `sum`

The full list of steps with the suffix naming can be found in the
link:https://tinkerpop.apache.org/docs/3.6.0/reference/#gremlin-python-differences[Reference Documentation].

In addition to removing the conflicting names, camel cased naming has been deprecated for all Gremlin steps and
replaced with more Pythonic snake cased names. As this change was merely deprecation, this change is non-breaking at
this time, but the camel cased steps will be removed in some future major version.

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

==== `property()` with Map

The `property()` step has been extended to take a `Map` of property key/value pairs as an argument with two new
signatures:

```text
property(Map)
property(Cardinality, Map)
```

When called, each individual key/value pair in the `Map` is saved as a property to the element. When the `Cardinality`
is specified, that cardinality will be applied to all elements in the map as they are saved to the element.

If users need different cardinalities per property, then please use the existing pattern of stringing multiple
`property()`` calls together.

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

=== Upgrading for Providers

==== Graph System Providers

===== Gherkin Tests

TinkerPop originally introduced Gherkin-based feature tests when GLVs were first introduced to help provide a language
agnostic test capability. The Gherkin tests were a near one-to-one copy of the tests of the Gremlin Process Test Suite
which focus on Gremlin semantics. Unfortunately, having both JVM tests and Gherkin tests meant maintaining two sets
of tests which were testing identical things.

To simplify the ongoing maintenance of the test suite and to make it even easier to contribute to the enforcement of
Gremlin semantics, TinkerPop now provides infrastructure in the `gremlin-test` module to run the Gherkin-based tests.
For 3.6.0, the old test suite remains intact and is not deprecated, but providers are encouraged to implement the
Gherkin tests as they will include newer tests that may not be in the old test suite and it would be good to gather
feedback on the new test suite's usage so that when deprecation and removal of the old suite comes to pass the
transition will not carry as much friction.

Note that the 3.6.0 release includes a convenience zip distribution for `gremlin-test` that packages both the data
files and Gherkin features files for a release. This new file can be found on the
link:https://tinkerpop.apache.org/downloads.html[Downloads page] on the website.

See: link:https://issues.apache.org/jira/browse/TINKERPOP-2601[TINKERPOP-2601],
link:https://tinkerpop.apache.org/docs/3.6.0/dev/provider/#gherkin-tests-suite[Provider Documentation]

===== Filters with Mixed Id Types

The requirement that "ids" passed to `Graph.vertices` and `Graph.edges` all be of a single type has been removed. This
requirement was a bit to prescriptive when there really wasn't a need to enforce such a validation. It even conflicted
with TinkerGraph operations where mixed `T.id` types is a feature. Graph providers may continue to support this
requirement if they wish, but it is no longer enforced by TinkerPop and the `Graph.idArgsMustBeEitherIdOrElement` has
been removed so providers will need to construct their own exception.

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

===== Comparability/Orderability Semantics

Prior to 3.6, comparability was not well defined and produced exceptions in a variety of cases. The 3.6 release
rationalizes the comparability semantics, defined in the Graph Provider Documentation. One feature of these semantics
is the introduction of a Ternary Boolean Logics, where `ERROR` cases are well defined, and errors are no longer
propagated back to the client as an exception. The `ERROR` value is eventually reduced to `false`, which results in
the solution being quietly filtered and allows the traversal to proceed normally. For example:

[source,text]
----
gremlin> g.inject(1, "foo").is(P.gt(0)).count() // 3.5.x
Cannot compare 'foo' (String) and '0' (Integer) as both need to be an instance of Number or Comparable (and of the same type)
Type ':help' or ':h' for help.

gremlin> g.inject(1, "foo").is(P.gt(0)).count() // 3.6.0
==>1
----

Prior to 3.6, orderability (OrderGlobalStep) only applied to a single typespace and only to certain types. Attempts to
order across types resulted in an exception. The 3.6 release introduces total orderability semantics, defined in the
Graph Provider Documentation. Order now works on all types in the Gremlin language, including collections, structure
elements (Vertex, Edge, VertexProperty, Property), paths, and all the allowed property value types. Additionally,
ordering is possible across types, with the type priority defined in the orderability semantics section of the Provider
Documentation.

[source,text]
----
gremlin> g = traversal().withEmbedded(TinkerFactory.createModern())
==>graphtraversalsource[tinkergraph[vertices:6 edges:6], standard]

// Order across types
gremlin> g.V().values().order()     // 3.5.x
java.lang.String cannot be cast to java.lang.Integer
Type ':help' or ':h' for help.
gremlin> g.V().values().order()     // 3.6.0
==>27
==>29
==>32
==>35
==>java
==>java
==>josh
==>lop
==>marko
==>peter
==>ripple
==>vadas

// Order by Vertex
gremlin> g.V().order()      // 3.5.x
org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerVertex cannot be cast to java.lang.Comparable
Type ':help' or ':h' for help.
Display stack trace? [yN]
gremlin> g.V().order()      // 3.6.0
==>v[1]
==>v[2]
==>v[3]
==>v[4]
==>v[5]
==>v[6]

// Order by Map / Map.Entry
gremlin> g.V().valueMap().order()   // 3,5,x
java.util.LinkedHashMap cannot be cast to java.lang.Comparable
Type ':help' or ':h' for help.
Display stack trace? [yN]
gremlin> g.V().valueMap().order()   // 3.6.0
==>[name:[josh],age:[32]]
==>[name:[lop],lang:[java]]
==>[name:[marko],age:[29]]
==>[name:[peter],age:[35]]
==>[name:[ripple],lang:[java]]
==>[name:[vadas],age:[27]]
----

Feature tags have been introduced for feature tests that stress these new semantics (see Committer Documentation).
A new GraphFeature has been added "OrderabilitySemantics" to signify compliance with the new comparability/orderability
semantics.

See: link:https://tinkerpop.apache.org/docs/3.6.0/dev/provider/#gremlin-semantics-concepts[Gremlin Semantics]

===== Service Call API

3.6 introduces a `call()` API that allows providers to provide custom service calls with their implementation. Providers
using the reference implementation for `Traversal` execution will implement the `ServiceFactory` and `Service`
interfaces for each named service they provide. Providers using their own query engines for traveral execution will need
to provide a call operation that can list the available services (directory service) and execute named services.

See: link:https://issues.apache.org/jira/browse/TINKERPOP-2680[TINKERPOP-2680]
link:https://tinkerpop.apache.org/docs/3.6.0/reference/#element-step[Reference Documentation]
link:https://tinkerpop.apache.org/docs/3.6.0/dev/provider/#_call[Provider Documentation]
