| //// |
| 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::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/images/gremlin-victorian.png[width=185] |
| |
| *Tinkerheart* |
| |
| == TinkerPop 3.6.0 |
| |
| *Release Date: NOT OFFICIALLY RELEASED YET* |
| |
| 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/#mergee-step[mergeE()-step], |
| link:https://tinkerpop.apache.org/docs/3.6.0/reference/#mergev-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/current/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] |