You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by sp...@apache.org on 2019/12/03 17:58:52 UTC

[tinkerpop] branch TINKERPOP-2235 updated (f5ba938 -> db3933f)

This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a change to branch TINKERPOP-2235
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git.


    omit f5ba938  TINKERPOP-2235 Fixed up label overrides for property(label,Object)
    omit e440c6b  TINKERPOP-2235 Consistent behavior for multi/meta properties and null
    omit 1eee5f0  TINKERPOP-2235 Ensured addE(null) behaved consistently.
    omit 47daa34  TINKERPOP-2235 More consistent null handling for vertex mutations
    omit 6ae0016  TINKERPOP-2235 Ensured null defaults to default vertex label for all overloads of addV()
    omit 4e6c6e5  TINKERPOP-2235 Refactored Scoping interface
    omit deb3394  TINKERPOP-2235 Major refactoring and introduction of ScalarMapStep
    omit e5239ee  TINKERPOP-2235 Fixed ImmutablePath again
    omit 588fa38  TINKERPOP-2235 Removed some uneccessary code
    omit 87218ec  TINKERPOP-2235 Be smarter about null in SelectOneStep
    omit aced550  TINKERPOP-2235 Allow for nulls in Path toString()
    omit fdc0a28  TINKERPOP-2235 Improve map checks for null
    omit a2d470b  TINKERPOP-2235 Minor refactoring to get rid of duplicate code
    omit 44668cd  TINKERPOP-2235 Adjusted semantics of null a bit for Graph
    omit a7f8554  TINKERPOP-2235 Improved upgrade docs for null handling
    omit 7632762  TINKERPOP-2235 Expand imports to match code style
    omit 0ff4d85  TINKERPOP-2235 Cleanup comments/javadoc a bit for MapStep
    omit beaa86d  TINKERPOP-2235 Added upgrade and reference docs
    omit 764d412  TINKERPOP-2235 Exposed the configuration option for null as a feature
    omit f8ae887  TINKERPOP-2235 Added tests and Graph level null support
    omit ff4c0fc  TINKERPOP-2235 Fixed null handling in .NET
    omit 503b62a  TINKERPOP-2235 Fix C# gherkin test harness
    omit 7fb515e  TINKERPOP-2235 Allow null to work within Gremlin
     add 035afd1  TINKERPOP-2314 Employ by(String) for Map when possible
     add 057cd3b  Merge branch 'tp34'
     add 65eda6b  Bumped docker build to use xenial
     add 6610e7b  Merge branch 'tp33' into tp34
     add 7e96133  Merge branch 'tp34'
     new 0ab7a52  TINKERPOP-2235 Allow null to work within Gremlin
     new 381d1e5  TINKERPOP-2235 Fix C# gherkin test harness
     new 5824b53  TINKERPOP-2235 Fixed null handling in .NET
     new 1f7d34e  TINKERPOP-2235 Added tests and Graph level null support
     new a2fce50  TINKERPOP-2235 Exposed the configuration option for null as a feature
     new 86c0db8  TINKERPOP-2235 Added upgrade and reference docs
     new f94956a  TINKERPOP-2235 Cleanup comments/javadoc a bit for MapStep
     new 46a4cef  TINKERPOP-2235 Expand imports to match code style
     new 98e722b  TINKERPOP-2235 Improved upgrade docs for null handling
     new f80990b  TINKERPOP-2235 Adjusted semantics of null a bit for Graph
     new 8ff4322  TINKERPOP-2235 Minor refactoring to get rid of duplicate code
     new ba454fd  TINKERPOP-2235 Improve map checks for null
     new 5901ddf  TINKERPOP-2235 Allow for nulls in Path toString()
     new 4e688ca  TINKERPOP-2235 Be smarter about null in SelectOneStep
     new f5e937b  TINKERPOP-2235 Removed some uneccessary code
     new c102bc3  TINKERPOP-2235 Fixed ImmutablePath again
     new 4ef74c3  TINKERPOP-2235 Major refactoring and introduction of ScalarMapStep
     new b15437c  TINKERPOP-2235 Refactored Scoping interface
     new 185a32d  TINKERPOP-2235 Ensured null defaults to default vertex label for all overloads of addV()
     new b5c033e  TINKERPOP-2235 More consistent null handling for vertex mutations
     new 446d92c  TINKERPOP-2235 Ensured addE(null) behaved consistently.
     new d2be888  TINKERPOP-2235 Consistent behavior for multi/meta properties and null
     new db3933f  TINKERPOP-2235 Fixed up label overrides for property(label,Object)

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (f5ba938)
            \
             N -- N -- N   refs/heads/TINKERPOP-2235 (db3933f)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 23 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 CHANGELOG.asciidoc                                 |  2 +
 docker/Dockerfile                                  |  7 +-
 docs/src/recipes/appendix.asciidoc                 | 10 +--
 docs/src/recipes/centrality.asciidoc               |  2 +-
 docs/src/recipes/duplicate-edge.asciidoc           |  2 +-
 docs/src/upgrade/release-3.4.x.asciidoc            | 66 ++++++++++++++++++
 .../traversal/lambda/ElementValueTraversal.java    | 24 ++++++-
 .../lambda/ElementValueTraversalTest.java          | 78 ++++++++++++++++++++++
 gremlin-test/features/map/Project.feature          | 18 ++++-
 .../process/traversal/step/map/ProjectTest.java    | 22 ++++++
 10 files changed, 219 insertions(+), 12 deletions(-)
 create mode 100644 gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/lambda/ElementValueTraversalTest.java


[tinkerpop] 16/23: TINKERPOP-2235 Fixed ImmutablePath again

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2235
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit c102bc3fa82ab99aec2f305046a3640ba867c941
Author: stephen <sp...@gmail.com>
AuthorDate: Mon Nov 18 19:22:14 2019 -0500

    TINKERPOP-2235 Fixed ImmutablePath again
    
    Can't use null for the currentObject - has to be some kind of special indicator. Still not convinced that this is the best way to make this work, but so much of our infrastructure is predicated on null having some special meaning rather than having some property that describes its state.
---
 .../tinkerpop/gremlin/process/traversal/step/util/ImmutablePath.java | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/ImmutablePath.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/ImmutablePath.java
index 1f38ece..1b5e562 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/ImmutablePath.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/ImmutablePath.java
@@ -36,7 +36,8 @@ import java.util.Set;
  */
 public class ImmutablePath implements Path, Serializable, Cloneable {
 
-    private static final ImmutablePath TAIL_PATH = new ImmutablePath(null, null, null);
+    private static final Object END = EmptyPath.instance();
+    private static final ImmutablePath TAIL_PATH = new ImmutablePath(null, END, null);
 
     private ImmutablePath previousPath;
     private Object currentObject;
@@ -59,7 +60,7 @@ public class ImmutablePath implements Path, Serializable, Cloneable {
     }
 
     private final boolean isTail() {
-        return null == this.currentObject;
+        return END.equals(this.currentObject);
     }
 
     @Override


[tinkerpop] 08/23: TINKERPOP-2235 Expand imports to match code style

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2235
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 46a4cefaaf7802e1dcea42db31fadd9d49f754b7
Author: stephen <sp...@gmail.com>
AuthorDate: Wed Nov 6 11:49:01 2019 -0500

    TINKERPOP-2235 Expand imports to match code style
---
 .../org/apache/tinkerpop/gremlin/util/iterator/ArrayIteratorTest.java | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/iterator/ArrayIteratorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/iterator/ArrayIteratorTest.java
index 32f7401..2293277 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/iterator/ArrayIteratorTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/iterator/ArrayIteratorTest.java
@@ -23,7 +23,9 @@ import org.junit.Test;
 
 import java.util.Iterator;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 
 /**
  * @author Stephen Mallette (http://stephen.genoprime.com)


[tinkerpop] 05/23: TINKERPOP-2235 Exposed the configuration option for null as a feature

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2235
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit a2fce50b4544f63095ba906e434d7febeeb68de4
Author: stephen <sp...@gmail.com>
AuthorDate: Tue Nov 5 16:04:37 2019 -0500

    TINKERPOP-2235 Exposed the configuration option for null as a feature
---
 .../gremlin/tinkergraph/structure/TinkerGraph.java        | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java
index 2f83a87..2410445 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java
@@ -388,6 +388,11 @@ public final class TinkerGraph implements Graph {
         }
 
         @Override
+        public boolean supportsNullPropertyValues() {
+            return allowNullPropertyValues;
+        }
+
+        @Override
         public Features.VertexPropertyFeatures properties() {
             return vertexPropertyFeatures;
         }
@@ -414,6 +419,11 @@ public final class TinkerGraph implements Graph {
         }
 
         @Override
+        public boolean supportsNullPropertyValues() {
+            return allowNullPropertyValues;
+        }
+
+        @Override
         public boolean supportsCustomIds() {
             return false;
         }
@@ -452,6 +462,11 @@ public final class TinkerGraph implements Graph {
         }
 
         @Override
+        public boolean supportsNullPropertyValues() {
+            return allowNullPropertyValues;
+        }
+
+        @Override
         public boolean supportsCustomIds() {
             return false;
         }


[tinkerpop] 22/23: TINKERPOP-2235 Consistent behavior for multi/meta properties and null

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2235
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit d2be888b6706e4b489553952adb4be01e86fd8e4
Author: stephen <sp...@gmail.com>
AuthorDate: Tue Nov 26 13:00:06 2019 -0500

    TINKERPOP-2235 Consistent behavior for multi/meta properties and null
---
 docs/src/upgrade/release-3.5.x.asciidoc            | 109 ++++++++++++++++++---
 .../traversal/dsl/graph/GraphTraversal.java        |   7 +-
 .../gremlin/structure/VertexPropertyTest.java      |  92 ++++++++++++++++-
 .../tinkergraph/structure/TinkerVertex.java        |   6 +-
 .../structure/TinkerVertexProperty.java            |   3 +-
 5 files changed, 198 insertions(+), 19 deletions(-)

diff --git a/docs/src/upgrade/release-3.5.x.asciidoc b/docs/src/upgrade/release-3.5.x.asciidoc
index 841176d..ebe5456 100644
--- a/docs/src/upgrade/release-3.5.x.asciidoc
+++ b/docs/src/upgrade/release-3.5.x.asciidoc
@@ -363,15 +363,24 @@ link:https://issues.apache.org/jira/browse/TINKERPOP-2311[TINKERPOP-2311]
 Graph providers should take note of the changes to `null` semantics described in the "users" section of these upgrade
 notes. As `null` is now acceptable as a `Traverser` object, this change may affect custom steps. Further note that
 `null` now works more consistently with mutation steps and graph providers may need to include additional logic to
-deal with those possible conditions. Please see the console session below which uses TinkerGraph to demonstrate the
-current behavioral expectations:
+deal with those possible conditions. Please see the console sessions below which uses TinkerGraph to demonstrate the
+current behavioral expectations.
 
 [source,text]
 ----
+gremlin> g.getGraph().features().vertex().supportsNullPropertyValues()
+==>false
 gremlin> g.addV(null).property(id, null).property('name',null)
 ==>v[0]
 gremlin> g.V().elementMap()
 ==>[id:0,label:vertex]
+...
+gremlin> g.getGraph().features().vertex().supportsNullPropertyValues()
+==>true
+gremlin> g.addV(null).property(id, null).property('name',null)
+==>v[0]
+gremlin> g.V().elementMap()
+==>[id:0,label:vertex,name:null]
 ----
 
 In the above example, `addV()` defaults to `Vertex.DEFAULT_LABEL`, the `id` is generated and setting the "name"
@@ -380,14 +389,25 @@ to `null` TinkerGraph will remove the property key all together:
 
 [source,text]
 ----
-gremlin> g.V().property('name','stephen')
+gremlin> g.getGraph().features().vertex().supportsNullPropertyValues()
+==>false
+gremlin> g.addV().property('name','stephen')
 ==>v[0]
 gremlin> g.V().elementMap()
 ==>[id:0,label:vertex,name:stephen]
-gremlin> g.V().property('name',null)
+gremlin> g.V().has('vertex','name','stephen').property('name',null)
 ==>v[0]
 gremlin> g.V().elementMap()
 ==>[id:0,label:vertex]
+...
+gremlin> g.getGraph().features().vertex().supportsNullPropertyValues()
+==>true
+gremlin> g.addV().property('name','stephen')
+==>v[2]
+gremlin> g.V().has('vertex','name','stephen').property('name',null)
+==>v[2]
+gremlin> g.V().elementMap()
+==>[id:2,label:vertex,name:null]
 ----
 
 The above examples point out the default operations of TinkerGraph, but it can be configured to actually accept the
@@ -413,20 +433,85 @@ TinkerGraph is being used here):
 
 [source,text]
 ----
-gremlin> g.V(0L).as('a').addE('knows').to('a').property(id,null).property('weight',null)
-==>e[1][0-knows->0]
+gremlin> g.getGraph().features().vertex().supportsNullPropertyValues()
+==>false
+gremlin> g.addV().property('name','stephen')
+==>v[0]
+gremlin> g.V().has('vertex','name','stephen').as('a').addE('knows').to('a').property(id,null).property('weight',null)
+==>e[2][0-knows->0]
 gremlin> g.E().elementMap()
-==>[id:1,label:knows,IN:[id:0,label:vertex],OUT:[id:0,label:vertex]]
-gremlin> g.V(0L).as('a').addE('knows').to('a').property('weight',0.5)
-==>e[3][0-knows->0]
+==>[id:2,label:knows,IN:[id:0,label:vertex],OUT:[id:0,label:vertex]]
+gremlin> g.E().property('weight',0.5)
+==>e[2][0-knows->0]
 gremlin> g.E().elementMap()
-==>[id:3,label:knows,IN:[id:0,label:vertex],OUT:[id:0,label:vertex],weight:0.5]
+==>[id:2,label:knows,IN:[id:0,label:vertex],OUT:[id:0,label:vertex],weight:0.5]
 gremlin> g.E().property('weight',null)
-==>e[3][0-knows->0]
+==>e[2][0-knows->0]
 gremlin> g.E().elementMap()
-==>[id:3,label:knows,IN:[id:0,label:vertex],OUT:[id:0,label:vertex]]
+==>[id:2,label:knows,IN:[id:0,label:vertex],OUT:[id:0,label:vertex]]
+...
+gremlin> g.getGraph().features().vertex().supportsNullPropertyValues()
+==>true
+gremlin> g.addV().property('name','stephen')
+==>v[8]
+gremlin> g.V().has('vertex','name','stephen').as('a').addE('knows').to('a').property(id,null).property('weight',null)
+==>e[10][8-knows->8]
+gremlin> g.E().elementMap()
+==>[id:10,label:knows,IN:[id:8,label:vertex],OUT:[id:8,label:vertex],weight:null]
+gremlin> g.E().property('weight',0.5)
+==>e[10][8-knows->8]
+gremlin> g.E().elementMap()
+==>[id:10,label:knows,IN:[id:8,label:vertex],OUT:[id:8,label:vertex],weight:0.5]
+gremlin> g.E().property('weight',null)
+==>e[10][8-knows->8]
+gremlin> g.E().elementMap()
+==>[id:10,label:knows,IN:[id:8,label:vertex],OUT:[id:8,label:vertex],weight:null]
 ----
 
+Graphs that support multi/meta-properties have some issues to consider as well as demonstrated with TinkerGraph:
+
+[source,text]
+----
+gremlin> g.getGraph().features().vertex().supportsNullPropertyValues()
+==>false
+gremlin> g.addV().property(list,'foo',"x").property(list,"foo", null).property(list,'foo','bar')
+==>v[0]
+gremlin> g.V().elementMap()
+==>[id:0,label:vertex,foo:bar]
+gremlin> g.V().valueMap()
+==>[foo:[x,bar]]
+gremlin> g.V().property('foo',null)
+==>v[0]
+gremlin> g.V().valueMap(true)
+==>[id:0,label:vertex]
+...
+gremlin> g.addV().property(list,'foo','bar','x',1,'y',null)
+==>v[0]
+gremlin> g.V().properties('foo').valueMap(true)
+==>[id:1,key:foo,value:bar,x:1]
+gremlin> g.V().properties('foo').property('x',null)
+==>vp[foo->bar]
+gremlin> g.V().properties('foo').valueMap(true)
+==>[id:1,key:foo,value:bar]
+...
+gremlin> g.getGraph().features().vertex().supportsNullPropertyValues()
+==>false
+gremlin> g.addV().property(list,'foo',"x").property(list,"foo", null).property(list,'foo','bar')
+==>v[11]
+gremlin> g.V().elementMap()
+==>[id:11,label:vertex,foo:bar]
+gremlin> g.V().valueMap()
+==>[foo:[x,null,bar]]
+...
+gremlin> g.addV().property(list,'foo','bar','x',1,'y',null)
+==>v[0]
+gremlin> g.V().properties('foo').valueMap(true)
+==>[id:1,key:foo,value:bar,x:1,y:null]
+gremlin> g.V().properties('foo').property('x',null)
+==>vp[foo->bar]
+gremlin> g.V().properties('foo').valueMap(true)
+==>[id:1,key:foo,value:bar,x:null,y:null]
+----
 
 See: link:https://issues.apache.org/jira/browse/TINKERPOP-2235[TINKERPOP-2235],
 link:https://issues.apache.org/jira/browse/TINKERPOP-2099[TINKERPOP-2099]
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java
index a8f352a..ef20e52 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java
@@ -2191,8 +2191,7 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
      * {@link VertexProperty.Cardinality} is defaulted to {@code null} which  means that the default cardinality for
      * the {@link Graph} will be used.
      * <p/>
-     * This method is effectively calls
-     * {@link #property(org.apache.tinkerpop.gremlin.structure.VertexProperty.Cardinality, Object, Object, Object...)}
+     * This method is effectively calls {@link #property(VertexProperty.Cardinality, Object, Object, Object...)}
      * as {@code property(null, key, value, keyValues}.
      *
      * @param key       the key for the property
@@ -2204,8 +2203,8 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
      */
     public default GraphTraversal<S, E> property(final Object key, final Object value, final Object... keyValues) {
         return key instanceof VertexProperty.Cardinality ?
-                this.property((VertexProperty.Cardinality) key, value, keyValues[0],
-                        keyValues.length > 1 ?
+                this.property((VertexProperty.Cardinality) key, value, null == keyValues ? null : keyValues[0],
+                        keyValues != null && keyValues.length > 1 ?
                                 Arrays.copyOfRange(keyValues, 1, keyValues.length) :
                                 new Object[]{}) :
                 this.property(null, key, value, keyValues);
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexPropertyTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexPropertyTest.java
index 04b431d..c8b3c66 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexPropertyTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexPropertyTest.java
@@ -111,7 +111,8 @@ public class VertexPropertyTest extends AbstractGremlinTest {
         @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_META_PROPERTIES)
         @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_MULTI_PROPERTIES)
         @FeatureRequirement(featureClass = Graph.Features.VertexPropertyFeatures.class, feature = Graph.Features.VertexPropertyFeatures.FEATURE_INTEGER_VALUES)
-        public void shouldHandleListVertexProperties() {
+        @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_NULL_PROPERTY_VALUES, supported = false)
+        public void shouldHandleListVertexPropertiesWithoutNullPropertyValues() {
             final Vertex v = graph.addVertex("name", "marko", "age", 34);
             tryCommit(graph, g -> {
                 assertEquals("marko", v.property("name").value());
@@ -169,6 +170,95 @@ public class VertexPropertyTest extends AbstractGremlinTest {
 
                 assertVertexEdgeCounts(graph, 1, 0);
             });
+
+            // null property values are not supported so the value should not be added as set or list cardinality,
+            // but single will remove it
+            assertEquals(VertexProperty.empty(), v.property(VertexProperty.Cardinality.list, "name", null));
+            tryCommit(graph, g -> {
+                assertEquals(3, IteratorUtils.count(v.properties("name")));
+                assertEquals(4, IteratorUtils.count(v.properties()));
+                assertVertexEdgeCounts(graph, 1, 0);
+            });
+            assertEquals(VertexProperty.empty(), v.property(VertexProperty.Cardinality.single, "name", null));
+            tryCommit(graph, g -> {
+                assertEquals(0, IteratorUtils.count(v.properties("name")));
+                assertEquals(1, IteratorUtils.count(v.properties()));
+                assertVertexEdgeCounts(graph, 1, 0);
+            });
+        }
+
+        @Test
+        @FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
+        @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_META_PROPERTIES)
+        @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_MULTI_PROPERTIES)
+        @FeatureRequirement(featureClass = Graph.Features.VertexPropertyFeatures.class, feature = Graph.Features.VertexPropertyFeatures.FEATURE_INTEGER_VALUES)
+        @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_NULL_PROPERTY_VALUES)
+        public void shouldHandleListVertexPropertiesWithNullPropertyValues() {
+            final Vertex v = graph.addVertex("name", "marko", "age", 34);
+            tryCommit(graph, g -> {
+                assertEquals("marko", v.property("name").value());
+                assertEquals("marko", v.value("name"));
+                assertEquals(34, v.property("age").value());
+                assertEquals(34, v.<Integer>value("age").intValue());
+                assertEquals(1, IteratorUtils.count(v.properties("name")));
+                assertEquals(2, IteratorUtils.count(v.properties()));
+                assertVertexEdgeCounts(graph, 1, 0);
+            });
+
+            final VertexProperty<String> property = v.property(VertexProperty.Cardinality.list, "name", "marko a. rodriguez");
+            tryCommit(graph, g -> assertEquals(v, property.element()));
+
+            try {
+                v.property("name");
+                fail("This should throw a: " + Vertex.Exceptions.multiplePropertiesExistForProvidedKey("name"));
+            } catch (final Exception e) {
+                validateException(Vertex.Exceptions.multiplePropertiesExistForProvidedKey("name"), e);
+            }
+
+            assertTrue(IteratorUtils.list(v.values("name")).contains("marko"));
+            assertTrue(IteratorUtils.list(v.values("name")).contains("marko a. rodriguez"));
+            assertEquals(3, IteratorUtils.count(v.properties()));
+            assertEquals(2, IteratorUtils.count(v.properties("name")));
+            assertVertexEdgeCounts(graph, 1, 0);
+
+            assertEquals(v, v.property(VertexProperty.Cardinality.list, "name", "mrodriguez").element());
+            tryCommit(graph, g -> {
+                assertEquals(3, IteratorUtils.count(v.properties("name")));
+                assertEquals(4, IteratorUtils.count(v.properties()));
+                assertVertexEdgeCounts(graph, 1, 0);
+            });
+
+            v.<String>properties("name").forEachRemaining(meta -> {
+                meta.property("counter", meta.value().length());
+            });
+
+            tryCommit(graph, g -> {
+                v.properties().forEachRemaining(meta -> {
+                    assertEquals(meta.key(), meta.label());
+                    assertTrue(meta.isPresent());
+                    assertEquals(v, meta.element());
+                    if (meta.key().equals("age")) {
+                        assertEquals(meta.value(), 34);
+                        assertEquals(0, IteratorUtils.count(meta.properties()));
+                    }
+                    if (meta.key().equals("name")) {
+                        assertEquals(((String) meta.value()).length(), meta.<Integer>value("counter").intValue());
+                        assertEquals(1, IteratorUtils.count(meta.properties()));
+                        assertEquals(1, meta.keys().size());
+                        assertTrue(meta.keys().contains("counter"));
+                    }
+                });
+
+                assertVertexEdgeCounts(graph, 1, 0);
+            });
+
+            // null property values are supported so the value should be added
+            assertEquals(v, v.property(VertexProperty.Cardinality.list, "name", null).element());
+            tryCommit(graph, g -> {
+                assertEquals(4, IteratorUtils.count(v.properties("name")));
+                assertEquals(5, IteratorUtils.count(v.properties()));
+                assertVertexEdgeCounts(graph, 1, 0);
+            });
         }
 
         @Test
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertex.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertex.java
index 52518d6..1f055fb 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertex.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertex.java
@@ -89,8 +89,12 @@ public final class TinkerVertex extends TinkerElement implements Vertex {
         ElementHelper.validateProperty(key, value);
         final Optional<Object> optionalId = ElementHelper.getIdValue(keyValues);
 
+        // if we don't allow null property values and the value is null then the key can be removed but only if the
+        // cardinality is single. if it is list/set then we can just ignore the null.
         if (!allowNullPropertyValues && null == value) {
-            properties(key).forEachRemaining(VertexProperty::remove);
+            final VertexProperty.Cardinality card = null == cardinality ? graph.features().vertex().getCardinality(key) : cardinality;
+            if (VertexProperty.Cardinality.single == card)
+                properties(key).forEachRemaining(VertexProperty::remove);
             return VertexProperty.empty();
         }
 
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertexProperty.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertexProperty.java
index ed8acec..f2635df 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertexProperty.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertexProperty.java
@@ -143,7 +143,8 @@ public class TinkerVertexProperty<V> extends TinkerElement implements VertexProp
             }
             final AtomicBoolean delete = new AtomicBoolean(true);
             this.vertex.properties(this.key).forEachRemaining(property -> {
-                if (property.value().equals(this.value))
+                final Object currentPropertyValue = property.value();
+                if ((currentPropertyValue != null && currentPropertyValue.equals(this.value) || null == currentPropertyValue && null == this.value))
                     delete.set(false);
             });
             if (delete.get()) TinkerHelper.removeIndex(this.vertex, this.key, this.value);


[tinkerpop] 01/23: TINKERPOP-2235 Allow null to work within Gremlin

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2235
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 0ab7a52ddaecb5046b4f63b680d0071e90c95919
Author: stephen <sp...@gmail.com>
AuthorDate: Mon Nov 4 15:56:11 2019 -0500

    TINKERPOP-2235 Allow null to work within Gremlin
    
    In previous versions null as a value was not allowed as a Traverser in a Traversal. It was part of a filter in DefaultTraversal that would automatically remove it from the stream. While this was a design choice for Gremlin to use null that way, it removed a bit of functionality from Gremlin that could have use at times. As it so happened, the use of null as a filter really didn't need to work that way as we already had a secondary filter which was even better for representing an Empty [...]
---
 .../tinkerpop/gremlin/jsr223/JavaTranslator.java   |  2 +-
 .../tinkerpop/gremlin/process/traversal/Order.java |  8 ++++--
 .../gremlin/process/traversal/Traverser.java       |  9 ++++++-
 .../process/traversal/step/map/MapStep.java        | 16 +++++++++++-
 .../process/traversal/step/map/SelectOneStep.java  |  7 ++++-
 .../process/traversal/step/map/SelectStep.java     |  6 +++++
 .../traversal/step/map/TraversalMapStep.java       |  6 +++++
 .../process/traversal/step/util/AbstractStep.java  |  5 ++--
 .../traversal/traverser/B_O_S_SE_SL_Traverser.java |  2 +-
 .../traverser/util/AbstractTraverser.java          |  7 ++---
 .../traversal/traverser/util/EmptyTraverser.java   |  9 ++++++-
 .../gremlin/process/traversal/OrderTest.java       |  4 +++
 .../traverser/util/EmptyTraverserTest.java}        | 30 +++++++++-------------
 .../gremlin/util/iterator/ArrayIteratorTest.java   | 18 +++++++++++--
 .../groovy/jsr223/GroovyTranslatorProvider.java    |  4 ---
 .../ParameterizedGroovyTranslatorProvider.java     |  4 ---
 .../test/cucumber/feature-steps.js                 |  7 ++++-
 .../gremlin-javascript/test/unit/graphson-test.js  |  5 ++++
 .../gremlin_python/structure/io/graphbinaryV1.py   |  4 +++
 .../src/main/jython/radish/feature_steps.py        |  6 ++++-
 .../tests/structure/io/test_graphbinaryV1.py       |  9 +++++++
 .../jython/tests/structure/io/test_graphsonV2d0.py |  4 +++
 .../jython/tests/structure/io/test_graphsonV3d0.py |  4 +++
 .../GraphBinaryRemoteGraphComputerProvider.java    |  6 +----
 .../GraphSONRemoteGraphComputerProvider.java       |  6 +----
 .../remote/GryoRemoteGraphComputerProvider.java    |  6 +----
 ...emoteGraphGroovyTranslatorComputerProvider.java |  6 +----
 gremlin-test/features/sideEffect/Inject.feature    | 14 ++++++++++
 .../process/traversal/CoreTraversalTest.java       | 12 ---------
 .../traversal/step/sideEffect/InjectTest.java      | 14 ++++++++++
 .../strategy/decoration/TranslationStrategy.java   |  4 ++-
 ...tractTinkerGraphGraphSONTranslatorProvider.java |  4 ---
 .../io/gryo/TinkerGraphGryoTranslatorProvider.java |  4 ---
 33 files changed, 168 insertions(+), 84 deletions(-)

diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
index e71f112..515bc93 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
@@ -214,7 +214,7 @@ public final class JavaTranslator<S extends TraversalSource, T extends Traversal
                         for (int i = 0; i < parameters.length; i++) {
                             if (parameters[i].isVarArgs()) {
                                 final Class<?> parameterClass = parameters[i].getType().getComponentType();
-                                if (argumentsCopy.length > i && !parameterClass.isAssignableFrom(argumentsCopy[i].getClass())) {
+                                if (argumentsCopy.length > i && argumentsCopy[i] != null && !parameterClass.isAssignableFrom(argumentsCopy[i].getClass())) {
                                     found = false;
                                     break;
                                 }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Order.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Order.java
index 34a61a7..1176f54 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Order.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Order.java
@@ -54,11 +54,13 @@ public enum Order implements Comparator<Object> {
      * @since 3.3.4
      */
     asc {
+        private final Comparator<Comparable> ascendingComparator = Comparator.nullsFirst(Comparator.<Comparable>naturalOrder());
+
         @Override
         public int compare(final Object first, final Object second) {
             return first instanceof Number && second instanceof Number
                     ? NumberHelper.compare((Number) first, (Number) second)
-                    : Comparator.<Comparable>naturalOrder().compare((Comparable) first, (Comparable) second);
+                    : ascendingComparator.compare((Comparable) first, (Comparable) second);
         }
 
         @Override
@@ -73,11 +75,13 @@ public enum Order implements Comparator<Object> {
      * @since 3.3.4
      */
     desc {
+        private final Comparator<Comparable> descendingComparator = Comparator.nullsLast(Comparator.<Comparable>reverseOrder());
+
         @Override
         public int compare(final Object first, final Object second) {
             return first instanceof Number && second instanceof Number
                     ? NumberHelper.compare((Number) second, (Number) first)
-                    : Comparator.<Comparable>reverseOrder().compare((Comparable) first, (Comparable) second);
+                    : descendingComparator.compare((Comparable) first, (Comparable) second);
         }
 
         @Override
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Traverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Traverser.java
index e07bec1..fc58c62 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Traverser.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Traverser.java
@@ -18,9 +18,11 @@
  */
 package org.apache.tinkerpop.gremlin.process.traversal;
 
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.LoopsStep;
 import org.apache.tinkerpop.gremlin.structure.util.Attachable;
 
 import java.io.Serializable;
+import java.util.Comparator;
 import java.util.Set;
 import java.util.function.Function;
 
@@ -134,7 +136,12 @@ public interface Traverser<T> extends Serializable, Comparable<Traverser<T>>, Cl
      */
     @Override
     public default int compareTo(final Traverser<T> other) throws ClassCastException {
-        return ((Comparable) this.get()).compareTo(other.get());
+        final Object thisObj = this.get();
+        final Object otherObj = other.get();
+        if (thisObj == otherObj) return 0;
+        if (null == thisObj) return -1;
+        if (null == otherObj) return 1;
+        return ((Comparable) thisObj).compareTo(otherObj);
     }
 
     /**
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MapStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MapStep.java
index 03e544e..2747b9c 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MapStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MapStep.java
@@ -21,6 +21,7 @@ package org.apache.tinkerpop.gremlin.process.traversal.step.map;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.AbstractStep;
+import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.EmptyTraverser;
 
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
@@ -34,10 +35,23 @@ public abstract class MapStep<S, E> extends AbstractStep<S, E> {
     @Override
     protected Traverser.Admin<E> processNextStart() {
         final Traverser.Admin<S> traverser = this.starts.next();
-        return traverser.split(this.map(traverser), this);
+        final E obj = this.map(traverser);
+
+        // maybe looks tricky, but it's just a play on Java generics without having to tear apart every MapStep
+        // instance. basically just want to respect the fact that a subclass can return an EmptyTraverser from
+        // map() and if so, just return that empty.
+        return isEmptyTraverser(obj) ? EmptyTraverser.instance() : traverser.split(obj, this);
     }
 
     protected abstract E map(final Traverser.Admin<S> traverser);
 
+    /**
+     * Determines if the value returned from {@link #map(Traverser.Admin)} should be representative of an
+     * {@link EmptyTraverser}. Such traversers will effectively be filtered out by the traversal.
+     */
+    protected boolean isEmptyTraverser(E obj) {
+        return false;
+    }
+
 }
 
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectOneStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectOneStep.java
index 2c7aa82..98ee83f 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectOneStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectOneStep.java
@@ -26,7 +26,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.PathProcessor;
 import org.apache.tinkerpop.gremlin.process.traversal.step.Scoping;
 import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
 import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
-import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper;
+import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.EmptyTraverser;
 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil;
 import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
 
@@ -58,6 +58,11 @@ public final class SelectOneStep<S, E> extends MapStep<S, E> implements Traversa
     }
 
     @Override
+    protected boolean isEmptyTraverser(final E obj) {
+        return null == obj;
+    }
+
+    @Override
     public String toString() {
         return StringFactory.stepString(this, this.pop, this.selectKey, this.selectTraversal);
     }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectStep.java
index 875cf93..db15a28 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectStep.java
@@ -26,6 +26,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.PathProcessor;
 import org.apache.tinkerpop.gremlin.process.traversal.step.Scoping;
 import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
 import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
+import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.EmptyTraverser;
 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper;
 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalRing;
 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil;
@@ -77,6 +78,11 @@ public final class SelectStep<S, E> extends MapStep<S, Map<String, E>> implement
     }
 
     @Override
+    protected boolean isEmptyTraverser(final Map<String, E> obj) {
+        return null == obj;
+    }
+
+    @Override
     public void reset() {
         super.reset();
         this.traversalRing.reset();
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TraversalMapStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TraversalMapStep.java
index 896b833..32c2e3c 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TraversalMapStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TraversalMapStep.java
@@ -22,6 +22,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
 import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
+import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.EmptyTraverser;
 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil;
 import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
 
@@ -49,6 +50,11 @@ public final class TraversalMapStep<S, E> extends MapStep<S, E> implements Trave
     }
 
     @Override
+    protected boolean isEmptyTraverser(final E obj) {
+        return null == obj;
+    }
+
+    @Override
     public List<Traversal.Admin<S, E>> getLocalChildren() {
         return Collections.singletonList(this.mapTraversal);
     }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/AbstractStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/AbstractStep.java
index e2757e2..fe49e56 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/AbstractStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/AbstractStep.java
@@ -21,6 +21,7 @@ package org.apache.tinkerpop.gremlin.process.traversal.step.util;
 import org.apache.tinkerpop.gremlin.process.traversal.Step;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.EmptyTraverser;
 import org.apache.tinkerpop.gremlin.process.traversal.util.EmptyTraversal;
 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalInterruptedException;
 import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
@@ -126,7 +127,7 @@ public abstract class AbstractStep<S, E> implements Step<S, E> {
             while (true) {
                 if (Thread.interrupted()) throw new TraversalInterruptedException();
                 final Traverser.Admin<E> traverser = this.processNextStart();
-                if (null != traverser.get() && 0 != traverser.bulk())
+                if (traverser.bulk() > 0)
                     return this.prepareTraversalForNextStep(traverser);
             }
         }
@@ -141,7 +142,7 @@ public abstract class AbstractStep<S, E> implements Step<S, E> {
                 while (true) {
                     if (Thread.interrupted()) throw new TraversalInterruptedException();
                     this.nextEnd = this.processNextStart();
-                    if (null != this.nextEnd.get() && 0 != this.nextEnd.bulk())
+                    if (this.nextEnd.bulk() > 0)
                         return true;
                     else
                         this.nextEnd = null;
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_O_S_SE_SL_Traverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_O_S_SE_SL_Traverser.java
index 33c5520..57fa47a 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_O_S_SE_SL_Traverser.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_O_S_SE_SL_Traverser.java
@@ -136,7 +136,7 @@ public class B_O_S_SE_SL_Traverser<T> extends B_O_Traverser<T> {
 
     protected final boolean equals(final B_O_S_SE_SL_Traverser other) {
         return super.equals(other) && other.loops == this.loops
-                && (this.loopName != null ? this.loopName.equals(other.loopName) : other.loopName == null)
+                && Objects.equals(this.loopName, other.loopName)
                 && !carriesUnmergeableSack();
     }
 
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/util/AbstractTraverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/util/AbstractTraverser.java
index 658ba4b..e76016a 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/util/AbstractTraverser.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/util/AbstractTraverser.java
@@ -27,6 +27,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.util.EmptyTraversalSideEff
 import org.apache.tinkerpop.gremlin.structure.util.Attachable;
 import org.apache.tinkerpop.gremlin.structure.util.reference.ReferenceFactory;
 
+import java.util.Objects;
 import java.util.Set;
 import java.util.function.Function;
 
@@ -203,16 +204,16 @@ public abstract class AbstractTraverser<T> implements Traverser<T>, Traverser.Ad
 
     @Override
     public int hashCode() {
-        return this.t.hashCode();
+        return Objects.hashCode(this.t);
     }
 
     @Override
     public boolean equals(final Object object) {
-        return object instanceof AbstractTraverser && ((AbstractTraverser) object).get().equals(this.t);
+        return object instanceof AbstractTraverser && Objects.equals(this.t, ((AbstractTraverser) object).t);
     }
 
     @Override
     public String toString() {
-        return this.t.toString();
+        return Objects.toString(this.t);
     }
 }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/util/EmptyTraverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/util/EmptyTraverser.java
index 3aeafbd..c68fdc6 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/util/EmptyTraverser.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/util/EmptyTraverser.java
@@ -20,6 +20,7 @@ package org.apache.tinkerpop.gremlin.process.traversal.traverser.util;
 
 import org.apache.tinkerpop.gremlin.process.traversal.Path;
 import org.apache.tinkerpop.gremlin.process.traversal.Step;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalSideEffects;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.EmptyPath;
@@ -30,12 +31,18 @@ import java.util.Set;
 import java.util.function.Function;
 
 /**
+ * A {@link Traverser} with no bulk which effectively means that it will no longer be propagated through a
+ * {@link Traversal}.
+ *
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  */
 public final class EmptyTraverser<T> implements Traverser<T>, Traverser.Admin<T> {
 
     private static final EmptyTraverser INSTANCE = new EmptyTraverser();
 
+    /**
+     * The empty {@link Traverser} instance.
+     */
     public static <R> EmptyTraverser<R> instance() {
         return INSTANCE;
     }
@@ -95,7 +102,7 @@ public final class EmptyTraverser<T> implements Traverser<T>, Traverser.Admin<T>
     }
 
     @Override
-    public void setBulk(long count) {
+    public void setBulk(final long count) {
 
     }
 
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/OrderTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/OrderTest.java
index a70cfc8..d094772 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/OrderTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/OrderTest.java
@@ -43,13 +43,17 @@ public class OrderTest {
     @Parameterized.Parameters(name = "{0}.test({1},{2})")
     public static Iterable<Object[]> data() throws ParseException {
         return new ArrayList<>(Arrays.asList(new Object[][]{
+                {Order.asc, Arrays.asList("a", "c", null, "d"), Arrays.asList(null, "a", "c", "d")},
                 {Order.asc, Arrays.asList("b", "a", "c", "d"), Arrays.asList("a", "b", "c", "d")},
                 {Order.desc, Arrays.asList("b", "a", "c", "d"), Arrays.asList("d", "c", "b", "a")},
+                {Order.desc, Arrays.asList("c", "a", null, "d"), Arrays.asList("d", "c", "a", null)},
                 {Order.asc, Arrays.asList(formatter.parse("1-Jan-2018"), formatter.parse("1-Jan-2020"), formatter.parse("1-Jan-2008")),
                             Arrays.asList(formatter.parse("1-Jan-2008"), formatter.parse("1-Jan-2018"), formatter.parse("1-Jan-2020"))},
                 {Order.desc, Arrays.asList(formatter.parse("1-Jan-2018"), formatter.parse("1-Jan-2020"), formatter.parse("1-Jan-2008")),
                              Arrays.asList(formatter.parse("1-Jan-2020"), formatter.parse("1-Jan-2018"), formatter.parse("1-Jan-2008"))},
+                {Order.desc, Arrays.asList(100L, 1L, null, -1L, 0L), Arrays.asList(100L, 1L, 0L, -1L, null)},
                 {Order.desc, Arrays.asList(100L, 1L, -1L, 0L), Arrays.asList(100L, 1L, 0L, -1L)},
+                {Order.asc, Arrays.asList(100L, 1L, null, -1L, 0L), Arrays.asList(null, -1L, 0L, 1L, 100L)},
                 {Order.asc, Arrays.asList(100.1f, 1.1f, -1.1f, 0.1f), Arrays.asList(-1.1f, 0.1f, 1.1f, 100.1f)},
                 {Order.desc, Arrays.asList(100.1f, 1.1f, -1.1f, 0.1f), Arrays.asList(100.1f, 1.1f, 0.1f, -1.1f)},
                 {Order.asc, Arrays.asList(100.1d, 1.1d, -1.1d, 0.1d), Arrays.asList(-1.1d, 0.1d, 1.1d, 100.1d)},
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MapStep.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/util/EmptyTraverserTest.java
similarity index 52%
copy from gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MapStep.java
copy to gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/util/EmptyTraverserTest.java
index 03e544e..da41900 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MapStep.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/util/EmptyTraverserTest.java
@@ -16,28 +16,22 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.tinkerpop.gremlin.process.traversal.step.map;
+package org.apache.tinkerpop.gremlin.process.traversal.traverser.util;
 
-import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
-import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
-import org.apache.tinkerpop.gremlin.process.traversal.step.util.AbstractStep;
+import org.junit.Test;
 
-/**
- * @author Marko A. Rodriguez (http://markorodriguez.com)
- */
-public abstract class MapStep<S, E> extends AbstractStep<S, E> {
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
 
-    public MapStep(final Traversal.Admin traversal) {
-        super(traversal);
-    }
+public class EmptyTraverserTest {
 
-    @Override
-    protected Traverser.Admin<E> processNextStart() {
-        final Traverser.Admin<S> traverser = this.starts.next();
-        return traverser.split(this.map(traverser), this);
+    @Test
+    public void shouldHaveSameInstance() {
+        assertSame(EmptyTraverser.instance(), EmptyTraverser.instance());
     }
 
-    protected abstract E map(final Traverser.Admin<S> traverser);
-
+    @Test
+    public void shouldHaveZeroBulk() {
+        assertEquals(0, EmptyTraverser.instance().bulk());
+    }
 }
-
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/iterator/ArrayIteratorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/iterator/ArrayIteratorTest.java
index 60ddd88..32f7401 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/iterator/ArrayIteratorTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/iterator/ArrayIteratorTest.java
@@ -23,8 +23,7 @@ import org.junit.Test;
 
 import java.util.Iterator;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.*;
 
 /**
  * @author Stephen Mallette (http://stephen.genoprime.com)
@@ -45,6 +44,21 @@ public class ArrayIteratorTest {
         assertFalse(itty.hasNext());
     }
 
+    @Test
+    public void shouldIterateAnArrayWithNull() {
+        final String[] arr = new String[3];
+        arr[0] = "test1";
+        arr[1] = "test2";
+        arr[2] = null;
+
+        final Iterator<String> itty = new ArrayIterator<>(arr);
+        assertEquals("test1", itty.next());
+        assertEquals("test2", itty.next());
+        assertNull(itty.next());
+
+        assertFalse(itty.hasNext());
+    }
+
     @Test(expected = FastNoSuchElementException.class)
     public void shouldThrowFastNoSuchElementException() {
         final Iterator<String> itty = new ArrayIterator<>(new String[0]);
diff --git a/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GroovyTranslatorProvider.java b/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GroovyTranslatorProvider.java
index 5f556b8..b4d2452 100644
--- a/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GroovyTranslatorProvider.java
+++ b/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GroovyTranslatorProvider.java
@@ -207,10 +207,6 @@ import org.apache.tinkerpop.gremlin.util.TinkerGraphProvider;
         method = "shouldNeverPropagateANoBulkTraverser",
         reason = "Reason requires investigation")
 @Graph.OptOut(
-        test = "org.apache.tinkerpop.gremlin.process.traversal.CoreTraversalTest",
-        method = "shouldNeverPropagateANullValuedTraverser",
-        reason = "Reason requires investigation")
-@Graph.OptOut(
         test = "org.apache.tinkerpop.gremlin.process.traversal.step.map.ReadTest",
         method = "*",
         reason = "read and write tests don't translate locally well because of calling iterate() inside read()/write() add a none()")
diff --git a/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/ParameterizedGroovyTranslatorProvider.java b/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/ParameterizedGroovyTranslatorProvider.java
index 2e60cba..56466a5 100644
--- a/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/ParameterizedGroovyTranslatorProvider.java
+++ b/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/ParameterizedGroovyTranslatorProvider.java
@@ -207,10 +207,6 @@ import org.apache.tinkerpop.gremlin.util.TinkerGraphProvider;
         method = "shouldNeverPropagateANoBulkTraverser",
         reason = "Reason requires investigation")
 @Graph.OptOut(
-        test = "org.apache.tinkerpop.gremlin.process.traversal.CoreTraversalTest",
-        method = "shouldNeverPropagateANullValuedTraverser",
-        reason = "Reason requires investigation")
-@Graph.OptOut(
         test = "org.apache.tinkerpop.gremlin.process.traversal.step.map.ReadTest",
         method = "*",
         reason = "read and write tests don't translate locally well because of calling iterate() inside read()/write() add a none()")
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js
index fed551d..d85d678 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js
@@ -223,6 +223,10 @@ function parseRow(row) {
 }
 
 function parseValue(stringValue) {
+
+  if(stringValue === "null")
+    return null;
+
   let extractedValue = null;
   let parser = null;
   for (let item of parsers) {
@@ -234,6 +238,7 @@ function parseValue(stringValue) {
       break;
     }
   }
+
   return parser !== null ? parser.call(this, extractedValue) : stringValue;
 }
 
@@ -279,7 +284,7 @@ function toT(value) {
 }
 
 function toDirection(value) {
-    return direction[value.toLowerCase()];
+  return direction[value.toLowerCase()];
 }
 
 function toArray(stringList) {
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphson-test.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphson-test.js
index f355fb5..2942d01 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphson-test.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphson-test.js
@@ -32,6 +32,11 @@ const GraphSONWriter = gs.GraphSONWriter;
 const P = t.P;
 
 describe('GraphSONReader', function () {
+  it('should parse GraphSON null', function () {
+    const reader = new GraphSONReader();
+    const result = reader.read(null);
+    assert.equal(result, null);
+  });
   it('should parse GraphSON int32, float and double to Number from GraphSON', function () {
     const reader = new GraphSONReader();
     [
diff --git a/gremlin-python/src/main/jython/gremlin_python/structure/io/graphbinaryV1.py b/gremlin-python/src/main/jython/gremlin_python/structure/io/graphbinaryV1.py
index c06ee12..b5aca52 100644
--- a/gremlin-python/src/main/jython/gremlin_python/structure/io/graphbinaryV1.py
+++ b/gremlin-python/src/main/jython/gremlin_python/structure/io/graphbinaryV1.py
@@ -156,6 +156,10 @@ class GraphBinaryWriter(object):
         if to_extend is None:
             to_extend = bytearray()
 
+        if obj is None:
+            to_extend.extend(NULL_BYTES)
+            return
+
         try:
             t = type(obj)
             
diff --git a/gremlin-python/src/main/jython/radish/feature_steps.py b/gremlin-python/src/main/jython/radish/feature_steps.py
index 441a3fb..6514f3c 100644
--- a/gremlin-python/src/main/jython/radish/feature_steps.py
+++ b/gremlin-python/src/main/jython/radish/feature_steps.py
@@ -38,6 +38,7 @@ regex_or = re.compile(r"([(.,\s])or\(")
 regex_with = re.compile(r"([(.,\s])with\(")
 regex_true = re.compile(r"(true)")
 regex_false = re.compile(r"(false)")
+regex_null = re.compile(r"(null)")
 
 outV = __.outV
 label = __.label
@@ -182,6 +183,8 @@ def _convert(val, ctx):
         return T[val[2:-1]]
     elif isinstance(val, str) and re.match("^D\[.*\]$", val):           # parse instance of Direction enum
         return Direction[val[2:-1]]
+    elif isinstance(val, str) and re.match("^null$", val):              # parse null to None
+        return None
     else:
         return val
 
@@ -247,7 +250,8 @@ def _translate(traversal_):
     replaced = regex_in.sub(r"\1in_(", replaced)
     replaced = regex_with.sub(r"\1with_(", replaced)
     replaced = regex_true.sub(r"True", replaced)
-    return regex_false.sub(r"False", replaced)
+    replaced = regex_false.sub(r"False", replaced)
+    return regex_null.sub(r"None", replaced)
 
 
 def _make_traversal(g, traversal_string, params):
diff --git a/gremlin-python/src/main/jython/tests/structure/io/test_graphbinaryV1.py b/gremlin-python/src/main/jython/tests/structure/io/test_graphbinaryV1.py
index a2320bc..7416a94 100644
--- a/gremlin-python/src/main/jython/tests/structure/io/test_graphbinaryV1.py
+++ b/gremlin-python/src/main/jython/tests/structure/io/test_graphbinaryV1.py
@@ -44,6 +44,10 @@ class TestGraphSONWriter(object):
     graphbinary_writer = GraphBinaryWriter()
     graphbinary_reader = GraphBinaryReader()
 
+    def test_null(self):
+        c = self.graphbinary_reader.readObject(self.graphbinary_writer.writeObject(None))
+        assert c is None
+
     def test_int(self):
         x = 100
         output = self.graphbinary_reader.readObject(self.graphbinary_writer.writeObject(x))
@@ -101,6 +105,11 @@ class TestGraphSONWriter(object):
         output = self.graphbinary_reader.readObject(self.graphbinary_writer.writeObject(x))
         assert x == output
 
+    def test_heterogeneous_list_with_none(self):
+        x = ["serialize this!", 0, "serialize that!", "serialize that!", 1, "stop telling me what to serialize", 2, None]
+        output = self.graphbinary_reader.readObject(self.graphbinary_writer.writeObject(x))
+        assert x == output
+
     def test_homogeneous_set(self):
         x = {"serialize this!", "serialize that!", "stop telling me what to serialize"}
         output = self.graphbinary_reader.readObject(self.graphbinary_writer.writeObject(x))
diff --git a/gremlin-python/src/main/jython/tests/structure/io/test_graphsonV2d0.py b/gremlin-python/src/main/jython/tests/structure/io/test_graphsonV2d0.py
index 1f54736..cebe29c 100644
--- a/gremlin-python/src/main/jython/tests/structure/io/test_graphsonV2d0.py
+++ b/gremlin-python/src/main/jython/tests/structure/io/test_graphsonV2d0.py
@@ -289,6 +289,10 @@ class TestGraphSONReader(object):
         assert isinstance(c, SingleChar)
         assert chr(76) == c
 
+    def test_null(self):
+        c = self.graphson_reader.readObject(json.dumps(None))
+        assert c is None
+
 
 class TestGraphSONWriter(object):
     graphson_writer = GraphSONWriter()
diff --git a/gremlin-python/src/main/jython/tests/structure/io/test_graphsonV3d0.py b/gremlin-python/src/main/jython/tests/structure/io/test_graphsonV3d0.py
index d68e5be..9366937 100644
--- a/gremlin-python/src/main/jython/tests/structure/io/test_graphsonV3d0.py
+++ b/gremlin-python/src/main/jython/tests/structure/io/test_graphsonV3d0.py
@@ -336,6 +336,10 @@ class TestGraphSONReader(object):
         assert isinstance(c, SingleChar)
         assert chr(76) == c
 
+    def test_null(self):
+        c = self.graphson_reader.readObject(json.dumps(None))
+        assert c is None
+
 
 class TestGraphSONWriter(object):
     graphson_writer = GraphSONWriter()
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/driver/remote/GraphBinaryRemoteGraphComputerProvider.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/driver/remote/GraphBinaryRemoteGraphComputerProvider.java
index 2133a0b..57d0a28 100644
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/driver/remote/GraphBinaryRemoteGraphComputerProvider.java
+++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/driver/remote/GraphBinaryRemoteGraphComputerProvider.java
@@ -85,11 +85,7 @@ import org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComp
         reason = "Local traversals may not traverse past the local star-graph on GraphComputer")
 @Graph.OptOut(
         test = "org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.InjectTest",
-        method = "g_VX1X_injectXg_VX4XX_out_name",
-        reason = "The inject() step is not supported by GraphComputer")
-@Graph.OptOut(
-        test = "org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.InjectTest",
-        method = "g_VX1X_out_injectXv2X_name",
+        method = "*",
         reason = "The inject() step is not supported by GraphComputer")
 @Graph.OptOut(
         test = "org.apache.tinkerpop.gremlin.process.traversal.step.branch.LocalTest",
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/driver/remote/GraphSONRemoteGraphComputerProvider.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/driver/remote/GraphSONRemoteGraphComputerProvider.java
index 7b80c2c..100aef6 100644
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/driver/remote/GraphSONRemoteGraphComputerProvider.java
+++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/driver/remote/GraphSONRemoteGraphComputerProvider.java
@@ -85,11 +85,7 @@ import org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComp
         reason = "Local traversals may not traverse past the local star-graph on GraphComputer")
 @Graph.OptOut(
         test = "org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.InjectTest",
-        method = "g_VX1X_injectXg_VX4XX_out_name",
-        reason = "The inject() step is not supported by GraphComputer")
-@Graph.OptOut(
-        test = "org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.InjectTest",
-        method = "g_VX1X_out_injectXv2X_name",
+        method = "*",
         reason = "The inject() step is not supported by GraphComputer")
 @Graph.OptOut(
         test = "org.apache.tinkerpop.gremlin.process.traversal.step.branch.LocalTest",
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/driver/remote/GryoRemoteGraphComputerProvider.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/driver/remote/GryoRemoteGraphComputerProvider.java
index 1269322..ba0ccda 100644
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/driver/remote/GryoRemoteGraphComputerProvider.java
+++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/driver/remote/GryoRemoteGraphComputerProvider.java
@@ -86,11 +86,7 @@ import org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComp
         reason = "Local traversals may not traverse past the local star-graph on GraphComputer")
 @Graph.OptOut(
         test = "org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.InjectTest",
-        method = "g_VX1X_injectXg_VX4XX_out_name",
-        reason = "The inject() step is not supported by GraphComputer")
-@Graph.OptOut(
-        test = "org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.InjectTest",
-        method = "g_VX1X_out_injectXv2X_name",
+        method = "*",
         reason = "The inject() step is not supported by GraphComputer")
 @Graph.OptOut(
         test = "org.apache.tinkerpop.gremlin.process.traversal.step.branch.LocalTest",
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GryoRemoteGraphGroovyTranslatorComputerProvider.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GryoRemoteGraphGroovyTranslatorComputerProvider.java
index 77fa5f6..5cfe2fe 100644
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GryoRemoteGraphGroovyTranslatorComputerProvider.java
+++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GryoRemoteGraphGroovyTranslatorComputerProvider.java
@@ -88,11 +88,7 @@ import org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComp
         reason = "Local traversals may not traverse past the local star-graph on GraphComputer")
 @Graph.OptOut(
         test = "org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.InjectTest",
-        method = "g_VX1X_injectXg_VX4XX_out_name",
-        reason = "The inject() step is not supported by GraphComputer")
-@Graph.OptOut(
-        test = "org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.InjectTest",
-        method = "g_VX1X_out_injectXv2X_name",
+        method = "*",
         reason = "The inject() step is not supported by GraphComputer")
 @Graph.OptOut(
         test = "org.apache.tinkerpop.gremlin.process.traversal.step.branch.LocalTest",
diff --git a/gremlin-test/features/sideEffect/Inject.feature b/gremlin-test/features/sideEffect/Inject.feature
index 740928d..db5a26c 100644
--- a/gremlin-test/features/sideEffect/Inject.feature
+++ b/gremlin-test/features/sideEffect/Inject.feature
@@ -66,3 +66,17 @@ Feature: Step - inject()
       | lop   |
       | vadas |
       | josh  |
+
+  Scenario: g_injectXnull_1_3_nullX
+    Given the empty graph
+    And the traversal of
+      """
+      g.inject(null, 1, 3, null)
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | null |
+      | d[1].i |
+      | d[3].i |
+      | null |
\ No newline at end of file
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/CoreTraversalTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/CoreTraversalTest.java
index 04417a0..92794c5 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/CoreTraversalTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/CoreTraversalTest.java
@@ -78,18 +78,6 @@ public class CoreTraversalTest extends AbstractGremlinProcessTest {
     }
 
     @Test
-    @LoadGraphWith
-    public void shouldNeverPropagateANullValuedTraverser() {
-        try {
-            assertFalse(g.V().map(t -> null).hasNext());
-            assertEquals(0, g.V().map(t -> null).toList().size());
-            g.V().map(t -> null).sideEffect(t -> fail("this should not have happened")).iterate();
-        } catch (VerificationException e) {
-            // its okay if lambdas can't be serialized by the test suite
-        }
-    }
-
-    @Test
     @LoadGraphWith(MODERN)
     public void shouldFilterOnIterate() {
         final Traversal<Vertex,String> traversal = g.V().out().out().<String>values("name").aggregate("x").iterate();
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/InjectTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/InjectTest.java
index d3fc1ed..0b3b584 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/InjectTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/InjectTest.java
@@ -48,6 +48,8 @@ public abstract class InjectTest extends AbstractGremlinProcessTest {
 
     public abstract Traversal<Vertex, String> get_g_VX1X_injectXg_VX4XX_out_name(final Object v1Id, final Object v4Id);
 
+    public abstract Traversal<Integer, Integer> get_g_injectXnull_1_3_nullX();
+
     @Test
     @LoadGraphWith(MODERN)
     public void g_VX1X_out_injectXv2X_name() {
@@ -85,6 +87,13 @@ public abstract class InjectTest extends AbstractGremlinProcessTest {
     }
 
     @Test
+    public void g_injectXnull_1_3_nullX() {
+        final Traversal<Integer, Integer> traversal = get_g_injectXnull_1_3_nullX();
+        printTraversalForm(traversal);
+        checkResults(Arrays.asList(null, 1, 3, null), traversal);
+    }
+
+    @Test
     @LoadGraphWith(MODERN)
     public void g_VX1X_injectXg_VX4XX_out_name() {
         final Traversal<Vertex, String> traversal = get_g_VX1X_injectXg_VX4XX_out_name(convertToVertexId("marko"), convertToVertexId("josh"));
@@ -108,5 +117,10 @@ public abstract class InjectTest extends AbstractGremlinProcessTest {
         public Traversal<Vertex, String> get_g_VX1X_injectXg_VX4XX_out_name(final Object v1Id, final Object v4Id) {
             return g.V(v1Id).inject(g.V(v4Id).next()).out().values("name");
         }
+
+        @Override
+        public Traversal<Integer, Integer> get_g_injectXnull_1_3_nullX() {
+            return g.inject(null, 1, 3, null);
+        }
     }
 }
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/TranslationStrategy.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/TranslationStrategy.java
index d2d63b2..c8451bb 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/TranslationStrategy.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/TranslationStrategy.java
@@ -134,7 +134,9 @@ public final class TranslationStrategy extends AbstractTraversalStrategy<Travers
             final Object[] newArgs = new Object[args.length];
 
             for (int i = 0; i < args.length; i++) {
-                if (args[i].equals("knows"))
+                if (args[i] == null)
+                    newArgs[i] = null;
+                else if (args[i].equals("knows"))
                     newArgs[i] = new Bytecode.Binding<>("a", "knows");
                 else if (args[i].equals("created"))
                     newArgs[i] = new Bytecode.Binding<>("b", "created");
diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/io/graphson/AbstractTinkerGraphGraphSONTranslatorProvider.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/io/graphson/AbstractTinkerGraphGraphSONTranslatorProvider.java
index 4d4ee00..7f4ebe8 100644
--- a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/io/graphson/AbstractTinkerGraphGraphSONTranslatorProvider.java
+++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/io/graphson/AbstractTinkerGraphGraphSONTranslatorProvider.java
@@ -213,10 +213,6 @@ import org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComp
         method = "shouldNeverPropagateANoBulkTraverser",
         reason = "Reason requires investigation")
 @Graph.OptOut(
-        test = "org.apache.tinkerpop.gremlin.process.traversal.CoreTraversalTest",
-        method = "shouldNeverPropagateANullValuedTraverser",
-        reason = "Reason requires investigation")
-@Graph.OptOut(
         test = "org.apache.tinkerpop.gremlin.process.traversal.step.map.ReadTest",
         method = "*",
         reason = "read and write tests don't translate locally well because of calling iterate() inside read()/write() add a none()")
diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/io/gryo/TinkerGraphGryoTranslatorProvider.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/io/gryo/TinkerGraphGryoTranslatorProvider.java
index d6d93a0..0d30df9 100644
--- a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/io/gryo/TinkerGraphGryoTranslatorProvider.java
+++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/io/gryo/TinkerGraphGryoTranslatorProvider.java
@@ -209,10 +209,6 @@ import org.apache.tinkerpop.gremlin.tinkergraph.TinkerGraphProvider;
         method = "shouldNeverPropagateANoBulkTraverser",
         reason = "Reason requires investigation")
 @Graph.OptOut(
-        test = "org.apache.tinkerpop.gremlin.process.traversal.CoreTraversalTest",
-        method = "shouldNeverPropagateANullValuedTraverser",
-        reason = "Reason requires investigation")
-@Graph.OptOut(
         test = "org.apache.tinkerpop.gremlin.process.traversal.step.map.ReadTest",
         method = "*",
         reason = "read and write tests don't translate locally well because of calling iterate() inside read()/write() add a none()")


[tinkerpop] 10/23: TINKERPOP-2235 Adjusted semantics of null a bit for Graph

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2235
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit f80990b0cc99df428a16743e704199c019c716dc
Author: stephen <sp...@gmail.com>
AuthorDate: Thu Nov 14 09:58:33 2019 -0500

    TINKERPOP-2235 Adjusted semantics of null a bit for Graph
    
    If the graph supports null as a value then it will store it as such. If it does not then setting a property to null is the same as doing a property drop().
---
 CHANGELOG.asciidoc                                 |  1 +
 docs/src/upgrade/release-3.5.x.asciidoc            | 48 +++++++--------
 .../gremlin/process/computer/VertexComputeKey.java |  2 +-
 .../apache/tinkerpop/gremlin/structure/Graph.java  |  4 +-
 .../tinkerpop/gremlin/structure/Property.java      |  4 --
 .../gremlin/structure/util/ElementHelper.java      | 60 +++++++++++-------
 .../gremlin/structure/util/star/StarGraph.java     | 16 ++---
 .../gremlin/structure/util/ElementHelperTest.java  | 71 ++++++++++++++--------
 gremlin-test/features/map/AddEdge.feature          | 11 ++--
 gremlin-test/features/map/AddVertex.feature        |  8 +--
 .../process/traversal/CoreTraversalTest.java       | 39 ++++++++++++
 .../process/traversal/step/map/AddEdgeTest.java    | 30 +++------
 .../process/traversal/step/map/AddVertexTest.java  | 24 +++-----
 .../tinkerpop/gremlin/structure/PropertyTest.java  | 33 +++-------
 .../gremlin/neo4j/structure/Neo4jEdge.java         |  8 ++-
 .../gremlin/neo4j/structure/Neo4jGraph.java        |  2 +-
 .../gremlin/neo4j/structure/Neo4jVertex.java       | 10 ++-
 .../process/computer/TinkerGraphComputerView.java  |  2 +-
 .../gremlin/tinkergraph/structure/TinkerEdge.java  |  8 ++-
 .../gremlin/tinkergraph/structure/TinkerGraph.java |  4 +-
 .../tinkergraph/structure/TinkerHelper.java        |  2 +-
 .../tinkergraph/structure/TinkerVertex.java        | 10 ++-
 .../structure/TinkerVertexProperty.java            | 20 +++---
 .../tinkergraph/structure/TinkerGraphTest.java     |  1 -
 24 files changed, 238 insertions(+), 180 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 96d3d4f..58dc701 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -41,6 +41,7 @@ This release also includes changes from <<release-3-4-3, 3.4.3>>.
 * Added a parameterized `TypeTranslator` for use with `GroovyTranslator` that should produce more cache hits.
 * Added support for `TextP` in Neo4j using its string search functions.
 * Changed `TraversalStrategy` application methodology to apply each strategy in turn to each level of the traversal hierarchy starting from root down to children.
+* Removed `Property.Exceptions.propertyValueCanNotBeNull` exception type as `null` now has meaning in Gremlin.
 * Removed the "experimental" support for multi/meta-properties in Neo4j.
 * Removed Gryo serialization configurations from Gremlin Server sample configurations and default configurations.
 * Removed previously deprecated `TraversalStrategies.applyStrategies()`.
diff --git a/docs/src/upgrade/release-3.5.x.asciidoc b/docs/src/upgrade/release-3.5.x.asciidoc
index fcd66de..f0739e5 100644
--- a/docs/src/upgrade/release-3.5.x.asciidoc
+++ b/docs/src/upgrade/release-3.5.x.asciidoc
@@ -105,12 +105,6 @@ gremlin> g.inject(1, null, null, 2, null)
 ==>null
 ==>null
 ==>2
-gremlin> g.addV("person").property("name", 'stephen').property("age", null)
-==>v[13]
-gremlin> g.V().has('person','name','stephen').elementMap()
-==>[id:13,label:person,name:stephen,age:null]
-gremlin> g.V().has('person','age',null)
-==>v[13]
 gremlin> g.V().constant(null)
 ==>null
 ==>null
@@ -120,13 +114,12 @@ gremlin> g.V().constant(null)
 ==>null
 ----
 
-Note that the above examples use TinkerGraph which now supports `null` as a property value (though it can be configured
-to work in the prior fashion) and all graphs may not support this feature (for example, Neo4j does not). Please be
-sure to check the new `supportsNullPropertyValues()` feature (or their documentation) to determine if the `Graph`
-implementation allows `null` in this same fashion.
+TinkerGraph can be configured to support `null` as a property value and all graphs may not support this feature (for
+example, Neo4j does not). Please be sure to check the new `supportsNullPropertyValues()` feature (or the documentation
+of the graph provider) to determine if the `Graph` implementation allows `null` as a property value.
 
-As a final consideration, there was a bit of inconsistency in the handling of `null` in calls to `property()`
-depending on the type of mutation being executed demonstrated as follows in earlier versions:
+With respect to `null` in relation to properties, there was a bit of inconsistency in the handling of `null` in calls
+to `property()` depending on the type of mutation being executed demonstrated as follows in earlier versions:
 
 [source,text]
 ----
@@ -141,29 +134,34 @@ gremlin> g.V(13).properties()
 ==>vp[z->2]
 ----
 
-In this release, this behavior has been altered to become consistent. First, assuming `null` is not supported as a
-property value:
+This behavior has been altered to become consistent. First, assuming `null` is not supported as a property value, the
+setting of a property to `null` should have the behavior of removing the property in the same way in which you might
+do `g.V().properties().drop()`:
 
 [source,text]
 ----
 gremlin> g.V(1).property("x", 1).property("y", null).property("z", 2)
-Property value can not be null
-Type ':help' or ':h' for help.
-Display stack trace? [yN]n
-gremlin> g.addV().property("x", 1).property("y", null).property("z", 2)
-Property value can not be null
-Type ':help' or ':h' for help.
-Display stack trace? [yN]
+==>v[1]
+gremlin> g.V(1).elementMap()
+==>[id:1,label:person,name:marko,x:1,z:2,age:29]
+gremlin> g.V().hasLabel('person').property('age',null).iterate()
+gremlin> g.V().hasLabel('person').elementMap()
+==>[id:1,label:person,name:marko]
+==>[id:2,label:person,name:vadas]
+==>[id:4,label:person,name:josh]
+==>[id:6,label:person,name:peter]
 ----
 
 Then, assuming `null` is supported as a property value:
 
 [source,text]
 ----
-gremlin> g.V(1).property("x", 1).property("y", null).property("z", 2)
-==>v[1]
-gremlin> g.addV().property("x", 1).property("y", null).property("z", 2)
-==>v[15]
+gremlin> g.addV("person").property("name", 'stephen').property("age", null)
+==>v[13]
+gremlin> g.V().has('person','name','stephen').elementMap()
+==>[id:13,label:person,name:stephen,age:null]
+gremlin> g.V().has('person','age',null)
+==>v[13]
 ----
 
 In conclusion, this change in greater support of `null` may affect the behavior of existing traversals written in past
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/VertexComputeKey.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/VertexComputeKey.java
index 072ad59..284430a 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/VertexComputeKey.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/VertexComputeKey.java
@@ -37,7 +37,7 @@ public final class VertexComputeKey implements Serializable {
     private VertexComputeKey(final String key, final boolean isTransient) {
         this.key = key;
         this.isTransient = isTransient;
-        ElementHelper.validateProperty(true, key, key);
+        ElementHelper.validateProperty(key, key);
     }
 
     public String getKey() {
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/Graph.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/Graph.java
index 6609dab..c35ec2e 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/Graph.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/Graph.java
@@ -685,7 +685,9 @@ public interface Graph extends AutoCloseable, Host {
             public static final String FEATURE_NULL_PROPERTY_VALUES = "NullPropertyValues";
 
             /**
-             * Determines if an {@link Element} allows properties with {@code null} property values.
+             * Determines if an {@link Element} allows properties with {@code null} property values. In the event that
+             * this value is {@code false}, the underlying graph must treat {@code null} as an indication to remove
+             * the property.
              */
             @FeatureDescriptor(name = FEATURE_NULL_PROPERTY_VALUES)
             public default boolean supportsNullPropertyValues() {
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/Property.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/Property.java
index 5b67f07..eac8355 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/Property.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/Property.java
@@ -139,10 +139,6 @@ public interface Property<V> {
             return new IllegalArgumentException("Property key can not be null");
         }
 
-        public static IllegalArgumentException propertyValueCanNotBeNull() {
-            return new IllegalArgumentException("Property value can not be null");
-        }
-
         public static IllegalArgumentException propertyKeyCanNotBeAHiddenKey(final String key) {
             return new IllegalArgumentException("Property key can not be a hidden key: " + key);
         }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelper.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelper.java
index b117745..a121abd 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelper.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelper.java
@@ -87,15 +87,12 @@ public final class ElementHelper {
      * Determines whether the property key/value for the specified thing can be legally set. This is typically used as
      * a pre-condition check prior to setting a property.
      *
-     * @param allowNullValue true if {@code null} is allowed as a value to the property
      * @param key   the key of the property
      * @param value the value of the property\
      * @throws IllegalArgumentException whether the key/value pair is legal and if not, a clear reason exception
      *                                  message is provided
      */
-    public static void validateProperty(final boolean allowNullValue, final String key, final Object value) throws IllegalArgumentException {
-        if (!allowNullValue && null == value)
-            throw Property.Exceptions.propertyValueCanNotBeNull();
+    public static void validateProperty(final String key, final Object value) throws IllegalArgumentException {
         if (null == key)
             throw Property.Exceptions.propertyKeyCanNotBeNull();
         if (key.isEmpty())
@@ -106,22 +103,17 @@ public final class ElementHelper {
 
     /**
      * Determines whether a list of key/values are legal, ensuring that there are an even number of values submitted
-     * and that the key values in the list of arguments are {@link String} or {@link Element} objects.
+     * and that the keys in the list of arguments are {@link String} or {@link T} objects.
      *
-     * @param allowNullValues true if {@code null} is allowed as a value to the property
      * @param propertyKeyValues a list of key/value pairs
      * @throws IllegalArgumentException if something in the pairs is illegal
      */
-    public static void legalPropertyKeyValueArray(final boolean allowNullValues, final Object... propertyKeyValues) throws IllegalArgumentException {
+    public static void legalPropertyKeyValueArray(final Object... propertyKeyValues) throws IllegalArgumentException {
         if (propertyKeyValues.length % 2 != 0)
             throw Element.Exceptions.providedKeyValuesMustBeAMultipleOfTwo();
         for (int i = 0; i < propertyKeyValues.length; i = i + 2) {
             if (!(propertyKeyValues[i] instanceof String) && !(propertyKeyValues[i] instanceof T))
                 throw Element.Exceptions.providedKeyValuesMustHaveALegalKeyOnEvenIndices();
-
-            if (!allowNullValues && null == propertyKeyValues[i + 1]) {
-                throw Property.Exceptions.propertyValueCanNotBeNull();
-            }
         }
     }
 
@@ -282,16 +274,24 @@ public final class ElementHelper {
         if (null == element)
             throw Graph.Exceptions.argumentCanNotBeNull("element");
 
+        final boolean allowNullPropertyValues = element instanceof Vertex ?
+                element.graph().features().vertex().supportsNullPropertyValues() : element instanceof Edge ?
+                element.graph().features().edge().supportsNullPropertyValues() :
+                element.graph().features().vertex().properties().supportsNullPropertyValues();
+
         for (int i = 0; i < propertyKeyValues.length; i = i + 2) {
             if (!propertyKeyValues[i].equals(T.id) && !propertyKeyValues[i].equals(T.label))
-                element.property((String) propertyKeyValues[i], propertyKeyValues[i + 1]);
+                if (!allowNullPropertyValues && null == propertyKeyValues[i + 1])
+                    element.properties(((String) propertyKeyValues[i])).forEachRemaining(Property::remove);
+                else
+                    element.property((String) propertyKeyValues[i], propertyKeyValues[i + 1]);
         }
     }
 
     /**
-     * Assign key/value pairs as properties to an {@link Vertex}.  If the value of {@link T#id} or
-     * {@link T#label} is in the set of pairs, then they are ignored.
-     * The {@link VertexProperty.Cardinality} of the key is determined from the {@link Graph.Features.VertexFeatures}.
+     * Assign key/value pairs as properties to an {@link Vertex}.  If the value of {@link T#id} or {@link T#label} is
+     * in the set of pairs, then they are ignored. The {@link VertexProperty.Cardinality} of the key is determined from
+     * the {@link Graph.Features.VertexFeatures}.
      *
      * @param vertex            the graph vertex to assign the {@code propertyKeyValues}
      * @param propertyKeyValues the key/value pairs to assign to the {@code element}
@@ -302,15 +302,20 @@ public final class ElementHelper {
         if (null == vertex)
             throw Graph.Exceptions.argumentCanNotBeNull("vertex");
 
+        final boolean allowNullPropertyValues = vertex.graph().features().vertex().supportsNullPropertyValues();
+
         for (int i = 0; i < propertyKeyValues.length; i = i + 2) {
             if (!propertyKeyValues[i].equals(T.id) && !propertyKeyValues[i].equals(T.label))
-                vertex.property(vertex.graph().features().vertex().getCardinality((String) propertyKeyValues[i]), (String) propertyKeyValues[i], propertyKeyValues[i + 1]);
+                if (!allowNullPropertyValues && null == propertyKeyValues[i + 1])
+                    vertex.properties(((String) propertyKeyValues[i])).forEachRemaining(VertexProperty::remove);
+                else
+                    vertex.property(vertex.graph().features().vertex().getCardinality((String) propertyKeyValues[i]), (String) propertyKeyValues[i], propertyKeyValues[i + 1]);
         }
     }
 
     /**
-     * Assign key/value pairs as properties to a {@link Vertex}.
-     * If the value of {@link T#id} or {@link T#label} is in the set of pairs, then they are ignored.
+     * Assign key/value pairs as properties to a {@link Vertex}. If the value of {@link T#id} or {@link T#label} is
+     * in the set of pairs, then they are ignored.
      *
      * @param vertex            the vertex to attach the properties to
      * @param cardinality       the cardinality of the key value pair settings
@@ -318,18 +323,24 @@ public final class ElementHelper {
      * @throws ClassCastException       if the value of the key is not a {@link String}
      * @throws IllegalArgumentException if the value of {@code element} is null
      */
-    public static void attachProperties(final Vertex vertex, final VertexProperty.Cardinality cardinality, final Object... propertyKeyValues) {
+    public static void attachProperties(final Vertex vertex, final VertexProperty.Cardinality cardinality,
+                                        final Object... propertyKeyValues) {
         if (null == vertex)
             throw Graph.Exceptions.argumentCanNotBeNull("vertex");
 
+        final boolean allowNullPropertyValues = vertex.graph().features().vertex().supportsNullPropertyValues();
+
         for (int i = 0; i < propertyKeyValues.length; i = i + 2) {
             if (!propertyKeyValues[i].equals(T.id) && !propertyKeyValues[i].equals(T.label))
-                vertex.property(cardinality, (String) propertyKeyValues[i], propertyKeyValues[i + 1]);
+                if (!allowNullPropertyValues && null == propertyKeyValues[i + 1])
+                    vertex.properties(((String) propertyKeyValues[i])).forEachRemaining(VertexProperty::remove);
+                else
+                    vertex.property(cardinality, (String) propertyKeyValues[i], propertyKeyValues[i + 1]);
         }
     }
 
     /**
-     * This is a helper method for dealing with vertex property cardinality and typically used in {@link Vertex#property(org.apache.tinkerpop.gremlin.structure.VertexProperty.Cardinality, String, Object, Object...)}.
+     * This is a helper method for dealing with vertex property cardinality and typically used in {@link Vertex#property(VertexProperty.Cardinality, String, Object, Object...)}.
      * If the cardinality is list, simply return {@link Optional#empty}.
      * If the cardinality is single, delete all existing properties of the provided key and return {@link Optional#empty}.
      * If the cardinality is set, find one that has the same key/value and attached the properties to it and return it. Else, if no equal value is found, return {@link Optional#empty}.
@@ -342,7 +353,9 @@ public final class ElementHelper {
      * @param <V>         the type of the vertex property value
      * @return a vertex property if it has been found in set with equal value
      */
-    public static <V> Optional<VertexProperty<V>> stageVertexProperty(final Vertex vertex, final VertexProperty.Cardinality cardinality, final String key, final V value, final Object... keyValues) {
+    public static <V> Optional<VertexProperty<V>> stageVertexProperty(final Vertex vertex,
+                                                                      final VertexProperty.Cardinality cardinality,
+                                                                      final String key, final V value, final Object... keyValues) {
         if (cardinality.equals(VertexProperty.Cardinality.single))
             vertex.properties(key).forEachRemaining(VertexProperty::remove);
         else if (cardinality.equals(VertexProperty.Cardinality.set)) {
@@ -389,7 +402,8 @@ public final class ElementHelper {
 
     /**
      * A standard method for determining if two {@link Element} objects are equal. This method should be used by any
-     * {@link Object#equals(Object)} implementation to ensure consistent behavior. This method is used for Vertex, Edge, and VertexProperty.
+     * {@link Object#equals(Object)} implementation to ensure consistent behavior. This method is used for Vertex,
+     * Edge, and VertexProperty.
      *
      * @param a The first {@link Element}
      * @param b The second {@link Element} (as an {@link Object})
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraph.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraph.java
index 935ebef..3bcdb7d 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraph.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraph.java
@@ -95,7 +95,7 @@ public final class StarGraph implements Graph, Serializable {
     @Override
     public Vertex addVertex(final Object... keyValues) {
         if (null == this.starVertex) {
-            ElementHelper.legalPropertyKeyValueArray(true, keyValues);
+            ElementHelper.legalPropertyKeyValueArray(keyValues);
             this.starVertex = new StarVertex(ElementHelper.getIdValue(keyValues).orElse(this.nextId()), ElementHelper.getLabelValue(keyValues).orElse(Vertex.DEFAULT_LABEL));
             ElementHelper.attachProperties(this.starVertex, VertexProperty.Cardinality.list, keyValues); // TODO: is this smart? I say no... cause vertex property ids are not preserved.
             return this.starVertex;
@@ -413,14 +413,14 @@ public final class StarGraph implements Graph, Serializable {
 
         @Override
         public <V> VertexProperty<V> property(final String key, final V value, final Object... keyValues) {
-            ElementHelper.validateProperty(true, key, value);
-            ElementHelper.legalPropertyKeyValueArray(true, keyValues);
+            ElementHelper.validateProperty(key, value);
+            ElementHelper.legalPropertyKeyValueArray(keyValues);
             return this.property(VertexProperty.Cardinality.single, key, value, keyValues);
         }
 
         Edge addOutEdge(final String label, final Vertex inVertex, final Object... keyValues) {
             ElementHelper.validateLabel(label);
-            ElementHelper.legalPropertyKeyValueArray(true, keyValues);
+            ElementHelper.legalPropertyKeyValueArray(keyValues);
             if (null == this.outEdges)
                 this.outEdges = new HashMap<>();
             List<Edge> outE = this.outEdges.get(label);
@@ -436,7 +436,7 @@ public final class StarGraph implements Graph, Serializable {
 
         Edge addInEdge(final String label, final Vertex outVertex, final Object... keyValues) {
             ElementHelper.validateLabel(label);
-            ElementHelper.legalPropertyKeyValueArray(true, keyValues);
+            ElementHelper.legalPropertyKeyValueArray(keyValues);
             if (null == this.inEdges)
                 this.inEdges = new HashMap<>();
             List<Edge> inE = this.inEdges.get(label);
@@ -452,7 +452,7 @@ public final class StarGraph implements Graph, Serializable {
 
         @Override
         public <V> VertexProperty<V> property(final VertexProperty.Cardinality cardinality, final String key, V value, final Object... keyValues) {
-            ElementHelper.legalPropertyKeyValueArray(true, keyValues);
+            ElementHelper.legalPropertyKeyValueArray(keyValues);
             if (null == this.vertexProperties)
                 this.vertexProperties = new HashMap<>();
             final List<VertexProperty> list = cardinality.equals(VertexProperty.Cardinality.single) ? new ArrayList<>(1) : this.vertexProperties.getOrDefault(key, new ArrayList<>());
@@ -645,7 +645,7 @@ public final class StarGraph implements Graph, Serializable {
 
         @Override
         public <U> Property<U> property(final String key, final U value) {
-            ElementHelper.validateProperty(true, key, value);
+            ElementHelper.validateProperty(key, value);
             if (null == metaProperties)
                 metaProperties = new HashMap<>();
             Map<String, Object> properties = metaProperties.get(this.id);
@@ -760,7 +760,7 @@ public final class StarGraph implements Graph, Serializable {
 
         @Override
         public <V> Property<V> property(final String key, final V value) {
-            ElementHelper.validateProperty(true, key, value);
+            ElementHelper.validateProperty(key, value);
             if (null == edgeProperties)
                 edgeProperties = new HashMap<>();
             Map<String, Object> properties = edgeProperties.get(this.id);
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelperTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelperTest.java
index b72e6cd..d4f1a77 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelperTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelperTest.java
@@ -54,24 +54,14 @@ public class ElementHelperTest {
     }
 
     @Test
-    public void shouldValidatePropertyAndNotAllowNullValue() {
-        try {
-            ElementHelper.validateProperty(false,"test", null);
-            fail("Should fail as property value cannot be null");
-        } catch (IllegalArgumentException iae) {
-            assertEquals(Property.Exceptions.propertyValueCanNotBeNull().getMessage(), iae.getMessage());
-        }
-    }
-
-    @Test
     public void shouldValidatePropertyAndAllowNullValue() {
-        ElementHelper.validateProperty(true,"test", null);
+        ElementHelper.validateProperty("test", null);
     }
 
     @Test
     public void shouldValidatePropertyAndNotAllowNullKey() {
         try {
-            ElementHelper.validateProperty(false,null, "test");
+            ElementHelper.validateProperty(null, "test");
             fail("Should fail as property key cannot be null");
         } catch (IllegalArgumentException iae) {
             assertEquals(Property.Exceptions.propertyKeyCanNotBeNull().getMessage(), iae.getMessage());
@@ -81,7 +71,7 @@ public class ElementHelperTest {
     @Test
     public void shouldValidatePropertyAndNotAllowEmptyKey() {
         try {
-            ElementHelper.validateProperty(false,"", "test");
+            ElementHelper.validateProperty("", "test");
             fail("Should fail as property key cannot be empty");
         } catch (IllegalArgumentException iae) {
             assertEquals(Property.Exceptions.propertyKeyCanNotBeEmpty().getMessage(), iae.getMessage());
@@ -92,7 +82,7 @@ public class ElementHelperTest {
     public void shouldValidatePropertyAndNotAllowHiddenKey() {
         final String key = Graph.Hidden.hide("key");
         try {
-            ElementHelper.validateProperty(false, key, "test");
+            ElementHelper.validateProperty(key, "test");
             fail("Should fail as property key cannot be hidden");
         } catch (IllegalArgumentException iae) {
             assertEquals(Property.Exceptions.propertyKeyCanNotBeAHiddenKey(key).getMessage(), iae.getMessage());
@@ -101,13 +91,13 @@ public class ElementHelperTest {
 
     @Test
     public void shouldHaveValidProperty() {
-        ElementHelper.validateProperty(false,"aKey", "value");
+        ElementHelper.validateProperty("aKey", "value");
     }
 
     @Test
     public void shouldAllowEvenNumberOfKeyValues() {
         try {
-            ElementHelper.legalPropertyKeyValueArray(false,"aKey", "test", "no-value-for-this-one");
+            ElementHelper.legalPropertyKeyValueArray("aKey", "test", "no-value-for-this-one");
             fail("Should fail as there is an odd number of key-values");
         } catch (IllegalArgumentException iae) {
             assertEquals(Element.Exceptions.providedKeyValuesMustBeAMultipleOfTwo().getMessage(), iae.getMessage());
@@ -117,7 +107,7 @@ public class ElementHelperTest {
     @Test
     public void shouldNotAllowEvenNumberOfKeyValuesAndInvalidKeys() {
         try {
-            ElementHelper.legalPropertyKeyValueArray(false,"aKey", "test", "value-for-this-one", 1, 1, "none");
+            ElementHelper.legalPropertyKeyValueArray("aKey", "test", "value-for-this-one", 1, 1, "none");
             fail("Should fail as there is an even number of key-values, but a bad key");
         } catch (IllegalArgumentException iae) {
             assertEquals(Element.Exceptions.providedKeyValuesMustHaveALegalKeyOnEvenIndices().getMessage(), iae.getMessage());
@@ -126,16 +116,7 @@ public class ElementHelperTest {
 
     @Test
     public void shouldAllowEvenNumberOfKeyValuesAndValidKeys() {
-        ElementHelper.legalPropertyKeyValueArray(false,"aKey", "test", "value-for-this-one", 1, "1", "none");
-    }
-
-    @Test
-    public void shouldNotAllowEvenNumberOfKeyValuesAndInvalidValues() {
-        try {
-            ElementHelper.legalPropertyKeyValueArray(false,"aKey", "test", "value-for-this-one", 1, "1", null);
-        } catch (IllegalArgumentException iae) {
-            assertEquals(Property.Exceptions.propertyValueCanNotBeNull().getMessage(), iae.getMessage());
-        }
+        ElementHelper.legalPropertyKeyValueArray("aKey", "test", "value-for-this-one", 1, "1", "none");
     }
 
     @Test
@@ -196,6 +177,15 @@ public class ElementHelperTest {
     @Test
     public void shouldAttachPropertiesButNotLabelsOrId() {
         final Element mockElement = mock(Element.class);
+        final Graph mockGraph = mock(Graph.class);
+        final Graph.Features mockGraphFeatures = mock(Graph.Features.class);
+        final Graph.Features.VertexFeatures mockVertexFeatures = mock(Graph.Features.VertexFeatures.class);
+        final Graph.Features.VertexPropertyFeatures mockVertexPropertyFeatures = mock(Graph.Features.VertexPropertyFeatures.class);
+        when(mockElement.graph()).thenReturn(mockGraph);
+        when(mockGraph.features()).thenReturn(mockGraphFeatures);
+        when(mockGraphFeatures.vertex()).thenReturn(mockVertexFeatures);
+        when(mockVertexFeatures.properties()).thenReturn(mockVertexPropertyFeatures);
+        when(mockVertexPropertyFeatures.supportsNullPropertyValues()).thenReturn(true);
         ElementHelper.attachProperties(mockElement, "test", 123, T.id, 321, T.label, "friends");
         verify(mockElement, times(1)).property("test", 123);
         verify(mockElement, times(0)).property(T.id.getAccessor(), 321);
@@ -205,6 +195,15 @@ public class ElementHelperTest {
     @Test(expected = ClassCastException.class)
     public void shouldFailTryingToAttachPropertiesNonStringKey() {
         final Element mockElement = mock(Element.class);
+        final Graph mockGraph = mock(Graph.class);
+        final Graph.Features mockGraphFeatures = mock(Graph.Features.class);
+        final Graph.Features.VertexFeatures mockVertexFeatures = mock(Graph.Features.VertexFeatures.class);
+        final Graph.Features.VertexPropertyFeatures mockVertexPropertyFeatures = mock(Graph.Features.VertexPropertyFeatures.class);
+        when(mockElement.graph()).thenReturn(mockGraph);
+        when(mockGraph.features()).thenReturn(mockGraphFeatures);
+        when(mockGraphFeatures.vertex()).thenReturn(mockVertexFeatures);
+        when(mockVertexFeatures.properties()).thenReturn(mockVertexPropertyFeatures);
+        when(mockVertexPropertyFeatures.supportsNullPropertyValues()).thenReturn(true);
         ElementHelper.attachProperties(mockElement, "test", 123, 321, "test");
     }
 
@@ -221,6 +220,15 @@ public class ElementHelperTest {
     @Test
     public void shouldAttachPropertiesWithCardinalityButNotLabelsOrId() {
         final Vertex mockElement = mock(Vertex.class);
+        final Graph mockGraph = mock(Graph.class);
+        final Graph.Features mockGraphFeatures = mock(Graph.Features.class);
+        final Graph.Features.VertexFeatures mockVertexFeatures = mock(Graph.Features.VertexFeatures.class);
+        final Graph.Features.VertexPropertyFeatures mockVertexPropertyFeatures = mock(Graph.Features.VertexPropertyFeatures.class);
+        when(mockElement.graph()).thenReturn(mockGraph);
+        when(mockGraph.features()).thenReturn(mockGraphFeatures);
+        when(mockGraphFeatures.vertex()).thenReturn(mockVertexFeatures);
+        when(mockVertexFeatures.properties()).thenReturn(mockVertexPropertyFeatures);
+        when(mockVertexPropertyFeatures.supportsNullPropertyValues()).thenReturn(true);
         ElementHelper.attachProperties(mockElement, VertexProperty.Cardinality.single, "test", 123, T.id, 321, T.label, "friends");
         verify(mockElement, times(1)).property(VertexProperty.Cardinality.single, "test", 123);
         verify(mockElement, times(0)).property(VertexProperty.Cardinality.single, T.id.getAccessor(), 321);
@@ -230,6 +238,15 @@ public class ElementHelperTest {
     @Test(expected = ClassCastException.class)
     public void shouldFailTryingToAttachPropertiesWithCardinalityNonStringKey() {
         final Element mockElement = mock(Vertex.class);
+        final Graph mockGraph = mock(Graph.class);
+        final Graph.Features mockGraphFeatures = mock(Graph.Features.class);
+        final Graph.Features.VertexFeatures mockVertexFeatures = mock(Graph.Features.VertexFeatures.class);
+        final Graph.Features.VertexPropertyFeatures mockVertexPropertyFeatures = mock(Graph.Features.VertexPropertyFeatures.class);
+        when(mockElement.graph()).thenReturn(mockGraph);
+        when(mockGraph.features()).thenReturn(mockGraphFeatures);
+        when(mockGraphFeatures.vertex()).thenReturn(mockVertexFeatures);
+        when(mockVertexFeatures.properties()).thenReturn(mockVertexPropertyFeatures);
+        when(mockVertexPropertyFeatures.supportsNullPropertyValues()).thenReturn(true);
         ElementHelper.attachProperties(mockElement, VertexProperty.Cardinality.single, "test", 123, 321, "test");
     }
 
diff --git a/gremlin-test/features/map/AddEdge.feature b/gremlin-test/features/map/AddEdge.feature
index 875e474..8e03ac3 100644
--- a/gremlin-test/features/map/AddEdge.feature
+++ b/gremlin-test/features/map/AddEdge.feature
@@ -72,7 +72,7 @@ Feature: Step - addE()
     And the graph should return 4 for count of "g.V(v1Id).bothE()"
     And the graph should return 1 for count of "g.V(v1Id).inE().has(\"weight\", 2.0)"
 
-  Scenario: g_VX1X_asXaX_outXcreatedX_addEXcreatedByX_toXaX_propertyXweight_nullX
+  Scenario: g_V_outE_propertyXweight_nullX
     Given the empty graph
     And the graph initializer of
       """
@@ -89,16 +89,13 @@ Feature: Step - addE()
         addE("created").from("josh").to("lop").property(T.id, 11).property("weight", 0.4).
         addE("created").from("peter").to("lop").property(T.id, 12).property("weight", 0.2)
       """
-    And using the parameter v1Id defined as "v[marko].id"
     And the traversal of
       """
-      g.V(v1Id).as("a").out("created").addE("createdBy").to("a").property("weight", null)
+      g.V().outE().property("weight", null)
       """
     When iterated to list
-    Then the result should have a count of 1
-    And the graph should return 7 for count of "g.E()"
-    And the graph should return 4 for count of "g.V(v1Id).bothE()"
-    And the graph should return 1 for count of "g.V(v1Id).inE().has(\"weight\", null)"
+    Then the result should have a count of 6
+    And the graph should return 0 for count of "g.E().properties(\"weight\")"
 
   Scenario: g_V_aggregateXxX_asXaX_selectXxX_unfold_addEXexistsWithX_toXaX_propertyXtime_nowX
     Given the empty graph
diff --git a/gremlin-test/features/map/AddVertex.feature b/gremlin-test/features/map/AddVertex.feature
index efe3fc0..0fc338f 100644
--- a/gremlin-test/features/map/AddVertex.feature
+++ b/gremlin-test/features/map/AddVertex.feature
@@ -93,7 +93,7 @@ Feature: Step - addV()
     Then the result should have a count of 1
     And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"stephen\")"
 
-  Scenario: g_addVXpersonX_propertyXname_nullX
+  Scenario: g_V_hasLabelXpersonX_propertyXname_nullX
     Given the empty graph
     And the graph initializer of
       """
@@ -112,11 +112,11 @@ Feature: Step - addV()
       """
     And the traversal of
       """
-      g.addV("person").property("name", null)
+      g.V().hasLabel("person").property("name", null)
       """
     When iterated to list
-    Then the result should have a count of 1
-    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",null)"
+    Then the result should have a count of 4
+    And the graph should return 2 for count of "g.V().properties(\"name\")"
 
   Scenario: g_addVXpersonX_propertyXsingle_name_stephenX_propertyXsingle_name_stephenmX
     Given the empty graph
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/CoreTraversalTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/CoreTraversalTest.java
index 92794c5..afdc1ef 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/CoreTraversalTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/CoreTraversalTest.java
@@ -50,6 +50,7 @@ import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.inject
 import static org.apache.tinkerpop.gremlin.structure.Graph.Features.GraphFeatures.FEATURE_TRANSACTIONS;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -66,6 +67,44 @@ import static org.junit.Assert.fail;
 public class CoreTraversalTest extends AbstractGremlinProcessTest {
 
     @Test
+    @LoadGraphWith(MODERN)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_ADD_VERTICES)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_ADD_PROPERTY)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_NULL_PROPERTY_VALUES)
+    public void g_addVXpersonX_propertyXname_nullX() {
+        final Traversal<Vertex, Vertex> traversal = g.addV("person").property("name", null);
+        printTraversalForm(traversal);
+        final Vertex nulled = traversal.next();
+        assertFalse(traversal.hasNext());
+        assertEquals("person", nulled.label());
+        assertNull(nulled.value("name"));
+        assertEquals(1, IteratorUtils.count(nulled.properties()));
+        assertEquals(7, IteratorUtils.count(g.V()));
+    }
+
+    @Test
+    @LoadGraphWith(MODERN)
+    @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = Graph.Features.EdgeFeatures.FEATURE_ADD_EDGES)
+    @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = Graph.Features.EdgeFeatures.FEATURE_NULL_PROPERTY_VALUES)
+    public void g_VX1X_asXaX_outXcreatedX_addEXcreatedByX_toXaX_propertyXweight_nullX() {
+        final Traversal<Vertex, Edge> traversal = g.V(convertToVertexId("marko")).as("a").out("created").addE("createdBy").to("a").property("weight", null);
+        printTraversalForm(traversal);
+        int count = 0;
+        while (traversal.hasNext()) {
+            final Edge edge = traversal.next();
+            assertEquals("createdBy", edge.label());
+            assertNull(g.E(edge).<Double>values("weight").next());
+            assertEquals(1, g.E(edge).properties().count().next().intValue());
+            count++;
+
+
+        }
+        assertEquals(1, count);
+        assertEquals(7, IteratorUtils.count(g.E()));
+        assertEquals(6, IteratorUtils.count(g.V()));
+    }
+
+    @Test
     @LoadGraphWith
     public void shouldNeverPropagateANoBulkTraverser() {
         try {
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddEdgeTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddEdgeTest.java
index f534a4b..1e7f162 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddEdgeTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddEdgeTest.java
@@ -44,9 +44,10 @@ import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.outE;
 import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.select;
 import static org.apache.tinkerpop.gremlin.structure.Column.keys;
 import static org.apache.tinkerpop.gremlin.structure.Column.values;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
 
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
@@ -59,7 +60,7 @@ public abstract class AddEdgeTest extends AbstractGremlinProcessTest {
 
     public abstract Traversal<Vertex, Edge> get_g_VX1X_asXaX_outXcreatedX_addEXcreatedByX_toXaX_propertyXweight_2X(final Object v1Id);
 
-    public abstract Traversal<Vertex, Edge> get_g_VX1X_asXaX_outXcreatedX_addEXcreatedByX_toXaX_propertyXweight_nullX(final Object v1Id);
+    public abstract Traversal<Vertex, Edge> get_g_V_outE_propertyXweight_nullX();
 
     public abstract Traversal<Vertex, Edge> get_g_V_aggregateXxX_asXaX_selectXxX_unfold_addEXexistsWithX_toXaX_propertyXtime_nowX();
 
@@ -123,24 +124,11 @@ public abstract class AddEdgeTest extends AbstractGremlinProcessTest {
 
     @Test
     @LoadGraphWith(MODERN)
-    @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = Graph.Features.EdgeFeatures.FEATURE_ADD_EDGES)
-    @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = Graph.Features.EdgeFeatures.FEATURE_NULL_PROPERTY_VALUES)
-    public void g_VX1X_asXaX_outXcreatedX_addEXcreatedByX_toXaX_propertyXweight_nullX() {
-        final Traversal<Vertex, Edge> traversal = get_g_VX1X_asXaX_outXcreatedX_addEXcreatedByX_toXaX_propertyXweight_nullX(convertToVertexId("marko"));
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_NULL_PROPERTY_VALUES, supported = false)
+    public void g_V_outE_propertyXweight_nullX() {
+        final Traversal<Vertex, Edge> traversal = get_g_V_outE_propertyXweight_nullX();
         printTraversalForm(traversal);
-        int count = 0;
-        while (traversal.hasNext()) {
-            final Edge edge = traversal.next();
-            assertEquals("createdBy", edge.label());
-            assertNull(g.E(edge).<Double>values("weight").next());
-            assertEquals(1, g.E(edge).properties().count().next().intValue());
-            count++;
-
-
-        }
-        assertEquals(1, count);
-        assertEquals(7, IteratorUtils.count(g.E()));
-        assertEquals(6, IteratorUtils.count(g.V()));
+        traversal.forEachRemaining(e -> assertThat(e.properties("weight").hasNext(), is(false)));
     }
 
     @Test
@@ -331,8 +319,8 @@ public abstract class AddEdgeTest extends AbstractGremlinProcessTest {
         }
 
         @Override
-        public Traversal<Vertex, Edge> get_g_VX1X_asXaX_outXcreatedX_addEXcreatedByX_toXaX_propertyXweight_nullX(final Object v1Id) {
-            return g.V(v1Id).as("a").out("created").addE("createdBy").to("a").property("weight", null);
+        public Traversal<Vertex, Edge> get_g_V_outE_propertyXweight_nullX() {
+            return g.V().outE().property("weight", null);
         }
 
         @Override
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexTest.java
index ebad007..8937a44 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexTest.java
@@ -40,9 +40,10 @@ import java.util.Map;
 import static org.apache.tinkerpop.gremlin.LoadGraphWith.GraphData.MODERN;
 import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.V;
 import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.select;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 /**
@@ -57,7 +58,7 @@ public abstract class AddVertexTest extends AbstractGremlinTest {
 
     public abstract Traversal<Vertex, Vertex> get_g_addVXpersonX_propertyXname_stephenX();
 
-    public abstract Traversal<Vertex, Vertex> get_g_addVXpersonX_propertyXname_nullX();
+    public abstract Traversal<Vertex, Vertex> get_g_V_hasLabelXpersonX_propertyXname_nullX();
 
     public abstract Traversal<Vertex, Vertex> get_g_addVXpersonX_propertyXsingle_name_stephenX_propertyXsingle_name_stephenmX();
 
@@ -131,18 +132,11 @@ public abstract class AddVertexTest extends AbstractGremlinTest {
 
     @Test
     @LoadGraphWith(MODERN)
-    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_ADD_VERTICES)
-    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_ADD_PROPERTY)
-    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_NULL_PROPERTY_VALUES)
-    public void g_addVXpersonX_propertyXname_nullX() {
-        final Traversal<Vertex, Vertex> traversal = get_g_addVXpersonX_propertyXname_nullX();
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_NULL_PROPERTY_VALUES, supported = false)
+    public void g_V_hasLabelXpersonX_propertyXname_nullX() {
+        final Traversal<Vertex, Vertex> traversal = get_g_V_hasLabelXpersonX_propertyXname_nullX();
         printTraversalForm(traversal);
-        final Vertex nulled = traversal.next();
-        assertFalse(traversal.hasNext());
-        assertEquals("person", nulled.label());
-        assertNull(nulled.value("name"));
-        assertEquals(1, IteratorUtils.count(nulled.properties()));
-        assertEquals(7, IteratorUtils.count(g.V()));
+        traversal.forEachRemaining(v -> assertThat(v.properties("name").hasNext(), is(false)));
     }
 
     @Test
@@ -334,8 +328,8 @@ public abstract class AddVertexTest extends AbstractGremlinTest {
         }
 
         @Override
-        public Traversal<Vertex, Vertex> get_g_addVXpersonX_propertyXname_nullX() {
-            return g.addV("person").property("name", null);
+        public Traversal<Vertex, Vertex> get_g_V_hasLabelXpersonX_propertyXname_nullX() {
+            return g.V().hasLabel("person").property("name", null);
         }
 
         @Override
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/PropertyTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/PropertyTest.java
index b76f137..099b3a3 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/PropertyTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/PropertyTest.java
@@ -40,6 +40,7 @@ import java.util.Map;
 
 import static org.apache.tinkerpop.gremlin.structure.Graph.Features.PropertyFeatures.FEATURE_PROPERTIES;
 import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
@@ -57,9 +58,6 @@ public class PropertyTest {
     /**
      * Basic tests for the {@link Property} class.
      */
-    @ExceptionCoverage(exceptionClass = Property.Exceptions.class, methods = {
-            "propertyValueCanNotBeNull"
-    })
     public static class BasicPropertyTest extends AbstractGremlinTest {
         @Test
         @FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
@@ -150,25 +148,17 @@ public class PropertyTest {
         @FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
         @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_NULL_PROPERTY_VALUES, supported = false)
         public void shouldNotAllowNullAddVertex() throws Exception {
-            try {
-                this.graph.addVertex("name", null);
-                fail("Call to addVertex() should have thrown an exception as null is not a supported property value");
-            } catch (Exception ex) {
-                validateException(Property.Exceptions.propertyValueCanNotBeNull(), ex);
-            }
+            final Vertex v = this.graph.addVertex("name", null);
+            assertThat(v.properties("name").hasNext(), is(false));
         }
 
         @Test
         @FeatureRequirementSet(FeatureRequirementSet.Package.SIMPLE)
         @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_NULL_PROPERTY_VALUES, supported = false)
         public void shouldNotAllowNullAddEdge() throws Exception {
-            try {
-                final Vertex v = this.graph.addVertex();
-                v.addEdge("self", v, "name", null);
-                fail("Call to addEdge() should have thrown an exception as null is not a supported property value");
-            } catch (Exception ex) {
-                validateException(Property.Exceptions.propertyValueCanNotBeNull(), ex);
-            }
+            final Vertex v = this.graph.addVertex();
+            final Edge e = v.addEdge("self", v, "name", null);
+            assertThat(e.properties("name").hasNext(), is(false));
         }
 
         @Test
@@ -204,13 +194,10 @@ public class PropertyTest {
         @FeatureRequirement(featureClass = Graph.Features.VertexPropertyFeatures.class, feature = Graph.Features.VertexPropertyFeatures.FEATURE_NULL_PROPERTY_VALUES, supported = false)
         @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_META_PROPERTIES)
         public void shouldNotAllowNullAddVertexProperty() throws Exception {
-            try {
-                final Vertex v = this.graph.addVertex("person");
-                final VertexProperty vp = v.property("location", "santa fe", "startTime", 1995, "endTime", null);
-                fail("Call to property() should have thrown an exception as null is not a supported property value");
-            } catch (Exception ex) {
-                validateException(Property.Exceptions.propertyValueCanNotBeNull(), ex);
-            }
+            final Vertex v = this.graph.addVertex("person");
+            final VertexProperty vp = v.property("location", "santa fe", "startTime", 1995, "endTime", null);
+            assertEquals(1995, (int) vp.value("startTime"));
+            assertThat(vp.properties("endTime").hasNext(), is(false));
         }
     }
 
diff --git a/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jEdge.java b/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jEdge.java
index 2729f9f..08282fc 100644
--- a/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jEdge.java
+++ b/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jEdge.java
@@ -114,7 +114,13 @@ public final class Neo4jEdge extends Neo4jElement implements Edge, WrappedEdge<N
 
     @Override
     public <V> Property<V> property(final String key, final V value) {
-        ElementHelper.validateProperty(false, key, value);
+        ElementHelper.validateProperty(key, value);
+        
+        if (null == value) {
+            properties(key).forEachRemaining(Property::remove);
+            return Property.empty();
+        }
+
         this.graph.tx().readWrite();
         try {
             this.baseElement.setProperty(key, value);
diff --git a/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jGraph.java b/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jGraph.java
index 74270d3..55be5f6 100644
--- a/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jGraph.java
+++ b/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jGraph.java
@@ -127,7 +127,7 @@ public final class Neo4jGraph implements Graph, WrappedGraph<Neo4jGraphAPI> {
 
     @Override
     public Vertex addVertex(final Object... keyValues) {
-        ElementHelper.legalPropertyKeyValueArray(false, keyValues);
+        ElementHelper.legalPropertyKeyValueArray(keyValues);
         if (ElementHelper.getIdValue(keyValues).isPresent())
             throw Vertex.Exceptions.userSuppliedIdsNotSupported();
         this.tx().readWrite();
diff --git a/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jVertex.java b/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jVertex.java
index 2bbca2f..98b1893 100644
--- a/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jVertex.java
+++ b/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jVertex.java
@@ -56,7 +56,7 @@ public final class Neo4jVertex extends Neo4jElement implements Vertex, WrappedVe
     public Edge addEdge(final String label, final Vertex inVertex, final Object... keyValues) {
         if (null == inVertex) throw Graph.Exceptions.argumentCanNotBeNull("inVertex");
         ElementHelper.validateLabel(label);
-        ElementHelper.legalPropertyKeyValueArray(false, keyValues);
+        ElementHelper.legalPropertyKeyValueArray(keyValues);
         if (ElementHelper.getIdValue(keyValues).isPresent())
             throw Edge.Exceptions.userSuppliedIdsNotSupported();
 
@@ -91,13 +91,19 @@ public final class Neo4jVertex extends Neo4jElement implements Vertex, WrappedVe
 
     @Override
     public <V> VertexProperty<V> property(final VertexProperty.Cardinality cardinality, final String key, final V value, final Object... keyValues) {
-        ElementHelper.validateProperty(false, key, value);
+        ElementHelper.validateProperty(key, value);
         if (ElementHelper.getIdValue(keyValues).isPresent())
             throw Vertex.Exceptions.userSuppliedIdsNotSupported();
         this.graph.tx().readWrite();
 
         if (cardinality != VertexProperty.Cardinality.single)
             throw VertexProperty.Exceptions.multiPropertiesNotSupported();
+
+        if (null == value) {
+            properties(key).forEachRemaining(VertexProperty::remove);
+            return VertexProperty.empty();
+        }
+
         if (keyValues.length > 0)
             throw VertexProperty.Exceptions.metaPropertiesNotSupported();
         try {
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/computer/TinkerGraphComputerView.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/computer/TinkerGraphComputerView.java
index b61fc2a..43998fb 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/computer/TinkerGraphComputerView.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/computer/TinkerGraphComputerView.java
@@ -79,7 +79,7 @@ public final class TinkerGraphComputerView {
     }
 
     public <V> Property<V> addProperty(final TinkerVertex vertex, final String key, final V value) {
-        ElementHelper.validateProperty(true, key, value);
+        ElementHelper.validateProperty(key, value);
         if (isComputeKey(key)) {
             final TinkerVertexProperty<V> property = new TinkerVertexProperty<V>((TinkerVertex) vertex, key, value) {
                 @Override
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerEdge.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerEdge.java
index d594fea..02f7ede 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerEdge.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerEdge.java
@@ -56,7 +56,13 @@ public final class TinkerEdge extends TinkerElement implements Edge {
     @Override
     public <V> Property<V> property(final String key, final V value) {
         if (this.removed) throw elementAlreadyRemoved(Edge.class, id);
-        ElementHelper.validateProperty(allowNullPropertyValues, key, value);
+        ElementHelper.validateProperty(key, value);
+
+        if (!allowNullPropertyValues && null == value) {
+            properties(key).forEachRemaining(Property::remove);
+            return Property.empty();
+        }
+
         final Property oldProperty = super.property(key);
         final Property<V> newProperty = new TinkerProperty<>(this, key, value);
         if (null == this.properties) this.properties = new HashMap<>();
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java
index 2410445..6f4f821 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java
@@ -116,7 +116,7 @@ public final class TinkerGraph implements Graph {
         vertexPropertyIdManager = selectIdManager(configuration, GREMLIN_TINKERGRAPH_VERTEX_PROPERTY_ID_MANAGER, VertexProperty.class);
         defaultVertexPropertyCardinality = VertexProperty.Cardinality.valueOf(
                 configuration.getString(GREMLIN_TINKERGRAPH_DEFAULT_VERTEX_PROPERTY_CARDINALITY, VertexProperty.Cardinality.single.name()));
-        allowNullPropertyValues = configuration.getBoolean(GREMLIN_TINKERGRAPH_ALLOW_NULL_PROPERTY_VALUES, true);
+        allowNullPropertyValues = configuration.getBoolean(GREMLIN_TINKERGRAPH_ALLOW_NULL_PROPERTY_VALUES, false);
 
         graphLocation = configuration.getString(GREMLIN_TINKERGRAPH_GRAPH_LOCATION, null);
         graphFormat = configuration.getString(GREMLIN_TINKERGRAPH_GRAPH_FORMAT, null);
@@ -161,7 +161,7 @@ public final class TinkerGraph implements Graph {
 
     @Override
     public Vertex addVertex(final Object... keyValues) {
-        ElementHelper.legalPropertyKeyValueArray(allowNullPropertyValues, keyValues);
+        ElementHelper.legalPropertyKeyValueArray(keyValues);
         Object idValue = vertexIdManager.convert(ElementHelper.getIdValue(keyValues).orElse(null));
         final String label = ElementHelper.getLabelValue(keyValues).orElse(Vertex.DEFAULT_LABEL);
 
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerHelper.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerHelper.java
index 2b1a928..4ed3fdd 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerHelper.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerHelper.java
@@ -49,7 +49,7 @@ public final class TinkerHelper {
 
     protected static Edge addEdge(final TinkerGraph graph, final TinkerVertex outVertex, final TinkerVertex inVertex, final String label, final Object... keyValues) {
         ElementHelper.validateLabel(label);
-        ElementHelper.legalPropertyKeyValueArray(graph.features().edge().supportsNullPropertyValues(), keyValues);
+        ElementHelper.legalPropertyKeyValueArray(keyValues);
 
         Object idValue = graph.edgeIdManager.convert(ElementHelper.getIdValue(keyValues).orElse(null));
 
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertex.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertex.java
index 7fe7ce0..52518d6 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertex.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertex.java
@@ -85,9 +85,15 @@ public final class TinkerVertex extends TinkerElement implements Vertex {
     @Override
     public <V> VertexProperty<V> property(final VertexProperty.Cardinality cardinality, final String key, final V value, final Object... keyValues) {
         if (this.removed) throw elementAlreadyRemoved(Vertex.class, id);
-        ElementHelper.legalPropertyKeyValueArray(allowNullPropertyValues, keyValues);
-        ElementHelper.validateProperty(allowNullPropertyValues, key, value);
+        ElementHelper.legalPropertyKeyValueArray(keyValues);
+        ElementHelper.validateProperty(key, value);
         final Optional<Object> optionalId = ElementHelper.getIdValue(keyValues);
+
+        if (!allowNullPropertyValues && null == value) {
+            properties(key).forEachRemaining(VertexProperty::remove);
+            return VertexProperty.empty();
+        }
+
         final Optional<VertexProperty<V>> optionalVertexProperty = ElementHelper.stageVertexProperty(this, cardinality, key, value, keyValues);
         if (optionalVertexProperty.isPresent()) return optionalVertexProperty.get();
 
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertexProperty.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertexProperty.java
index f01a3f2..ed8acec 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertexProperty.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertexProperty.java
@@ -18,7 +18,6 @@
  */
 package org.apache.tinkerpop.gremlin.tinkergraph.structure;
 
-import org.apache.tinkerpop.gremlin.structure.Element;
 import org.apache.tinkerpop.gremlin.structure.Graph;
 import org.apache.tinkerpop.gremlin.structure.Property;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
@@ -53,13 +52,7 @@ public class TinkerVertexProperty<V> extends TinkerElement implements VertexProp
      * with {@link TinkerGraphComputerView}.
      */
     public TinkerVertexProperty(final TinkerVertex vertex, final String key, final V value, final Object... propertyKeyValues) {
-        super(((TinkerGraph) vertex.graph()).vertexPropertyIdManager.getNextId((TinkerGraph) vertex.graph()), key);
-        this.allowNullPropertyValues = vertex.graph().features().vertex().properties().supportsNullPropertyValues();
-        this.vertex = vertex;
-        this.key = key;
-        this.value = value;
-        ElementHelper.legalPropertyKeyValueArray(allowNullPropertyValues, propertyKeyValues);
-        ElementHelper.attachProperties(this, propertyKeyValues);
+        this(((TinkerGraph) vertex.graph()).vertexPropertyIdManager.getNextId((TinkerGraph) vertex.graph()), vertex, key, value, propertyKeyValues);
     }
 
     /**
@@ -69,10 +62,13 @@ public class TinkerVertexProperty<V> extends TinkerElement implements VertexProp
     public TinkerVertexProperty(final Object id, final TinkerVertex vertex, final String key, final V value, final Object... propertyKeyValues) {
         super(id, key);
         this.allowNullPropertyValues = vertex.graph().features().vertex().properties().supportsNullPropertyValues();
+        if (!allowNullPropertyValues && null == value)
+            throw new IllegalArgumentException("value cannot be null as feature supportsNullPropertyValues is false");
+
         this.vertex = vertex;
         this.key = key;
         this.value = value;
-        ElementHelper.legalPropertyKeyValueArray(allowNullPropertyValues, propertyKeyValues);
+        ElementHelper.legalPropertyKeyValueArray(propertyKeyValues);
         ElementHelper.attachProperties(this, propertyKeyValues);
     }
 
@@ -120,6 +116,12 @@ public class TinkerVertexProperty<V> extends TinkerElement implements VertexProp
     @Override
     public <U> Property<U> property(final String key, final U value) {
         if (this.removed) throw elementAlreadyRemoved(VertexProperty.class, id);
+
+        if ((!allowNullPropertyValues && null == value)) {
+            properties(key).forEachRemaining(Property::remove);
+            return Property.empty();
+        }
+
         final Property<U> property = new TinkerProperty<>(this, key, value);
         if (this.properties == null) this.properties = new HashMap<>();
         this.properties.put(key, property);
diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphTest.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphTest.java
index f9c661e..555c7ca 100644
--- a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphTest.java
+++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphTest.java
@@ -27,7 +27,6 @@ import org.apache.tinkerpop.gremlin.process.traversal.P;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReservedKeysVerificationStrategy;
-import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.VerificationException;
 import org.apache.tinkerpop.gremlin.process.traversal.util.Metrics;
 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalMetrics;
 import org.apache.tinkerpop.gremlin.structure.Edge;


[tinkerpop] 15/23: TINKERPOP-2235 Removed some uneccessary code

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2235
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit f5e937b72d2b2c4cb468a284393595af820e282b
Author: stephen <sp...@gmail.com>
AuthorDate: Mon Nov 18 14:49:54 2019 -0500

    TINKERPOP-2235 Removed some uneccessary code
---
 .../tinkerpop/gremlin/process/traversal/step/util/ImmutablePath.java | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/ImmutablePath.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/ImmutablePath.java
index 0ff981d..1f38ece 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/ImmutablePath.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/ImmutablePath.java
@@ -36,8 +36,7 @@ import java.util.Set;
  */
 public class ImmutablePath implements Path, Serializable, Cloneable {
 
-    private static final Object END = new Object();
-    private static final ImmutablePath TAIL_PATH = new ImmutablePath(null, END, null);
+    private static final ImmutablePath TAIL_PATH = new ImmutablePath(null, null, null);
 
     private ImmutablePath previousPath;
     private Object currentObject;
@@ -60,7 +59,7 @@ public class ImmutablePath implements Path, Serializable, Cloneable {
     }
 
     private final boolean isTail() {
-        return END == this.currentObject;
+        return null == this.currentObject;
     }
 
     @Override


[tinkerpop] 09/23: TINKERPOP-2235 Improved upgrade docs for null handling

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2235
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 98e722b44122b1671cbff79b0b81743c0b15de38
Author: stephen <sp...@gmail.com>
AuthorDate: Wed Nov 6 12:24:57 2019 -0500

    TINKERPOP-2235 Improved upgrade docs for null handling
---
 docs/src/upgrade/release-3.5.x.asciidoc | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/docs/src/upgrade/release-3.5.x.asciidoc b/docs/src/upgrade/release-3.5.x.asciidoc
index 287d2eb..fcd66de 100644
--- a/docs/src/upgrade/release-3.5.x.asciidoc
+++ b/docs/src/upgrade/release-3.5.x.asciidoc
@@ -166,6 +166,11 @@ gremlin> g.addV().property("x", 1).property("y", null).property("z", 2)
 ==>v[15]
 ----
 
+In conclusion, this change in greater support of `null` may affect the behavior of existing traversals written in past
+versions of TinkerPop as it is no longer possible to rely on `null` to expect a filtering action for traversers.
+Please review existing Gremlin carefully to ensure that there are no unintended consequences of this change and that
+there are no opportunities to improve existing logic to take greater advantage of this expansion of `null` semantics.
+
 See: link:https://issues.apache.org/jira/browse/TINKERPOP-2235[TINKERPOP-2235],
 link:https://issues.apache.org/jira/browse/TINKERPOP-2099[TINKERPOP-2099]
 


[tinkerpop] 11/23: TINKERPOP-2235 Minor refactoring to get rid of duplicate code

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2235
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 8ff4322aae05a6765c31a8f1a5c5ea86a6e601df
Author: stephen <sp...@gmail.com>
AuthorDate: Fri Nov 15 17:27:33 2019 -0500

    TINKERPOP-2235 Minor refactoring to get rid of duplicate code
---
 .../tinkerpop/gremlin/process/traversal/step/Scoping.java | 15 +++------------
 1 file changed, 3 insertions(+), 12 deletions(-)

diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/Scoping.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/Scoping.java
index da7b0eb..fae7431 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/Scoping.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/Scoping.java
@@ -24,6 +24,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Step;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 
 /**
@@ -110,18 +111,8 @@ public interface Scoping {
     public enum Variable {START, END}
 
     public default <S> S getScopeValue(final Pop pop, final String key, final Traverser.Admin<?> traverser) throws IllegalArgumentException {
-        final Object object = traverser.get();
-        if (object instanceof Map && ((Map<String, S>) object).containsKey(key))
-            return ((Map<String, S>) object).get(key);
-        ///
-        if (traverser.getSideEffects().exists(key))
-            return traverser.getSideEffects().get(key);
-        ///
-        final Path path = traverser.path();
-        if (path.hasLabel(key))
-            return path.get(pop, key);
-        ///
-        throw new IllegalArgumentException("Neither the sideEffects, map, nor path has a " + key + "-key: " + this);
+        return Optional.<S>ofNullable(getNullableScopeValue(pop, key, traverser)).orElseThrow(
+                () -> new IllegalArgumentException("Neither the sideEffects, map, nor path has a " + key + "-key: " + this));
     }
 
     public default <S> S getNullableScopeValue(final Pop pop, final String key, final Traverser.Admin<?> traverser) {


[tinkerpop] 21/23: TINKERPOP-2235 Ensured addE(null) behaved consistently.

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2235
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 446d92cc661b3b206617e85f0832320645d487db
Author: stephen <sp...@gmail.com>
AuthorDate: Tue Nov 26 10:26:54 2019 -0500

    TINKERPOP-2235 Ensured addE(null) behaved consistently.
    
    Should now throw the same nice error for String and Traversal overloads.
---
 docs/src/upgrade/release-3.5.x.asciidoc            | 34 ++++++++++++++++++++++
 .../traversal/dsl/graph/GraphTraversal.java        |  4 +--
 2 files changed, 36 insertions(+), 2 deletions(-)

diff --git a/docs/src/upgrade/release-3.5.x.asciidoc b/docs/src/upgrade/release-3.5.x.asciidoc
index ce7a00e..841176d 100644
--- a/docs/src/upgrade/release-3.5.x.asciidoc
+++ b/docs/src/upgrade/release-3.5.x.asciidoc
@@ -394,6 +394,40 @@ The above examples point out the default operations of TinkerGraph, but it can b
 `null` as a property value and it is up to graph providers to decided how they wish to treat a `null` property value.
 Providers should use the new `supportsNullPropertyValues()` feature to indicate to users how `null` is handled.
 
+For edges, the `label` still cannot be defaulted and must be specified, therefore:
+
+[source,text]
+----
+gremlin> g.V(0L).as('a').addE(null).to('a')
+Label can not be null
+Type ':help' or ':h' for help.
+Display stack trace? [yN]n
+gremlin> g.V(0L).as('a').addE(constant(null)).to('a')
+Label can not be null
+Type ':help' or ':h' for help.
+Display stack trace? [yN]
+----
+
+Also, edges have similar behavior to vertices when it comes to setting properties (again, the default configuration for
+TinkerGraph is being used here):
+
+[source,text]
+----
+gremlin> g.V(0L).as('a').addE('knows').to('a').property(id,null).property('weight',null)
+==>e[1][0-knows->0]
+gremlin> g.E().elementMap()
+==>[id:1,label:knows,IN:[id:0,label:vertex],OUT:[id:0,label:vertex]]
+gremlin> g.V(0L).as('a').addE('knows').to('a').property('weight',0.5)
+==>e[3][0-knows->0]
+gremlin> g.E().elementMap()
+==>[id:3,label:knows,IN:[id:0,label:vertex],OUT:[id:0,label:vertex],weight:0.5]
+gremlin> g.E().property('weight',null)
+==>e[3][0-knows->0]
+gremlin> g.E().elementMap()
+==>[id:3,label:knows,IN:[id:0,label:vertex],OUT:[id:0,label:vertex]]
+----
+
+
 See: link:https://issues.apache.org/jira/browse/TINKERPOP-2235[TINKERPOP-2235],
 link:https://issues.apache.org/jira/browse/TINKERPOP-2099[TINKERPOP-2099]
 
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java
index defb68c..a8f352a 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java
@@ -1046,7 +1046,7 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
      */
     public default GraphTraversal<S, Vertex> addV(final Traversal<?, String> vertexLabelTraversal) {
         this.asAdmin().getBytecode().addStep(Symbols.addV, vertexLabelTraversal);
-        return this.asAdmin().addStep(new AddVertexStep<>(this.asAdmin(), vertexLabelTraversal.asAdmin()));
+        return this.asAdmin().addStep(new AddVertexStep<>(this.asAdmin(), null == vertexLabelTraversal ? null : vertexLabelTraversal.asAdmin()));
     }
 
     /**
@@ -1083,7 +1083,7 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
      */
     public default GraphTraversal<S, Edge> addE(final Traversal<?, String> edgeLabelTraversal) {
         this.asAdmin().getBytecode().addStep(Symbols.addE, edgeLabelTraversal);
-        return this.asAdmin().addStep(new AddEdgeStep<>(this.asAdmin(), edgeLabelTraversal.asAdmin()));
+        return this.asAdmin().addStep(new AddEdgeStep<>(this.asAdmin(), null == edgeLabelTraversal ? null : edgeLabelTraversal.asAdmin()));
     }
 
     /**


[tinkerpop] 17/23: TINKERPOP-2235 Major refactoring and introduction of ScalarMapStep

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2235
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 4ef74c3aa17bac2b18006ee3144e16eee833fb9c
Author: stephen <sp...@gmail.com>
AuthorDate: Tue Nov 19 13:20:15 2019 -0500

    TINKERPOP-2235 Major refactoring and introduction of ScalarMapStep
    
    ScalarMapStep extends and replaces MapStep as the "easy" way to implement a new MapStep. ScalarMapStep works nicely when you don't have to fuss with null logic and is still appropriate for use in those cases where a step will not produce a null value (e.g. addV()). For all other cases, you basically have to extend MapStep directly and implement processNextStart() by hand.
---
 CHANGELOG.asciidoc                                 |  1 +
 docs/src/upgrade/release-3.5.x.asciidoc            | 40 ++++++++++++++++
 .../traversal/step/filter/WhereTraversalStep.java  |  4 +-
 .../process/traversal/step/map/AddEdgeStep.java    |  2 +-
 .../process/traversal/step/map/AddVertexStep.java  |  2 +-
 .../process/traversal/step/map/ConstantStep.java   |  2 +-
 .../process/traversal/step/map/CountLocalStep.java |  2 +-
 .../process/traversal/step/map/DedupLocalStep.java |  2 +-
 .../traversal/step/map/EdgeOtherVertexStep.java    |  2 +-
 .../process/traversal/step/map/ElementMapStep.java |  2 +-
 .../gremlin/process/traversal/step/map/IdStep.java |  2 +-
 .../process/traversal/step/map/IndexStep.java      |  2 +-
 .../process/traversal/step/map/LabelStep.java      |  2 +-
 .../process/traversal/step/map/LambdaMapStep.java  |  2 +-
 .../process/traversal/step/map/LoopsStep.java      |  2 +-
 .../process/traversal/step/map/MapStep.java        | 27 ++---------
 .../process/traversal/step/map/MathStep.java       |  2 +-
 .../process/traversal/step/map/MaxLocalStep.java   |  2 +-
 .../process/traversal/step/map/MeanLocalStep.java  |  2 +-
 .../process/traversal/step/map/MinLocalStep.java   |  2 +-
 .../process/traversal/step/map/OrderLocalStep.java |  2 +-
 .../process/traversal/step/map/PathStep.java       |  2 +-
 .../process/traversal/step/map/ProjectStep.java    |  2 +-
 .../traversal/step/map/PropertyKeyStep.java        |  2 +-
 .../traversal/step/map/PropertyMapStep.java        |  2 +-
 .../traversal/step/map/PropertyValueStep.java      |  2 +-
 .../process/traversal/step/map/RangeLocalStep.java |  2 +-
 .../process/traversal/step/map/SackStep.java       |  2 +-
 .../traversal/step/map/SampleLocalStep.java        |  2 +-
 .../{PropertyKeyStep.java => ScalarMapStep.java}   | 25 +++++-----
 .../process/traversal/step/map/SelectOneStep.java  | 54 +++++++++++++++-------
 .../process/traversal/step/map/SelectStep.java     | 20 +++-----
 .../process/traversal/step/map/SumLocalStep.java   |  2 +-
 .../process/traversal/step/map/TailLocalStep.java  |  2 +-
 .../traversal/step/map/TraversalMapStep.java       | 12 ++---
 .../traversal/step/map/TraversalSelectStep.java    | 41 ++++++++--------
 .../finalization/ReferenceElementStrategy.java     |  4 +-
 gremlin-test/features/map/Map.feature              | 16 +++++++
 .../process/traversal/step/map/MapTest.java        | 22 +++++++++
 .../strategy/decoration/OptionsStrategyTest.java   |  4 +-
 40 files changed, 198 insertions(+), 126 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 58dc701..b88d717 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -27,6 +27,7 @@ This release also includes changes from <<release-3-4-3, 3.4.3>>.
 
 * Allowed the possibility for the user of `null` in Gremlin.
 * Added a `Graph.Feature` for `supportsNullPropertyValues`.
+* Refactored `MapStep` to move its logic to `ScalarMapStep` so that the old behavior could be preserved while allow other implementations to have more flexibility.
 * Modified TinkerGraph to support `null` property values and can be configured to disable that feature.
 * Modified `null` handling in mutations to be consistent for a new `Vertex` as well as update to an existing one.
 * Upgraded to Apache Commons Configuration2.
diff --git a/docs/src/upgrade/release-3.5.x.asciidoc b/docs/src/upgrade/release-3.5.x.asciidoc
index f0739e5..cdd920b 100644
--- a/docs/src/upgrade/release-3.5.x.asciidoc
+++ b/docs/src/upgrade/release-3.5.x.asciidoc
@@ -257,6 +257,46 @@ link:http://tinkerpop.apache.org/docs/3.5.0/upgrade/#_ssl_security[3.2.10 Upgrad
 
 ==== Graph System Providers
 
+===== ScalarMapStep
+
+`MapStep` had a single abstract method that needed to be implemented:
+
+[source,java]
+----
+protected abstract E map(final Traverser.Admin<S> traverser);
+----
+
+This method made it easy to implement new implementations because it hid certain processing logic and made it so that
+the implementer only had to reason about how to take the current object from the `Traverser` and transform it to a
+new value. As 3.5.0 changed semantics around how `null` is processed, this method became a bit of a hindrance to the
+more complex logic which those semantics entailed. Specifically, this method could not easily communicate to underlying
+processing what a `null` might mean - is the `null` the end of the traversal stream or should the `null` be promoted
+down the stream as a value to be processed.
+
+Interestingly, the method that enabled the handling of this more complex decision making already existed in
+`AbstractStep`:
+
+[source,java]
+----
+protected Traverser.Admin<E> processNextStart()
+----
+
+It returns a whole `Traverser` object and forces manual retrieval of the "next" `Traverser`. At this level it becomes
+possible to make choices on `null` and return it if it should be propagated or dismiss it and return an
+`EmptyTraverser`. To better accommodate the `MapStep` which provides the nice helper `map(Traverser)` method as well
+as the more flexible version that doesn't need that infrastructure, `ScalarMapStep` was added to extend `MapStep`. The
+`map(Traverser)` was then moved to `ScalarMapStep` and those steps that could rely on that helper method now extend
+from it. All other steps of this sort still extend `MapStep` and directly implement `processNextStart()`.
+
+Providers will get compile errors if they extended `MapStep`. The easy solution will be to simply modify that code so
+that their step instead extends `ScalarMapStep`. As a secondary task, providers should then examine their step
+implementation to ensure that `null` semantics as presented in 3.5.0 apply properly. If they do not, then it is likely
+that the step should simply implement `MapStep` directly and former `map(Traverser)` logic should be migrated to
+`processNextStart()`.
+
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-2235[TINKERPOP-2235],
+link:https://issues.apache.org/jira/browse/TINKERPOP-2099[TINKERPOP-2099]
+
 ===== TraversalStrategy Application
 
 The methodology for strategy application has been altered and the change is most easily described by example. Given a
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/WhereTraversalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/WhereTraversalStep.java
index 384bbce..8c701a9 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/WhereTraversalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/WhereTraversalStep.java
@@ -25,7 +25,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 import org.apache.tinkerpop.gremlin.process.traversal.step.PathProcessor;
 import org.apache.tinkerpop.gremlin.process.traversal.step.Scoping;
 import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.MapStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.ScalarMapStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.StartStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.ProfileStep;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.ConnectiveStrategy;
@@ -149,7 +149,7 @@ public final class WhereTraversalStep<S> extends FilterStep<S> implements Traver
 
     //////////////////////////////
 
-    public static class WhereStartStep<S> extends MapStep<S, Object> implements Scoping {
+    public static class WhereStartStep<S> extends ScalarMapStep<S, Object> implements Scoping {
 
         private String selectKey;
 
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddEdgeStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddEdgeStep.java
index aa3818b..b8d7d35 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddEdgeStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddEdgeStep.java
@@ -45,7 +45,7 @@ import java.util.Set;
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  * @author Stephen Mallette (http://stephen.genoprime.com)
  */
-public class AddEdgeStep<S> extends MapStep<S, Edge>
+public class AddEdgeStep<S> extends ScalarMapStep<S, Edge>
         implements Mutating<Event.EdgeAddedEvent>, TraversalParent, Scoping, FromToModulating {
 
     private static final String FROM = Graph.Hidden.hide("from");
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStep.java
index 4961025..296e8b5 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStep.java
@@ -40,7 +40,7 @@ import java.util.Set;
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  * @author Stephen Mallette (http://stephen.genoprime.com)
  */
-public class AddVertexStep<S> extends MapStep<S, Vertex>
+public class AddVertexStep<S> extends ScalarMapStep<S, Vertex>
         implements Mutating<Event.VertexAddedEvent>, TraversalParent, Scoping {
 
     private Parameters parameters = new Parameters();
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConstantStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConstantStep.java
index 749de31..9434cc3 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConstantStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConstantStep.java
@@ -27,7 +27,7 @@ import java.util.Collections;
 import java.util.Objects;
 import java.util.Set;
 
-public class ConstantStep<S, E> extends MapStep<S, E> {
+public class ConstantStep<S, E> extends ScalarMapStep<S, E> {
 
     private final E constant;
 
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/CountLocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/CountLocalStep.java
index 87b699d..25e75e4 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/CountLocalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/CountLocalStep.java
@@ -32,7 +32,7 @@ import java.util.Set;
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  * @author Daniel Kuppitz (http://gremlin.guru)
  */
-public final class CountLocalStep<S> extends MapStep<S, Long> {
+public final class CountLocalStep<S> extends ScalarMapStep<S, Long> {
 
     public CountLocalStep(final Traversal.Admin traversal) {
         super(traversal);
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/DedupLocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/DedupLocalStep.java
index 59c66c7..4d75270 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/DedupLocalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/DedupLocalStep.java
@@ -29,7 +29,7 @@ import java.util.Set;
 /**
  * @author Daniel Kuppitz (http://gremlin.guru)
  */
-public final class DedupLocalStep<E, S extends Iterable<E>> extends MapStep<S, Set<E>> {
+public final class DedupLocalStep<E, S extends Iterable<E>> extends ScalarMapStep<S, Set<E>> {
 
     public DedupLocalStep(final Traversal.Admin traversal) {
         super(traversal);
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/EdgeOtherVertexStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/EdgeOtherVertexStep.java
index a53dd50..0d151d0 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/EdgeOtherVertexStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/EdgeOtherVertexStep.java
@@ -34,7 +34,7 @@ import java.util.Set;
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  */
-public class EdgeOtherVertexStep extends MapStep<Edge, Vertex> implements Configuring {
+public class EdgeOtherVertexStep extends ScalarMapStep<Edge, Vertex> implements Configuring {
     protected Parameters parameters = new Parameters();
 
     public EdgeOtherVertexStep(final Traversal.Admin traversal) {
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ElementMapStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ElementMapStep.java
index 2f1db43..1654aa1 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ElementMapStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ElementMapStep.java
@@ -45,7 +45,7 @@ import java.util.Set;
  * @author Daniel Kuppitz (http://gremlin.guru)
  * @author Stephen Mallette (http://stephen.genoprime.com)
  */
-public class ElementMapStep<K,E> extends MapStep<Element, Map<K, E>> implements TraversalParent, GraphComputing {
+public class ElementMapStep<K,E> extends ScalarMapStep<Element, Map<K, E>> implements TraversalParent, GraphComputing {
 
     protected final String[] propertyKeys;
     private boolean onGraphComputer = false;
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/IdStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/IdStep.java
index 018c91e..c5eb7c4 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/IdStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/IdStep.java
@@ -29,7 +29,7 @@ import java.util.Set;
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  */
-public final class IdStep<S extends Element> extends MapStep<S, Object> {
+public final class IdStep<S extends Element> extends ScalarMapStep<S, Object> {
 
     public IdStep(final Traversal.Admin traversal) {
         super(traversal);
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/IndexStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/IndexStep.java
index b903c15..314e751 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/IndexStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/IndexStep.java
@@ -40,7 +40,7 @@ import java.util.function.Function;
 /**
  * @author Daniel Kuppitz (http://gremlin.guru)
  */
-public class IndexStep<S, E> extends MapStep<S, E> implements TraversalParent, Configuring {
+public class IndexStep<S, E> extends ScalarMapStep<S, E> implements TraversalParent, Configuring {
 
     private final static IllegalArgumentException INVALID_CONFIGURATION_EXCEPTION =
             new IllegalArgumentException("WithOptions.indexer requires a single Integer argument (possible " + "" +
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LabelStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LabelStep.java
index 2b2313f..060dc68 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LabelStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LabelStep.java
@@ -29,7 +29,7 @@ import java.util.Set;
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  */
-public final class LabelStep<S extends Element> extends MapStep<S, String> {
+public final class LabelStep<S extends Element> extends ScalarMapStep<S, String> {
 
     public LabelStep(final Traversal.Admin traversal) {
         super(traversal);
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LambdaMapStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LambdaMapStep.java
index b2024f8..ce6a1bd 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LambdaMapStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LambdaMapStep.java
@@ -28,7 +28,7 @@ import java.util.function.Function;
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  */
-public final class LambdaMapStep<S, E> extends MapStep<S, E> implements LambdaHolder {
+public final class LambdaMapStep<S, E> extends ScalarMapStep<S, E> implements LambdaHolder {
 
     private final Function<Traverser<S>, E> function;
 
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LoopsStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LoopsStep.java
index 11d8729..c3d2ac8 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LoopsStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LoopsStep.java
@@ -24,7 +24,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 /**
  * @author Daniel Kuppitz (http://gremlin.guru)
  */
-public final class LoopsStep<S> extends MapStep<S, Integer> {
+public final class LoopsStep<S> extends ScalarMapStep<S, Integer> {
 
     private String loopName;
 
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MapStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MapStep.java
index 51535e7..5d5103c 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MapStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MapStep.java
@@ -18,12 +18,16 @@
  */
 package org.apache.tinkerpop.gremlin.process.traversal.step.map;
 
+import org.apache.tinkerpop.gremlin.process.traversal.Step;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.AbstractStep;
-import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.EmptyTraverser;
 
 /**
+ * A marker base class that designates an extending {@link Step} as a "Map" step which will transform the object of one
+ * {@link Traverser} into another. In many cases, it may be easier to simply extend from {@link ScalarMapStep} which
+ * has a straightforward implementation pattern.
+ *
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  * @author Stephen Mallette (http://stephen.genoprime.com)
  */
@@ -32,26 +36,5 @@ public abstract class MapStep<S, E> extends AbstractStep<S, E> {
     public MapStep(final Traversal.Admin traversal) {
         super(traversal);
     }
-
-    @Override
-    protected Traverser.Admin<E> processNextStart() {
-        final Traverser.Admin<S> traverser = this.starts.next();
-        final E obj = this.map(traverser);
-        return isEmptyTraverser(obj) ? EmptyTraverser.instance() : traverser.split(obj, this);
-    }
-
-    protected abstract E map(final Traverser.Admin<S> traverser);
-
-    /**
-     * Determines if the value returned from {@link #map(Traverser.Admin)} should be representative of an
-     * {@link EmptyTraverser}. Such traversers will effectively be filtered out by the traversal. This method works in
-     * conjunction with {@link #map(Traverser.Admin)} in the sense that both work are called in the default
-     * implementation of {@link #processNextStart()} which will call this method after "map()"-ing the traverser to
-     * determine if that step consider the returned value as "empty".
-     */
-    protected boolean isEmptyTraverser(final E obj) {
-        return false;
-    }
-
 }
 
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MathStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MathStep.java
index 0994411..b463833 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MathStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MathStep.java
@@ -44,7 +44,7 @@ import java.util.regex.Pattern;
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  */
-public final class MathStep<S> extends MapStep<S, Double> implements ByModulating, TraversalParent, Scoping, PathProcessor {
+public final class MathStep<S> extends ScalarMapStep<S, Double> implements ByModulating, TraversalParent, Scoping, PathProcessor {
 
     private static final String CURRENT = "_";
     private final String equation;
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MaxLocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MaxLocalStep.java
index 3ad326f..187ad39 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MaxLocalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MaxLocalStep.java
@@ -33,7 +33,7 @@ import static org.apache.tinkerpop.gremlin.util.NumberHelper.max;
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  * @author Daniel Kuppitz (http://gremlin.guru)
  */
-public final class MaxLocalStep<E extends Comparable, S extends Iterable<E>> extends MapStep<S, E> {
+public final class MaxLocalStep<E extends Comparable, S extends Iterable<E>> extends ScalarMapStep<S, E> {
 
     public MaxLocalStep(final Traversal.Admin traversal) {
         super(traversal);
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MeanLocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MeanLocalStep.java
index 91447fd..5ffb1a6 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MeanLocalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MeanLocalStep.java
@@ -32,7 +32,7 @@ import java.util.Set;
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  * @author Daniel Kuppitz (http://gremlin.guru)
  */
-public final class MeanLocalStep<E extends Number, S extends Iterable<E>> extends MapStep<S, Number> {
+public final class MeanLocalStep<E extends Number, S extends Iterable<E>> extends ScalarMapStep<S, Number> {
 
     public MeanLocalStep(final Traversal.Admin traversal) {
         super(traversal);
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MinLocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MinLocalStep.java
index 4139a7d..3bdb969 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MinLocalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MinLocalStep.java
@@ -33,7 +33,7 @@ import static org.apache.tinkerpop.gremlin.util.NumberHelper.min;
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  * @author Daniel Kuppitz (http://gremlin.guru)
  */
-public final class MinLocalStep<E extends Comparable, S extends Iterable<E>> extends MapStep<S, E> {
+public final class MinLocalStep<E extends Comparable, S extends Iterable<E>> extends ScalarMapStep<S, E> {
 
     public MinLocalStep(final Traversal.Admin traversal) {
         super(traversal);
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/OrderLocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/OrderLocalStep.java
index 95cb6db..97a68ff 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/OrderLocalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/OrderLocalStep.java
@@ -43,7 +43,7 @@ import java.util.stream.Collectors;
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  */
-public final class OrderLocalStep<S, C extends Comparable> extends MapStep<S, S> implements ComparatorHolder<S, C>, ByModulating, TraversalParent {
+public final class OrderLocalStep<S, C extends Comparable> extends ScalarMapStep<S, S> implements ComparatorHolder<S, C>, ByModulating, TraversalParent {
 
     private List<Pair<Traversal.Admin<S, C>, Comparator<C>>> comparators = new ArrayList<>();
     private ChainedComparator<S, C> chainedComparator = null;
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PathStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PathStep.java
index b49a1e4..82f19b4 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PathStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PathStep.java
@@ -38,7 +38,7 @@ import java.util.Set;
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  */
-public final class PathStep<S> extends MapStep<S, Path> implements TraversalParent, PathProcessor, ByModulating, FromToModulating {
+public final class PathStep<S> extends ScalarMapStep<S, Path> implements TraversalParent, PathProcessor, ByModulating, FromToModulating {
 
     private TraversalRing<Object, Object> traversalRing;
     private Set<String> keepLabels;
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ProjectStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ProjectStep.java
index 3ddd4a6..66c63e0 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ProjectStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ProjectStep.java
@@ -37,7 +37,7 @@ import java.util.Set;
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  */
-public final class ProjectStep<S, E> extends MapStep<S, Map<String, E>> implements TraversalParent, ByModulating {
+public final class ProjectStep<S, E> extends ScalarMapStep<S, Map<String, E>> implements TraversalParent, ByModulating {
 
     private final List<String> projectKeys;
     private TraversalRing<S, E> traversalRing;
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PropertyKeyStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PropertyKeyStep.java
index ea1db27..9e7a0ab 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PropertyKeyStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PropertyKeyStep.java
@@ -29,7 +29,7 @@ import java.util.Set;
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  */
-public final class PropertyKeyStep extends MapStep<Property, String> {
+public final class PropertyKeyStep extends ScalarMapStep<Property, String> {
 
     public PropertyKeyStep(final Traversal.Admin traversal) {
         super(traversal);
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PropertyMapStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PropertyMapStep.java
index a58aaaf..ecc82f9 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PropertyMapStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PropertyMapStep.java
@@ -49,7 +49,7 @@ import java.util.Set;
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  * @author Daniel Kuppitz (http://gremlin.guru)
  */
-public class PropertyMapStep<K,E> extends MapStep<Element, Map<K, E>>
+public class PropertyMapStep<K,E> extends ScalarMapStep<Element, Map<K, E>>
         implements TraversalParent, ByModulating, Configuring {
 
     protected final String[] propertyKeys;
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PropertyValueStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PropertyValueStep.java
index eab0ea6..6f41010 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PropertyValueStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PropertyValueStep.java
@@ -29,7 +29,7 @@ import java.util.Set;
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  */
-public final class PropertyValueStep<E> extends MapStep<Property<E>, E> {
+public final class PropertyValueStep<E> extends ScalarMapStep<Property<E>, E> {
 
     public PropertyValueStep(final Traversal.Admin traversal) {
         super(traversal);
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/RangeLocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/RangeLocalStep.java
index 905a80c..f72a0cb 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/RangeLocalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/RangeLocalStep.java
@@ -36,7 +36,7 @@ import java.util.Set;
 /**
  * @author Daniel Kuppitz (http://gremlin.guru)
  */
-public final class RangeLocalStep<S> extends MapStep<S, S> {
+public final class RangeLocalStep<S> extends ScalarMapStep<S, S> {
 
     private final long low;
     private final long high;
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SackStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SackStep.java
index 3e0385b..7f12a00 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SackStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SackStep.java
@@ -28,7 +28,7 @@ import java.util.Set;
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  */
-public final class SackStep<S, E> extends MapStep<S, E> {
+public final class SackStep<S, E> extends ScalarMapStep<S, E> {
 
     public SackStep(final Traversal.Admin traversal) {
         super(traversal);
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SampleLocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SampleLocalStep.java
index 5f83c53..ef65202 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SampleLocalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SampleLocalStep.java
@@ -36,7 +36,7 @@ import java.util.Set;
  * @author Daniel Kuppitz (http://gremlin.guru)
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  */
-public final class SampleLocalStep<S> extends MapStep<S, S> {
+public final class SampleLocalStep<S> extends ScalarMapStep<S, S> {
 
     private static final Random RANDOM = new Random();
 
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PropertyKeyStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ScalarMapStep.java
similarity index 60%
copy from gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PropertyKeyStep.java
copy to gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ScalarMapStep.java
index ea1db27..78e0766 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PropertyKeyStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ScalarMapStep.java
@@ -20,28 +20,25 @@ package org.apache.tinkerpop.gremlin.process.traversal.step.map;
 
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
-import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
-import org.apache.tinkerpop.gremlin.structure.Property;
-
-import java.util.Collections;
-import java.util.Set;
 
 /**
+ * A type of {@link MapStep} class which will transform the object of one {@link Traverser} into another. This class
+ * simply requires the implementation of the {@link #map(Traverser.Admin)} method to extract the object of the given
+ * {@link Traverser} and return the transformation of that object as {@code E}.
+ *
  * @author Marko A. Rodriguez (http://markorodriguez.com)
+ * @author Stephen Mallette (http://stephen.genoprime.com)
  */
-public final class PropertyKeyStep extends MapStep<Property, String> {
-
-    public PropertyKeyStep(final Traversal.Admin traversal) {
+public abstract class ScalarMapStep<S, E> extends MapStep<S,E> {
+    public ScalarMapStep(final Traversal.Admin traversal) {
         super(traversal);
     }
 
     @Override
-    protected String map(final Traverser.Admin<Property> traverser) {
-        return traverser.get().key();
+    protected Traverser.Admin<E> processNextStart() {
+        final Traverser.Admin<S> traverser = this.starts.next();
+        return traverser.split(this.map(traverser), this);
     }
 
-    @Override
-    public Set<TraverserRequirement> getRequirements() {
-        return Collections.singleton(TraverserRequirement.OBJECT);
-    }
+    protected abstract E map(final Traverser.Admin<S> traverser);
 }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectOneStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectOneStep.java
index a87f9ef..44e22fe 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectOneStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectOneStep.java
@@ -18,6 +18,7 @@
  */
 package org.apache.tinkerpop.gremlin.process.traversal.step.map;
 
+import org.apache.tinkerpop.gremlin.process.traversal.Path;
 import org.apache.tinkerpop.gremlin.process.traversal.Pop;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
@@ -26,12 +27,16 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.PathProcessor;
 import org.apache.tinkerpop.gremlin.process.traversal.step.Scoping;
 import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
 import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
+import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.EmptyTraverser;
 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil;
 import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
 
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
 import java.util.Set;
 
 /**
@@ -39,7 +44,6 @@ import java.util.Set;
  */
 public final class SelectOneStep<S, E> extends MapStep<S, E> implements TraversalParent, Scoping, PathProcessor, ByModulating {
 
-    private static final Object NOTHING_SELECTED = new Object();
     private final Pop pop;
     private final String selectKey;
     private Traversal.Admin<S, E> selectTraversal = null;
@@ -52,15 +56,39 @@ public final class SelectOneStep<S, E> extends MapStep<S, E> implements Traversa
     }
 
     @Override
-    protected E map(final Traverser.Admin<S> traverser) {
-        final E end = this.getScopeValue(this.pop, this.selectKey, traverser, (E) NOTHING_SELECTED);
-        if (NOTHING_SELECTED == end) return end;
-        return null != end ? TraversalUtil.applyNullable((S) end, this.selectTraversal) : null;
-    }
+    protected Traverser.Admin<E> processNextStart() throws NoSuchElementException {
+        final Traverser.Admin<S> traverser = this.starts.next();
+
+        final Object object = traverser.get();
+        if (object instanceof Map && ((Map<String, S>) object).containsKey(this.selectKey)) {
+            final S o = ((Map<String, S>) object).get(this.selectKey);
+            if (null == o) return traverser.split(null, this);
+            final Traverser.Admin<E> outTraverser = traverser.split(TraversalUtil.applyNullable(o, this.selectTraversal), this);
+            if (!(this.getTraversal().getParent() instanceof MatchStep))
+                PathProcessor.processTraverserPathLabels(outTraverser, this.keepLabels);
+            return outTraverser;
+        }
 
-    @Override
-    protected boolean isEmptyTraverser(final E obj) {
-        return NOTHING_SELECTED == obj;
+        if (traverser.getSideEffects().exists(this.selectKey)) {
+            final S o = (S) traverser.getSideEffects().get(this.selectKey);
+            if (null == o) return traverser.split(null, this);
+            final Traverser.Admin<E> outTraverser = traverser.split(TraversalUtil.applyNullable(o, this.selectTraversal), this);
+            if (!(this.getTraversal().getParent() instanceof MatchStep))
+                PathProcessor.processTraverserPathLabels(outTraverser, this.keepLabels);
+            return outTraverser;
+        }
+
+        final Path path = traverser.path();
+        if (path.hasLabel(this.selectKey)) {
+            final S o = (S) path.get(pop, this.selectKey);
+            if (null == o) return traverser.split(null, this);
+            final Traverser.Admin<E> outTraverser = traverser.split(TraversalUtil.applyNullable(o, this.selectTraversal), this);
+            if (!(this.getTraversal().getParent() instanceof MatchStep))
+                PathProcessor.processTraverserPathLabels(outTraverser, this.keepLabels);
+            return outTraverser;
+        }
+
+        return EmptyTraverser.instance();
     }
 
     @Override
@@ -132,14 +160,6 @@ public final class SelectOneStep<S, E> extends MapStep<S, E> implements Traversa
         return this.keepLabels;
     }
 
-    @Override
-    protected Traverser.Admin<E> processNextStart() {
-        final Traverser.Admin<E> traverser = super.processNextStart();
-        if (!(this.getTraversal().getParent() instanceof MatchStep)) {
-            PathProcessor.processTraverserPathLabels(traverser, this.keepLabels);
-        }
-        return traverser;
-    }
 }
 
 
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectStep.java
index db15a28..2fa3e1f 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectStep.java
@@ -27,7 +27,6 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.Scoping;
 import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
 import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
 import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.EmptyTraverser;
-import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper;
 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalRing;
 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil;
 import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
@@ -39,10 +38,12 @@ import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.NoSuchElementException;
 import java.util.Set;
 
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
+ * @author Stephen Mallette (http://stephen.genoprime.com)
  */
 public final class SelectStep<S, E> extends MapStep<S, Map<String, E>> implements Scoping, TraversalParent, PathProcessor, ByModulating {
 
@@ -62,7 +63,8 @@ public final class SelectStep<S, E> extends MapStep<S, Map<String, E>> implement
     }
 
     @Override
-    protected Map<String, E> map(final Traverser.Admin<S> traverser) {
+    protected Traverser.Admin<Map<String, E>> processNextStart() throws NoSuchElementException {
+        final Traverser.Admin<S> traverser = this.starts.next();
         final Map<String, E> bindings = new LinkedHashMap<>(this.selectKeys.size(), 1.0f);
         for (final String selectKey : this.selectKeys) {
             final E end = this.getNullableScopeValue(this.pop, selectKey, traverser);
@@ -70,16 +72,11 @@ public final class SelectStep<S, E> extends MapStep<S, Map<String, E>> implement
                 bindings.put(selectKey, TraversalUtil.applyNullable(end, this.traversalRing.next()));
             else {
                 this.traversalRing.reset();
-                return null;
+                return EmptyTraverser.instance();
             }
         }
         this.traversalRing.reset();
-        return bindings;
-    }
-
-    @Override
-    protected boolean isEmptyTraverser(final Map<String, E> obj) {
-        return null == obj;
+        return PathProcessor.processTraverserPathLabels(traverser.split(bindings, this), this.keepLabels);
     }
 
     @Override
@@ -156,9 +153,4 @@ public final class SelectStep<S, E> extends MapStep<S, Map<String, E>> implement
     public Set<String> getKeepLabels() {
         return this.keepLabels;
     }
-
-    @Override
-    protected Traverser.Admin<Map<String, E>> processNextStart() {
-        return PathProcessor.processTraverserPathLabels(super.processNextStart(), this.keepLabels);
-    }
 }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SumLocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SumLocalStep.java
index 72e6539..3249e31 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SumLocalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SumLocalStep.java
@@ -32,7 +32,7 @@ import java.util.Set;
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  * @author Daniel Kuppitz (http://gremlin.guru)
  */
-public final class SumLocalStep<E extends Number, S extends Iterable<E>> extends MapStep<S, E> {
+public final class SumLocalStep<E extends Number, S extends Iterable<E>> extends ScalarMapStep<S, E> {
 
     public SumLocalStep(final Traversal.Admin traversal) {
         super(traversal);
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TailLocalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TailLocalStep.java
index ea678a5..336323e 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TailLocalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TailLocalStep.java
@@ -34,7 +34,7 @@ import java.util.Set;
 /**
  * @author Matt Frantz (http://github.com/mhfrantz)
  */
-public final class TailLocalStep<S> extends MapStep<S, S> {
+public final class TailLocalStep<S> extends ScalarMapStep<S, S> {
 
     private final long limit;
 
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TraversalMapStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TraversalMapStep.java
index 32c2e3c..7dc82d7 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TraversalMapStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TraversalMapStep.java
@@ -29,10 +29,12 @@ import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
+import java.util.NoSuchElementException;
 import java.util.Set;
 
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
+ * @author Stephen Mallette (http://stephen.genoprime.com)
  */
 public final class TraversalMapStep<S, E> extends MapStep<S, E> implements TraversalParent {
 
@@ -44,14 +46,10 @@ public final class TraversalMapStep<S, E> extends MapStep<S, E> implements Trave
     }
 
     @Override
-    protected E map(final Traverser.Admin<S> traverser) {
+    protected Traverser.Admin<E> processNextStart() throws NoSuchElementException {
+        final Traverser.Admin<S> traverser = this.starts.next();
         final Iterator<E> iterator = TraversalUtil.applyAll(traverser, this.mapTraversal);
-        return iterator.hasNext() ? iterator.next() : null;
-    }
-
-    @Override
-    protected boolean isEmptyTraverser(final E obj) {
-        return null == obj;
+        return  iterator.hasNext() ? traverser.split(iterator.next(), this) : EmptyTraverser.instance();
     }
 
     @Override
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TraversalSelectStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TraversalSelectStep.java
index 71ac06c..b1b1263 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TraversalSelectStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TraversalSelectStep.java
@@ -26,6 +26,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.ByModulating;
 import org.apache.tinkerpop.gremlin.process.traversal.step.PathProcessor;
 import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
 import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
+import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.EmptyTraverser;
 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil;
 import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
 
@@ -54,26 +55,42 @@ public final class TraversalSelectStep<S, E> extends MapStep<S, E> implements Tr
     }
 
     @Override
-    protected E map(final Traverser.Admin<S> traverser) {
+    protected Traverser.Admin<E> processNextStart() {
+        final Traverser.Admin<S> traverser = this.starts.next();
+
+        boolean found = false;
         E end = null;
         final Iterator<E> keyIterator = TraversalUtil.applyAll(traverser, this.keyTraversal);
         if (keyIterator.hasNext()) {
             final E key = keyIterator.next();
             final Object object = traverser.get();
-            if (object instanceof Map && ((Map) object).containsKey(key))
+            if (object instanceof Map && ((Map) object).containsKey(key)) {
                 end = (E) ((Map) object).get(key);
-            else if (key instanceof String) {
+                found = true;
+            } else if (key instanceof String) {
                 final String skey = (String) key;
                 if (traverser.getSideEffects().exists(skey)) {
                     end = traverser.getSideEffects().get((String) key);
+                    found = true;
                 } else {
                     final Path path = traverser.path();
-                    if (path.hasLabel(skey))
+                    if (path.hasLabel(skey)) {
                         end = null == pop ? path.get(skey) : path.get(pop, skey);
+                        found = true;
+                    }
                 }
             }
         }
-        return null != end ? TraversalUtil.applyNullable(end, this.selectTraversal) : null;
+
+        if (found) {
+            final Traverser.Admin<E> outTraverser = traverser.split(null == end ? null : TraversalUtil.applyNullable(end, this.selectTraversal), this);
+            if (!(this.getTraversal().getParent() instanceof MatchStep)) {
+                PathProcessor.processTraverserPathLabels(outTraverser, this.keepLabels);
+            }
+            return outTraverser;
+        } else {
+            return EmptyTraverser.instance();
+        }
     }
 
     @Override
@@ -130,11 +147,6 @@ public final class TraversalSelectStep<S, E> extends MapStep<S, E> implements Tr
                 TraverserRequirement.PATH);
     }
 
-    //@Override
-    //public Set<String> getScopeKeys() {
-    //    return Collections.singleton(this.selectKey);
-    //}
-
     public Pop getPop() {
         return this.pop;
     }
@@ -148,15 +160,6 @@ public final class TraversalSelectStep<S, E> extends MapStep<S, E> implements Tr
     public Set<String> getKeepLabels() {
         return this.keepLabels;
     }
-
-    @Override
-    protected Traverser.Admin<E> processNextStart() {
-        final Traverser.Admin<E> traverser = super.processNextStart();
-        if (!(this.getTraversal().getParent() instanceof MatchStep)) {
-            PathProcessor.processTraverserPathLabels(traverser, this.keepLabels);
-        }
-        return traverser;
-    }
 }
 
 
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/finalization/ReferenceElementStrategy.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/finalization/ReferenceElementStrategy.java
index 41e1325..ebb0423 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/finalization/ReferenceElementStrategy.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/finalization/ReferenceElementStrategy.java
@@ -21,7 +21,7 @@ package org.apache.tinkerpop.gremlin.process.traversal.strategy.finalization;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.MapStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.ScalarMapStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.ProfileSideEffectStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.EmptyStep;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.AbstractTraversalStrategy;
@@ -56,7 +56,7 @@ public final class ReferenceElementStrategy extends AbstractTraversalStrategy<Tr
         return INSTANCE;
     }
 
-    public static class ReferenceElementStep<S, E> extends MapStep<S, E> {
+    public static class ReferenceElementStep<S, E> extends ScalarMapStep<S, E> {
 
         public ReferenceElementStep(final Traversal.Admin traversal) {
             super(traversal);
diff --git a/gremlin-test/features/map/Map.feature b/gremlin-test/features/map/Map.feature
index 8d1029f..5f404ea 100644
--- a/gremlin-test/features/map/Map.feature
+++ b/gremlin-test/features/map/Map.feature
@@ -106,3 +106,19 @@ Feature: Step - map()
       | v[josh] |
       | v[ripple] |
       | v[peter]  |
+
+  Scenario: g_V_mapXconstantXnullXX
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().map(__.constant(null))
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | null |
+      | null |
+      | null |
+      | null |
+      | null |
+      | null |
\ No newline at end of file
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MapTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MapTest.java
index 9006fec..35faa03 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MapTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MapTest.java
@@ -31,9 +31,11 @@ import org.junit.runner.RunWith;
 import java.util.List;
 
 import static org.apache.tinkerpop.gremlin.LoadGraphWith.GraphData.MODERN;
+import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.constant;
 import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.select;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 /**
@@ -53,6 +55,8 @@ public abstract class MapTest extends AbstractGremlinProcessTest {
 
     public abstract Traversal<Vertex, Vertex> get_g_V_mapXselectXaXX();
 
+    public abstract Traversal<Vertex, Vertex> get_g_V_mapXconstantXnullXX();
+
     @Test
     @LoadGraphWith(MODERN)
     public void g_VX1X_mapXnameX() {
@@ -139,6 +143,19 @@ public abstract class MapTest extends AbstractGremlinProcessTest {
         assertEquals(6, counter);
     }
 
+    @Test
+    @LoadGraphWith(MODERN)
+    public void g_V_mapXconstantXnullXX() {
+        final Traversal<Vertex, Vertex> traversal = get_g_V_mapXconstantXnullXX();
+        printTraversalForm(traversal);
+        int counter = 0;
+        while (traversal.hasNext()) {
+            counter++;
+            assertNull(traversal.next());
+        }
+        assertEquals(6, counter);
+    }
+
     public static class Traversals extends MapTest {
         @Override
         public Traversal<Vertex, String> get_g_VX1X_mapXnameX(final Object v1Id) {
@@ -169,5 +186,10 @@ public abstract class MapTest extends AbstractGremlinProcessTest {
         public Traversal<Vertex, Vertex> get_g_V_mapXselectXaXX() {
             return g.V().as("a").map(select("a"));
         }
+
+        @Override
+        public Traversal<Vertex, Vertex> get_g_V_mapXconstantXnullXX() {
+            return g.V().map(constant(null));
+        }
     }
 }
diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/decoration/OptionsStrategyTest.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/decoration/OptionsStrategyTest.java
index ce39953..32a77f2 100644
--- a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/decoration/OptionsStrategyTest.java
+++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/decoration/OptionsStrategyTest.java
@@ -21,7 +21,7 @@ package org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.strategy.deco
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
-import org.apache.tinkerpop.gremlin.process.traversal.step.map.MapStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.ScalarMapStep;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.OptionsStrategy;
 import org.apache.tinkerpop.gremlin.structure.Graph;
 import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
@@ -54,7 +54,7 @@ public class OptionsStrategyTest {
 
     private static void assertOptions(final GraphTraversalSource optionedG) {
         GraphTraversal t = optionedG.inject(1);
-        t = t.asAdmin().addStep(new MapStep<Object, Object>(t.asAdmin()) {
+        t = t.asAdmin().addStep(new ScalarMapStep<Object, Object>(t.asAdmin()) {
             @Override
             protected Object map(final Traverser.Admin<Object> traverser) {
                 final OptionsStrategy strategy = traversal.asAdmin().getStrategies().getStrategy(OptionsStrategy.class).get();


[tinkerpop] 18/23: TINKERPOP-2235 Refactored Scoping interface

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2235
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit b15437c6fc10b14ddf03389c154f3509e2eb0d86
Author: stephen <sp...@gmail.com>
AuthorDate: Wed Nov 20 08:39:09 2019 -0500

    TINKERPOP-2235 Refactored Scoping interface
    
    Pulled back all functionality that grabs scope values to Scoping. In this way we have just one place for this logic. Also decided to use exceptions to deal with keys not being found. Not thinking the performance costs of the exception will matter here much as we won't be generating exceptions repeatedly for purpose of flow control - we really just generate them when there is actual error in the traversal syntax and the traversal is headed for exception anyway. if we find that this is  [...]
---
 .../gremlin/process/traversal/step/Scoping.java    | 83 +++++++++++++++++-----
 .../traversal/step/filter/DedupGlobalStep.java     |  4 +-
 .../traversal/step/filter/WherePredicateStep.java  |  4 +-
 .../traversal/step/filter/WhereTraversalStep.java  |  4 +-
 .../process/traversal/step/map/SelectOneStep.java  | 31 ++------
 .../process/traversal/step/map/SelectStep.java     | 15 ++--
 .../traversal/step/map/TraversalSelectStep.java    | 51 ++++++-------
 .../Gherkin/TraversalEvaluation/TraversalParser.cs |  3 +-
 gremlin-test/features/sideEffect/Inject.feature    | 14 +++-
 .../traversal/step/sideEffect/InjectTest.java      | 17 +++++
 10 files changed, 135 insertions(+), 91 deletions(-)

diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/Scoping.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/Scoping.java
index dee401c..e2c0e17 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/Scoping.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/Scoping.java
@@ -24,7 +24,6 @@ import org.apache.tinkerpop.gremlin.process.traversal.Step;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 
 import java.util.Map;
-import java.util.Optional;
 import java.util.Set;
 
 /**
@@ -110,28 +109,54 @@ public interface Scoping {
 
     public enum Variable {START, END}
 
-    public default <S> S getScopeValue(final Pop pop, final String key, final Traverser.Admin<?> traverser) throws IllegalArgumentException {
-        return Optional.<S>ofNullable(getNullableScopeValue(pop, key, traverser)).orElseThrow(
-                () -> new IllegalArgumentException("Neither the sideEffects, map, nor path has a " + key + "-key: " + this));
+    /**
+     * Finds the object with the specified key for the current traverser and throws an exception if the key cannot
+     * be found.
+     *
+     * @throws KeyNotFoundException if the key does not exist
+     */
+    public default <S> S getScopeValue(final Pop pop, final Object key, final Traverser.Admin<?> traverser) throws KeyNotFoundException {
+        final Object object = traverser.get();
+        if (object instanceof Map && ((Map) object).containsKey(key))
+            return (S) ((Map) object).get(key);
+
+        if (key instanceof String) {
+            final String k = (String) key;
+            if (traverser.getSideEffects().exists(k))
+                return traverser.getSideEffects().get(k);
+
+            final Path path = traverser.path();
+            if (path.hasLabel(k))
+                return null == pop ? path.get(k) : path.get(pop, k);
+        }
+
+        throw new KeyNotFoundException(key, this);
     }
 
-    public default <S> S getNullableScopeValue(final Pop pop, final String key, final Traverser.Admin<?> traverser) {
-        return getScopeValue(pop, key, traverser, null);
+    /**
+     * Calls {@link #getScopeValue(Pop, Object, Traverser.Admin)} but throws an unchecked {@code IllegalStateException}
+     * if the key cannot be found.
+     */
+    public default <S> S getSafeScopeValue(final Pop pop, final Object key, final Traverser.Admin<?> traverser) {
+        try {
+            return getScopeValue(pop, key, traverser);
+        } catch (KeyNotFoundException nfe) {
+            throw new IllegalArgumentException(nfe.getMessage(), nfe);
+        }
     }
 
-    public default <S> S getScopeValue(final Pop pop, final String key, final Traverser.Admin<?> traverser, final S valueIfNotFound) {
-        final Object object = traverser.get();
-        if (object instanceof Map && ((Map<String, S>) object).containsKey(key))
-            return ((Map<String, S>) object).get(key);
-        ///
-        if (traverser.getSideEffects().exists(key))
-            return traverser.getSideEffects().get(key);
-        ///
-        final Path path = traverser.path();
-        if (path.hasLabel(key))
-            return path.get(pop, key);
-        ///
-        return valueIfNotFound;
+    /**
+     * Calls {@link #getScopeValue(Pop, Object, Traverser.Admin)} and returns {@code null} if the key is not found.
+     * Use this method with caution as {@code null} has one of two meanings as a return value. It could be that the
+     * key was found and its value was {@code null} or it might mean that the key was not found and {@code null} was
+     * simply returned.
+     */
+    public default <S> S getNullableScopeValue(final Pop pop, final String key, final Traverser.Admin<?> traverser) {
+        try {
+            return getScopeValue(pop, key, traverser);
+        } catch (KeyNotFoundException nfe) {
+            return null;
+        }
     }
 
     /**
@@ -140,4 +165,24 @@ public interface Scoping {
      * @return the accessed labels of the scoping step
      */
     public Set<String> getScopeKeys();
+
+    public static class KeyNotFoundException extends Exception {
+
+        private final Object key;
+        private final Scoping step;
+
+        public KeyNotFoundException(final Object key, final Scoping current) {
+            super("Neither the map, sideEffects, nor path has a " + key + "-key: " + current);
+            this.key = key;
+            this.step = current;
+        }
+
+        public Object getKey() {
+            return key;
+        }
+
+        public Scoping getStep() {
+            return step;
+        }
+    }
 }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/DedupGlobalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/DedupGlobalStep.java
index b4f70d9..911d2eb 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/DedupGlobalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/DedupGlobalStep.java
@@ -74,7 +74,7 @@ public final class DedupGlobalStep<S> extends FilterStep<S> implements Traversal
             return this.duplicateSet.add(TraversalUtil.applyNullable(traverser, this.dedupTraversal));
         } else {
             final List<Object> objects = new ArrayList<>(this.dedupLabels.size());
-            this.dedupLabels.forEach(label -> objects.add(TraversalUtil.applyNullable((S) this.getScopeValue(Pop.last, label, traverser), this.dedupTraversal)));
+            this.dedupLabels.forEach(label -> objects.add(TraversalUtil.applyNullable((S) this.getSafeScopeValue(Pop.last, label, traverser), this.dedupTraversal)));
             return this.duplicateSet.add(objects);
         }
     }
@@ -189,7 +189,7 @@ public final class DedupGlobalStep<S> extends FilterStep<S> implements Traversal
             if (null != this.dedupLabels) {
                 object = new ArrayList<>(this.dedupLabels.size());
                 for (final String label : this.dedupLabels) {
-                    ((List) object).add(TraversalUtil.applyNullable((S) this.getScopeValue(Pop.last, label, traverser), this.dedupTraversal));
+                    ((List) object).add(TraversalUtil.applyNullable((S) this.getSafeScopeValue(Pop.last, label, traverser), this.dedupTraversal));
                 }
             } else {
                 object = TraversalUtil.applyNullable(traverser, this.dedupTraversal);
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/WherePredicateStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/WherePredicateStep.java
index 240a4cc..62a88f4 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/WherePredicateStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/WherePredicateStep.java
@@ -79,7 +79,7 @@ public final class WherePredicateStep<S> extends FilterStep<S> implements Scopin
         if (predicate instanceof ConnectiveP)
             ((ConnectiveP<Object>) predicate).getPredicates().forEach(p -> this.setPredicateValues(p, traverser, selectKeysIterator));
         else
-            predicate.setValue(TraversalUtil.applyNullable((S) this.getScopeValue(Pop.last, selectKeysIterator.next(), traverser), this.traversalRing.next()));
+            predicate.setValue(TraversalUtil.applyNullable((S) this.getSafeScopeValue(Pop.last, selectKeysIterator.next(), traverser), this.traversalRing.next()));
     }
 
     public Optional<P<?>> getPredicate() {
@@ -99,7 +99,7 @@ public final class WherePredicateStep<S> extends FilterStep<S> implements Scopin
     protected boolean filter(final Traverser.Admin<S> traverser) {
         final Object value = null == this.startKey ?
                 TraversalUtil.applyNullable(traverser, this.traversalRing.next()) :
-                TraversalUtil.applyNullable((S) this.getScopeValue(Pop.last, this.startKey, traverser), this.traversalRing.next());
+                TraversalUtil.applyNullable((S) this.getSafeScopeValue(Pop.last, this.startKey, traverser), this.traversalRing.next());
         this.setPredicateValues(this.predicate, traverser, this.selectKeys.iterator());
         this.traversalRing.reset();
         return this.predicate.test(value);
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/WhereTraversalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/WhereTraversalStep.java
index 8c701a9..264575d 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/WhereTraversalStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/WhereTraversalStep.java
@@ -164,7 +164,7 @@ public final class WhereTraversalStep<S> extends FilterStep<S> implements Traver
                 ((WhereEndStep) this.getTraversal().getEndStep()).processStartTraverser(traverser);
             else if (this.getTraversal().getEndStep() instanceof ProfileStep && this.getTraversal().getEndStep().getPreviousStep() instanceof WhereEndStep)     // TOTAL SUCKY HACK!
                 ((WhereEndStep) this.getTraversal().getEndStep().getPreviousStep()).processStartTraverser(traverser);
-            return null == this.selectKey ? traverser.get() : this.getScopeValue(Pop.last, this.selectKey, traverser);
+            return null == this.selectKey ? traverser.get() : this.getSafeScopeValue(Pop.last, this.selectKey, traverser);
         }
 
         @Override
@@ -199,7 +199,7 @@ public final class WhereTraversalStep<S> extends FilterStep<S> implements Traver
 
         public void processStartTraverser(final Traverser.Admin traverser) {
             if (null != this.matchKey)
-                this.matchValue = this.getScopeValue(Pop.last, this.matchKey, traverser);
+                this.matchValue = this.getSafeScopeValue(Pop.last, this.matchKey, traverser);
         }
 
         @Override
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectOneStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectOneStep.java
index 44e22fe..1f3ebdc 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectOneStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectOneStep.java
@@ -18,7 +18,6 @@
  */
 package org.apache.tinkerpop.gremlin.process.traversal.step.map;
 
-import org.apache.tinkerpop.gremlin.process.traversal.Path;
 import org.apache.tinkerpop.gremlin.process.traversal.Pop;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
@@ -33,9 +32,7 @@ import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
 
 import java.util.Collections;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Set;
 
@@ -59,36 +56,16 @@ public final class SelectOneStep<S, E> extends MapStep<S, E> implements Traversa
     protected Traverser.Admin<E> processNextStart() throws NoSuchElementException {
         final Traverser.Admin<S> traverser = this.starts.next();
 
-        final Object object = traverser.get();
-        if (object instanceof Map && ((Map<String, S>) object).containsKey(this.selectKey)) {
-            final S o = ((Map<String, S>) object).get(this.selectKey);
+        try {
+            final S o = getScopeValue(pop, selectKey, traverser);
             if (null == o) return traverser.split(null, this);
             final Traverser.Admin<E> outTraverser = traverser.split(TraversalUtil.applyNullable(o, this.selectTraversal), this);
             if (!(this.getTraversal().getParent() instanceof MatchStep))
                 PathProcessor.processTraverserPathLabels(outTraverser, this.keepLabels);
             return outTraverser;
+        } catch (KeyNotFoundException nfe) {
+            return EmptyTraverser.instance();
         }
-
-        if (traverser.getSideEffects().exists(this.selectKey)) {
-            final S o = (S) traverser.getSideEffects().get(this.selectKey);
-            if (null == o) return traverser.split(null, this);
-            final Traverser.Admin<E> outTraverser = traverser.split(TraversalUtil.applyNullable(o, this.selectTraversal), this);
-            if (!(this.getTraversal().getParent() instanceof MatchStep))
-                PathProcessor.processTraverserPathLabels(outTraverser, this.keepLabels);
-            return outTraverser;
-        }
-
-        final Path path = traverser.path();
-        if (path.hasLabel(this.selectKey)) {
-            final S o = (S) path.get(pop, this.selectKey);
-            if (null == o) return traverser.split(null, this);
-            final Traverser.Admin<E> outTraverser = traverser.split(TraversalUtil.applyNullable(o, this.selectTraversal), this);
-            if (!(this.getTraversal().getParent() instanceof MatchStep))
-                PathProcessor.processTraverserPathLabels(outTraverser, this.keepLabels);
-            return outTraverser;
-        }
-
-        return EmptyTraverser.instance();
     }
 
     @Override
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectStep.java
index 2fa3e1f..5034c84 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectStep.java
@@ -66,16 +66,17 @@ public final class SelectStep<S, E> extends MapStep<S, Map<String, E>> implement
     protected Traverser.Admin<Map<String, E>> processNextStart() throws NoSuchElementException {
         final Traverser.Admin<S> traverser = this.starts.next();
         final Map<String, E> bindings = new LinkedHashMap<>(this.selectKeys.size(), 1.0f);
-        for (final String selectKey : this.selectKeys) {
-            final E end = this.getNullableScopeValue(this.pop, selectKey, traverser);
-            if (null != end)
+        try {
+            for (final String selectKey : this.selectKeys) {
+                final E end = this.getScopeValue(this.pop, selectKey, traverser);
                 bindings.put(selectKey, TraversalUtil.applyNullable(end, this.traversalRing.next()));
-            else {
-                this.traversalRing.reset();
-                return EmptyTraverser.instance();
             }
+        } catch (KeyNotFoundException nfe) {
+            return EmptyTraverser.instance();
+        } finally {
+            this.traversalRing.reset();
         }
-        this.traversalRing.reset();
+
         return PathProcessor.processTraverserPathLabels(traverser.split(bindings, this), this.keepLabels);
     }
 
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TraversalSelectStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TraversalSelectStep.java
index b1b1263..3813454 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TraversalSelectStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TraversalSelectStep.java
@@ -18,12 +18,12 @@
  */
 package org.apache.tinkerpop.gremlin.process.traversal.step.map;
 
-import org.apache.tinkerpop.gremlin.process.traversal.Path;
 import org.apache.tinkerpop.gremlin.process.traversal.Pop;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
 import org.apache.tinkerpop.gremlin.process.traversal.step.ByModulating;
 import org.apache.tinkerpop.gremlin.process.traversal.step.PathProcessor;
+import org.apache.tinkerpop.gremlin.process.traversal.step.Scoping;
 import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
 import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
 import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.EmptyTraverser;
@@ -34,14 +34,12 @@ import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
-
 /**
  * @author Daniel Kuppitz (http://gremlin.guru)
  */
-public final class TraversalSelectStep<S, E> extends MapStep<S, E> implements TraversalParent, PathProcessor, ByModulating {
+public final class TraversalSelectStep<S, E> extends MapStep<S, E> implements TraversalParent, PathProcessor, ByModulating, Scoping {
 
     private final Pop pop;
     private Traversal.Admin<S, E> keyTraversal;
@@ -57,43 +55,36 @@ public final class TraversalSelectStep<S, E> extends MapStep<S, E> implements Tr
     @Override
     protected Traverser.Admin<E> processNextStart() {
         final Traverser.Admin<S> traverser = this.starts.next();
-
-        boolean found = false;
-        E end = null;
         final Iterator<E> keyIterator = TraversalUtil.applyAll(traverser, this.keyTraversal);
         if (keyIterator.hasNext()) {
             final E key = keyIterator.next();
-            final Object object = traverser.get();
-            if (object instanceof Map && ((Map) object).containsKey(key)) {
-                end = (E) ((Map) object).get(key);
-                found = true;
-            } else if (key instanceof String) {
-                final String skey = (String) key;
-                if (traverser.getSideEffects().exists(skey)) {
-                    end = traverser.getSideEffects().get((String) key);
-                    found = true;
-                } else {
-                    final Path path = traverser.path();
-                    if (path.hasLabel(skey)) {
-                        end = null == pop ? path.get(skey) : path.get(pop, skey);
-                        found = true;
-                    }
+            try {
+                final E end = getScopeValue(pop, key, traverser);
+                final Traverser.Admin<E> outTraverser = traverser.split(null == end ? null : TraversalUtil.applyNullable(end, this.selectTraversal), this);
+                if (!(this.getTraversal().getParent() instanceof MatchStep)) {
+                    PathProcessor.processTraverserPathLabels(outTraverser, this.keepLabels);
                 }
+                return outTraverser;
+            } catch (KeyNotFoundException nfe) {
+                return EmptyTraverser.instance();
             }
-        }
-
-        if (found) {
-            final Traverser.Admin<E> outTraverser = traverser.split(null == end ? null : TraversalUtil.applyNullable(end, this.selectTraversal), this);
-            if (!(this.getTraversal().getParent() instanceof MatchStep)) {
-                PathProcessor.processTraverserPathLabels(outTraverser, this.keepLabels);
-            }
-            return outTraverser;
         } else {
             return EmptyTraverser.instance();
         }
     }
 
     @Override
+    public Set<String> getScopeKeys() {
+        // can't return scope keys here because they aren't known prior to traversal execution and this method is
+        // used at strategy application time. not getting any test failures as a result of returning empty. assuming
+        // that strategies don't use Scoping in a way that requires the keys to be known and if they aren't doesn't
+        // hose the whole traversal. in the worst case, strategies will hopefully just leave steps alone rather than
+        // make their own assumptions that the step is not selecting anything. if that is happening somehow we might
+        // need to modify Scoping to better suite this runtime evaluation of the key.
+        return Collections.emptySet();
+    }
+
+    @Override
     public String toString() {
         return StringFactory.stepString(this, this.pop, this.keyTraversal, this.selectTraversal);
     }
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalParser.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalParser.cs
index e81e00e..b747200 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalParser.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalParser.cs
@@ -38,7 +38,8 @@ namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation
             {
                 { "g.V().fold().count(Scope.local)", g => g.V().Fold().Count(Scope.Local)},
                 { "g.inject(10,20,null,20,10,10).groupCount(\"x\").dedup().as(\"y\").project(\"a\",\"b\").by().by(__.select(\"x\").select(__.select(\"y\")))", 
-                    g => g.Inject<object>(10,20,null,20,10,10).GroupCount("x").Dedup().As("y").Project<object>("a","b").By().By(__.Select<int>("x").Select<object>(__.Select<int>("y")))}
+                    g => g.Inject<object>(10,20,null,20,10,10).GroupCount("x").Dedup().As("y").Project<object>("a","b").By().By(__.Select<int>("x").Select<object>(__.Select<int>("y")))},
+                { "g.inject(m).select(\"name\",\"age\")", g => g.Inject(new Dictionary<string,object>() {{"name", "marko"}, {"age", null}}).Select<object>("name", "age") }
             };
 
         private static readonly Regex RegexNumeric =
diff --git a/gremlin-test/features/sideEffect/Inject.feature b/gremlin-test/features/sideEffect/Inject.feature
index 7e4983d..20d6bd2 100644
--- a/gremlin-test/features/sideEffect/Inject.feature
+++ b/gremlin-test/features/sideEffect/Inject.feature
@@ -92,4 +92,16 @@ Feature: Step - inject()
       | result |
       | m[{"a":"d[10].i", "b":"d[3].l"}] |
       | m[{"a":"d[20].i", "b":"d[2].l"}] |
-      | m[{"a":null, "b":"d[1].l"}] |
\ No newline at end of file
+      | m[{"a":null, "b":"d[1].l"}] |
+
+  Scenario: g_injectXname_marko_age_nullX_selectXname_ageX
+    Given the empty graph
+    And using the parameter m defined as "m[{\"name\":\"marko\", \"age\":null}]"
+    And the traversal of
+      """
+      g.inject(m).select("name","age")
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | m[{"name":"marko", "age":null}] |
\ No newline at end of file
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/InjectTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/InjectTest.java
index 6ce50c9..fd752d2 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/InjectTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/InjectTest.java
@@ -53,6 +53,8 @@ public abstract class InjectTest extends AbstractGremlinProcessTest {
 
     public abstract Traversal<Integer, Map<String, Object>> get_g_injectX10_20_null_20_10_10X_groupCountXxX_dedup_asXyX_projectXa_bX_by_byXselectXxX_selectXselectXyXXX();
 
+    public abstract Traversal<Map<String,Object>, Map<String, Object>> get_g_injectXname_marko_age_nullX_selectXname_ageX();
+
     @Test
     @LoadGraphWith(MODERN)
     public void g_VX1X_out_injectXv2X_name() {
@@ -107,6 +109,13 @@ public abstract class InjectTest extends AbstractGremlinProcessTest {
     }
 
     @Test
+    public void g_injectXname_marko_age_nullX_selectXname_ageX() {
+        final Traversal<Map<String, Object>, Map<String, Object>> traversal = get_g_injectXname_marko_age_nullX_selectXname_ageX();
+        printTraversalForm(traversal);
+        checkResults(makeMapList(2, "name", "marko", "age", null), traversal);
+    }
+
+    @Test
     @LoadGraphWith(MODERN)
     public void g_VX1X_injectXg_VX4XX_out_name() {
         final Traversal<Vertex, String> traversal = get_g_VX1X_injectXg_VX4XX_out_name(convertToVertexId("marko"), convertToVertexId("josh"));
@@ -144,5 +153,13 @@ public abstract class InjectTest extends AbstractGremlinProcessTest {
                        by().
                        by(__.select("x").select(__.select("y")));
         }
+
+        @Override
+        public Traversal<Map<String, Object>, Map<String, Object>> get_g_injectXname_marko_age_nullX_selectXname_ageX() {
+            final Map<String,Object> m = new HashMap<>();
+            m.put("name", "marko");
+            m.put("age", null);
+            return g.inject(m).select("name","age");
+        }
     }
 }


[tinkerpop] 20/23: TINKERPOP-2235 More consistent null handling for vertex mutations

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2235
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit b5c033e25f9123e5f6d25150ead139e6baebf9e5
Author: stephen <sp...@gmail.com>
AuthorDate: Tue Nov 26 09:50:41 2019 -0500

    TINKERPOP-2235 More consistent null handling for vertex mutations
    
    Made addV(null) and property(id, null) work more consistently.
---
 docs/src/upgrade/release-3.5.x.asciidoc            | 41 ++++++++++++++++++++++
 .../tinkerpop/gremlin/jsr223/JavaTranslator.java   | 14 ++++++--
 .../gremlin/structure/util/ElementHelper.java      |  3 +-
 .../gremlin/structure/util/ElementHelperTest.java  |  4 +--
 gremlin-test/features/map/AddVertex.feature        |  9 +++++
 .../process/traversal/step/map/AddVertexTest.java  | 24 ++++++++++++-
 6 files changed, 88 insertions(+), 7 deletions(-)

diff --git a/docs/src/upgrade/release-3.5.x.asciidoc b/docs/src/upgrade/release-3.5.x.asciidoc
index cdd920b..ce7a00e 100644
--- a/docs/src/upgrade/release-3.5.x.asciidoc
+++ b/docs/src/upgrade/release-3.5.x.asciidoc
@@ -358,6 +358,45 @@ See: link:https://issues.apache.org/jira/browse/TINKERPOP-1568[TINKERPOP-1568],
 link:https://issues.apache.org/jira/browse/TINKERPOP-2310[TINKERPOP-2310],
 link:https://issues.apache.org/jira/browse/TINKERPOP-2311[TINKERPOP-2311]
 
+===== Null Semantics
+
+Graph providers should take note of the changes to `null` semantics described in the "users" section of these upgrade
+notes. As `null` is now acceptable as a `Traverser` object, this change may affect custom steps. Further note that
+`null` now works more consistently with mutation steps and graph providers may need to include additional logic to
+deal with those possible conditions. Please see the console session below which uses TinkerGraph to demonstrate the
+current behavioral expectations:
+
+[source,text]
+----
+gremlin> g.addV(null).property(id, null).property('name',null)
+==>v[0]
+gremlin> g.V().elementMap()
+==>[id:0,label:vertex]
+----
+
+In the above example, `addV()` defaults to `Vertex.DEFAULT_LABEL`, the `id` is generated and setting the "name"
+property to `null` results in the value not being set. If the property value is set to an actual value and then set
+to `null` TinkerGraph will remove the property key all together:
+
+[source,text]
+----
+gremlin> g.V().property('name','stephen')
+==>v[0]
+gremlin> g.V().elementMap()
+==>[id:0,label:vertex,name:stephen]
+gremlin> g.V().property('name',null)
+==>v[0]
+gremlin> g.V().elementMap()
+==>[id:0,label:vertex]
+----
+
+The above examples point out the default operations of TinkerGraph, but it can be configured to actually accept the
+`null` as a property value and it is up to graph providers to decided how they wish to treat a `null` property value.
+Providers should use the new `supportsNullPropertyValues()` feature to indicate to users how `null` is handled.
+
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-2235[TINKERPOP-2235],
+link:https://issues.apache.org/jira/browse/TINKERPOP-2099[TINKERPOP-2099]
+
 ===== AbstractOpProcessor API Change
 
 The `generateMetaData()` method was removed as it was deprecated in a previous version. There already was a preferred
@@ -373,6 +412,8 @@ that rely on the old step names.
 
 See: link:https://issues.apache.org/jira/browse/TINKERPOP-2254[TINKERPOP-2254]
 
+==== Graph Driver Providers
+
 ===== TraversalOpProcessor Side-effects
 
 `TraversalOpProcessor` no longer holds a cache of side-effects and more generally the entire side-effect protocol has
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
index bb9273c..6034793 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
@@ -235,8 +235,18 @@ public final class JavaTranslator<S extends TraversalSource, T extends Traversal
                                 // is valid hits like four different possible overloads, but we can rely on the most
                                 // generic one which takes Object as the second parameter. that seems to work in this
                                 // case, but it's a shame this isn't nicer. seems like nicer would mean a heavy
-                                // overhaul to Gremlin or to GLVs/bytecode and/or to serialization mechanisms
-                                if (i < argumentsCopy.length && ((null == argumentsCopy[i] && parameters[i].getType() == Object.class) ||
+                                // overhaul to Gremlin or to GLVs/bytecode and/or to serialization mechanisms.
+                                //
+                                // the check where argumentsCopy[i] is null could be accompanied by a type check for
+                                // allowable signatures like:
+                                // null == argumentsCopy[i] && parameters[i].getType() == Object.class
+                                // but that doesn't seem helpful. perhaps this approach is fine as long as we ensure
+                                // consistency of null calls to all overloads. in other words addV(String) must behave
+                                // the same as addV(Traversal) if null is used as the argument. so far, that seems to
+                                // be the case. if we find that is not happening we either fix that specific
+                                // inconsistency, start special casing those method finds here, or as mentioned above
+                                // do something far more drastic that doesn't involve reflection.
+                                if (i < argumentsCopy.length && (null == argumentsCopy[i] ||
                                         (argumentsCopy[i] != null && (
                                         parameters[i].getType().isAssignableFrom(argumentsCopy[i].getClass()) ||
                                                 (parameters[i].getType().isPrimitive() &&
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelper.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelper.java
index a121abd..0d6900b 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelper.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelper.java
@@ -122,12 +122,11 @@ public final class ElementHelper {
      *
      * @param keyValues a list of key/value pairs
      * @return the value associated with {@link T#id}
-     * @throws NullPointerException if the value for the {@link T#id} key is {@code null}
      */
     public static Optional<Object> getIdValue(final Object... keyValues) {
         for (int i = 0; i < keyValues.length; i = i + 2) {
             if (keyValues[i].equals(T.id))
-                return Optional.of(keyValues[i + 1]);
+                return Optional.ofNullable(keyValues[i + 1]);
         }
         return Optional.empty();
     }
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelperTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelperTest.java
index d4f1a77..3d7be9c 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelperTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelperTest.java
@@ -134,9 +134,9 @@ public class ElementHelperTest {
         assertFalse(ElementHelper.getIdValue("test", 321, "xyz", 123l, "testagain", "that").isPresent());
     }
 
-    @Test(expected = NullPointerException.class)
+    @Test
     public void shouldNotFindAnIdValueBecauseItIsNull() {
-        ElementHelper.getIdValue("test", 321, T.id, null, "testagain", "that");
+        assertEquals("default", ElementHelper.getIdValue("test", 321, T.id, null, "testagain", "that").orElse("default"));
     }
 
     @Test
diff --git a/gremlin-test/features/map/AddVertex.feature b/gremlin-test/features/map/AddVertex.feature
index 0fc338f..e8e1248 100644
--- a/gremlin-test/features/map/AddVertex.feature
+++ b/gremlin-test/features/map/AddVertex.feature
@@ -412,3 +412,12 @@ Feature: Step - addV()
       | result |
       | name |
 
+  Scenario: g_addVXnullX_propertyXid_nullX
+    Given the empty graph
+    And the traversal of
+      """
+      g.addV(null).property(T.id, null)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 1 for count of "g.V().hasLabel(\"vertex\")"
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexTest.java
index 8937a44..fec146d 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexTest.java
@@ -44,6 +44,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.Is.is;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 /**
@@ -60,6 +61,8 @@ public abstract class AddVertexTest extends AbstractGremlinTest {
 
     public abstract Traversal<Vertex, Vertex> get_g_V_hasLabelXpersonX_propertyXname_nullX();
 
+    public abstract Traversal<Vertex, Vertex> get_g_addVXnullX_propertyXid_nullX();
+
     public abstract Traversal<Vertex, Vertex> get_g_addVXpersonX_propertyXsingle_name_stephenX_propertyXsingle_name_stephenmX();
 
     public abstract Traversal<Vertex, Vertex> get_g_addVXpersonX_propertyXsingle_name_stephenX_propertyXsingle_name_stephenm_since_2010X();
@@ -95,7 +98,6 @@ public abstract class AddVertexTest extends AbstractGremlinTest {
         assertEquals(7, IteratorUtils.count(g.V()));
     }
 
-
     @Test
     @LoadGraphWith(MODERN)
     @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_ADD_VERTICES)
@@ -116,6 +118,20 @@ public abstract class AddVertexTest extends AbstractGremlinTest {
     }
 
     @Test
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_ADD_VERTICES)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_ADD_PROPERTY)
+    public void g_addVXnullX_propertyXid_nullX() {
+        final Traversal<Vertex, Vertex> traversal = get_g_addVXnullX_propertyXid_nullX();
+        printTraversalForm(traversal);
+
+        final Vertex vertex = traversal.next();
+        assertEquals(Vertex.DEFAULT_LABEL, vertex.label());
+
+        // should generate an id for the null value
+        assertNotNull(vertex.id());
+    }
+
+    @Test
     @LoadGraphWith(MODERN)
     @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_ADD_VERTICES)
     @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_ADD_PROPERTY)
@@ -381,5 +397,11 @@ public abstract class AddVertexTest extends AbstractGremlinTest {
         public Traversal<Vertex, Map<Object, Object>> get_g_V_asXaX_hasXname_markoX_outXcreatedX_asXbX_addVXselectXaX_labelX_propertyXtest_selectXbX_labelX_valueMap_withXtokensX() {
             return g.V().as("a").has("name", "marko").out("created").as("b").addV(select("a").label()).property("test", select("b").label()).valueMap().with(WithOptions.tokens);
         }
+
+        @Override
+        public Traversal<Vertex, Vertex> get_g_addVXnullX_propertyXid_nullX() {
+            // testing Traversal but should work the same for String
+            return g.addV((Traversal.Admin<?, String>) null).property(T.id, null);
+        }
     }
 }
\ No newline at end of file


[tinkerpop] 19/23: TINKERPOP-2235 Ensured null defaults to default vertex label for all overloads of addV()

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2235
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 185a32db316a0a5eabf589a52aeeb1b2188e53bb
Author: stephen <sp...@gmail.com>
AuthorDate: Tue Nov 26 08:02:40 2019 -0500

    TINKERPOP-2235 Ensured null defaults to default vertex label for all overloads of addV()
---
 CHANGELOG.asciidoc                                 |  3 +-
 .../traversal/dsl/graph/GraphTraversalSource.java  |  6 ++-
 .../traversal/step/map/AddVertexStartStep.java     |  2 +-
 .../process/traversal/step/map/AddVertexStep.java  |  2 +-
 .../traversal/step/map/AddVertexStepTest.java      | 48 ++++++++++++++++++++++
 5 files changed, 56 insertions(+), 5 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index b88d717..c7a2b36 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -25,7 +25,8 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 
 This release also includes changes from <<release-3-4-3, 3.4.3>>.
 
-* Allowed the possibility for the user of `null` in Gremlin.
+* Allowed the possibility for the propagation of `null` as a `Traverser` in Gremlin.
+* Ensured better consistency of the use of `null` as arguments to mutation steps.
 * Added a `Graph.Feature` for `supportsNullPropertyValues`.
 * Refactored `MapStep` to move its logic to `ScalarMapStep` so that the old behavior could be preserved while allow other implementations to have more flexibility.
 * Modified TinkerGraph to support `null` property values and can be configured to disable that feature.
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java
index 0b2398f..011bae7 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java
@@ -297,7 +297,8 @@ public class GraphTraversalSource implements TraversalSource {
 
 
     /**
-     * Spawns a {@link GraphTraversal} by adding a vertex with the specified label.
+     * Spawns a {@link GraphTraversal} by adding a vertex with the specified label. If the {@code label} is
+     * {@code null} then it will default to {@link Vertex#DEFAULT_LABEL}.
      */
     public GraphTraversal<Vertex, Vertex> addV(final String label) {
         final GraphTraversalSource clone = this.clone();
@@ -307,7 +308,8 @@ public class GraphTraversalSource implements TraversalSource {
     }
 
     /**
-     * Spawns a {@link GraphTraversal} by adding a vertex with the label as determined by a {@link Traversal}.
+     * Spawns a {@link GraphTraversal} by adding a vertex with the label as determined by a {@link Traversal}. If the
+     * {@code vertexLabelTraversal} is {@code null} then it will default to {@link Vertex#DEFAULT_LABEL}.
      */
     public GraphTraversal<Vertex, Vertex> addV(final Traversal<?, String> vertexLabelTraversal) {
         final GraphTraversalSource clone = this.clone();
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStartStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStartStep.java
index 547517c..6273fa2 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStartStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStartStep.java
@@ -58,7 +58,7 @@ public class AddVertexStartStep extends AbstractStep<Vertex, Vertex>
 
     public AddVertexStartStep(final Traversal.Admin traversal, final Traversal<?, String> vertexLabelTraversal) {
         super(traversal);
-        this.parameters.set(this, T.label, vertexLabelTraversal);
+        this.parameters.set(this, T.label, null == vertexLabelTraversal ? Vertex.DEFAULT_LABEL : vertexLabelTraversal);
     }
 
     @Override
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStep.java
index 296e8b5..b963db2 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStep.java
@@ -53,7 +53,7 @@ public class AddVertexStep<S> extends ScalarMapStep<S, Vertex>
 
     public AddVertexStep(final Traversal.Admin traversal, final Traversal.Admin<S,String> vertexLabelTraversal) {
         super(traversal);
-        this.parameters.set(this, T.label, vertexLabelTraversal);
+        this.parameters.set(this, T.label, null == vertexLabelTraversal ? Vertex.DEFAULT_LABEL : vertexLabelTraversal);
     }
 
     @Override
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStepTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStepTest.java
new file mode 100644
index 0000000..7022bc8
--- /dev/null
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStepTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+package org.apache.tinkerpop.gremlin.process.traversal.step.map;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.structure.T;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+public class AddVertexStepTest {
+
+    @Test
+    public void shouldDefaultTheLabelIfNullString() {
+        final Traversal.Admin t = mock(Traversal.Admin.class);
+        final AddVertexStartStep starStep = new AddVertexStartStep(t, (String) null);
+        assertEquals(Vertex.DEFAULT_LABEL, starStep.getParameters().getRaw().get(T.label).get(0));
+        final AddVertexStep step = new AddVertexStep(t, (String) null);
+        assertEquals(Vertex.DEFAULT_LABEL, starStep.getParameters().getRaw().get(T.label).get(0));
+    }
+
+    @Test
+    public void shouldDefaultTheLabelIfNullTraversal() {
+        final Traversal.Admin t = mock(Traversal.Admin.class);
+        final AddVertexStartStep starStep = new AddVertexStartStep(t, (Traversal<?,String>) null);
+        assertEquals(Vertex.DEFAULT_LABEL, starStep.getParameters().getRaw().get(T.label).get(0));
+        final AddVertexStep step = new AddVertexStep(t, (String) null);
+        assertEquals(Vertex.DEFAULT_LABEL, starStep.getParameters().getRaw().get(T.label).get(0));
+    }
+}


[tinkerpop] 07/23: TINKERPOP-2235 Cleanup comments/javadoc a bit for MapStep

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2235
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit f94956a19831706c41bf46f4d9a7c985fb576623
Author: stephen <sp...@gmail.com>
AuthorDate: Wed Nov 6 11:46:45 2019 -0500

    TINKERPOP-2235 Cleanup comments/javadoc a bit for MapStep
---
 .../gremlin/process/traversal/step/map/MapStep.java          | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MapStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MapStep.java
index 2747b9c..51535e7 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MapStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MapStep.java
@@ -25,6 +25,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.EmptyTraver
 
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
+ * @author Stephen Mallette (http://stephen.genoprime.com)
  */
 public abstract class MapStep<S, E> extends AbstractStep<S, E> {
 
@@ -36,10 +37,6 @@ public abstract class MapStep<S, E> extends AbstractStep<S, E> {
     protected Traverser.Admin<E> processNextStart() {
         final Traverser.Admin<S> traverser = this.starts.next();
         final E obj = this.map(traverser);
-
-        // maybe looks tricky, but it's just a play on Java generics without having to tear apart every MapStep
-        // instance. basically just want to respect the fact that a subclass can return an EmptyTraverser from
-        // map() and if so, just return that empty.
         return isEmptyTraverser(obj) ? EmptyTraverser.instance() : traverser.split(obj, this);
     }
 
@@ -47,9 +44,12 @@ public abstract class MapStep<S, E> extends AbstractStep<S, E> {
 
     /**
      * Determines if the value returned from {@link #map(Traverser.Admin)} should be representative of an
-     * {@link EmptyTraverser}. Such traversers will effectively be filtered out by the traversal.
+     * {@link EmptyTraverser}. Such traversers will effectively be filtered out by the traversal. This method works in
+     * conjunction with {@link #map(Traverser.Admin)} in the sense that both work are called in the default
+     * implementation of {@link #processNextStart()} which will call this method after "map()"-ing the traverser to
+     * determine if that step consider the returned value as "empty".
      */
-    protected boolean isEmptyTraverser(E obj) {
+    protected boolean isEmptyTraverser(final E obj) {
         return false;
     }
 


[tinkerpop] 23/23: TINKERPOP-2235 Fixed up label overrides for property(label, Object)

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2235
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit db3933f229350c83b90a2c83bb7c35b6be35ca0a
Author: stephen <sp...@gmail.com>
AuthorDate: Tue Nov 26 15:47:56 2019 -0500

    TINKERPOP-2235 Fixed up label overrides for property(label,Object)
---
 CHANGELOG.asciidoc                                     |  1 +
 .../process/traversal/step/map/AddVertexStartStep.java | 15 ++++++++++++++-
 .../process/traversal/step/map/AddVertexStep.java      | 15 ++++++++++++++-
 .../process/traversal/step/util/Parameters.java        | 11 +++++++++++
 .../process/traversal/step/util/ParametersTest.java    | 16 ++++++++++++++++
 gremlin-test/features/map/AddVertex.feature            | 10 ++++++++++
 .../process/traversal/step/map/AddVertexTest.java      | 18 ++++++++++++++++++
 .../gremlin/tinkergraph/structure/TinkerVertex.java    |  2 +-
 8 files changed, 85 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index c7a2b36..234e1b5 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -27,6 +27,7 @@ This release also includes changes from <<release-3-4-3, 3.4.3>>.
 
 * Allowed the possibility for the propagation of `null` as a `Traverser` in Gremlin.
 * Ensured better consistency of the use of `null` as arguments to mutation steps.
+* Allowed `property(T.label,Object)` to be used if no value was supplied to `addV(String)`.
 * Added a `Graph.Feature` for `supportsNullPropertyValues`.
 * Refactored `MapStep` to move its logic to `ScalarMapStep` so that the old behavior could be preserved while allow other implementations to have more flexibility.
 * Modified TinkerGraph to support `null` property values and can be configured to disable that feature.
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStartStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStartStep.java
index 6273fa2..eb54e6a 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStartStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStartStep.java
@@ -78,7 +78,20 @@ public class AddVertexStartStep extends AbstractStep<Vertex, Vertex>
 
     @Override
     public void configure(final Object... keyValues) {
-        this.parameters.set(this, keyValues);
+        if (keyValues[0] == T.label && this.parameters.contains(T.label)) {
+            if (this.parameters.contains(T.label, Vertex.DEFAULT_LABEL)) {
+                this.parameters.remove(T.label);
+                this.parameters.set(this, keyValues);
+            } else {
+                throw new IllegalArgumentException(String.format("Vertex T.label has already been set to [%s] and cannot be overridden with [%s]",
+                        this.parameters.getRaw().get(T.label).get(0), keyValues[1]));
+            }
+        } else if (keyValues[0] == T.id && this.parameters.contains(T.id)) {
+            throw new IllegalArgumentException(String.format("Vertex T.id has already been set to [%s] and cannot be overridden with [%s]",
+                    this.parameters.getRaw().get(T.id).get(0), keyValues[1]));
+        } else {
+            this.parameters.set(this, keyValues);
+        }
     }
 
     @Override
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStep.java
index b963db2..5b22d4e 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStep.java
@@ -73,7 +73,20 @@ public class AddVertexStep<S> extends ScalarMapStep<S, Vertex>
 
     @Override
     public void configure(final Object... keyValues) {
-        this.parameters.set(this, keyValues);
+        if (keyValues[0] == T.label && this.parameters.contains(T.label)) {
+            if (this.parameters.contains(T.label, Vertex.DEFAULT_LABEL)) {
+                this.parameters.remove(T.label);
+                this.parameters.set(this, keyValues);
+            } else {
+                throw new IllegalArgumentException(String.format("Vertex T.label has already been set to [%s] and cannot be overridden with [%s]",
+                        this.parameters.getRaw().get(T.label).get(0), keyValues[1]));
+            }
+        } else if (keyValues[0] == T.id && this.parameters.contains(T.id)) {
+            throw new IllegalArgumentException(String.format("Vertex T.id has already been set to [%s] and cannot be overridden with [%s]",
+                    this.parameters.getRaw().get(T.id).get(0), keyValues[1]));
+        } else {
+            this.parameters.set(this, keyValues);
+        }
     }
 
     @Override
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/Parameters.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/Parameters.java
index 22bb54f..f16fd70 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/Parameters.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/Parameters.java
@@ -74,6 +74,17 @@ public final class Parameters implements Cloneable, Serializable {
     }
 
     /**
+     * Checks for existence of a key and value in a parameter set.
+     *
+     * @param key the key to check
+     * @param value the value to check
+     * @return {@code true} if the key and value are present and {@code false} otherwise
+     */
+    public boolean contains(final Object key, final Object value) {
+        return this.contains(key) && this.parameters.get(key).contains(value);
+    }
+
+    /**
      * Renames a key in the parameter set.
      *
      * @param oldKey the key to rename
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/ParametersTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/ParametersTest.java
index c832cb7..27d8e44 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/ParametersTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/ParametersTest.java
@@ -221,6 +221,14 @@ public class ParametersTest {
     }
 
     @Test
+    public void shouldContainKeyValue() {
+        final Parameters parameters = new Parameters();
+        parameters.set(null, "a", "axe", "b", "bat", "c", "cat");
+
+        assertThat(parameters.contains("b", "bat"), is(true));
+    }
+
+    @Test
     public void shouldNotContainKey() {
         final Parameters parameters = new Parameters();
         parameters.set(null, "a", "axe", "b", "bat", "c", "cat");
@@ -229,6 +237,14 @@ public class ParametersTest {
     }
 
     @Test
+    public void shouldNotContainKeyAndValue() {
+        final Parameters parameters = new Parameters();
+        parameters.set(null, "a", "axe", "b", "bat", "c", "cat");
+
+        assertThat(parameters.contains("b", "mat"), is(false));
+    }
+
+    @Test
     public void shouldGetSetMultiple() {
         final Parameters parameters = new Parameters();
         parameters.set(null, "a", "axe", "a", "ant", "b", "bat", "b", "ball", "c", "cat");
diff --git a/gremlin-test/features/map/AddVertex.feature b/gremlin-test/features/map/AddVertex.feature
index e8e1248..943b303 100644
--- a/gremlin-test/features/map/AddVertex.feature
+++ b/gremlin-test/features/map/AddVertex.feature
@@ -421,3 +421,13 @@ Feature: Step - addV()
     When iterated to list
     Then the result should have a count of 1
     And the graph should return 1 for count of "g.V().hasLabel(\"vertex\")"
+
+  Scenario: g_addV_propertyXlabel_personX
+    Given the empty graph
+    And the traversal of
+      """
+      g.addV().property(T.label, "person")
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 1 for count of "g.V().hasLabel(\"person\")"
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexTest.java
index fec146d..ad0e1dd 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexTest.java
@@ -63,6 +63,8 @@ public abstract class AddVertexTest extends AbstractGremlinTest {
 
     public abstract Traversal<Vertex, Vertex> get_g_addVXnullX_propertyXid_nullX();
 
+    public abstract Traversal<Vertex, Vertex> get_g_addV_propertyXlabel_personX();
+
     public abstract Traversal<Vertex, Vertex> get_g_addVXpersonX_propertyXsingle_name_stephenX_propertyXsingle_name_stephenmX();
 
     public abstract Traversal<Vertex, Vertex> get_g_addVXpersonX_propertyXsingle_name_stephenX_propertyXsingle_name_stephenm_since_2010X();
@@ -132,6 +134,17 @@ public abstract class AddVertexTest extends AbstractGremlinTest {
     }
 
     @Test
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_ADD_VERTICES)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_ADD_PROPERTY)
+    public void g_addV_propertyXlabel_personX() {
+        final Traversal<Vertex, Vertex> traversal = get_g_addV_propertyXlabel_personX();
+        printTraversalForm(traversal);
+
+        final Vertex vertex = traversal.next();
+        assertEquals("person", vertex.label());
+    }
+
+    @Test
     @LoadGraphWith(MODERN)
     @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_ADD_VERTICES)
     @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_ADD_PROPERTY)
@@ -403,5 +416,10 @@ public abstract class AddVertexTest extends AbstractGremlinTest {
             // testing Traversal but should work the same for String
             return g.addV((Traversal.Admin<?, String>) null).property(T.id, null);
         }
+
+        @Override
+        public Traversal<Vertex, Vertex> get_g_addV_propertyXlabel_personX() {
+            return g.addV().property(T.label, "person");
+        }
     }
 }
\ No newline at end of file
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertex.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertex.java
index 1f055fb..1765054 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertex.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertex.java
@@ -87,7 +87,6 @@ public final class TinkerVertex extends TinkerElement implements Vertex {
         if (this.removed) throw elementAlreadyRemoved(Vertex.class, id);
         ElementHelper.legalPropertyKeyValueArray(keyValues);
         ElementHelper.validateProperty(key, value);
-        final Optional<Object> optionalId = ElementHelper.getIdValue(keyValues);
 
         // if we don't allow null property values and the value is null then the key can be removed but only if the
         // cardinality is single. if it is list/set then we can just ignore the null.
@@ -98,6 +97,7 @@ public final class TinkerVertex extends TinkerElement implements Vertex {
             return VertexProperty.empty();
         }
 
+        final Optional<Object> optionalId = ElementHelper.getIdValue(keyValues);
         final Optional<VertexProperty<V>> optionalVertexProperty = ElementHelper.stageVertexProperty(this, cardinality, key, value, keyValues);
         if (optionalVertexProperty.isPresent()) return optionalVertexProperty.get();
 


[tinkerpop] 12/23: TINKERPOP-2235 Improve map checks for null

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2235
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit ba454fdf15582879a184679f3a4e02806dda2b46
Author: stephen <sp...@gmail.com>
AuthorDate: Fri Nov 15 17:28:12 2019 -0500

    TINKERPOP-2235 Improve map checks for null
---
 .../apache/tinkerpop/gremlin/process/AbstractGremlinProcessTest.java | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/AbstractGremlinProcessTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/AbstractGremlinProcessTest.java
index ec9492b..09044de 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/AbstractGremlinProcessTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/AbstractGremlinProcessTest.java
@@ -34,6 +34,7 @@ import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.UUID;
 import java.util.stream.Collectors;
 
@@ -184,10 +185,10 @@ public abstract class AbstractGremlinProcessTest extends AbstractGremlinTest {
         }
 
         for (int i = 0; i < actualList.size(); i++) {
-            if (!actualList.get(i).getKey().equals(expectedList.get(i).getKey())) {
+            if (!Objects.equals(actualList.get(i).getKey(), expectedList.get(i).getKey())) {
                 return false;
             }
-            if (!actualList.get(i).getValue().equals(expectedList.get(i).getValue())) {
+            if (!Objects.equals(actualList.get(i).getValue(), expectedList.get(i).getValue())) {
                 return false;
             }
         }


[tinkerpop] 02/23: TINKERPOP-2235 Fix C# gherkin test harness

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2235
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 381d1e5488d73a7ed0344d607f7604a15f456042
Author: Jorge Bay Gondra <jo...@gmail.com>
AuthorDate: Tue Nov 5 12:26:58 2019 +0100

    TINKERPOP-2235 Fix C# gherkin test harness
    
    Fix C# gherkin test harness for method with params and generic type parameters.
    Add null as token.
---
 .../Gherkin/CommonSteps.cs                            |  3 ++-
 .../Gherkin/TraversalEvaluation/LiteralParameter.cs   | 11 ++++++++---
 .../Gherkin/TraversalEvaluation/TraversalParser.cs    | 19 +++++++++++++------
 3 files changed, 23 insertions(+), 10 deletions(-)

diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs
index d4b2aab..504f7a9 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs
@@ -63,7 +63,8 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                 {@"s\[(.*)\]", ToSet},
                 {@"m\[(.+)\]", ToMap},
                 {@"c\[(.+)\]", ToLambda},
-                {@"t\[(.+)\]", ToT}
+                {@"t\[(.+)\]", ToT},
+                {"null", (_, __) => null}
             }.ToDictionary(kv => new Regex("^" + kv.Key + "$", RegexOptions.Compiled), kv => kv.Value);
 
         private static readonly IDictionary<char, Func<string, object>> NumericParsers =
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/LiteralParameter.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/LiteralParameter.cs
index da0ac24..10a88a2 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/LiteralParameter.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/LiteralParameter.cs
@@ -29,10 +29,15 @@ namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation
     /// <summary>
     /// Represents a literal (number / boolean) that is allowed as a gremlin parameter.
     /// </summary>
-    public class LiteralParameter<T> : ITokenParameter, IEquatable<LiteralParameter<T>> where T : struct
+    public class LiteralParameter<T> : ITokenParameter, IEquatable<LiteralParameter<T>>
     {
         public bool Equals(LiteralParameter<T> other)
         {
+            if (Value == null)
+            {
+                return other.Value == null;
+            }
+
             return Value.Equals(other.Value);
         }
 
@@ -46,7 +51,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation
 
         public override int GetHashCode()
         {
-            return Value.GetHashCode();
+            return Value?.GetHashCode() ?? 0;
         }
 
         public T Value { get; }
@@ -79,7 +84,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation
 
     internal static class LiteralParameter
     {
-        public static LiteralParameter<TType> Create<TType>(TType value) where TType : struct
+        public static LiteralParameter<TType> Create<TType>(TType value)
         {
             return new LiteralParameter<TType>(value);
         }
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalParser.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalParser.cs
index 105932f..fb84fdd 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalParser.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalParser.cs
@@ -233,6 +233,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation
             var paramsInfo = method.GetParameters();
             var parameters = new object[paramsInfo.Length];
             genericParameterTypes = new Dictionary<string, Type>();
+
             for (var i = 0; i < paramsInfo.Length; i++)
             {
                 var info = paramsInfo[i];
@@ -246,15 +247,18 @@ namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation
                         // We've provided a value for parameter of a generic type, we can infer the
                         // type of the generic argument based on the parameter.
                         // For example, in the case of `Constant<E2>(E2 value)`
-                        // if we have the type of value we have the type of E2. 
+                        // if we have the type of value we have the type of E2.
                         genericParameterTypes.Add(info.ParameterType.Name, tokenParameter.GetParameterType());
                     }
                     else if (IsParamsArray(info) && info.ParameterType.GetElementType().IsGenericParameter)
                     {
                         // Its a method where the type parameter comes from an params Array
                         // e.g., Inject<S>(params S[] value)
-                        genericParameterTypes.Add(info.ParameterType.GetElementType().Name,
-                            tokenParameter.GetParameterType());
+                        var type = tokenParameter.GetParameterType();
+                        genericParameterTypes.Add(info.ParameterType.GetElementType().Name, type);
+
+                        // Use a placeholder value
+                        value = type.IsValueType ? Activator.CreateInstance(type) : new object();
                     }
 
                     if (info.ParameterType != tokenParameter.GetParameterType() && IsNumeric(info.ParameterType) &&
@@ -270,7 +274,6 @@ namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation
                     // For `params type[] value` we should provide an empty array
                     if (value == null)
                     {
-                        // An empty array
                         value = Array.CreateInstance(info.ParameterType.GetElementType(), 0);
                     }
                     else if (!value.GetType().IsArray)
@@ -286,8 +289,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation
                         }
 
                         var arr = Array.CreateInstance(elementType, token.Parameters.Count - i);
-                        arr.SetValue(value, 0);
-                        for (var j = 1; j < token.Parameters.Count - i; j++)
+                        for (var j = 0; j < token.Parameters.Count - i; j++)
                         {
                             arr.SetValue(token.Parameters[i + j].GetValue(), j);
                         }
@@ -449,6 +451,11 @@ namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation
                 i += parameterText.Length - 1;
                 return LiteralParameter.Create(Convert.ToBoolean(parameterText));
             }
+            if (parameterText == "null")
+            {
+                i += parameterText.Length - 1;
+                return LiteralParameter.Create<object>(null);
+            }
             if (RegexIO.IsMatch(parameterText))
             {
                 i += parameterText.Length - 1;


[tinkerpop] 06/23: TINKERPOP-2235 Added upgrade and reference docs

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2235
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 86c0db89fa4f77b69501d9be344fd5b53dac658e
Author: stephen <sp...@gmail.com>
AuthorDate: Tue Nov 5 16:05:11 2019 -0500

    TINKERPOP-2235 Added upgrade and reference docs
---
 CHANGELOG.asciidoc                                 |  3 +
 .../reference/implementations-tinkergraph.asciidoc |  1 +
 docs/src/upgrade/release-3.5.x.asciidoc            | 74 ++++++++++++++++++++--
 3 files changed, 72 insertions(+), 6 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 602c629..96d3d4f 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -25,6 +25,9 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 
 This release also includes changes from <<release-3-4-3, 3.4.3>>.
 
+* Allowed the possibility for the user of `null` in Gremlin.
+* Added a `Graph.Feature` for `supportsNullPropertyValues`.
+* Modified TinkerGraph to support `null` property values and can be configured to disable that feature.
 * Modified `null` handling in mutations to be consistent for a new `Vertex` as well as update to an existing one.
 * Upgraded to Apache Commons Configuration2.
 * Renamed `StoreStep` to `AggregateLocalStep`.
diff --git a/docs/src/reference/implementations-tinkergraph.asciidoc b/docs/src/reference/implementations-tinkergraph.asciidoc
index fe14d0f..e919bd4 100644
--- a/docs/src/reference/implementations-tinkergraph.asciidoc
+++ b/docs/src/reference/implementations-tinkergraph.asciidoc
@@ -121,6 +121,7 @@ TinkerGraph has several settings that can be provided on creation via `Configura
 |gremlin.tinkergraph.edgeIdManager |The `IdManager` implementation to use for edges.
 |gremlin.tinkergraph.vertexPropertyIdManager |The `IdManager` implementation to use for vertex properties.
 |gremlin.tinkergraph.defaultVertexPropertyCardinality |The default `VertexProperty.Cardinality` to use when `Vertex.property(k,v)` is called.
+|gremlin.tinkergraph.allowNullPropertyValues |A boolean value that determines whether or not `null` property values are allowed and defaults to `true`.
 |gremlin.tinkergraph.graphLocation |The path and file name for where TinkerGraph should persist the graph data. If a
 value is specified here, the `gremlin.tinkergraph.graphFormat` should also be specified.  If this value is not
 included (default), then the graph will stay in-memory and not be loaded/persisted to disk.
diff --git a/docs/src/upgrade/release-3.5.x.asciidoc b/docs/src/upgrade/release-3.5.x.asciidoc
index 7cf27a0..287d2eb 100644
--- a/docs/src/upgrade/release-3.5.x.asciidoc
+++ b/docs/src/upgrade/release-3.5.x.asciidoc
@@ -60,7 +60,6 @@ org.apache.commons.configuration.*
 
 to
 
-
 [source,text]
 ----
 org.apache.commons.configuration2.*
@@ -73,10 +72,61 @@ upgrade information can be found in the link:https://commons.apache.org/proper/c
 
 See: link:https://issues.apache.org/jira/browse/TINKERPOP-2185[TINKERPOP-2185]
 
-==== addV() and null
+==== Use of null
+
+Gremlin has traditionally disallowed `null` as a value in traversals and not always in consistent ways:
+
+[source,text]
+----
+gremlin> g.inject(1, null, null, 2, null)
+java.lang.NullPointerException
+Type ':help' or ':h' for help.
+Display stack trace? [yN]n
+gremlin> g.V().has('person','name','marko').property('age', null)
+The AddPropertyStep does not have a provided value: AddPropertyStep({key=[age]})
+Type ':help' or ':h' for help.
+Display stack trace? [yN]
+gremlin> g.addV("person").property("name", 'stephen').property("age", null)
+==>v[13]
+gremlin> g.V().has('person','name','stephen').elementMap()
+==>[id:13,label:person,name:stephen]
+gremlin> g.V().constant(null)
+gremlin>
+----
+
+Note how `null` can produce exception behavior or act as a filter. For 3.5.0, TinkerPop has not only made `null` usage
+consistent, but has also made it an allowable value within a `Traversal`:
+
+[source,text]
+----
+gremlin> g.inject(1, null, null, 2, null)
+==>1
+==>null
+==>null
+==>null
+==>2
+gremlin> g.addV("person").property("name", 'stephen').property("age", null)
+==>v[13]
+gremlin> g.V().has('person','name','stephen').elementMap()
+==>[id:13,label:person,name:stephen,age:null]
+gremlin> g.V().has('person','age',null)
+==>v[13]
+gremlin> g.V().constant(null)
+==>null
+==>null
+==>null
+==>null
+==>null
+==>null
+----
+
+Note that the above examples use TinkerGraph which now supports `null` as a property value (though it can be configured
+to work in the prior fashion) and all graphs may not support this feature (for example, Neo4j does not). Please be
+sure to check the new `supportsNullPropertyValues()` feature (or their documentation) to determine if the `Graph`
+implementation allows `null` in this same fashion.
 
-There was a bit of inconsistency in the handling of `null` in calls to `property()` depending on the type of mutation
-being executed demonstrated as follows in earlier versions:
+As a final consideration, there was a bit of inconsistency in the handling of `null` in calls to `property()`
+depending on the type of mutation being executed demonstrated as follows in earlier versions:
 
 [source,text]
 ----
@@ -91,7 +141,8 @@ gremlin> g.V(13).properties()
 ==>vp[z->2]
 ----
 
-In this release, this behavior has been altered to become consistent as follows:
+In this release, this behavior has been altered to become consistent. First, assuming `null` is not supported as a
+property value:
 
 [source,text]
 ----
@@ -105,7 +156,18 @@ Type ':help' or ':h' for help.
 Display stack trace? [yN]
 ----
 
-See: link:https://issues.apache.org/jira/browse/TINKERPOP-2099[TINKERPOP-2099]
+Then, assuming `null` is supported as a property value:
+
+[source,text]
+----
+gremlin> g.V(1).property("x", 1).property("y", null).property("z", 2)
+==>v[1]
+gremlin> g.addV().property("x", 1).property("y", null).property("z", 2)
+==>v[15]
+----
+
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-2235[TINKERPOP-2235],
+link:https://issues.apache.org/jira/browse/TINKERPOP-2099[TINKERPOP-2099]
 
 ==== Remote SideEffects
 


[tinkerpop] 04/23: TINKERPOP-2235 Added tests and Graph level null support

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2235
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 1f7d34e699f508543105621bee25341b9aee6295
Author: stephen <sp...@gmail.com>
AuthorDate: Tue Nov 5 15:26:13 2019 -0500

    TINKERPOP-2235 Added tests and Graph level null support
    
    The problem that reared its head here is in JavaTranslator (and likely GroovyTranslator) as well where the overloads of Gremlin steps become ambiguous when null is used. Left a big blob comment in JavaTranslator where I came up with a possibly reasonable solution for right now. Added a Graph Feature for null support in property values. Allowed TinkerGraph to support it by default but added a configuration to turn that support off. Neo4j does not support nulls so that feature is disabl [...]
---
 .../tinkerpop/gremlin/jsr223/JavaTranslator.java   | 17 ++++-
 .../gremlin/process/computer/VertexComputeKey.java |  2 +-
 .../apache/tinkerpop/gremlin/structure/Graph.java  | 18 +++++
 .../gremlin/structure/util/ElementHelper.java      | 12 ++--
 .../gremlin/structure/util/star/StarGraph.java     | 16 ++---
 .../gremlin/structure/util/ElementHelperTest.java  | 23 ++++---
 gremlin-test/features/map/AddEdge.feature          | 28 ++++++++
 gremlin-test/features/map/AddVertex.feature        | 25 +++++++
 gremlin-test/features/map/Constant.feature         | 17 ++++-
 .../process/traversal/step/map/AddEdgeTest.java    | 30 ++++++++
 .../process/traversal/step/map/AddVertexTest.java  | 24 +++++++
 .../process/traversal/step/map/ConstantTest.java   | 15 +++-
 .../tinkerpop/gremlin/structure/PropertyTest.java  | 80 ++++++++++++++++++++--
 .../gremlin/neo4j/structure/Neo4jEdge.java         |  2 +-
 .../gremlin/neo4j/structure/Neo4jGraph.java        | 12 +++-
 .../gremlin/neo4j/structure/Neo4jVertex.java       |  4 +-
 .../process/computer/TinkerGraphComputerView.java  |  2 +-
 .../gremlin/tinkergraph/structure/TinkerEdge.java  |  4 +-
 .../gremlin/tinkergraph/structure/TinkerGraph.java |  5 +-
 .../tinkergraph/structure/TinkerHelper.java        |  2 +-
 .../gremlin/tinkergraph/structure/TinkerIndex.java | 48 +++++++++----
 .../tinkergraph/structure/TinkerProperty.java      |  5 +-
 .../tinkergraph/structure/TinkerVertex.java        |  6 +-
 .../structure/TinkerVertexProperty.java            |  7 +-
 24 files changed, 343 insertions(+), 61 deletions(-)

diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
index 515bc93..bb9273c 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/JavaTranslator.java
@@ -226,13 +226,24 @@ public final class JavaTranslator<S extends TraversalSource, T extends Traversal
                                 newArguments[i] = varArgs;
                                 break;
                             } else {
-                                if (i < argumentsCopy.length &&
-                                        (parameters[i].getType().isAssignableFrom(argumentsCopy[i].getClass()) ||
+                                // try to detect the right method by comparing the type of the parameter to the type
+                                // of the argument. doesn't always work so well because of null arguments which don't
+                                // bring their type in bytecode and rely on position. this doesn't seem to happen often
+                                // ...luckily...because method signatures tend to be sufficiently unique and do not
+                                // encourage whacky use - like g.V().has(null, null) is clearly invalid so we don't
+                                // even need to try to sort that out. on the other hand g.V().has('name',null) which
+                                // is valid hits like four different possible overloads, but we can rely on the most
+                                // generic one which takes Object as the second parameter. that seems to work in this
+                                // case, but it's a shame this isn't nicer. seems like nicer would mean a heavy
+                                // overhaul to Gremlin or to GLVs/bytecode and/or to serialization mechanisms
+                                if (i < argumentsCopy.length && ((null == argumentsCopy[i] && parameters[i].getType() == Object.class) ||
+                                        (argumentsCopy[i] != null && (
+                                        parameters[i].getType().isAssignableFrom(argumentsCopy[i].getClass()) ||
                                                 (parameters[i].getType().isPrimitive() &&
                                                         (Number.class.isAssignableFrom(argumentsCopy[i].getClass()) ||
                                                                 argumentsCopy[i].getClass().equals(Boolean.class) ||
                                                                 argumentsCopy[i].getClass().equals(Byte.class) ||
-                                                                argumentsCopy[i].getClass().equals(Character.class))))) {
+                                                                argumentsCopy[i].getClass().equals(Character.class))))))) {
                                     newArguments[i] = argumentsCopy[i];
                                 } else {
                                     found = false;
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/VertexComputeKey.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/VertexComputeKey.java
index 284430a..072ad59 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/VertexComputeKey.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/VertexComputeKey.java
@@ -37,7 +37,7 @@ public final class VertexComputeKey implements Serializable {
     private VertexComputeKey(final String key, final boolean isTransient) {
         this.key = key;
         this.isTransient = isTransient;
-        ElementHelper.validateProperty(key, key);
+        ElementHelper.validateProperty(true, key, key);
     }
 
     public String getKey() {
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/Graph.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/Graph.java
index e41f110..6609dab 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/Graph.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/Graph.java
@@ -682,6 +682,15 @@ public interface Graph extends AutoCloseable, Host {
             public static final String FEATURE_ANY_IDS = "AnyIds";
             public static final String FEATURE_ADD_PROPERTY = "AddProperty";
             public static final String FEATURE_REMOVE_PROPERTY = "RemoveProperty";
+            public static final String FEATURE_NULL_PROPERTY_VALUES = "NullPropertyValues";
+
+            /**
+             * Determines if an {@link Element} allows properties with {@code null} property values.
+             */
+            @FeatureDescriptor(name = FEATURE_NULL_PROPERTY_VALUES)
+            public default boolean supportsNullPropertyValues() {
+                return true;
+            }
 
             /**
              * Determines if an {@link Element} allows properties to be added.  This feature is set independently from
@@ -816,6 +825,15 @@ public interface Graph extends AutoCloseable, Host {
             public static final String FEATURE_UUID_IDS = "UuidIds";
             public static final String FEATURE_CUSTOM_IDS = "CustomIds";
             public static final String FEATURE_ANY_IDS = "AnyIds";
+            public static final String FEATURE_NULL_PROPERTY_VALUES = "NullPropertyValues";
+
+            /**
+             * Determines if meta-properties allow for {@code null} property values.
+             */
+            @FeatureDescriptor(name = FEATURE_NULL_PROPERTY_VALUES)
+            public default boolean supportsNullPropertyValues() {
+                return true;
+            }
 
             /**
              * Determines if a {@link VertexProperty} allows properties to be removed.
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelper.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelper.java
index 0a71788..b117745 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelper.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelper.java
@@ -87,13 +87,14 @@ public final class ElementHelper {
      * Determines whether the property key/value for the specified thing can be legally set. This is typically used as
      * a pre-condition check prior to setting a property.
      *
+     * @param allowNullValue true if {@code null} is allowed as a value to the property
      * @param key   the key of the property
-     * @param value the value of the property
+     * @param value the value of the property\
      * @throws IllegalArgumentException whether the key/value pair is legal and if not, a clear reason exception
      *                                  message is provided
      */
-    public static void validateProperty(final String key, final Object value) throws IllegalArgumentException {
-        if (null == value)
+    public static void validateProperty(final boolean allowNullValue, final String key, final Object value) throws IllegalArgumentException {
+        if (!allowNullValue && null == value)
             throw Property.Exceptions.propertyValueCanNotBeNull();
         if (null == key)
             throw Property.Exceptions.propertyKeyCanNotBeNull();
@@ -107,17 +108,18 @@ public final class ElementHelper {
      * Determines whether a list of key/values are legal, ensuring that there are an even number of values submitted
      * and that the key values in the list of arguments are {@link String} or {@link Element} objects.
      *
+     * @param allowNullValues true if {@code null} is allowed as a value to the property
      * @param propertyKeyValues a list of key/value pairs
      * @throws IllegalArgumentException if something in the pairs is illegal
      */
-    public static void legalPropertyKeyValueArray(final Object... propertyKeyValues) throws IllegalArgumentException {
+    public static void legalPropertyKeyValueArray(final boolean allowNullValues, final Object... propertyKeyValues) throws IllegalArgumentException {
         if (propertyKeyValues.length % 2 != 0)
             throw Element.Exceptions.providedKeyValuesMustBeAMultipleOfTwo();
         for (int i = 0; i < propertyKeyValues.length; i = i + 2) {
             if (!(propertyKeyValues[i] instanceof String) && !(propertyKeyValues[i] instanceof T))
                 throw Element.Exceptions.providedKeyValuesMustHaveALegalKeyOnEvenIndices();
 
-            if (null == propertyKeyValues[i + 1]) {
+            if (!allowNullValues && null == propertyKeyValues[i + 1]) {
                 throw Property.Exceptions.propertyValueCanNotBeNull();
             }
         }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraph.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraph.java
index 3bcdb7d..935ebef 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraph.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/star/StarGraph.java
@@ -95,7 +95,7 @@ public final class StarGraph implements Graph, Serializable {
     @Override
     public Vertex addVertex(final Object... keyValues) {
         if (null == this.starVertex) {
-            ElementHelper.legalPropertyKeyValueArray(keyValues);
+            ElementHelper.legalPropertyKeyValueArray(true, keyValues);
             this.starVertex = new StarVertex(ElementHelper.getIdValue(keyValues).orElse(this.nextId()), ElementHelper.getLabelValue(keyValues).orElse(Vertex.DEFAULT_LABEL));
             ElementHelper.attachProperties(this.starVertex, VertexProperty.Cardinality.list, keyValues); // TODO: is this smart? I say no... cause vertex property ids are not preserved.
             return this.starVertex;
@@ -413,14 +413,14 @@ public final class StarGraph implements Graph, Serializable {
 
         @Override
         public <V> VertexProperty<V> property(final String key, final V value, final Object... keyValues) {
-            ElementHelper.validateProperty(key, value);
-            ElementHelper.legalPropertyKeyValueArray(keyValues);
+            ElementHelper.validateProperty(true, key, value);
+            ElementHelper.legalPropertyKeyValueArray(true, keyValues);
             return this.property(VertexProperty.Cardinality.single, key, value, keyValues);
         }
 
         Edge addOutEdge(final String label, final Vertex inVertex, final Object... keyValues) {
             ElementHelper.validateLabel(label);
-            ElementHelper.legalPropertyKeyValueArray(keyValues);
+            ElementHelper.legalPropertyKeyValueArray(true, keyValues);
             if (null == this.outEdges)
                 this.outEdges = new HashMap<>();
             List<Edge> outE = this.outEdges.get(label);
@@ -436,7 +436,7 @@ public final class StarGraph implements Graph, Serializable {
 
         Edge addInEdge(final String label, final Vertex outVertex, final Object... keyValues) {
             ElementHelper.validateLabel(label);
-            ElementHelper.legalPropertyKeyValueArray(keyValues);
+            ElementHelper.legalPropertyKeyValueArray(true, keyValues);
             if (null == this.inEdges)
                 this.inEdges = new HashMap<>();
             List<Edge> inE = this.inEdges.get(label);
@@ -452,7 +452,7 @@ public final class StarGraph implements Graph, Serializable {
 
         @Override
         public <V> VertexProperty<V> property(final VertexProperty.Cardinality cardinality, final String key, V value, final Object... keyValues) {
-            ElementHelper.legalPropertyKeyValueArray(keyValues);
+            ElementHelper.legalPropertyKeyValueArray(true, keyValues);
             if (null == this.vertexProperties)
                 this.vertexProperties = new HashMap<>();
             final List<VertexProperty> list = cardinality.equals(VertexProperty.Cardinality.single) ? new ArrayList<>(1) : this.vertexProperties.getOrDefault(key, new ArrayList<>());
@@ -645,7 +645,7 @@ public final class StarGraph implements Graph, Serializable {
 
         @Override
         public <U> Property<U> property(final String key, final U value) {
-            ElementHelper.validateProperty(key, value);
+            ElementHelper.validateProperty(true, key, value);
             if (null == metaProperties)
                 metaProperties = new HashMap<>();
             Map<String, Object> properties = metaProperties.get(this.id);
@@ -760,7 +760,7 @@ public final class StarGraph implements Graph, Serializable {
 
         @Override
         public <V> Property<V> property(final String key, final V value) {
-            ElementHelper.validateProperty(key, value);
+            ElementHelper.validateProperty(true, key, value);
             if (null == edgeProperties)
                 edgeProperties = new HashMap<>();
             Map<String, Object> properties = edgeProperties.get(this.id);
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelperTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelperTest.java
index b1bb763..b72e6cd 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelperTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/structure/util/ElementHelperTest.java
@@ -56,7 +56,7 @@ public class ElementHelperTest {
     @Test
     public void shouldValidatePropertyAndNotAllowNullValue() {
         try {
-            ElementHelper.validateProperty("test", null);
+            ElementHelper.validateProperty(false,"test", null);
             fail("Should fail as property value cannot be null");
         } catch (IllegalArgumentException iae) {
             assertEquals(Property.Exceptions.propertyValueCanNotBeNull().getMessage(), iae.getMessage());
@@ -64,9 +64,14 @@ public class ElementHelperTest {
     }
 
     @Test
+    public void shouldValidatePropertyAndAllowNullValue() {
+        ElementHelper.validateProperty(true,"test", null);
+    }
+
+    @Test
     public void shouldValidatePropertyAndNotAllowNullKey() {
         try {
-            ElementHelper.validateProperty(null, "test");
+            ElementHelper.validateProperty(false,null, "test");
             fail("Should fail as property key cannot be null");
         } catch (IllegalArgumentException iae) {
             assertEquals(Property.Exceptions.propertyKeyCanNotBeNull().getMessage(), iae.getMessage());
@@ -76,7 +81,7 @@ public class ElementHelperTest {
     @Test
     public void shouldValidatePropertyAndNotAllowEmptyKey() {
         try {
-            ElementHelper.validateProperty("", "test");
+            ElementHelper.validateProperty(false,"", "test");
             fail("Should fail as property key cannot be empty");
         } catch (IllegalArgumentException iae) {
             assertEquals(Property.Exceptions.propertyKeyCanNotBeEmpty().getMessage(), iae.getMessage());
@@ -87,7 +92,7 @@ public class ElementHelperTest {
     public void shouldValidatePropertyAndNotAllowHiddenKey() {
         final String key = Graph.Hidden.hide("key");
         try {
-            ElementHelper.validateProperty(key, "test");
+            ElementHelper.validateProperty(false, key, "test");
             fail("Should fail as property key cannot be hidden");
         } catch (IllegalArgumentException iae) {
             assertEquals(Property.Exceptions.propertyKeyCanNotBeAHiddenKey(key).getMessage(), iae.getMessage());
@@ -96,13 +101,13 @@ public class ElementHelperTest {
 
     @Test
     public void shouldHaveValidProperty() {
-        ElementHelper.validateProperty("aKey", "value");
+        ElementHelper.validateProperty(false,"aKey", "value");
     }
 
     @Test
     public void shouldAllowEvenNumberOfKeyValues() {
         try {
-            ElementHelper.legalPropertyKeyValueArray("aKey", "test", "no-value-for-this-one");
+            ElementHelper.legalPropertyKeyValueArray(false,"aKey", "test", "no-value-for-this-one");
             fail("Should fail as there is an odd number of key-values");
         } catch (IllegalArgumentException iae) {
             assertEquals(Element.Exceptions.providedKeyValuesMustBeAMultipleOfTwo().getMessage(), iae.getMessage());
@@ -112,7 +117,7 @@ public class ElementHelperTest {
     @Test
     public void shouldNotAllowEvenNumberOfKeyValuesAndInvalidKeys() {
         try {
-            ElementHelper.legalPropertyKeyValueArray("aKey", "test", "value-for-this-one", 1, 1, "none");
+            ElementHelper.legalPropertyKeyValueArray(false,"aKey", "test", "value-for-this-one", 1, 1, "none");
             fail("Should fail as there is an even number of key-values, but a bad key");
         } catch (IllegalArgumentException iae) {
             assertEquals(Element.Exceptions.providedKeyValuesMustHaveALegalKeyOnEvenIndices().getMessage(), iae.getMessage());
@@ -121,13 +126,13 @@ public class ElementHelperTest {
 
     @Test
     public void shouldAllowEvenNumberOfKeyValuesAndValidKeys() {
-        ElementHelper.legalPropertyKeyValueArray("aKey", "test", "value-for-this-one", 1, "1", "none");
+        ElementHelper.legalPropertyKeyValueArray(false,"aKey", "test", "value-for-this-one", 1, "1", "none");
     }
 
     @Test
     public void shouldNotAllowEvenNumberOfKeyValuesAndInvalidValues() {
         try {
-            ElementHelper.legalPropertyKeyValueArray("aKey", "test", "value-for-this-one", 1, "1", null);
+            ElementHelper.legalPropertyKeyValueArray(false,"aKey", "test", "value-for-this-one", 1, "1", null);
         } catch (IllegalArgumentException iae) {
             assertEquals(Property.Exceptions.propertyValueCanNotBeNull().getMessage(), iae.getMessage());
         }
diff --git a/gremlin-test/features/map/AddEdge.feature b/gremlin-test/features/map/AddEdge.feature
index 7f4b0aa..875e474 100644
--- a/gremlin-test/features/map/AddEdge.feature
+++ b/gremlin-test/features/map/AddEdge.feature
@@ -72,6 +72,34 @@ Feature: Step - addE()
     And the graph should return 4 for count of "g.V(v1Id).bothE()"
     And the graph should return 1 for count of "g.V(v1Id).inE().has(\"weight\", 2.0)"
 
+  Scenario: g_VX1X_asXaX_outXcreatedX_addEXcreatedByX_toXaX_propertyXweight_nullX
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property(T.id, 1).property("name", "marko").property("age", 29).as("marko").
+        addV("person").property(T.id, 2).property("name", "vadas").property("age", 27).as("vadas").
+        addV("software").property(T.id, 3).property("name", "lop").property("lang", "java").as("lop").
+        addV("person").property(T.id, 4).property("name","josh").property("age", 32).as("josh").
+        addV("software").property(T.id, 5).property("name", "ripple").property("lang", "java").as("ripple").
+        addV("person").property(T.id, 6).property("name", "peter").property("age", 35).as('peter').
+        addE("knows").from("marko").to("vadas").property(T.id, 7).property("weight", 0.5).
+        addE("knows").from("marko").to("josh").property(T.id, 8).property("weight", 1.0).
+        addE("created").from("marko").to("lop").property(T.id, 9).property("weight", 0.4).
+        addE("created").from("josh").to("ripple").property(T.id, 10).property("weight", 1.0).
+        addE("created").from("josh").to("lop").property(T.id, 11).property("weight", 0.4).
+        addE("created").from("peter").to("lop").property(T.id, 12).property("weight", 0.2)
+      """
+    And using the parameter v1Id defined as "v[marko].id"
+    And the traversal of
+      """
+      g.V(v1Id).as("a").out("created").addE("createdBy").to("a").property("weight", null)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 7 for count of "g.E()"
+    And the graph should return 4 for count of "g.V(v1Id).bothE()"
+    And the graph should return 1 for count of "g.V(v1Id).inE().has(\"weight\", null)"
+
   Scenario: g_V_aggregateXxX_asXaX_selectXxX_unfold_addEXexistsWithX_toXaX_propertyXtime_nowX
     Given the empty graph
     And the graph initializer of
diff --git a/gremlin-test/features/map/AddVertex.feature b/gremlin-test/features/map/AddVertex.feature
index a60fefb..efe3fc0 100644
--- a/gremlin-test/features/map/AddVertex.feature
+++ b/gremlin-test/features/map/AddVertex.feature
@@ -93,6 +93,31 @@ Feature: Step - addV()
     Then the result should have a count of 1
     And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"stephen\")"
 
+  Scenario: g_addVXpersonX_propertyXname_nullX
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property(T.id, 1).property("name", "marko").property("age", 29).as("marko").
+        addV("person").property(T.id, 2).property("name", "vadas").property("age", 27).as("vadas").
+        addV("software").property(T.id, 3).property("name", "lop").property("lang", "java").as("lop").
+        addV("person").property(T.id, 4).property("name","josh").property("age", 32).as("josh").
+        addV("software").property(T.id, 5).property("name", "ripple").property("lang", "java").as("ripple").
+        addV("person").property(T.id, 6).property("name", "peter").property("age", 35).as('peter').
+        addE("knows").from("marko").to("vadas").property(T.id, 7).property("weight", 0.5).
+        addE("knows").from("marko").to("josh").property(T.id, 8).property("weight", 1.0).
+        addE("created").from("marko").to("lop").property(T.id, 9).property("weight", 0.4).
+        addE("created").from("josh").to("ripple").property(T.id, 10).property("weight", 1.0).
+        addE("created").from("josh").to("lop").property(T.id, 11).property("weight", 0.4).
+        addE("created").from("peter").to("lop").property(T.id, 12).property("weight", 0.2)
+      """
+    And the traversal of
+      """
+      g.addV("person").property("name", null)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",null)"
+
   Scenario: g_addVXpersonX_propertyXsingle_name_stephenX_propertyXsingle_name_stephenmX
     Given the empty graph
     And the graph initializer of
diff --git a/gremlin-test/features/map/Constant.feature b/gremlin-test/features/map/Constant.feature
index 9ad8d13..ed25b25 100644
--- a/gremlin-test/features/map/Constant.feature
+++ b/gremlin-test/features/map/Constant.feature
@@ -19,7 +19,6 @@ Feature: Step - constant()
 
   Scenario: g_V_constantX123X
     Given the modern graph
-    And using the parameter v1Id defined as "v[marko].id"
     And the traversal of
       """
       g.V().constant(123)
@@ -34,6 +33,22 @@ Feature: Step - constant()
       | d[123].i |
       | d[123].i |
 
+  Scenario: g_V_constantXnullX
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().constant(null)
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | null |
+      | null |
+      | null |
+      | null |
+      | null |
+      | null |
+
   Scenario: g_V_chooseXhasLabelXpersonX_valuesXnameX_constantXinhumanXX
     Given the modern graph
     And the traversal of
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddEdgeTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddEdgeTest.java
index 54c4091..f534a4b 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddEdgeTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddEdgeTest.java
@@ -46,6 +46,7 @@ import static org.apache.tinkerpop.gremlin.structure.Column.keys;
 import static org.apache.tinkerpop.gremlin.structure.Column.values;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
@@ -58,6 +59,8 @@ public abstract class AddEdgeTest extends AbstractGremlinProcessTest {
 
     public abstract Traversal<Vertex, Edge> get_g_VX1X_asXaX_outXcreatedX_addEXcreatedByX_toXaX_propertyXweight_2X(final Object v1Id);
 
+    public abstract Traversal<Vertex, Edge> get_g_VX1X_asXaX_outXcreatedX_addEXcreatedByX_toXaX_propertyXweight_nullX(final Object v1Id);
+
     public abstract Traversal<Vertex, Edge> get_g_V_aggregateXxX_asXaX_selectXxX_unfold_addEXexistsWithX_toXaX_propertyXtime_nowX();
 
     public abstract Traversal<Vertex, Edge> get_g_V_asXaX_outXcreatedX_inXcreatedX_whereXneqXaXX_asXbX_addEXcodeveloperX_fromXaX_toXbX_propertyXyear_2009X();
@@ -121,6 +124,28 @@ public abstract class AddEdgeTest extends AbstractGremlinProcessTest {
     @Test
     @LoadGraphWith(MODERN)
     @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = Graph.Features.EdgeFeatures.FEATURE_ADD_EDGES)
+    @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = Graph.Features.EdgeFeatures.FEATURE_NULL_PROPERTY_VALUES)
+    public void g_VX1X_asXaX_outXcreatedX_addEXcreatedByX_toXaX_propertyXweight_nullX() {
+        final Traversal<Vertex, Edge> traversal = get_g_VX1X_asXaX_outXcreatedX_addEXcreatedByX_toXaX_propertyXweight_nullX(convertToVertexId("marko"));
+        printTraversalForm(traversal);
+        int count = 0;
+        while (traversal.hasNext()) {
+            final Edge edge = traversal.next();
+            assertEquals("createdBy", edge.label());
+            assertNull(g.E(edge).<Double>values("weight").next());
+            assertEquals(1, g.E(edge).properties().count().next().intValue());
+            count++;
+
+
+        }
+        assertEquals(1, count);
+        assertEquals(7, IteratorUtils.count(g.E()));
+        assertEquals(6, IteratorUtils.count(g.V()));
+    }
+
+    @Test
+    @LoadGraphWith(MODERN)
+    @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = Graph.Features.EdgeFeatures.FEATURE_ADD_EDGES)
     public void g_V_aggregateXxX_asXaX_selectXxX_unfold_addEXexistsWithX_toXaX_propertyXtime_nowX() {
         final Traversal<Vertex, Edge> traversal = get_g_V_aggregateXxX_asXaX_selectXxX_unfold_addEXexistsWithX_toXaX_propertyXtime_nowX();
         printTraversalForm(traversal);
@@ -306,6 +331,11 @@ public abstract class AddEdgeTest extends AbstractGremlinProcessTest {
         }
 
         @Override
+        public Traversal<Vertex, Edge> get_g_VX1X_asXaX_outXcreatedX_addEXcreatedByX_toXaX_propertyXweight_nullX(final Object v1Id) {
+            return g.V(v1Id).as("a").out("created").addE("createdBy").to("a").property("weight", null);
+        }
+
+        @Override
         public Traversal<Vertex, Edge> get_g_V_aggregateXxX_asXaX_selectXxX_unfold_addEXexistsWithX_toXaX_propertyXtime_nowX() {
             return g.V().aggregate("x").as("a").select("x").unfold().addE("existsWith").to("a").property("time", "now");
         }
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexTest.java
index 21fbb8c..ebad007 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexTest.java
@@ -42,6 +42,7 @@ import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.V;
 import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.select;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 /**
@@ -56,6 +57,8 @@ public abstract class AddVertexTest extends AbstractGremlinTest {
 
     public abstract Traversal<Vertex, Vertex> get_g_addVXpersonX_propertyXname_stephenX();
 
+    public abstract Traversal<Vertex, Vertex> get_g_addVXpersonX_propertyXname_nullX();
+
     public abstract Traversal<Vertex, Vertex> get_g_addVXpersonX_propertyXsingle_name_stephenX_propertyXsingle_name_stephenmX();
 
     public abstract Traversal<Vertex, Vertex> get_g_addVXpersonX_propertyXsingle_name_stephenX_propertyXsingle_name_stephenm_since_2010X();
@@ -130,6 +133,22 @@ public abstract class AddVertexTest extends AbstractGremlinTest {
     @LoadGraphWith(MODERN)
     @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_ADD_VERTICES)
     @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_ADD_PROPERTY)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_NULL_PROPERTY_VALUES)
+    public void g_addVXpersonX_propertyXname_nullX() {
+        final Traversal<Vertex, Vertex> traversal = get_g_addVXpersonX_propertyXname_nullX();
+        printTraversalForm(traversal);
+        final Vertex nulled = traversal.next();
+        assertFalse(traversal.hasNext());
+        assertEquals("person", nulled.label());
+        assertNull(nulled.value("name"));
+        assertEquals(1, IteratorUtils.count(nulled.properties()));
+        assertEquals(7, IteratorUtils.count(g.V()));
+    }
+
+    @Test
+    @LoadGraphWith(MODERN)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_ADD_VERTICES)
+    @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_ADD_PROPERTY)
     public void g_addVXpersonX_propertyXsingle_name_stephenX_propertyXsingle_name_stephenmX() {
         final Traversal<Vertex, Vertex> traversal = get_g_addVXpersonX_propertyXsingle_name_stephenX_propertyXsingle_name_stephenmX();
         printTraversalForm(traversal);
@@ -315,6 +334,11 @@ public abstract class AddVertexTest extends AbstractGremlinTest {
         }
 
         @Override
+        public Traversal<Vertex, Vertex> get_g_addVXpersonX_propertyXname_nullX() {
+            return g.addV("person").property("name", null);
+        }
+
+        @Override
         public Traversal<Vertex, Vertex> get_g_addVXpersonX_propertyXsingle_name_stephenX_propertyXsingle_name_stephenmX() {
             return g.addV("person").property(VertexProperty.Cardinality.single, "name", "stephen").property(VertexProperty.Cardinality.single, "name", "stephenm");
         }
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConstantTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConstantTest.java
index e35db43..07a7b55 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConstantTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConstantTest.java
@@ -46,9 +46,10 @@ public abstract class ConstantTest extends AbstractGremlinProcessTest {
 
     public abstract Traversal<Vertex, Integer> get_g_V_constantX123X();
 
+    public abstract Traversal<Vertex, Void> get_g_V_constantXnullX();
+
     public abstract Traversal<Vertex, String> get_g_V_chooseXhasLabelXpersonX_valuesXnameX_constantXinhumanXX();
 
-    /** Scenario: Trivial use case from GraphTraversal */
     @Test
     @LoadGraphWith(MODERN)
     public void g_V_constantX123X() {
@@ -56,6 +57,13 @@ public abstract class ConstantTest extends AbstractGremlinProcessTest {
         printTraversalForm(traversal);
         assertEquals(Arrays.asList(123, 123, 123, 123, 123, 123), traversal.toList());
     }
+    @Test
+    @LoadGraphWith(MODERN)
+    public void g_V_constantXnullX() {
+        final Traversal<Vertex, Void> traversal = get_g_V_constantXnullX();
+        printTraversalForm(traversal);
+        assertEquals(Arrays.asList(null, null, null, null, null, null), traversal.toList());
+    }
 
     /** Scenario: Anonymous traversal within choose */
     @Test
@@ -74,6 +82,11 @@ public abstract class ConstantTest extends AbstractGremlinProcessTest {
         }
 
         @Override
+        public Traversal<Vertex, Void> get_g_V_constantXnullX() {
+            return g.V().constant(null);
+        }
+
+        @Override
         public Traversal<Vertex, String> get_g_V_chooseXhasLabelXpersonX_valuesXnameX_constantXinhumanXX() {
             return g.V().choose(hasLabel("person"), values("name"), constant("inhuman"));
         }
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/PropertyTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/PropertyTest.java
index 8b443fc..b76f137 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/PropertyTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/PropertyTest.java
@@ -41,6 +41,7 @@ import java.util.Map;
 import static org.apache.tinkerpop.gremlin.structure.Graph.Features.PropertyFeatures.FEATURE_PROPERTIES;
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeThat;
 
@@ -56,6 +57,9 @@ public class PropertyTest {
     /**
      * Basic tests for the {@link Property} class.
      */
+    @ExceptionCoverage(exceptionClass = Property.Exceptions.class, methods = {
+            "propertyValueCanNotBeNull"
+    })
     public static class BasicPropertyTest extends AbstractGremlinTest {
         @Test
         @FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
@@ -141,6 +145,73 @@ public class PropertyTest {
                 fail("Removing an edge property that was already removed should not throw an exception");
             }
         }
+
+        @Test
+        @FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
+        @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_NULL_PROPERTY_VALUES, supported = false)
+        public void shouldNotAllowNullAddVertex() throws Exception {
+            try {
+                this.graph.addVertex("name", null);
+                fail("Call to addVertex() should have thrown an exception as null is not a supported property value");
+            } catch (Exception ex) {
+                validateException(Property.Exceptions.propertyValueCanNotBeNull(), ex);
+            }
+        }
+
+        @Test
+        @FeatureRequirementSet(FeatureRequirementSet.Package.SIMPLE)
+        @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_NULL_PROPERTY_VALUES, supported = false)
+        public void shouldNotAllowNullAddEdge() throws Exception {
+            try {
+                final Vertex v = this.graph.addVertex();
+                v.addEdge("self", v, "name", null);
+                fail("Call to addEdge() should have thrown an exception as null is not a supported property value");
+            } catch (Exception ex) {
+                validateException(Property.Exceptions.propertyValueCanNotBeNull(), ex);
+            }
+        }
+
+        @Test
+        @FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
+        @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_NULL_PROPERTY_VALUES)
+        public void shouldAllowNullAddVertex() throws Exception {
+            final Vertex v = this.graph.addVertex("name", null);
+            assertNull(v.value("name"));
+        }
+
+        @Test
+        @FeatureRequirementSet(FeatureRequirementSet.Package.SIMPLE)
+        @FeatureRequirement(featureClass = Graph.Features.EdgeFeatures.class, feature = Graph.Features.EdgeFeatures.FEATURE_NULL_PROPERTY_VALUES)
+        public void shouldAllowNullAddEdge() throws Exception {
+            final Vertex v = this.graph.addVertex();
+            final Edge e = v.addEdge("self", v, "name", null);
+            assertNull(e.value("name"));
+        }
+
+        @Test
+        @FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
+        @FeatureRequirement(featureClass = Graph.Features.VertexPropertyFeatures.class, feature = Graph.Features.VertexPropertyFeatures.FEATURE_NULL_PROPERTY_VALUES)
+        @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_META_PROPERTIES)
+        public void shouldAllowNullAddVertexProperty() throws Exception {
+            final Vertex v = this.graph.addVertex("person");
+            final VertexProperty vp = v.property("location", "santa fe", "startTime", 1995, "endTime", null);
+            assertEquals(1995, (int) vp.value("startTime"));
+            assertNull(vp.value("endTime"));
+        }
+
+        @Test
+        @FeatureRequirementSet(FeatureRequirementSet.Package.VERTICES_ONLY)
+        @FeatureRequirement(featureClass = Graph.Features.VertexPropertyFeatures.class, feature = Graph.Features.VertexPropertyFeatures.FEATURE_NULL_PROPERTY_VALUES, supported = false)
+        @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class, feature = Graph.Features.VertexFeatures.FEATURE_META_PROPERTIES)
+        public void shouldNotAllowNullAddVertexProperty() throws Exception {
+            try {
+                final Vertex v = this.graph.addVertex("person");
+                final VertexProperty vp = v.property("location", "santa fe", "startTime", 1995, "endTime", null);
+                fail("Call to property() should have thrown an exception as null is not a supported property value");
+            } catch (Exception ex) {
+                validateException(Property.Exceptions.propertyValueCanNotBeNull(), ex);
+            }
+        }
     }
 
     /**
@@ -153,7 +224,6 @@ public class PropertyTest {
             "providedKeyValuesMustHaveALegalKeyOnEvenIndices"
     })
     @ExceptionCoverage(exceptionClass = Property.Exceptions.class, methods = {
-            "propertyValueCanNotBeNull",
             "propertyKeyCanNotBeEmpty"
     })
     public static class PropertyValidationOnAddExceptionConsistencyTest extends AbstractGremlinTest {
@@ -164,7 +234,6 @@ public class PropertyTest {
                     {"providedKeyValuesMustBeAMultipleOfTwo", new Object[]{"odd", "number", "arguments"}, Element.Exceptions.providedKeyValuesMustBeAMultipleOfTwo()},
                     {"providedKeyValuesMustBeAMultipleOfTwo", new Object[]{"odd"}, Element.Exceptions.providedKeyValuesMustBeAMultipleOfTwo()},
                     {"providedKeyValuesMustHaveALegalKeyOnEvenIndices", new Object[]{"odd", "number", 123, "test"}, Element.Exceptions.providedKeyValuesMustHaveALegalKeyOnEvenIndices()},
-                    {"propertyValueCanNotBeNull", new Object[]{"odd", null}, Property.Exceptions.propertyValueCanNotBeNull()},
                     {"providedKeyValuesMustHaveALegalKeyOnEvenIndices", new Object[]{null, "val"}, Element.Exceptions.providedKeyValuesMustHaveALegalKeyOnEvenIndices()},
                     {"propertyKeyCanNotBeEmpty", new Object[]{"", "val"}, Property.Exceptions.propertyKeyCanNotBeEmpty()}});
         }
@@ -245,13 +314,11 @@ public class PropertyTest {
 
 
     /**
-     * Checks that properties added to an {@link Element} are validated in a
-     * consistent way when they are set after {@link Vertex} or {@link Edge} construction by throwing an
-     * appropriate exception.
+     * Checks that properties added to an {@link Element} are validated in a consistent way when they are set after
+     * {@link Vertex} or {@link Edge} construction by throwing an appropriate exception.
      */
     @RunWith(Parameterized.class)
     @ExceptionCoverage(exceptionClass = Property.Exceptions.class, methods = {
-            "propertyValueCanNotBeNull",
             "propertyKeyCanNotBeNull",
             "propertyKeyCanNotBeEmpty",
             "propertyKeyCanNotBeAHiddenKey"
@@ -261,7 +328,6 @@ public class PropertyTest {
         @Parameterized.Parameters(name = "expect({0})")
         public static Iterable<Object[]> data() {
             return Arrays.asList(new Object[][]{
-                    {"propertyValueCanNotBeNull", "k", null, Property.Exceptions.propertyValueCanNotBeNull()},
                     {"propertyKeyCanNotBeNull", null, "v", Property.Exceptions.propertyKeyCanNotBeNull()},
                     {"propertyKeyCanNotBeEmpty", "", "v", Property.Exceptions.propertyKeyCanNotBeEmpty()},
                     {"propertyKeyCanNotBeAHiddenKey", Graph.Hidden.hide("systemKey"), "value", Property.Exceptions.propertyKeyCanNotBeAHiddenKey(Graph.Hidden.hide("systemKey"))}});
diff --git a/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jEdge.java b/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jEdge.java
index 29ccef1..2729f9f 100644
--- a/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jEdge.java
+++ b/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jEdge.java
@@ -114,7 +114,7 @@ public final class Neo4jEdge extends Neo4jElement implements Edge, WrappedEdge<N
 
     @Override
     public <V> Property<V> property(final String key, final V value) {
-        ElementHelper.validateProperty(key, value);
+        ElementHelper.validateProperty(false, key, value);
         this.graph.tx().readWrite();
         try {
             this.baseElement.setProperty(key, value);
diff --git a/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jGraph.java b/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jGraph.java
index b6fb8be..74270d3 100644
--- a/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jGraph.java
+++ b/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jGraph.java
@@ -127,7 +127,7 @@ public final class Neo4jGraph implements Graph, WrappedGraph<Neo4jGraphAPI> {
 
     @Override
     public Vertex addVertex(final Object... keyValues) {
-        ElementHelper.legalPropertyKeyValueArray(keyValues);
+        ElementHelper.legalPropertyKeyValueArray(false, keyValues);
         if (ElementHelper.getIdValue(keyValues).isPresent())
             throw Vertex.Exceptions.userSuppliedIdsNotSupported();
         this.tx().readWrite();
@@ -417,6 +417,11 @@ public final class Neo4jGraph implements Graph, WrappedGraph<Neo4jGraphAPI> {
             }
 
             @Override
+            public boolean supportsNullPropertyValues() {
+                return false;
+            }
+
+            @Override
             public boolean supportsUserSuppliedIds() {
                 return false;
             }
@@ -448,6 +453,11 @@ public final class Neo4jGraph implements Graph, WrappedGraph<Neo4jGraphAPI> {
             }
 
             @Override
+            public boolean supportsNullPropertyValues() {
+                return false;
+            }
+
+            @Override
             public boolean supportsMapValues() {
                 return false;
             }
diff --git a/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jVertex.java b/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jVertex.java
index c5cce7d..2bbca2f 100644
--- a/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jVertex.java
+++ b/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/structure/Neo4jVertex.java
@@ -56,7 +56,7 @@ public final class Neo4jVertex extends Neo4jElement implements Vertex, WrappedVe
     public Edge addEdge(final String label, final Vertex inVertex, final Object... keyValues) {
         if (null == inVertex) throw Graph.Exceptions.argumentCanNotBeNull("inVertex");
         ElementHelper.validateLabel(label);
-        ElementHelper.legalPropertyKeyValueArray(keyValues);
+        ElementHelper.legalPropertyKeyValueArray(false, keyValues);
         if (ElementHelper.getIdValue(keyValues).isPresent())
             throw Edge.Exceptions.userSuppliedIdsNotSupported();
 
@@ -91,7 +91,7 @@ public final class Neo4jVertex extends Neo4jElement implements Vertex, WrappedVe
 
     @Override
     public <V> VertexProperty<V> property(final VertexProperty.Cardinality cardinality, final String key, final V value, final Object... keyValues) {
-        ElementHelper.validateProperty(key, value);
+        ElementHelper.validateProperty(false, key, value);
         if (ElementHelper.getIdValue(keyValues).isPresent())
             throw Vertex.Exceptions.userSuppliedIdsNotSupported();
         this.graph.tx().readWrite();
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/computer/TinkerGraphComputerView.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/computer/TinkerGraphComputerView.java
index 43998fb..b61fc2a 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/computer/TinkerGraphComputerView.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/computer/TinkerGraphComputerView.java
@@ -79,7 +79,7 @@ public final class TinkerGraphComputerView {
     }
 
     public <V> Property<V> addProperty(final TinkerVertex vertex, final String key, final V value) {
-        ElementHelper.validateProperty(key, value);
+        ElementHelper.validateProperty(true, key, value);
         if (isComputeKey(key)) {
             final TinkerVertexProperty<V> property = new TinkerVertexProperty<V>((TinkerVertex) vertex, key, value) {
                 @Override
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerEdge.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerEdge.java
index bcfe485..d594fea 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerEdge.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerEdge.java
@@ -43,18 +43,20 @@ public final class TinkerEdge extends TinkerElement implements Edge {
     protected Map<String, Property> properties;
     protected final Vertex inVertex;
     protected final Vertex outVertex;
+    private final boolean allowNullPropertyValues;
 
     protected TinkerEdge(final Object id, final Vertex outVertex, final String label, final Vertex inVertex) {
         super(id, label);
         this.outVertex = outVertex;
         this.inVertex = inVertex;
+        this.allowNullPropertyValues = outVertex.graph().features().edge().supportsNullPropertyValues();
         TinkerHelper.autoUpdateIndex(this, T.label.getAccessor(), this.label, null);
     }
 
     @Override
     public <V> Property<V> property(final String key, final V value) {
         if (this.removed) throw elementAlreadyRemoved(Edge.class, id);
-        ElementHelper.validateProperty(key, value);
+        ElementHelper.validateProperty(allowNullPropertyValues, key, value);
         final Property oldProperty = super.property(key);
         final Property<V> newProperty = new TinkerProperty<>(this, key, value);
         if (null == this.properties) this.properties = new HashMap<>();
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java
index f48d341..2f83a87 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraph.java
@@ -83,6 +83,7 @@ public final class TinkerGraph implements Graph {
     public static final String GREMLIN_TINKERGRAPH_DEFAULT_VERTEX_PROPERTY_CARDINALITY = "gremlin.tinkergraph.defaultVertexPropertyCardinality";
     public static final String GREMLIN_TINKERGRAPH_GRAPH_LOCATION = "gremlin.tinkergraph.graphLocation";
     public static final String GREMLIN_TINKERGRAPH_GRAPH_FORMAT = "gremlin.tinkergraph.graphFormat";
+    public static final String GREMLIN_TINKERGRAPH_ALLOW_NULL_PROPERTY_VALUES = "gremlin.tinkergraph.allowNullPropertyValues";
 
     private final TinkerGraphFeatures features = new TinkerGraphFeatures();
 
@@ -99,6 +100,7 @@ public final class TinkerGraph implements Graph {
     protected final IdManager<?> edgeIdManager;
     protected final IdManager<?> vertexPropertyIdManager;
     protected final VertexProperty.Cardinality defaultVertexPropertyCardinality;
+    protected final boolean allowNullPropertyValues;
 
     private final Configuration configuration;
     private final String graphLocation;
@@ -114,6 +116,7 @@ public final class TinkerGraph implements Graph {
         vertexPropertyIdManager = selectIdManager(configuration, GREMLIN_TINKERGRAPH_VERTEX_PROPERTY_ID_MANAGER, VertexProperty.class);
         defaultVertexPropertyCardinality = VertexProperty.Cardinality.valueOf(
                 configuration.getString(GREMLIN_TINKERGRAPH_DEFAULT_VERTEX_PROPERTY_CARDINALITY, VertexProperty.Cardinality.single.name()));
+        allowNullPropertyValues = configuration.getBoolean(GREMLIN_TINKERGRAPH_ALLOW_NULL_PROPERTY_VALUES, true);
 
         graphLocation = configuration.getString(GREMLIN_TINKERGRAPH_GRAPH_LOCATION, null);
         graphFormat = configuration.getString(GREMLIN_TINKERGRAPH_GRAPH_FORMAT, null);
@@ -158,7 +161,7 @@ public final class TinkerGraph implements Graph {
 
     @Override
     public Vertex addVertex(final Object... keyValues) {
-        ElementHelper.legalPropertyKeyValueArray(keyValues);
+        ElementHelper.legalPropertyKeyValueArray(allowNullPropertyValues, keyValues);
         Object idValue = vertexIdManager.convert(ElementHelper.getIdValue(keyValues).orElse(null));
         final String label = ElementHelper.getLabelValue(keyValues).orElse(Vertex.DEFAULT_LABEL);
 
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerHelper.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerHelper.java
index 4ed3fdd..2b1a928 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerHelper.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerHelper.java
@@ -49,7 +49,7 @@ public final class TinkerHelper {
 
     protected static Edge addEdge(final TinkerGraph graph, final TinkerVertex outVertex, final TinkerVertex inVertex, final String label, final Object... keyValues) {
         ElementHelper.validateLabel(label);
-        ElementHelper.legalPropertyKeyValueArray(keyValues);
+        ElementHelper.legalPropertyKeyValueArray(graph.features().edge().supportsNullPropertyValues(), keyValues);
 
         Object idValue = graph.edgeIdManager.convert(ElementHelper.getIdValue(keyValues).orElse(null));
 
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIndex.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIndex.java
index f5872cf..75924da 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIndex.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerIndex.java
@@ -49,7 +49,7 @@ final class TinkerIndex<T extends Element> {
     protected void put(final String key, final Object value, final T element) {
         Map<Object, Set<T>> keyMap = this.index.get(key);
         if (null == keyMap) {
-            this.index.putIfAbsent(key, new ConcurrentHashMap<Object, Set<T>>());
+            this.index.putIfAbsent(key, new ConcurrentHashMap<>());
             keyMap = this.index.get(key);
         }
         Set<T> objects = keyMap.get(value);
@@ -65,7 +65,7 @@ final class TinkerIndex<T extends Element> {
         if (null == keyMap) {
             return Collections.emptyList();
         } else {
-            Set<T> set = keyMap.get(value);
+            Set<T> set = keyMap.get(indexable(value));
             if (null == set)
                 return Collections.emptyList();
             else
@@ -78,7 +78,7 @@ final class TinkerIndex<T extends Element> {
         if (null == keyMap) {
             return 0;
         } else {
-            Set<T> set = keyMap.get(value);
+            final Set<T> set = keyMap.get(indexable(value));
             if (null == set)
                 return 0;
             else
@@ -89,7 +89,7 @@ final class TinkerIndex<T extends Element> {
     public void remove(final String key, final Object value, final T element) {
         final Map<Object, Set<T>> keyMap = this.index.get(key);
         if (null != keyMap) {
-            Set<T> objects = keyMap.get(value);
+            final Set<T> objects = keyMap.get(indexable(value));
             if (null != objects) {
                 objects.remove(element);
                 if (objects.size() == 0) {
@@ -111,17 +111,11 @@ final class TinkerIndex<T extends Element> {
 
     public void autoUpdate(final String key, final Object newValue, final Object oldValue, final T element) {
         if (this.indexedKeys.contains(key)) {
-            if (oldValue != null)
-                this.remove(key, oldValue, element);
+            this.remove(key, oldValue, element);
             this.put(key, newValue, element);
         }
     }
 
-    public void autoRemove(final String key, final Object oldValue, final T element) {
-        if (this.indexedKeys.contains(key))
-            this.remove(key, oldValue, element);
-    }
-
     public void createKeyIndex(final String key) {
         if (null == key)
             throw Graph.Exceptions.argumentCanNotBeNull("key");
@@ -133,8 +127,8 @@ final class TinkerIndex<T extends Element> {
         this.indexedKeys.add(key);
 
         (Vertex.class.isAssignableFrom(this.indexClass) ?
-                this.graph.vertices.values().<T>parallelStream() :
-                this.graph.edges.values().<T>parallelStream())
+                this.graph.vertices.values().parallelStream() :
+                this.graph.edges.values().parallelStream())
                 .map(e -> new Object[]{((T) e).property(key), e})
                 .filter(a -> ((Property) a[0]).isPresent())
                 .forEach(a -> this.put(key, ((Property) a[0]).value(), (T) a[1]));
@@ -147,7 +141,35 @@ final class TinkerIndex<T extends Element> {
         this.indexedKeys.remove(key);
     }
 
+    /**
+     * Provides a way for an index to have a {@code null} value as {@code ConcurrentHashMap} will not allow a
+     * {@code null} key.
+     */
+    public static Object indexable(final Object obj) {
+        return null == obj ? IndexedNull.instance() : obj;
+    }
+
     public Set<String> getIndexedKeys() {
         return this.indexedKeys;
     }
+
+    public static final class IndexedNull {
+        private static final IndexedNull inst = new IndexedNull();
+
+        private IndexedNull() {}
+
+        static IndexedNull instance() {
+            return inst;
+        }
+
+        @Override
+        public int hashCode() {
+            return 751912123;
+        }
+
+        @Override
+        public boolean equals(final Object o) {
+            return o instanceof IndexedNull;
+        }
+    }
 }
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerProperty.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerProperty.java
index 0f7077b..db40346 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerProperty.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerProperty.java
@@ -54,9 +54,12 @@ public final class TinkerProperty<V> implements Property<V> {
         return this.value;
     }
 
+    /**
+     * The existence of this object implies the property is present, thus even a {@code null} value means "present".
+     */
     @Override
     public boolean isPresent() {
-        return null != this.value;
+        return true;
     }
 
     @Override
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertex.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertex.java
index be08137..7fe7ce0 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertex.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertex.java
@@ -46,10 +46,12 @@ public final class TinkerVertex extends TinkerElement implements Vertex {
     protected Map<String, Set<Edge>> outEdges;
     protected Map<String, Set<Edge>> inEdges;
     private final TinkerGraph graph;
+    private boolean allowNullPropertyValues;
 
     protected TinkerVertex(final Object id, final String label, final TinkerGraph graph) {
         super(id, label);
         this.graph = graph;
+        this.allowNullPropertyValues = graph.features().vertex().supportsNullPropertyValues();
     }
 
     @Override
@@ -83,8 +85,8 @@ public final class TinkerVertex extends TinkerElement implements Vertex {
     @Override
     public <V> VertexProperty<V> property(final VertexProperty.Cardinality cardinality, final String key, final V value, final Object... keyValues) {
         if (this.removed) throw elementAlreadyRemoved(Vertex.class, id);
-        ElementHelper.legalPropertyKeyValueArray(keyValues);
-        ElementHelper.validateProperty(key, value);
+        ElementHelper.legalPropertyKeyValueArray(allowNullPropertyValues, keyValues);
+        ElementHelper.validateProperty(allowNullPropertyValues, key, value);
         final Optional<Object> optionalId = ElementHelper.getIdValue(keyValues);
         final Optional<VertexProperty<V>> optionalVertexProperty = ElementHelper.stageVertexProperty(this, cardinality, key, value, keyValues);
         if (optionalVertexProperty.isPresent()) return optionalVertexProperty.get();
diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertexProperty.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertexProperty.java
index 3ca871a..f01a3f2 100644
--- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertexProperty.java
+++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerVertexProperty.java
@@ -45,6 +45,7 @@ public class TinkerVertexProperty<V> extends TinkerElement implements VertexProp
     private final TinkerVertex vertex;
     private final String key;
     private final V value;
+    private final boolean allowNullPropertyValues;
 
     /**
      * This constructor will not validate the ID type against the {@link Graph}.  It will always just use a
@@ -53,10 +54,11 @@ public class TinkerVertexProperty<V> extends TinkerElement implements VertexProp
      */
     public TinkerVertexProperty(final TinkerVertex vertex, final String key, final V value, final Object... propertyKeyValues) {
         super(((TinkerGraph) vertex.graph()).vertexPropertyIdManager.getNextId((TinkerGraph) vertex.graph()), key);
+        this.allowNullPropertyValues = vertex.graph().features().vertex().properties().supportsNullPropertyValues();
         this.vertex = vertex;
         this.key = key;
         this.value = value;
-        ElementHelper.legalPropertyKeyValueArray(propertyKeyValues);
+        ElementHelper.legalPropertyKeyValueArray(allowNullPropertyValues, propertyKeyValues);
         ElementHelper.attachProperties(this, propertyKeyValues);
     }
 
@@ -66,10 +68,11 @@ public class TinkerVertexProperty<V> extends TinkerElement implements VertexProp
      */
     public TinkerVertexProperty(final Object id, final TinkerVertex vertex, final String key, final V value, final Object... propertyKeyValues) {
         super(id, key);
+        this.allowNullPropertyValues = vertex.graph().features().vertex().properties().supportsNullPropertyValues();
         this.vertex = vertex;
         this.key = key;
         this.value = value;
-        ElementHelper.legalPropertyKeyValueArray(propertyKeyValues);
+        ElementHelper.legalPropertyKeyValueArray(allowNullPropertyValues, propertyKeyValues);
         ElementHelper.attachProperties(this, propertyKeyValues);
     }
 


[tinkerpop] 14/23: TINKERPOP-2235 Be smarter about null in SelectOneStep

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2235
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 4e688ca834e676819a6c685d9a5a510ff6ea2059
Author: stephen <sp...@gmail.com>
AuthorDate: Mon Nov 18 13:13:26 2019 -0500

    TINKERPOP-2235 Be smarter about null in SelectOneStep
    
    Can't rely on null to determine if the traverser is "empty" because null could be the value selected from the Map, Path, etc.
---
 .../gremlin/process/traversal/step/Scoping.java    |  6 +++++-
 .../process/traversal/step/map/SelectOneStep.java  |  7 ++++---
 .../process/traversal/step/util/AbstractStep.java  | 14 +++++++-------
 .../process/traversal/step/util/ImmutablePath.java |  8 +++++---
 .../Gremlin.Net/Process/Traversal/Instruction.cs   | 11 +++++++++++
 .../Gherkin/CommonSteps.cs                         |  1 +
 .../Gherkin/TraversalEvaluation/TraversalParser.cs |  6 ++++--
 .../Structure/IO/GraphSON/GraphSONReaderTests.cs   |  5 +++--
 .../test/cucumber/feature-steps.js                 |  3 +++
 gremlin-test/features/sideEffect/Inject.feature    | 15 ++++++++++++++-
 .../traversal/step/sideEffect/InjectTest.java      | 22 ++++++++++++++++++++++
 11 files changed, 79 insertions(+), 19 deletions(-)

diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/Scoping.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/Scoping.java
index fae7431..dee401c 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/Scoping.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/Scoping.java
@@ -116,6 +116,10 @@ public interface Scoping {
     }
 
     public default <S> S getNullableScopeValue(final Pop pop, final String key, final Traverser.Admin<?> traverser) {
+        return getScopeValue(pop, key, traverser, null);
+    }
+
+    public default <S> S getScopeValue(final Pop pop, final String key, final Traverser.Admin<?> traverser, final S valueIfNotFound) {
         final Object object = traverser.get();
         if (object instanceof Map && ((Map<String, S>) object).containsKey(key))
             return ((Map<String, S>) object).get(key);
@@ -127,7 +131,7 @@ public interface Scoping {
         if (path.hasLabel(key))
             return path.get(pop, key);
         ///
-        return null;
+        return valueIfNotFound;
     }
 
     /**
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectOneStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectOneStep.java
index 98ee83f..a87f9ef 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectOneStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectOneStep.java
@@ -26,7 +26,6 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.PathProcessor;
 import org.apache.tinkerpop.gremlin.process.traversal.step.Scoping;
 import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
 import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
-import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.EmptyTraverser;
 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil;
 import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
 
@@ -40,6 +39,7 @@ import java.util.Set;
  */
 public final class SelectOneStep<S, E> extends MapStep<S, E> implements TraversalParent, Scoping, PathProcessor, ByModulating {
 
+    private static final Object NOTHING_SELECTED = new Object();
     private final Pop pop;
     private final String selectKey;
     private Traversal.Admin<S, E> selectTraversal = null;
@@ -53,13 +53,14 @@ public final class SelectOneStep<S, E> extends MapStep<S, E> implements Traversa
 
     @Override
     protected E map(final Traverser.Admin<S> traverser) {
-        final E end = this.getNullableScopeValue(this.pop, this.selectKey, traverser);
+        final E end = this.getScopeValue(this.pop, this.selectKey, traverser, (E) NOTHING_SELECTED);
+        if (NOTHING_SELECTED == end) return end;
         return null != end ? TraversalUtil.applyNullable((S) end, this.selectTraversal) : null;
     }
 
     @Override
     protected boolean isEmptyTraverser(final E obj) {
-        return null == obj;
+        return NOTHING_SELECTED == obj;
     }
 
     @Override
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/AbstractStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/AbstractStep.java
index fe49e56..b3bfdf5 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/AbstractStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/AbstractStep.java
@@ -42,7 +42,7 @@ public abstract class AbstractStep<S, E> implements Step<S, E> {
     protected String id = Traverser.Admin.HALT;
     protected Traversal.Admin traversal;
     protected ExpandableStepIterator<S> starts;
-    protected Traverser.Admin<E> nextEnd = null;
+    protected Traverser.Admin<E> nextEnd = EmptyTraverser.instance();
     protected boolean traverserStepIdAndLabelsSetByChild = false;
 
     protected Step<?, S> previousStep = EmptyStep.instance();
@@ -82,7 +82,7 @@ public abstract class AbstractStep<S, E> implements Step<S, E> {
     @Override
     public void reset() {
         this.starts.clear();
-        this.nextEnd = null;
+        this.nextEnd = EmptyTraverser.instance();
     }
 
     @Override
@@ -117,11 +117,11 @@ public abstract class AbstractStep<S, E> implements Step<S, E> {
 
     @Override
     public Traverser.Admin<E> next() {
-        if (null != this.nextEnd) {
+        if (EmptyTraverser.instance() != this.nextEnd) {
             try {
                 return this.prepareTraversalForNextStep(this.nextEnd);
             } finally {
-                this.nextEnd = null;
+                this.nextEnd = EmptyTraverser.instance();
             }
         } else {
             while (true) {
@@ -135,7 +135,7 @@ public abstract class AbstractStep<S, E> implements Step<S, E> {
 
     @Override
     public boolean hasNext() {
-        if (null != this.nextEnd)
+        if (EmptyTraverser.instance() != this.nextEnd)
             return true;
         else {
             try {
@@ -145,7 +145,7 @@ public abstract class AbstractStep<S, E> implements Step<S, E> {
                     if (this.nextEnd.bulk() > 0)
                         return true;
                     else
-                        this.nextEnd = null;
+                        this.nextEnd = EmptyTraverser.instance();
                 }
             } catch (final NoSuchElementException e) {
                 return false;
@@ -178,7 +178,7 @@ public abstract class AbstractStep<S, E> implements Step<S, E> {
             clone.starts = new ExpandableStepIterator<>(clone);
             clone.previousStep = EmptyStep.instance();
             clone.nextStep = EmptyStep.instance();
-            clone.nextEnd = null;
+            clone.nextEnd = EmptyTraverser.instance();
             clone.traversal = EmptyTraversal.instance();
             clone.labels = new LinkedHashSet<>(this.labels);
             clone.reset();
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/ImmutablePath.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/ImmutablePath.java
index 623b810..0ff981d 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/ImmutablePath.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/ImmutablePath.java
@@ -28,6 +28,7 @@ import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -35,7 +36,8 @@ import java.util.Set;
  */
 public class ImmutablePath implements Path, Serializable, Cloneable {
 
-    private static final ImmutablePath TAIL_PATH = new ImmutablePath(null, null, null);
+    private static final Object END = new Object();
+    private static final ImmutablePath TAIL_PATH = new ImmutablePath(null, END, null);
 
     private ImmutablePath previousPath;
     private Object currentObject;
@@ -58,7 +60,7 @@ public class ImmutablePath implements Path, Serializable, Cloneable {
     }
 
     private final boolean isTail() {
-        return null == this.currentObject;
+        return END == this.currentObject;
     }
 
     @Override
@@ -230,7 +232,7 @@ public class ImmutablePath implements Path, Serializable, Cloneable {
         while (true) {
             if (currentPath.isTail())
                 break;
-            hashCodes[index] = currentPath.currentObject.hashCode();
+            hashCodes[index] = Objects.hashCode(currentPath.currentObject);
             currentPath = currentPath.previousPath;
             index--;
         }
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Instruction.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Instruction.cs
index 65847c5..55df44d 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Instruction.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Instruction.cs
@@ -21,6 +21,8 @@
 
 #endregion
 
+using System;
+
 namespace Gremlin.Net.Process.Traversal
 {
     /// <summary>
@@ -48,5 +50,14 @@ namespace Gremlin.Net.Process.Traversal
         ///     Gets the arguments.
         /// </summary>
         public dynamic[] Arguments { get; }
+
+        /// <summary>
+        ///     String representation of the <see cref="Instruction"/>.
+        /// </summary>
+        /// <returns></returns>
+        public override string ToString()
+        {
+            return OperatorName + " [" + String.Join(",", Arguments) + "]";
+        }
     }
 }
\ No newline at end of file
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs
index 504f7a9..b9135ed 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs
@@ -181,6 +181,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                     var rows = table.Rows.ToArray();
                     Assert.Equal("result", rows[0].Cells.First().Value);
                     var expected = rows.Skip(1).Select(x => ParseValue(x.Cells.First().Value, _graphName));
+
                     if (ordered)
                     {
                         Assert.Equal(expected, _result);
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalParser.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalParser.cs
index fb84fdd..e81e00e 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalParser.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalParser.cs
@@ -36,7 +36,9 @@ namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation
         private static readonly IDictionary<string, Func<GraphTraversalSource, ITraversal>> FixedTranslations = 
             new Dictionary<string, Func<GraphTraversalSource, ITraversal>>
             {
-                { "g.V().fold().count(Scope.local)", g => g.V().Fold().Count(Scope.Local)}
+                { "g.V().fold().count(Scope.local)", g => g.V().Fold().Count(Scope.Local)},
+                { "g.inject(10,20,null,20,10,10).groupCount(\"x\").dedup().as(\"y\").project(\"a\",\"b\").by().by(__.select(\"x\").select(__.select(\"y\")))", 
+                    g => g.Inject<object>(10,20,null,20,10,10).GroupCount("x").Dedup().As("y").Project<object>("a","b").By().By(__.Select<int>("x").Select<object>(__.Select<int>("y")))}
             };
 
         private static readonly Regex RegexNumeric =
@@ -169,7 +171,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation
                     {
                         if (IsNumeric(methodParameter.ParameterType) && IsNumeric(tokenParameterType))
                         {
-                            // Acount for implicit conversion of numeric values as an exact match 
+                            // Account for implicit conversion of numeric values as an exact match
                             exactMatches++;
                         }
                         else if (!methodParameter.ParameterType.GetTypeInfo().IsAssignableFrom(tokenParameterType))
diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONReaderTests.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONReaderTests.cs
index 15bd5f9..11b92fe 100644
--- a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONReaderTests.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONReaderTests.cs
@@ -120,7 +120,7 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphSON
         [Theory, MemberData(nameof(Versions))]
         public void ShouldDeserializeDictionary(int version)
         {
-            var serializedDict = "{\"age\":[{\"@type\":\"g:Int32\",\"@value\":29}],\"name\":[\"marko\"]}";
+            var serializedDict = "{\"age\":[{\"@type\":\"g:Int32\",\"@value\":29}],\"name\":[\"marko\"],\"gender\": null}";
             var reader = CreateStandardGraphSONReader(version);
 
             var jObject = JObject.Parse(serializedDict);
@@ -129,7 +129,8 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphSON
             var expectedDict = new Dictionary<string, dynamic>
             {
                 {"age", new List<object> {29}},
-                {"name", new List<object> {"marko"}}
+                {"name", new List<object> {"marko"}},
+                {"gender", null}
             };
             Assert.Equal(expectedDict, deserializedDict);
         }
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js
index d85d678..b536fe6 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js
@@ -299,6 +299,9 @@ function toMap(stringMap) {
 }
 
 function parseMapValue(value) {
+  if (value === null)
+    return null;
+
   if (typeof value === 'string') {
     return parseValue.call(this, value);
   }
diff --git a/gremlin-test/features/sideEffect/Inject.feature b/gremlin-test/features/sideEffect/Inject.feature
index db5a26c..7e4983d 100644
--- a/gremlin-test/features/sideEffect/Inject.feature
+++ b/gremlin-test/features/sideEffect/Inject.feature
@@ -79,4 +79,17 @@ Feature: Step - inject()
       | null |
       | d[1].i |
       | d[3].i |
-      | null |
\ No newline at end of file
+      | null |
+
+  Scenario: g_injectX10_20_null_20_10_10X_groupCountXxX_dedup_asXyX_projectXa_bX_by_byXselectXxX_selectXselectXyXXX
+    Given the empty graph
+    And the traversal of
+      """
+      g.inject(10,20,null,20,10,10).groupCount("x").dedup().as("y").project("a","b").by().by(__.select("x").select(__.select("y")))
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | m[{"a":"d[10].i", "b":"d[3].l"}] |
+      | m[{"a":"d[20].i", "b":"d[2].l"}] |
+      | m[{"a":null, "b":"d[1].l"}] |
\ No newline at end of file
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/InjectTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/InjectTest.java
index 0b3b584..6ce50c9 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/InjectTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/InjectTest.java
@@ -23,6 +23,7 @@ import org.apache.tinkerpop.gremlin.process.AbstractGremlinProcessTest;
 import org.apache.tinkerpop.gremlin.process.GremlinProcessRunner;
 import org.apache.tinkerpop.gremlin.process.traversal.Path;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.MapHelper;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
 import org.junit.Test;
@@ -50,6 +51,8 @@ public abstract class InjectTest extends AbstractGremlinProcessTest {
 
     public abstract Traversal<Integer, Integer> get_g_injectXnull_1_3_nullX();
 
+    public abstract Traversal<Integer, Map<String, Object>> get_g_injectX10_20_null_20_10_10X_groupCountXxX_dedup_asXyX_projectXa_bX_by_byXselectXxX_selectXselectXyXXX();
+
     @Test
     @LoadGraphWith(MODERN)
     public void g_VX1X_out_injectXv2X_name() {
@@ -94,6 +97,16 @@ public abstract class InjectTest extends AbstractGremlinProcessTest {
     }
 
     @Test
+    public void g_injectX10_20_null_20_10_10X_groupCountXxX_dedup_asXyX_projectXa_bX_by_byXselectXxX_selectXselectXyXXX() {
+        final Traversal<Integer, Map<String, Object>> traversal = get_g_injectX10_20_null_20_10_10X_groupCountXxX_dedup_asXyX_projectXa_bX_by_byXselectXxX_selectXselectXyXXX();
+        printTraversalForm(traversal);
+        checkResults(makeMapList(2,
+                "a", 10, "b", 3L,
+                    "a", 20, "b", 2L,
+                    "a", null, "b", 1L), traversal);
+    }
+
+    @Test
     @LoadGraphWith(MODERN)
     public void g_VX1X_injectXg_VX4XX_out_name() {
         final Traversal<Vertex, String> traversal = get_g_VX1X_injectXg_VX4XX_out_name(convertToVertexId("marko"), convertToVertexId("josh"));
@@ -122,5 +135,14 @@ public abstract class InjectTest extends AbstractGremlinProcessTest {
         public Traversal<Integer, Integer> get_g_injectXnull_1_3_nullX() {
             return g.inject(null, 1, 3, null);
         }
+
+        @Override
+        public Traversal<Integer, Map<String, Object>> get_g_injectX10_20_null_20_10_10X_groupCountXxX_dedup_asXyX_projectXa_bX_by_byXselectXxX_selectXselectXyXXX() {
+            return g.inject(10,20,null,20,10,10).groupCount("x").
+                     dedup().as("y").
+                     project("a","b").
+                       by().
+                       by(__.select("x").select(__.select("y")));
+        }
     }
 }


[tinkerpop] 03/23: TINKERPOP-2235 Fixed null handling in .NET

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2235
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 5824b53dcc2aed18225da230096d9462cdd7bc0b
Author: stephen <sp...@gmail.com>
AuthorDate: Tue Nov 5 08:26:25 2019 -0500

    TINKERPOP-2235 Fixed null handling in .NET
---
 .../src/Gremlin.Net/Process/Traversal/Bytecode.cs      |  6 ++++++
 .../Structure/IO/GraphSON/GraphSONWriter.cs            | 18 +++++++++++-------
 .../Structure/IO/GraphSON/GraphSONReaderTests.cs       |  4 ++--
 .../Structure/IO/GraphSON/GraphSONWriterTests.cs       |  4 ++--
 4 files changed, 21 insertions(+), 11 deletions(-)

diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Bytecode.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Bytecode.cs
index e09c533..490e4e4 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Bytecode.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Bytecode.cs
@@ -116,6 +116,12 @@ namespace Gremlin.Net.Process.Traversal
                 if (variable != null)
                     return new Binding(variable, ConvertArgument(argument, false));
             }
+
+            if (null == argument)
+            {
+                return null;
+            }
+            
             if (IsDictionaryType(argument.GetType()))
             {
                 var dict = new Dictionary<object, object>();
diff --git a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/GraphSONWriter.cs b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/GraphSONWriter.cs
index 3268ae4..fc7765f 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/GraphSONWriter.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/GraphSONWriter.cs
@@ -113,13 +113,17 @@ namespace Gremlin.Net.Structure.IO.GraphSON
         /// <returns>A GraphSON representation of the object ready to be serialized.</returns>
         public dynamic ToDict(dynamic objectData)
         {
-            var type = objectData.GetType();
-            if (TryGetSerializerFor(out IGraphSONSerializer serializer, type))
-                return serializer.Dictify(objectData, this);
-            if (IsDictionaryType(type))
-                return DictToGraphSONDict(objectData);
-            if (IsCollectionType(type))
-                return CollectionToGraphSONCollection(objectData);
+            if (objectData != null)
+            {
+                var type = objectData.GetType();
+                if (TryGetSerializerFor(out IGraphSONSerializer serializer, type))
+                    return serializer.Dictify(objectData, this);
+                if (IsDictionaryType(type))
+                    return DictToGraphSONDict(objectData);
+                if (IsCollectionType(type))
+                    return CollectionToGraphSONCollection(objectData);
+            }
+
             return objectData;
         }
 
diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONReaderTests.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONReaderTests.cs
index 3a49143..15bd5f9 100644
--- a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONReaderTests.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONReaderTests.cs
@@ -260,13 +260,13 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphSON
         [Theory, MemberData(nameof(Versions))]
         public void ShouldDeserializeList(int version)
         {
-            var serializedValue = "[{\"@type\":\"g:Int32\",\"@value\":5},{\"@type\":\"g:Int32\",\"@value\":6}]";
+            var serializedValue = "[{\"@type\":\"g:Int32\",\"@value\":5},{\"@type\":\"g:Int32\",\"@value\":6},null]";
             var reader = CreateStandardGraphSONReader(version);
 
             var jObject = JArray.Parse(serializedValue);
             var deserializedValue = reader.ToObject(jObject);
 
-            Assert.Equal(new List<object> {5, 6}, deserializedValue);
+            Assert.Equal(new List<object> {5, 6, null}, deserializedValue);
         }
 
         [Theory, MemberData(nameof(Versions))]
diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONWriterTests.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONWriterTests.cs
index 409776b..2eb7d51 100644
--- a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONWriterTests.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONWriterTests.cs
@@ -282,12 +282,12 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphSON
         public void ShouldSerializeGList(int version)
         {
             var writer = CreateGraphSONWriter(version);
-            var list = new List<object> {5, 6};
+            var list = new List<object> {5, 6, null};
 
             var serializedGraphSON = writer.WriteObject(list);
 
             var expectedGraphSON = "{\"@type\":\"g:List\",\"@value\":[{\"@type\":\"g:Int32\",\"@value\":5}," +
-                                   "{\"@type\":\"g:Int32\",\"@value\":6}]}";
+                                   "{\"@type\":\"g:Int32\",\"@value\":6},null]}";
             Assert.Equal(expectedGraphSON, serializedGraphSON);
         }
 


[tinkerpop] 13/23: TINKERPOP-2235 Allow for nulls in Path toString()

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2235
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 5901ddf33eb42989c72c32d3061b7d274682982d
Author: stephen <sp...@gmail.com>
AuthorDate: Mon Nov 18 06:24:56 2019 -0500

    TINKERPOP-2235 Allow for nulls in Path toString()
---
 .../org/apache/tinkerpop/gremlin/structure/util/StringFactory.java     | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/StringFactory.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/StringFactory.java
index 814bc09..3fc3591 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/StringFactory.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/StringFactory.java
@@ -49,6 +49,7 @@ import java.lang.reflect.Modifier;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.function.Function;
 import java.util.function.Predicate;
@@ -255,6 +256,6 @@ public final class StringFactory {
     }
 
     public static String pathString(final Path path) {
-        return PATH + L_BRACKET + String.join(", ", IteratorUtils.map(path, Object::toString)) + R_BRACKET;
+        return PATH + L_BRACKET + String.join(", ", IteratorUtils.map(path, Objects::toString)) + R_BRACKET;
     }
 }