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 2021/09/03 17:07:12 UTC

[tinkerpop] branch TINKERPOP-2605 updated (f8c9637 -> 73db22a)

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

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


    from f8c9637  TINKERPOP-2605 Call correct overload for hasValue(null)
     new 31ea72a  TINKERPOP-2605 withSideEffect() allows null value
     new 73db22a  TINKERPOP-2605 Support null in path()

The 2 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 +
 docs/src/upgrade/release-3.5.x.asciidoc            |   1 +
 .../gremlin/process/traversal/Operator.java        |  53 +++-
 .../tinkerpop/gremlin/process/traversal/Path.java  |  33 +-
 .../traversal/dsl/graph/GraphTraversal.java        |   3 +-
 .../process/traversal/step/util/ImmutablePath.java |  12 +-
 .../process/traversal/step/util/MutablePath.java   |   5 +-
 .../strategy/decoration/SideEffectStrategy.java    |   2 +-
 .../traverser/B_LP_O_S_SE_SL_Traverser.java        |   2 +-
 .../traverser/LP_O_OB_S_SE_SL_Traverser.java       |   2 +-
 .../util/DefaultTraversalSideEffects.java          |   4 -
 .../gremlin/process/traversal/OperatorTest.java    | 344 ++++++++++++++-------
 .../gremlin/process/traversal/PathTest.java        |  30 +-
 .../util/DefaultTraversalSideEffectsTest.java      |  26 ++
 .../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs |   7 +-
 .../gremlin-javascript/test/cucumber/gremlin.js    |   7 +-
 gremlin-python/src/main/python/radish/gremlin.py   |   7 +-
 .../GraphBinaryRemoteGraphComputerProvider.java    |  12 +
 .../GraphSONRemoteGraphComputerProvider.java       |  12 +
 .../remote/GryoRemoteGraphComputerProvider.java    |  12 +
 ...emoteGraphGroovyTranslatorComputerProvider.java |  12 +
 gremlin-test/features/filter/Has.feature           |   2 +-
 gremlin-test/features/map/Path.feature             |  27 +-
 gremlin-test/features/map/Select.feature           |  28 +-
 gremlin-test/features/sideEffect/Inject.feature    |  16 +-
 .../process/traversal/step/filter/HasTest.java     |   2 +-
 .../process/traversal/step/map/PathTest.java       |  35 +++
 .../process/traversal/step/map/SelectTest.java     |  18 ++
 .../traversal/step/sideEffect/InjectTest.java      |  14 +
 29 files changed, 556 insertions(+), 174 deletions(-)

[tinkerpop] 01/02: TINKERPOP-2605 withSideEffect() allows null value

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

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

commit 31ea72ae175e77f6e5d1f16239f048085726b187
Author: Stephen Mallette <st...@amazon.com>
AuthorDate: Fri Sep 3 07:19:33 2021 -0400

    TINKERPOP-2605 withSideEffect() allows null value
---
 CHANGELOG.asciidoc                                 |   1 +
 docs/src/upgrade/release-3.5.x.asciidoc            |   1 +
 .../gremlin/process/traversal/Operator.java        |  53 +++-
 .../strategy/decoration/SideEffectStrategy.java    |   2 +-
 .../util/DefaultTraversalSideEffects.java          |   4 -
 .../gremlin/process/traversal/OperatorTest.java    | 344 ++++++++++++++-------
 .../util/DefaultTraversalSideEffectsTest.java      |  26 ++
 .../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs |   4 +-
 .../gremlin-javascript/test/cucumber/gremlin.js    |   4 +-
 gremlin-python/src/main/python/radish/gremlin.py   |   4 +-
 .../GraphBinaryRemoteGraphComputerProvider.java    |   4 +
 .../GraphSONRemoteGraphComputerProvider.java       |   4 +
 .../remote/GryoRemoteGraphComputerProvider.java    |   4 +
 ...emoteGraphGroovyTranslatorComputerProvider.java |   4 +
 gremlin-test/features/map/Select.feature           |  28 +-
 .../process/traversal/step/map/SelectTest.java     |  18 ++
 16 files changed, 360 insertions(+), 145 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index edaff36..863f836 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -33,6 +33,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 * Improved handling of `null` when passed to `P` predicates.
 * Handled `null` for mathematical reducing operations of `sum()`, `mean()`, `max()` and `min()`.
 * Allowed `null` values in `Memory` for `GraphComputer`.
+* Allowed `null` assignment in `withSideEffect()`.
 * Fixed problems with `inject(null)` variations where `null` was the only value being submitted.
 * Fixed problem with `GroovyTranslator` and `inject(null,null)` which could be interpreted as the Groovy JDK extension `inject(Object,Closure).
 * Fixed error where certain variants of `inject()` with `null` might not properly construct a traversal in .NET.
diff --git a/docs/src/upgrade/release-3.5.x.asciidoc b/docs/src/upgrade/release-3.5.x.asciidoc
index ac21888..a2e6d0a 100644
--- a/docs/src/upgrade/release-3.5.x.asciidoc
+++ b/docs/src/upgrade/release-3.5.x.asciidoc
@@ -42,6 +42,7 @@ failures, but now filter results. As these structural elements of the graph can'
 filter all elements.
 * `hasValue(null)` - Formerly this method would throw a general `NullPointerException`, but now it will filter as
 expected.
+* `withSideEffect()` - This configuration step can now take a `null` for its value.
 * `sum()`, `mean()`, `max()` and `min()` - These methods used to throw general `NullPointerException` failures, but
 now ignore `null` values when other numbers are present. If all of the values in the stream are `null` then `hasNext()`
 return `false`.
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Operator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Operator.java
index 4fada2f..ea84f2d 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Operator.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Operator.java
@@ -34,6 +34,7 @@ public enum Operator implements BinaryOperator<Object> {
     /**
      * An addition function.
      *
+     * @see NumberHelper#add(Number, Number)
      * @since 3.0.0-incubating
      */
     sum {
@@ -45,6 +46,7 @@ public enum Operator implements BinaryOperator<Object> {
     /**
      * A subtraction function.
      *
+     * @see NumberHelper#sub(Number, Number)
      * @since 3.0.0-incubating
      */
     minus {
@@ -56,6 +58,7 @@ public enum Operator implements BinaryOperator<Object> {
     /**
      * A multiplication function.
      *
+     * @see NumberHelper#mul(Number, Number)
      * @since 3.0.0-incubating
      */
     mult {
@@ -67,6 +70,7 @@ public enum Operator implements BinaryOperator<Object> {
     /**
      * A division function.
      *
+     * @see NumberHelper#div(Number, Number)
      * @since 3.0.0-incubating
      */
     div {
@@ -78,6 +82,7 @@ public enum Operator implements BinaryOperator<Object> {
     /**
      * Selects the smaller of the values.
      *
+     * @see NumberHelper#min(Number, Number)
      * @since 3.0.0-incubating
      */
     min {
@@ -89,6 +94,7 @@ public enum Operator implements BinaryOperator<Object> {
     /**
      * Selects the larger of the values.
      *
+     * @see NumberHelper#max(Number, Number)
      * @since 3.0.0-incubating
      */
     max {
@@ -112,10 +118,24 @@ public enum Operator implements BinaryOperator<Object> {
     /**
      * Applies "and" to boolean values.
      *
+     * <pre>
+     *     a = true, b = null -> true
+     *     a = false, b = null -> false
+     *     a = null, b = true -> true
+     *     a = null, b = false -> false
+     *     a = null, b = null -> null
+     * </pre>
+     *
      * @since 3.2.0-incubating
      */
     and {
         public Object apply(final Object a, final Object b) {
+            if (null == a || null == b) {
+                if (null == a && null == b)
+                    return null;
+                else
+                    return null == b ? a : b;
+            }
             return ((boolean) a) && ((boolean) b);
         }
     },
@@ -123,25 +143,52 @@ public enum Operator implements BinaryOperator<Object> {
     /**
      * Applies "or" to boolean values.
      *
+     * <pre>
+     *     a = true, b = null -> true
+     *     a = false, b = null -> false
+     *     a = null, b = true -> true
+     *     a = null, b = false -> false
+     *     a = null, b = null -> null
+     * </pre>
+     *
      * @since 3.2.0-incubating
      */
     or {
         public Object apply(final Object a, final Object b) {
+            if (null == a || null == b) {
+                if (null == a && null == b)
+                    return null;
+                else
+                    return null == b ? a : b;
+            }
             return ((boolean) a) || ((boolean) b);
         }
     },
 
     /**
-     * Takes all objects in the second {@code Collection} and adds them to the first.
+     * Takes all objects in the second {@code Collection} and adds them to the first. If the first is {@code null},
+     * then the second {@code Collection} is returned and if the second is {@code null} then the first is returned.
+     * If both are {@code null} then {@code null} is returned. Arguments must be of type {@code Map} or
+     * {@code Collection}.
      *
      * @since 3.2.0-incubating
      */
     addAll {
         public Object apply(final Object a, final Object b) {
-            if (a instanceof Map)
+            if (null == a || null == b) {
+                if (null == a && null == b)
+                    return null;
+                else
+                    return null == b ? a : b;
+            }
+
+            if (a instanceof Map && b instanceof Map)
                 ((Map<?,?>) a).putAll((Map) b);
-            else
+            else if (a instanceof Collection && a instanceof Collection)
                 ((Collection<?>) a).addAll((Collection) b);
+            else
+                throw new IllegalArgumentException(String.format("Objects must be both of Map or Collection: a=%s b=%s",
+                        a.getClass().getSimpleName(), b.getClass().getSimpleName()));
             return a;
         }
     },
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/SideEffectStrategy.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/SideEffectStrategy.java
index 430f5ab..ce794b8 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/SideEffectStrategy.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/SideEffectStrategy.java
@@ -60,7 +60,7 @@ public final class SideEffectStrategy extends AbstractTraversalStrategy<Traversa
             strategy = cloneStrategy;
             traversalStrategies.addStrategies(strategy);
         }
-        strategy.sideEffects.add(new Triplet<>(key, null == value ? null : value instanceof Supplier ? (Supplier) value : new ConstantSupplier<>(value), reducer));
+        strategy.sideEffects.add(new Triplet<>(key, value instanceof Supplier ? (Supplier) value : new ConstantSupplier<>(value), reducer));
     }
 
     public boolean contains(final String sideEffectKey) {
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/DefaultTraversalSideEffects.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/DefaultTraversalSideEffects.java
index b512cf3..52f92a5 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/DefaultTraversalSideEffects.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/DefaultTraversalSideEffects.java
@@ -77,7 +77,6 @@ public class DefaultTraversalSideEffects implements TraversalSideEffects {
      */
     @Override
     public void set(final String key, final Object value) throws IllegalArgumentException {
-        SideEffectHelper.validateSideEffectValue(value);
         if (!this.keys.contains(key))
             throw TraversalSideEffects.Exceptions.sideEffectKeyDoesNotExist(key);
         this.objectMap.put(key, value);
@@ -88,7 +87,6 @@ public class DefaultTraversalSideEffects implements TraversalSideEffects {
      */
     @Override
     public void add(final String key, final Object value) throws IllegalArgumentException {
-        SideEffectHelper.validateSideEffectValue(value);
         this.set(key, this.getReducer(key).apply(this.get(key), value));
     }
 
@@ -99,7 +97,6 @@ public class DefaultTraversalSideEffects implements TraversalSideEffects {
     public <V> void register(final String key, final Supplier<V> initialValue, final BinaryOperator<V> reducer) {
         SideEffectHelper.validateSideEffectKey(key);
         this.keys.add(key);
-        //this.objectMap.remove(key);
         if (null != initialValue)
             this.supplierMap.put(key, initialValue);
         if (null != reducer)
@@ -113,7 +110,6 @@ public class DefaultTraversalSideEffects implements TraversalSideEffects {
     public <V> void registerIfAbsent(final String key, final Supplier<V> initialValue, final BinaryOperator<V> reducer) {
         SideEffectHelper.validateSideEffectKey(key);
         this.keys.add(key);
-        //this.objectMap.remove(key);
         if (null == this.supplierMap.get(key) && null != initialValue) {
             this.supplierMap.put(key, initialValue);
         }
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/OperatorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/OperatorTest.java
index e498cae..ce433c6 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/OperatorTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/OperatorTest.java
@@ -19,6 +19,7 @@
 package org.apache.tinkerpop.gremlin.process.traversal;
 
 import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
@@ -26,15 +27,241 @@ import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
 
 import static org.junit.Assert.assertEquals;
 
 /**
  * @author Stephen Mallette (http://stephen.genoprime.com)
  */
-@RunWith(Parameterized.class)
+@RunWith(Enclosed.class)
 public class OperatorTest {
 
+    @RunWith(Parameterized.class)
+    public static class LogicTest {
+        @Parameterized.Parameters(name = "{0}({1},{2}) = {3}")
+        public static Iterable<Object[]> data() {
+            return new ArrayList<>(Arrays.asList(new Object[][]{
+                    {Operator.and, true, true, true},
+                    {Operator.and, true, false, false},
+                    {Operator.and, false, true, false},
+                    {Operator.and, false, false, false},
+                    {Operator.and, null, null, null},
+                    {Operator.and, null, false, false},
+                    {Operator.and, false, null, false},
+                    {Operator.and, null, true, true},
+                    {Operator.and, true, null, true},
+                    {Operator.or, true, true, true},
+                    {Operator.or, true, false, true},
+                    {Operator.or, false, true, true},
+                    {Operator.or, false, false, false},
+                    {Operator.or, null, null, null},
+                    {Operator.or, null, false, false},
+                    {Operator.or, false, null, false},
+                    {Operator.or, null, true, true},
+                    {Operator.or, true, null, true},
+            }));
+        }
+
+        @Parameterized.Parameter(value = 0)
+        public Operator operator;
+
+        @Parameterized.Parameter(value = 1)
+        public Boolean a;
+
+        @Parameterized.Parameter(value = 2)
+        public Boolean b;
+
+        @Parameterized.Parameter(value = 3)
+        public Boolean expected;
+
+        @Test
+        public void shouldApplyOperator() {
+            assertEquals(expected, operator.apply(a, b));
+        }
+    }
+
+    @RunWith(Parameterized.class)
+    public static class CollectionTest {
+        @Parameterized.Parameters(name = "{0}({1},{2}) = {3}")
+        public static Iterable<Object[]> data() {
+            return new ArrayList<>(Arrays.asList(new Object[][]{
+                    {Operator.addAll, null, null, null},
+                    {Operator.addAll, new ArrayList<>(Arrays.asList(1,2,3)), null, Arrays.asList(1,2,3)},
+                    {Operator.addAll, null, new ArrayList<>(Arrays.asList(1,2,3)), Arrays.asList(1,2,3)},
+                    {Operator.addAll, new ArrayList<>(Arrays.asList(1,2,3)), new ArrayList<>(Arrays.asList(4,5,6)), Arrays.asList(1,2,3,4,5,6)},
+            }));
+        }
+
+        @Parameterized.Parameter(value = 0)
+        public Operator operator;
+
+        @Parameterized.Parameter(value = 1)
+        public Collection a;
+
+        @Parameterized.Parameter(value = 2)
+        public Collection b;
+
+        @Parameterized.Parameter(value = 3)
+        public Collection expected;
+
+        @Test
+        public void shouldApplyOperator() {
+            assertEquals(expected, operator.apply(a, b));
+        }
+    }
+
+    @RunWith(Parameterized.class)
+    public static class MapTest {
+        @Parameterized.Parameters(name = "{0}({1},{2}) = {3}")
+        public static Iterable<Object[]> data() {
+            return new ArrayList<>(Arrays.asList(new Object[][]{
+                    {Operator.addAll, null, null, null},
+                    {Operator.addAll, new HashMap<String,Integer>() {{ put("a", 1); }}, null, new HashMap<String,Integer>() {{ put("a", 1); }}},
+                    {Operator.addAll, null, new HashMap<String,Integer>() {{ put("a", 1); }}, new HashMap<String,Integer>() {{ put("a", 1); }}},
+                    {Operator.addAll, new HashMap<String,Integer>() {{ put("a", 1); }}, new HashMap<String,Integer>() {{ put("b", 2); }}, new HashMap<String,Integer>() {{ put("a", 1); put("b", 2); }}},
+                    {Operator.addAll, new HashMap<String,Integer>() {{ put("a", 1); }}, new HashMap<String,Integer>() {{ put("a", 2); }}, new HashMap<String,Integer>() {{ put("a", 2); }}},
+            }));
+        }
+
+        @Parameterized.Parameter(value = 0)
+        public Operator operator;
+
+        @Parameterized.Parameter(value = 1)
+        public Map a;
+
+        @Parameterized.Parameter(value = 2)
+        public Map b;
+
+        @Parameterized.Parameter(value = 3)
+        public Map expected;
+
+        @Test
+        public void shouldApplyOperator() {
+            assertEquals(expected, operator.apply(a, b));
+        }
+    }
+
+    @RunWith(Parameterized.class)
+    public static class NumberTest {
+        @Parameterized.Parameters(name = "{0}({1},{2}) = {3}")
+        public static Iterable<Object[]> data() {
+            return new ArrayList<>(Arrays.asList(new Object[][]{
+                    {Operator.div, 10, 2, 5},
+                    {Operator.div, 10l, 2l, 5l},
+                    {Operator.div, 10f, 2f, 5f},
+                    {Operator.div, 10d, 2d, 5d},
+                    {Operator.div, 10, 2d, 5d},
+                    {Operator.div, 10, 2f, 5f},
+                    {Operator.div, 10d, 2, 5d},
+                    {Operator.div, 10f, 2d, 5d},
+                    {Operator.div, 10f, 2l, 5d},
+                    {Operator.div, 10f, 2, 5f},
+                    {Operator.div, BigInteger.TEN, 2, BigInteger.valueOf(5l)},
+                    {Operator.div, BigInteger.TEN, 2f, BigDecimal.valueOf(5l)},
+                    {Operator.div, BigDecimal.TEN, 2, BigDecimal.valueOf(5l)},
+                    {Operator.div, CustomNumber.TEN, 2, BigDecimal.valueOf(5l)},
+                    {Operator.max, 10, 2, 10},
+                    {Operator.max, 10l, 2l, 10l},
+                    {Operator.max, 10f, 2f, 10f},
+                    {Operator.max, 10d, 2d, 10d},
+                    {Operator.max, 2, 10, 10},
+                    {Operator.max, 2l, 10l, 10l},
+                    {Operator.max, 2f, 10f, 10f},
+                    {Operator.max, 2d, 10d, 10d},
+                    {Operator.max, 10, 2d, 10d},
+                    {Operator.max, 10, 2f, 10f},
+                    {Operator.max, 10d, 2, 10d},
+                    {Operator.max, 10f, 2d, 10d},
+                    {Operator.max, 10f, 2l, 10d},
+                    {Operator.max, 10f, 2, 10f},
+                    {Operator.max, BigInteger.TEN, 1, BigInteger.TEN},
+                    {Operator.max, BigInteger.TEN, BigDecimal.ONE, BigDecimal.TEN},
+                    {Operator.max, BigDecimal.TEN, 1, BigDecimal.TEN},
+                    {Operator.max, 1, CustomNumber.TEN, BigDecimal.TEN},
+                    {Operator.min, 10, 2, 2},
+                    {Operator.min, 10l, 2l, 2l},
+                    {Operator.min, 10f, 2f, 2f},
+                    {Operator.min, 10d, 2d, 2d},
+                    {Operator.min, 2, 10, 2},
+                    {Operator.min, 2l, 10l, 2l},
+                    {Operator.min, 2f, 10f, 2f},
+                    {Operator.min, 2d, 10d, 2d},
+                    {Operator.min, 10, 2d, 2d},
+                    {Operator.min, 10, 2f, 2f},
+                    {Operator.min, 10d, 2, 2d},
+                    {Operator.min, 10f, 2d, 2d},
+                    {Operator.min, 10f, 2l, 2d},
+                    {Operator.min, 10f, 2, 2f},
+                    {Operator.min, BigInteger.TEN, 1, BigInteger.ONE},
+                    {Operator.min, BigInteger.TEN, BigDecimal.ONE, BigDecimal.ONE},
+                    {Operator.min, BigDecimal.TEN, 1, BigDecimal.ONE},
+                    {Operator.min, 1, CustomNumber.TEN, BigDecimal.ONE},
+                    {Operator.minus, 10, 2, 8},
+                    {Operator.minus, 10l, 2l, 8l},
+                    {Operator.minus, 10f, 2f, 8f},
+                    {Operator.minus, 10d, 2d, 8d},
+                    {Operator.minus, 10, 2d, 8d},
+                    {Operator.minus, 10, 2f, 8f},
+                    {Operator.minus, 10d, 2, 8d},
+                    {Operator.minus, 10f, 2d, 8d},
+                    {Operator.minus, 10f, 2l, 8d},
+                    {Operator.minus, 10f, 2, 8f},
+                    {Operator.minus, BigInteger.TEN, 2, BigInteger.valueOf(8l)},
+                    {Operator.minus, BigInteger.TEN, 2f, BigDecimal.valueOf(8d)},
+                    {Operator.minus, BigDecimal.TEN, 2, BigDecimal.valueOf(8l)},
+                    {Operator.minus, CustomNumber.TEN, 2, BigDecimal.valueOf(8l)},
+                    {Operator.mult, 5, 4, 20},
+                    {Operator.mult, 5l, 4l, 20l},
+                    {Operator.mult, 5f, 4f, 20f},
+                    {Operator.mult, 5d, 4d, 20d},
+                    {Operator.mult, 5, 4d, 20d},
+                    {Operator.mult, 5, 4f, 20f},
+                    {Operator.mult, 5d, 4, 20d},
+                    {Operator.mult, 5f, 4d, 20d},
+                    {Operator.mult, 5f, 4l, 20d},
+                    {Operator.mult, 5f, 4, 20f},
+                    {Operator.mult, BigInteger.valueOf(5l), 4, BigInteger.valueOf(20l)},
+                    {Operator.mult, BigInteger.valueOf(5l), 4f, BigDecimal.valueOf(20d)},
+                    {Operator.mult, BigDecimal.valueOf(5d), 4, BigDecimal.valueOf(20d)},
+                    {Operator.mult, CustomNumber.TEN, 2f, BigDecimal.valueOf(20d)},
+                    {Operator.sum, 7, 3, 10},
+                    {Operator.sum, 7l, 3l, 10l},
+                    {Operator.sum, 7f, 3f, 10f},
+                    {Operator.sum, 7d, 3d, 10d},
+                    {Operator.sum, 7, 3d, 10d},
+                    {Operator.sum, 7, 3f, 10f},
+                    {Operator.sum, 7d, 3, 10d},
+                    {Operator.sum, 7f, 3d, 10d},
+                    {Operator.sum, 7f, 3l, 10d},
+                    {Operator.sum, 7f, 3, 10f},
+                    {Operator.sum, BigInteger.valueOf(7l), 3, BigInteger.TEN},
+                    {Operator.sum, BigInteger.valueOf(7l), 3f, BigDecimal.valueOf(10d)},
+                    {Operator.sum, BigDecimal.valueOf(7d), 3, BigDecimal.valueOf(10d)},
+                    {Operator.sum, CustomNumber.TEN, CustomNumber.ONE, BigDecimal.valueOf(11l)}
+            }));
+        }
+
+        @Parameterized.Parameter(value = 0)
+        public Operator operator;
+
+        @Parameterized.Parameter(value = 1)
+        public Number a;
+
+        @Parameterized.Parameter(value = 2)
+        public Number b;
+
+        @Parameterized.Parameter(value = 3)
+        public Number expected;
+
+        @Test
+        public void shouldApplyOperator() {
+            assertEquals(expected, operator.apply(a, b));
+        }
+    }
+
     /**
      * Required to verify that Operator can handle Number type, that it doesn't know explicitly.
      */
@@ -74,119 +301,4 @@ public class OperatorTest {
             return Integer.compare(n, anotherCustomNumber.n);
         }
     }
-
-    @Parameterized.Parameters(name = "{0}({1},{2}) = {3}")
-    public static Iterable<Object[]> data() {
-        return new ArrayList<>(Arrays.asList(new Object[][]{
-                {Operator.div, 10, 2, 5},
-                {Operator.div, 10l, 2l, 5l},
-                {Operator.div, 10f, 2f, 5f},
-                {Operator.div, 10d, 2d, 5d},
-                {Operator.div, 10, 2d, 5d},
-                {Operator.div, 10, 2f, 5f},
-                {Operator.div, 10d, 2, 5d},
-                {Operator.div, 10f, 2d, 5d},
-                {Operator.div, 10f, 2l, 5d},
-                {Operator.div, 10f, 2, 5f},
-                {Operator.div, BigInteger.TEN, 2, BigInteger.valueOf(5l)},
-                {Operator.div, BigInteger.TEN, 2f, BigDecimal.valueOf(5l)},
-                {Operator.div, BigDecimal.TEN, 2, BigDecimal.valueOf(5l)},
-                {Operator.div, CustomNumber.TEN, 2, BigDecimal.valueOf(5l)},
-                {Operator.max, 10, 2, 10},
-                {Operator.max, 10l, 2l, 10l},
-                {Operator.max, 10f, 2f, 10f},
-                {Operator.max, 10d, 2d, 10d},
-                {Operator.max, 2, 10, 10},
-                {Operator.max, 2l, 10l, 10l},
-                {Operator.max, 2f, 10f, 10f},
-                {Operator.max, 2d, 10d, 10d},
-                {Operator.max, 10, 2d, 10d},
-                {Operator.max, 10, 2f, 10f},
-                {Operator.max, 10d, 2, 10d},
-                {Operator.max, 10f, 2d, 10d},
-                {Operator.max, 10f, 2l, 10d},
-                {Operator.max, 10f, 2, 10f},
-                {Operator.max, BigInteger.TEN, 1, BigInteger.TEN},
-                {Operator.max, BigInteger.TEN, BigDecimal.ONE, BigDecimal.TEN},
-                {Operator.max, BigDecimal.TEN, 1, BigDecimal.TEN},
-                {Operator.max, 1, CustomNumber.TEN, BigDecimal.TEN},
-                {Operator.min, 10, 2, 2},
-                {Operator.min, 10l, 2l, 2l},
-                {Operator.min, 10f, 2f, 2f},
-                {Operator.min, 10d, 2d, 2d},
-                {Operator.min, 2, 10, 2},
-                {Operator.min, 2l, 10l, 2l},
-                {Operator.min, 2f, 10f, 2f},
-                {Operator.min, 2d, 10d, 2d},
-                {Operator.min, 10, 2d, 2d},
-                {Operator.min, 10, 2f, 2f},
-                {Operator.min, 10d, 2, 2d},
-                {Operator.min, 10f, 2d, 2d},
-                {Operator.min, 10f, 2l, 2d},
-                {Operator.min, 10f, 2, 2f},
-                {Operator.min, BigInteger.TEN, 1, BigInteger.ONE},
-                {Operator.min, BigInteger.TEN, BigDecimal.ONE, BigDecimal.ONE},
-                {Operator.min, BigDecimal.TEN, 1, BigDecimal.ONE},
-                {Operator.min, 1, CustomNumber.TEN, BigDecimal.ONE},
-                {Operator.minus, 10, 2, 8},
-                {Operator.minus, 10l, 2l, 8l},
-                {Operator.minus, 10f, 2f, 8f},
-                {Operator.minus, 10d, 2d, 8d},
-                {Operator.minus, 10, 2d, 8d},
-                {Operator.minus, 10, 2f, 8f},
-                {Operator.minus, 10d, 2, 8d},
-                {Operator.minus, 10f, 2d, 8d},
-                {Operator.minus, 10f, 2l, 8d},
-                {Operator.minus, 10f, 2, 8f},
-                {Operator.minus, BigInteger.TEN, 2, BigInteger.valueOf(8l)},
-                {Operator.minus, BigInteger.TEN, 2f, BigDecimal.valueOf(8d)},
-                {Operator.minus, BigDecimal.TEN, 2, BigDecimal.valueOf(8l)},
-                {Operator.minus, CustomNumber.TEN, 2, BigDecimal.valueOf(8l)},
-                {Operator.mult, 5, 4, 20},
-                {Operator.mult, 5l, 4l, 20l},
-                {Operator.mult, 5f, 4f, 20f},
-                {Operator.mult, 5d, 4d, 20d},
-                {Operator.mult, 5, 4d, 20d},
-                {Operator.mult, 5, 4f, 20f},
-                {Operator.mult, 5d, 4, 20d},
-                {Operator.mult, 5f, 4d, 20d},
-                {Operator.mult, 5f, 4l, 20d},
-                {Operator.mult, 5f, 4, 20f},
-                {Operator.mult, BigInteger.valueOf(5l), 4, BigInteger.valueOf(20l)},
-                {Operator.mult, BigInteger.valueOf(5l), 4f, BigDecimal.valueOf(20d)},
-                {Operator.mult, BigDecimal.valueOf(5d), 4, BigDecimal.valueOf(20d)},
-                {Operator.mult, CustomNumber.TEN, 2f, BigDecimal.valueOf(20d)},
-                {Operator.sum, 7, 3, 10},
-                {Operator.sum, 7l, 3l, 10l},
-                {Operator.sum, 7f, 3f, 10f},
-                {Operator.sum, 7d, 3d, 10d},
-                {Operator.sum, 7, 3d, 10d},
-                {Operator.sum, 7, 3f, 10f},
-                {Operator.sum, 7d, 3, 10d},
-                {Operator.sum, 7f, 3d, 10d},
-                {Operator.sum, 7f, 3l, 10d},
-                {Operator.sum, 7f, 3, 10f},
-                {Operator.sum, BigInteger.valueOf(7l), 3, BigInteger.TEN},
-                {Operator.sum, BigInteger.valueOf(7l), 3f, BigDecimal.valueOf(10d)},
-                {Operator.sum, BigDecimal.valueOf(7d), 3, BigDecimal.valueOf(10d)},
-                {Operator.sum, CustomNumber.TEN, CustomNumber.ONE, BigDecimal.valueOf(11l)}
-        }));
-    }
-
-    @Parameterized.Parameter(value = 0)
-    public Operator operator;
-
-    @Parameterized.Parameter(value = 1)
-    public Number a;
-
-    @Parameterized.Parameter(value = 2)
-    public Number b;
-
-    @Parameterized.Parameter(value = 3)
-    public Number expected;
-
-    @Test
-    public void shouldApplyOperator() {
-        assertEquals(expected, operator.apply(a, b));
-    }
 }
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/util/DefaultTraversalSideEffectsTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/util/DefaultTraversalSideEffectsTest.java
index 679042d..9d1210f 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/util/DefaultTraversalSideEffectsTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/util/DefaultTraversalSideEffectsTest.java
@@ -26,8 +26,11 @@ import org.junit.Test;
 
 import java.util.Arrays;
 
+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;
 import static org.junit.Assert.fail;
 
@@ -37,6 +40,29 @@ import static org.junit.Assert.fail;
 public class DefaultTraversalSideEffectsTest {
 
     @Test
+    public void shouldAllowNulls() {
+        final TraversalSideEffects sideEffects = new DefaultTraversalSideEffects();
+        sideEffects.register("k", new ConstantSupplier<>(null), null);
+        assertNull(sideEffects.get("k"));
+
+        sideEffects.set("k", "x");
+        assertEquals("x", sideEffects.get("k"));
+
+        sideEffects.set("k", null);
+        assertNull(sideEffects.get("k"));
+
+        sideEffects.add("k", null);
+        assertNull(sideEffects.get("k"));
+
+        sideEffects.register("kand", new ConstantSupplier<>(null), Operator.and);
+        sideEffects.add("kand", null);
+        assertNull(sideEffects.get("kand"));
+
+        sideEffects.add("kand", true);
+        assertThat(sideEffects.get("kand"), is(true));
+    }
+
+    @Test
     public void shouldOperateCorrectly() {
         final TraversalSideEffects sideEffects = new DefaultTraversalSideEffects();
         try {
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index 6e606ea..74b346c 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -522,7 +522,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                {"g_V_hasXageX_propertiesXnameX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("age").Properties<object>("name").Value<object>()}}, 
                {"g_V_hasXageX_propertiesXname_ageX_value", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("age").Properties<object>("name","age").Value<object>()}}, 
                {"g_V_hasXageX_propertiesXage_nameX_value", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("age").Properties<object>("age","name").Value<object>()}}, 
-               {"get_g_VX1X_asXaX_outXknowsX_asXbX_selectXa_bX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).As("a").Out("knows").As("b").Select<object>("a","b")}}, 
+               {"g_VX1X_asXaX_outXknowsX_asXbX_selectXa_bX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).As("a").Out("knows").As("b").Select<object>("a","b")}}, 
                {"g_VX1X_asXaX_outXknowsX_asXbX_selectXa_bX_byXnameX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).As("a").Out("knows").As("b").Select<object>("a","b").By("name")}}, 
                {"g_VX1X_asXaX_outXknowsX_asXbX_selectXaX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).As("a").Out("knows").As("b").Select<object>("a")}}, 
                {"g_VX1X_asXaX_outXknowsX_asXbX_selectXaX_byXnameX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).As("a").Out("knows").As("b").Select<object>("a").By("name")}}, 
@@ -550,7 +550,6 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                {"g_V_outE_weight_groupCount_unfold_selectXvaluesX_unfold", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().OutE().Values<object>("weight").GroupCount<object>().Unfold<object>().Select<object>(Column.Values).Unfold<object>()}}, 
                {"g_V_untilXout_outX_repeatXin_asXaX_in_asXbXX_selectXa_bX_byXnameX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Until(__.Out().Out()).Repeat(__.In().As("a").In().As("b")).Select<object>("a","b").By("name")}}, 
                {"g_V_outE_weight_groupCount_selectXvaluesX_unfold", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().OutE().Values<object>("weight").GroupCount<object>().Select<object>(Column.Values).Unfold<object>()}}, 
-               {"g_VX1X_asXaX_outXknowsX_asXbX_selectXa_bX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).As("a").Out("knows").As("b").Select<object>("a","b")}}, 
                {"g_V_asXaX_whereXoutXknowsXX_selectXaX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().As("a").Where(__.Out("knows")).Select<object>("a")}}, 
                {"g_VX1X_asXaX_repeatXout_asXaXX_timesX2X_selectXfirst_aX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).As("a").Repeat(__.Out().As("a")).Times(2).Select<object>(Pop.First,"a")}}, 
                {"g_V_asXaX_outXknowsX_asXbX_localXselectXa_bX_byXnameXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().As("a").Out("knows").As("b").Local<object>(__.Select<object>("a","b").By("name"))}}, 
@@ -586,6 +585,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                {"g_EX11X_propertiesXweightX_asXaX_selectXaX_byXkeyX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.E(p["eid11"]).Properties<object>("weight").As("a").Select<object>("a").By(T.Key)}}, 
                {"g_EX11X_propertiesXweightX_asXaX_selectXaX_byXvalueX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.E(p["eid11"]).Properties<object>("weight").As("a").Select<object>("a").By(T.Value)}}, 
                {"g_V_asXaX_selectXaX_byXageX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().As("a").Select<object>("a").By("age")}}, 
+               {"g_withSideEffectXk_nullX_injectXxX_selectXkX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.WithSideEffect("k",null).Inject("x").Select<object>("k")}}, 
                {"g_V_shortestPath", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.WithStrategies(new VertexProgramStrategy(graphComputer: "org.apache.tinkerpop.gremlin.process.computer.GraphComputer")).V().Identity().ShortestPath()}}, 
                {"g_V_both_dedup_shortestPath", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.WithStrategies(new VertexProgramStrategy(graphComputer: "org.apache.tinkerpop.gremlin.process.computer.GraphComputer")).V().Both().Dedup().ShortestPath()}}, 
                {"g_V_shortestPath_edgesIncluded", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.WithStrategies(new VertexProgramStrategy(graphComputer: "org.apache.tinkerpop.gremlin.process.computer.GraphComputer")).V().Identity().ShortestPath().With("~tinkerpop.shortestPath.includeEdges")}}, 
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
index c3f01ac..f0b3c50 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
@@ -511,7 +511,7 @@ const gremlins = {
     g_V_hasXageX_propertiesXnameX: [function({g}) { return g.V().has("age").properties("name").value() }], 
     g_V_hasXageX_propertiesXname_ageX_value: [function({g}) { return g.V().has("age").properties("name","age").value() }], 
     g_V_hasXageX_propertiesXage_nameX_value: [function({g}) { return g.V().has("age").properties("age","name").value() }], 
-    get_g_VX1X_asXaX_outXknowsX_asXbX_selectXa_bX: [function({g, vid1}) { return g.V(vid1).as("a").out("knows").as("b").select("a","b") }], 
+    g_VX1X_asXaX_outXknowsX_asXbX_selectXa_bX: [function({g, vid1}) { return g.V(vid1).as("a").out("knows").as("b").select("a","b") }], 
     g_VX1X_asXaX_outXknowsX_asXbX_selectXa_bX_byXnameX: [function({g, vid1}) { return g.V(vid1).as("a").out("knows").as("b").select("a","b").by("name") }], 
     g_VX1X_asXaX_outXknowsX_asXbX_selectXaX: [function({g, vid1}) { return g.V(vid1).as("a").out("knows").as("b").select("a") }], 
     g_VX1X_asXaX_outXknowsX_asXbX_selectXaX_byXnameX: [function({g, vid1}) { return g.V(vid1).as("a").out("knows").as("b").select("a").by("name") }], 
@@ -539,7 +539,6 @@ const gremlins = {
     g_V_outE_weight_groupCount_unfold_selectXvaluesX_unfold: [function({g}) { return g.V().outE().values("weight").groupCount().unfold().select(Column.values).unfold() }], 
     g_V_untilXout_outX_repeatXin_asXaX_in_asXbXX_selectXa_bX_byXnameX: [function({g}) { return g.V().until(__.out().out()).repeat(__.in_().as("a").in_().as("b")).select("a","b").by("name") }], 
     g_V_outE_weight_groupCount_selectXvaluesX_unfold: [function({g}) { return g.V().outE().values("weight").groupCount().select(Column.values).unfold() }], 
-    g_VX1X_asXaX_outXknowsX_asXbX_selectXa_bX: [function({g, vid1}) { return g.V(vid1).as("a").out("knows").as("b").select("a","b") }], 
     g_V_asXaX_whereXoutXknowsXX_selectXaX: [function({g}) { return g.V().as("a").where(__.out("knows")).select("a") }], 
     g_VX1X_asXaX_repeatXout_asXaXX_timesX2X_selectXfirst_aX: [function({g, vid1}) { return g.V(vid1).as("a").repeat(__.out().as("a")).times(2).select(Pop.first,"a") }], 
     g_V_asXaX_outXknowsX_asXbX_localXselectXa_bX_byXnameXX: [function({g}) { return g.V().as("a").out("knows").as("b").local(__.select("a","b").by("name")) }], 
@@ -575,6 +574,7 @@ const gremlins = {
     g_EX11X_propertiesXweightX_asXaX_selectXaX_byXkeyX: [function({g, eid11}) { return g.E(eid11).properties("weight").as("a").select("a").by(T.key) }], 
     g_EX11X_propertiesXweightX_asXaX_selectXaX_byXvalueX: [function({g, eid11}) { return g.E(eid11).properties("weight").as("a").select("a").by(T.value) }], 
     g_V_asXaX_selectXaX_byXageX: [function({g}) { return g.V().as("a").select("a").by("age") }], 
+    g_withSideEffectXk_nullX_injectXxX_selectXkX: [function({g}) { return g.withSideEffect("k",null).inject("x").select("k") }], 
     g_V_shortestPath: [function({g}) { return g.withStrategies(new VertexProgramStrategy(new Map([["graphComputer","org.apache.tinkerpop.gremlin.process.computer.GraphComputer"]]))).V().identity().shortestPath() }], 
     g_V_both_dedup_shortestPath: [function({g}) { return g.withStrategies(new VertexProgramStrategy(new Map([["graphComputer","org.apache.tinkerpop.gremlin.process.computer.GraphComputer"]]))).V().both().dedup().shortestPath() }], 
     g_V_shortestPath_edgesIncluded: [function({g}) { return g.withStrategies(new VertexProgramStrategy(new Map([["graphComputer","org.apache.tinkerpop.gremlin.process.computer.GraphComputer"]]))).V().identity().shortestPath().with_("~tinkerpop.shortestPath.includeEdges") }], 
diff --git a/gremlin-python/src/main/python/radish/gremlin.py b/gremlin-python/src/main/python/radish/gremlin.py
index bfab55e..ac17b3b 100644
--- a/gremlin-python/src/main/python/radish/gremlin.py
+++ b/gremlin-python/src/main/python/radish/gremlin.py
@@ -496,7 +496,7 @@ world.gremlins = {
     'g_V_hasXageX_propertiesXnameX': [(lambda g:g.V().has('age').properties('name').value())], 
     'g_V_hasXageX_propertiesXname_ageX_value': [(lambda g:g.V().has('age').properties('name','age').value())], 
     'g_V_hasXageX_propertiesXage_nameX_value': [(lambda g:g.V().has('age').properties('age','name').value())], 
-    'get_g_VX1X_asXaX_outXknowsX_asXbX_selectXa_bX': [(lambda g, vid1=None:g.V(vid1).as_('a').out('knows').as_('b').select('a','b'))], 
+    'g_VX1X_asXaX_outXknowsX_asXbX_selectXa_bX': [(lambda g, vid1=None:g.V(vid1).as_('a').out('knows').as_('b').select('a','b'))], 
     'g_VX1X_asXaX_outXknowsX_asXbX_selectXa_bX_byXnameX': [(lambda g, vid1=None:g.V(vid1).as_('a').out('knows').as_('b').select('a','b').by('name'))], 
     'g_VX1X_asXaX_outXknowsX_asXbX_selectXaX': [(lambda g, vid1=None:g.V(vid1).as_('a').out('knows').as_('b').select('a'))], 
     'g_VX1X_asXaX_outXknowsX_asXbX_selectXaX_byXnameX': [(lambda g, vid1=None:g.V(vid1).as_('a').out('knows').as_('b').select('a').by('name'))], 
@@ -524,7 +524,6 @@ world.gremlins = {
     'g_V_outE_weight_groupCount_unfold_selectXvaluesX_unfold': [(lambda g:g.V().outE().weight.groupCount().unfold().select(Column.values).unfold())], 
     'g_V_untilXout_outX_repeatXin_asXaX_in_asXbXX_selectXa_bX_byXnameX': [(lambda g:g.V().until(__.out().out()).repeat(__.in_().as_('a').in_().as_('b')).select('a','b').by('name'))], 
     'g_V_outE_weight_groupCount_selectXvaluesX_unfold': [(lambda g:g.V().outE().weight.groupCount().select(Column.values).unfold())], 
-    'g_VX1X_asXaX_outXknowsX_asXbX_selectXa_bX': [(lambda g, vid1=None:g.V(vid1).as_('a').out('knows').as_('b').select('a','b'))], 
     'g_V_asXaX_whereXoutXknowsXX_selectXaX': [(lambda g:g.V().as_('a').where(__.out('knows')).select('a'))], 
     'g_VX1X_asXaX_repeatXout_asXaXX_timesX2X_selectXfirst_aX': [(lambda g, vid1=None:g.V(vid1).as_('a').repeat(__.out().as_('a')).times(2).select(Pop.first,'a'))], 
     'g_V_asXaX_outXknowsX_asXbX_localXselectXa_bX_byXnameXX': [(lambda g:g.V().as_('a').out('knows').as_('b').local(__.select('a','b').by('name')))], 
@@ -560,6 +559,7 @@ world.gremlins = {
     'g_EX11X_propertiesXweightX_asXaX_selectXaX_byXkeyX': [(lambda g, eid11=None:g.E(eid11).properties('weight').as_('a').select('a').by(T.key))], 
     'g_EX11X_propertiesXweightX_asXaX_selectXaX_byXvalueX': [(lambda g, eid11=None:g.E(eid11).properties('weight').as_('a').select('a').by(T.value))], 
     'g_V_asXaX_selectXaX_byXageX': [(lambda g:g.V().as_('a').select('a').by('age'))], 
+    'g_withSideEffectXk_nullX_injectXxX_selectXkX': [(lambda g:g.withSideEffect('k',None).inject('x').select('k'))], 
     'g_V_shortestPath': [(lambda g:g.withStrategies(*[TraversalStrategy('VertexProgramStrategy',{'graphComputer':'org.apache.tinkerpop.gremlin.process.computer.GraphComputer'}, 'org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.decoration.VertexProgramStrategy')]).V().identity().shortestPath())], 
     'g_V_both_dedup_shortestPath': [(lambda g:g.withStrategies(*[TraversalStrategy('VertexProgramStrategy',{'graphComputer':'org.apache.tinkerpop.gremlin.process.computer.GraphComputer'}, 'org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.decoration.VertexProgramStrategy')]).V().both().dedup().shortestPath())], 
     'g_V_shortestPath_edgesIncluded': [(lambda g:g.withStrategies(*[TraversalStrategy('VertexProgramStrategy',{'graphComputer':'org.apache.tinkerpop.gremlin.process.computer.GraphComputer'}, 'org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.decoration.VertexProgramStrategy')]).V().identity().shortestPath().with_('~tinkerpop.shortestPath.includeEdges'))], 
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 54ab3df..f0d34dc 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
@@ -104,6 +104,10 @@ import org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComp
         method = "g_injectXlistXnull_10_20_nullXX_meanXlocalX",
         reason = "The inject() step is not supported by GraphComputer")
 @Graph.OptOut(
+        test = "org.apache.tinkerpop.gremlin.process.traversal.step.map.SelectTest",
+        method = "g_withSideEffectXk_nullX_injectXxX_selectXkX",
+        reason = "The inject() step is not supported by GraphComputer")
+@Graph.OptOut(
         test = "org.apache.tinkerpop.gremlin.process.traversal.step.map.MeanTest",
         method = "g_injectXnull_10_20_nullX_mean",
         reason = "The inject() step is not supported by GraphComputer")
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 2678738..4932dde 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
@@ -108,6 +108,10 @@ import org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComp
         method = "g_injectXnull_10_20_nullX_mean",
         reason = "The inject() step is not supported by GraphComputer")
 @Graph.OptOut(
+        test = "org.apache.tinkerpop.gremlin.process.traversal.step.map.SelectTest",
+        method = "g_withSideEffectXk_nullX_injectXxX_selectXkX",
+        reason = "The inject() step is not supported by GraphComputer")
+@Graph.OptOut(
         test = "org.apache.tinkerpop.gremlin.process.traversal.step.map.ReadTest",
         method = "*",
         reason = "The io() step is not supported generally by GraphComputer")
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 3d5c510..1240741 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
@@ -109,6 +109,10 @@ import org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComp
         method = "g_injectXnull_10_20_nullX_mean",
         reason = "The inject() step is not supported by GraphComputer")
 @Graph.OptOut(
+        test = "org.apache.tinkerpop.gremlin.process.traversal.step.map.SelectTest",
+        method = "g_withSideEffectXk_nullX_injectXxX_selectXkX",
+        reason = "The inject() step is not supported by GraphComputer")
+@Graph.OptOut(
         test = "org.apache.tinkerpop.gremlin.process.traversal.step.map.ReadTest",
         method = "*",
         reason = "The io() step is not supported generally by GraphComputer")
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 01b3a21..924992d 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
@@ -111,6 +111,10 @@ import org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComp
         method = "g_injectXnull_10_20_nullX_mean",
         reason = "The inject() step is not supported by GraphComputer")
 @Graph.OptOut(
+        test = "org.apache.tinkerpop.gremlin.process.traversal.step.map.SelectTest",
+        method = "g_withSideEffectXk_nullX_injectXxX_selectXkX",
+        reason = "The inject() step is not supported by GraphComputer")
+@Graph.OptOut(
         test = "org.apache.tinkerpop.gremlin.process.traversal.step.map.ReadTest",
         method = "*",
         reason = "The io() step is not supported generally by GraphComputer")
diff --git a/gremlin-test/features/map/Select.feature b/gremlin-test/features/map/Select.feature
index 3169e0e..6da8ac6 100644
--- a/gremlin-test/features/map/Select.feature
+++ b/gremlin-test/features/map/Select.feature
@@ -17,7 +17,7 @@
 
 Feature: Step - select()
 
-  Scenario: get_g_VX1X_asXaX_outXknowsX_asXbX_selectXa_bX
+  Scenario: g_VX1X_asXaX_outXknowsX_asXbX_selectXa_bX
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
     And the traversal of
@@ -436,19 +436,6 @@ Feature: Step - select()
       | d[2].l |
       | d[1].l |
 
-  Scenario: g_VX1X_asXaX_outXknowsX_asXbX_selectXa_bX
-    Given the modern graph
-    And using the parameter vid1 defined as "v[marko].id"
-    And the traversal of
-      """
-      g.V(vid1).as("a").out("knows").as("b").select("a", "b")
-      """
-    When iterated to list
-    Then the result should be unordered
-      | result |
-      | m[{"a":"v[marko]","b":"v[vadas]"}] |
-      | m[{"a":"v[marko]","b":"v[josh]"}] |
-
   Scenario: g_V_asXaX_whereXoutXknowsXX_selectXaX
     Given the modern graph
     And the traversal of
@@ -830,4 +817,15 @@ Feature: Step - select()
       | null |
       | d[32].i |
       | null |
-      | d[35].i |
\ No newline at end of file
+      | d[35].i |
+
+  Scenario: g_withSideEffectXk_nullX_injectXxX_selectXkX
+    Given the empty graph
+    And the traversal of
+      """
+      g.withSideEffect("k",null).inject("x").select("k")
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | null |
\ No newline at end of file
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectTest.java
index f6c8d12..4b450ca 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SelectTest.java
@@ -47,8 +47,11 @@ import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.as;
 import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.identity;
 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;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -162,6 +165,8 @@ public abstract class SelectTest extends AbstractGremlinProcessTest {
 
     public abstract Traversal<Edge, Object> get_g_EX11X_propertiesXweightX_asXaX_selectXaX_byXvalueX(final Object e11Id);
 
+    public abstract Traversal<String, Object> get_g_withSideEffectXk_nullX_injectXxX_selectXkX();
+
     // when labels don't exist
 
     public abstract Traversal<Vertex, String> get_g_V_untilXout_outX_repeatXin_asXaXX_selectXaX_byXtailXlocalX_nameX();
@@ -896,6 +901,14 @@ public abstract class SelectTest extends AbstractGremlinProcessTest {
         checkResults(Arrays.asList(29, 27, null, 32, null, 35), traversal);
     }
 
+    @Test
+    public void g_withSideEffectXk_nullX_injectXxX_selectXkX() {
+        final Traversal<String, Object> traversal = get_g_withSideEffectXk_nullX_injectXxX_selectXkX();
+        printTraversalForm(traversal);
+        assertNull(traversal.next());
+        assertThat(traversal.hasNext(), is(false));
+    }
+
     public static class Traversals extends SelectTest {
         @Override
         public Traversal<Vertex, Map<String, Vertex>> get_g_VX1X_asXaX_outXknowsX_asXbX_selectXa_bX(final Object v1Id) {
@@ -1224,5 +1237,10 @@ public abstract class SelectTest extends AbstractGremlinProcessTest {
         public Traversal<Vertex, Object> get_g_V_asXaX_selectXaX_byXageX() {
             return g.V().as("a").select("a").by("age");
         }
+
+        @Override
+        public Traversal<String, Object> get_g_withSideEffectXk_nullX_injectXxX_selectXkX() {
+            return g.withSideEffect("k",null).inject("x").select("k");
+        }
     }
 }

[tinkerpop] 02/02: TINKERPOP-2605 Support null in path()

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

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

commit 73db22aeeac4c8c078258fd90d8ecca3c52d43e0
Author: Stephen Mallette <st...@amazon.com>
AuthorDate: Fri Sep 3 13:06:40 2021 -0400

    TINKERPOP-2605 Support null in path()
---
 CHANGELOG.asciidoc                                 |  1 +
 .../tinkerpop/gremlin/process/traversal/Path.java  | 33 +++++++++++---------
 .../traversal/dsl/graph/GraphTraversal.java        |  3 +-
 .../process/traversal/step/util/ImmutablePath.java | 12 ++++----
 .../process/traversal/step/util/MutablePath.java   |  5 +++-
 .../traverser/B_LP_O_S_SE_SL_Traverser.java        |  2 +-
 .../traverser/LP_O_OB_S_SE_SL_Traverser.java       |  2 +-
 .../gremlin/process/traversal/PathTest.java        | 30 ++++++++++++++++++-
 .../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs |  3 ++
 .../gremlin-javascript/test/cucumber/gremlin.js    |  3 ++
 gremlin-python/src/main/python/radish/gremlin.py   |  3 ++
 .../GraphBinaryRemoteGraphComputerProvider.java    |  8 +++++
 .../GraphSONRemoteGraphComputerProvider.java       |  8 +++++
 .../remote/GryoRemoteGraphComputerProvider.java    |  8 +++++
 ...emoteGraphGroovyTranslatorComputerProvider.java |  8 +++++
 gremlin-test/features/filter/Has.feature           |  2 +-
 gremlin-test/features/map/Path.feature             | 27 ++++++++++++++++-
 gremlin-test/features/sideEffect/Inject.feature    | 16 +++++++++-
 .../process/traversal/step/filter/HasTest.java     |  2 +-
 .../process/traversal/step/map/PathTest.java       | 35 ++++++++++++++++++++++
 .../traversal/step/sideEffect/InjectTest.java      | 14 +++++++++
 21 files changed, 196 insertions(+), 29 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 863f836..6ab0951 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -34,6 +34,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 * Handled `null` for mathematical reducing operations of `sum()`, `mean()`, `max()` and `min()`.
 * Allowed `null` values in `Memory` for `GraphComputer`.
 * Allowed `null` assignment in `withSideEffect()`.
+* Allowed labelling of steps that emit a traverser carrying `null`.
 * Fixed problems with `inject(null)` variations where `null` was the only value being submitted.
 * Fixed problem with `GroovyTranslator` and `inject(null,null)` which could be interpreted as the Groovy JDK extension `inject(Object,Closure).
 * Fixed error where certain variants of `inject()` with `null` might not properly construct a traversal in .NET.
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Path.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Path.java
index 07eadb8..d8e51c2 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Path.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Path.java
@@ -26,6 +26,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 import java.util.function.BiConsumer;
 import java.util.stream.IntStream;
@@ -105,24 +106,25 @@ public interface Path extends Cloneable, Iterable<Object> {
     public default <A> A get(final String label) throws IllegalArgumentException {
         final List<Object> objects = this.objects();
         final List<Set<String>> labels = this.labels();
-        Object object = null;
+        Optional<Object> object = null;
         for (int i = 0; i < labels.size(); i++) {
             if (labels.get(i).contains(label)) {
                 if (null == object) {
-                    object = objects.get(i);
-                } else if (object instanceof List) {
-                    ((List) object).add(objects.get(i));
+                    object = Optional.ofNullable(objects.get(i));
+                } else if (object.get() instanceof List) {
+                    ((List) object.get()).add(objects.get(i));
                 } else {
-                    final List list = new ArrayList(2);
-                    list.add(object);
+                    final List<Object> list = new ArrayList<>(2);
+                    list.add(object.get());
                     list.add(objects.get(i));
-                    object = list;
+                    object = Optional.of(list);
                 }
             }
         }
-        if (null == object)
-            throw Path.Exceptions.stepWithProvidedLabelDoesNotExist(label);
-        return (A) object;
+
+        if (null == object) throw Path.Exceptions.stepWithProvidedLabelDoesNotExist(label);
+
+        return (A) object.orElse(null);
     }
 
     /**
@@ -234,11 +236,14 @@ public interface Path extends Cloneable, Iterable<Object> {
         if (!(other instanceof Path))
             return false;
         final Path otherPath = (Path) other;
-        return !this.labels().stream().
+        return this.labels().stream().
                 flatMap(Set::stream).
-                filter(label -> !otherPath.hasLabel(label) || !otherPath.get(pop, label).equals(this.get(pop, label))).
-                findAny().
-                isPresent();
+                noneMatch(label -> {
+                    if (!otherPath.hasLabel(label)) return true;
+                    final Object o1 = otherPath.get(pop, label);
+                    final Object o2 = this.get(pop, label);
+                    return !((null == o1 && null == o2) || (o1 != null && o1.equals(o2)));
+                });
     }
 
     /**
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 1abf653..9a45300 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
@@ -1705,11 +1705,10 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
      * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#has-step" target="_blank">Reference Documentation - Has Step</a>
      */
     public default GraphTraversal<S, E> hasValue(final Object value, final Object... otherValues) {
-        this.asAdmin().getBytecode().addStep(Symbols.hasValue, value, otherValues);
-
         if (value instanceof P)
             return this.hasValue((P) value);
         else {
+            this.asAdmin().getBytecode().addStep(Symbols.hasValue, value, otherValues);
             final List<Object> values = new ArrayList<>();
             if (value instanceof Object[]) {
                 Collections.addAll(values, (Object[]) value);
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 1b5e562..a9c97c1 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
@@ -173,8 +173,7 @@ public class ImmutablePath implements Path, Serializable, Cloneable {
                     found = (A) currentPath.currentObject;
                 currentPath = currentPath.previousPath;
             }
-            if (null == found)
-                throw Path.Exceptions.stepWithProvidedLabelDoesNotExist(label);
+
             return found;
         }
     }
@@ -258,7 +257,7 @@ public class ImmutablePath implements Path, Serializable, Cloneable {
             for (int i = otherLabels.size() - 1; i >= 0; i--) {
                 if (currentPath.isTail())
                     return true;
-                else if (!currentPath.currentObject.equals(otherObjects.get(i)) ||
+                else if ((!(currentPath.currentObject == null && otherObjects.get(i) == null) && (currentPath.currentObject != null && !currentPath.currentObject.equals(otherObjects.get(i)))) ||
                         !currentPath.currentLabels.equals(otherLabels.get(i)))
                     return false;
                 else
@@ -278,8 +277,11 @@ public class ImmutablePath implements Path, Serializable, Cloneable {
             if (currentPath.isTail())
                 break;
             for (final String label : currentPath.currentLabels) {
-                if (!otherPath.hasLabel(label) || !this.get(pop, label).equals(otherPath.get(pop, label)))
-                    return false;
+                if (!otherPath.hasLabel(label)) return false;
+
+                final Object o1 = this.get(pop, label);
+                final Object o2 = otherPath.get(pop, label);
+                if (o1 != null && !o1.equals(o2)) return false;
             }
             currentPath = currentPath.previousPath;
         }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/MutablePath.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/MutablePath.java
index 427c601..7ecf68c 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/MutablePath.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/MutablePath.java
@@ -188,7 +188,10 @@ public class MutablePath implements Path, Serializable {
         final List<Object> otherPathObjects = otherPath.objects();
         final List<Set<String>> otherPathLabels = otherPath.labels();
         for (int i = this.objects.size() - 1; i >= 0; i--) {
-            if (!this.objects.get(i).equals(otherPathObjects.get(i)))
+            final Object o1 = this.objects.get(i);
+            final Object o2 = otherPathObjects.get(i);
+
+            if (!(o1 == null && o2 == null) && ((o1 != null) && !o1.equals(o2)))
                 return false;
             if (!this.labels.get(i).equals(otherPathLabels.get(i)))
                 return false;
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_LP_O_S_SE_SL_Traverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_LP_O_S_SE_SL_Traverser.java
index 35f967a..3a2aa38 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_LP_O_S_SE_SL_Traverser.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_LP_O_S_SE_SL_Traverser.java
@@ -82,7 +82,7 @@ public class B_LP_O_S_SE_SL_Traverser<T> extends B_O_S_SE_SL_Traverser<T> {
     @Override
     public void addLabels(final Set<String> labels) {
         if (!labels.isEmpty())
-            this.path = this.path.isEmpty() || !this.t.equals(this.path.head()) ?
+            this.path = this.path.isEmpty() || (null == this.t && this.path.head() == null) || !this.t.equals(this.path.head()) ?
                     this.path.extend(this.t, labels) :
                     this.path.extend(labels);
     }
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_O_OB_S_SE_SL_Traverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_O_OB_S_SE_SL_Traverser.java
index 1ce6b4d..f6bb9cc 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_O_OB_S_SE_SL_Traverser.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_O_OB_S_SE_SL_Traverser.java
@@ -82,7 +82,7 @@ public class LP_O_OB_S_SE_SL_Traverser<T> extends O_OB_S_SE_SL_Traverser<T> {
     @Override
     public void addLabels(final Set<String> labels) {
         if (!labels.isEmpty())
-            this.path = this.path.isEmpty() || !this.t.equals(this.path.head()) ?
+            this.path = this.path.isEmpty() || (null == this.t && this.path.head() == null) || !this.t.equals(this.path.head()) ?
                     this.path.extend(this.t, labels) :
                     this.path.extend(labels);
     }
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PathTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PathTest.java
index e9630f0..4adc701 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PathTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PathTest.java
@@ -36,6 +36,7 @@ import java.util.stream.Collectors;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -188,15 +189,18 @@ public class PathTest {
             Path path = supplier.get();
             path = path.extend("marko", new LinkedHashSet<String>(Arrays.asList("a", "b")));
             path = path.extend("stephen", new LinkedHashSet<>(Arrays.asList("a", "c")));
+            path = path.extend(null, new LinkedHashSet<>(Arrays.asList("x", "x")));
             path = path.extend("matthias", new LinkedHashSet<>(Arrays.asList("c", "d")));
-            assertEquals(3, path.size());
+            assertEquals(4, path.size());
             assertEquals("marko", path.get(Pop.first, "a"));
             assertEquals("marko", path.get(Pop.first, "b"));
             assertEquals("stephen", path.get(Pop.first, "c"));
+            assertNull(path.get(Pop.first, "x"));
             assertEquals("matthias", path.get(Pop.first, "d"));
             ///
             assertEquals("marko", path.get(Pop.last, "b"));
             assertEquals("stephen", path.get(Pop.last, "a"));
+            assertNull(path.get(Pop.last, "x"));
             assertEquals("matthias", path.get(Pop.last, "c"));
             assertEquals("matthias", path.get(Pop.last, "d"));
         });
@@ -258,6 +262,15 @@ public class PathTest {
             assertNotEquals(pathA2, pathB1);
             assertNotEquals(pathB1, pathB2);
             ///
+            pathA1 = pathA1.extend(null, new LinkedHashSet<>(Arrays.asList("aa", "aaa")));
+            pathA2 = pathA2.extend(null, new LinkedHashSet<>(Arrays.asList("aa", "aaa")));
+            pathB1 = pathB1.extend(null, new LinkedHashSet<>(Arrays.asList("bb", "bbb")));
+            pathB2 = pathB2.extend(null, Collections.singleton("bb"));
+            assertEquals(pathA1, pathA2);
+            assertEquals(pathA1.hashCode(), pathA2.hashCode());
+            assertNotEquals(pathA2, pathB1);
+            assertNotEquals(pathB1, pathB2);
+            ///
             pathA1 = pathA1.extend("matthias", new LinkedHashSet<>(Arrays.asList("aaaa", "aaaaa")));
             pathA2 = pathA2.extend("bob", new LinkedHashSet<>(Arrays.asList("aaaa", "aaaaa")));
             pathB1 = pathB1.extend("byn", Collections.singleton("bbbb"));
@@ -346,6 +359,21 @@ public class PathTest {
             assertTrue(pathB1.popEquals(Pop.last, pathB2));
 
             ///
+            pathA1 = pathA1.extend(null, Collections.singleton("x"));
+            pathA2 = pathA2.extend(null, Collections.singleton("x"));
+            pathB1 = pathB1.extend(null, Collections.singleton("x"));
+            pathB2 = pathB2.extend(null, Collections.singleton("x"));
+            assertTrue(pathA1.popEquals(Pop.all, pathA2));
+            assertFalse(pathA2.popEquals(Pop.all, pathB1));
+            assertTrue(pathB1.popEquals(Pop.all, pathB2));
+            assertTrue(pathA1.popEquals(Pop.first, pathA2));
+            assertFalse(pathA2.popEquals(Pop.first, pathB1));
+            assertTrue(pathB1.popEquals(Pop.first, pathB2));
+            assertTrue(pathA1.popEquals(Pop.last, pathA2));
+            assertTrue(pathA2.popEquals(Pop.last, pathB1));
+            assertTrue(pathB1.popEquals(Pop.last, pathB2));
+
+            ///
             pathA1 = pathA1.extend("stephen", Collections.singleton("c"));
             pathA2 = pathA2.extend("stephen", Collections.singleton("d"));
             pathB1 = pathB1.extend("marko", Collections.singleton("e"));
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index 74b346c..578e919 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -512,6 +512,8 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                {"g_VX1X_outEXcreatedX_inV_inE_outV_path", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).OutE("created").InV().InE().OutV().Path()}}, 
                {"g_V_asXaX_out_asXbX_out_asXcX_path_fromXbX_toXcX_byXnameX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().As("a").Out().As("b").Out().As("c").Path().From("b").To("c").By("name")}}, 
                {"g_VX1X_out_path_byXageX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).Out().Path().By("age")}}, 
+               {"g_injectX1_null_nullX_path", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(1,null,null).Path()}}, 
+               {"g_injectX1_null_nullX_path_dedup", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(1,null,null).Path().Dedup()}}, 
                {"g_V_peerPressure_hasXclusterX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.WithStrategies(new VertexProgramStrategy(graphComputer: "org.apache.tinkerpop.gremlin.process.computer.GraphComputer")).V().PeerPressure().Has("gremlin.peerPressureVertexProgram.cluster"), (g,p) =>g.WithStrategies(new VertexProgramStrategy(graphComputer: "org.apache.tinkerpop.gremlin.process.computer.GraphComputer")).V().PeerPressure().Has("gremlin.pee [...]
                {"g_V_peerPressure_withXpropertyName_clusterX_withXedges_outEXknowsXX_pageRankX1X_byXrankX_withXedges_outEXknowsX_withXtimes_2X_group_byXclusterX_byXrank_sumX_limitX100X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.WithStrategies(new VertexProgramStrategy(graphComputer: "org.apache.tinkerpop.gremlin.process.computer.GraphComputer")).V().PeerPressure().With("~tinkerpop.peerPressure.propertyName","cluster").With("~tinkerpop.peerP [...]
                {"g_V_hasXname_rippleX_inXcreatedX_peerPressure_withXedges_outEX_withyXpropertyName_clusterX_repeatXunionXidentity__bothX_timesX2X_dedup_valueMapXname_clusterX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.WithStrategies(new VertexProgramStrategy(graphComputer: "org.apache.tinkerpop.gremlin.process.computer.GraphComputer")).V().Has("name","ripple").In("created").PeerPressure().With("~tinkerpop.peerPressure.edges",__.OutE()).With [...]
@@ -726,6 +728,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                {"g_VX1X_valuesXageX_injectXnull_nullX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["xx1"]).Values<object>("age").Inject(null,null)}}, 
                {"g_VX1X_valuesXageX_injectXnullX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["xx1"]).Values<object>("age").Inject(null)}}, 
                {"g_VX1X_valuesXageX_inject", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["xx1"]).Values<object>("age").Inject()}}, 
+               {"g_injectXnull_1_3_nullX_asXaX_selectXaX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(null,1,3,null).As("a").Select<object>("a")}}, 
                {"g_io_readXkryoX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Io<object>("data/tinkerpop-modern.kryo").Read(), (g,p) =>g.V(), (g,p) =>g.E()}}, 
                {"g_io_read_withXreader_gryoX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Io<object>("data/tinkerpop-modern.kryo").With("~tinkerpop.io.reader","gryo").Read(), (g,p) =>g.V(), (g,p) =>g.E()}}, 
                {"g_io_readXgraphsonX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Io<object>("data/tinkerpop-modern.json").Read(), (g,p) =>g.V(), (g,p) =>g.E()}}, 
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
index f0b3c50..a412b8d 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
@@ -501,6 +501,8 @@ const gremlins = {
     g_VX1X_outEXcreatedX_inV_inE_outV_path: [function({g, vid1}) { return g.V(vid1).outE("created").inV().inE().outV().path() }], 
     g_V_asXaX_out_asXbX_out_asXcX_path_fromXbX_toXcX_byXnameX: [function({g}) { return g.V().as("a").out().as("b").out().as("c").path().from_("b").to("c").by("name") }], 
     g_VX1X_out_path_byXageX: [function({g, vid1}) { return g.V(vid1).out().path().by("age") }], 
+    g_injectX1_null_nullX_path: [function({g}) { return g.inject(1,null,null).path() }], 
+    g_injectX1_null_nullX_path_dedup: [function({g}) { return g.inject(1,null,null).path().dedup() }], 
     g_V_peerPressure_hasXclusterX: [function({g}) { return g.withStrategies(new VertexProgramStrategy(new Map([["graphComputer","org.apache.tinkerpop.gremlin.process.computer.GraphComputer"]]))).V().peerPressure().has("gremlin.peerPressureVertexProgram.cluster") }, function({g}) { return g.withStrategies(new VertexProgramStrategy(new Map([["graphComputer","org.apache.tinkerpop.gremlin.process.computer.GraphComputer"]]))).V().peerPressure().has("gremlin.peerPressureVertexProgram.cluster") }], 
     g_V_peerPressure_withXpropertyName_clusterX_withXedges_outEXknowsXX_pageRankX1X_byXrankX_withXedges_outEXknowsX_withXtimes_2X_group_byXclusterX_byXrank_sumX_limitX100X: [function({g}) { return g.withStrategies(new VertexProgramStrategy(new Map([["graphComputer","org.apache.tinkerpop.gremlin.process.computer.GraphComputer"]]))).V().peerPressure().with_("~tinkerpop.peerPressure.propertyName","cluster").with_("~tinkerpop.peerPressure.edges",__.outE("knows")).pageRank(1.0).with_("~tinker [...]
     g_V_hasXname_rippleX_inXcreatedX_peerPressure_withXedges_outEX_withyXpropertyName_clusterX_repeatXunionXidentity__bothX_timesX2X_dedup_valueMapXname_clusterX: [function({g}) { return g.withStrategies(new VertexProgramStrategy(new Map([["graphComputer","org.apache.tinkerpop.gremlin.process.computer.GraphComputer"]]))).V().has("name","ripple").in_("created").peerPressure().with_("~tinkerpop.peerPressure.edges",__.outE()).with_("~tinkerpop.peerPressure.propertyName","cluster").repeat(__ [...]
@@ -715,6 +717,7 @@ const gremlins = {
     g_VX1X_valuesXageX_injectXnull_nullX: [function({g, xx1}) { return g.V(xx1).values("age").inject(null,null) }], 
     g_VX1X_valuesXageX_injectXnullX: [function({g, xx1}) { return g.V(xx1).values("age").inject(null) }], 
     g_VX1X_valuesXageX_inject: [function({g, xx1}) { return g.V(xx1).values("age").inject() }], 
+    g_injectXnull_1_3_nullX_asXaX_selectXaX: [function({g}) { return g.inject(null,1,3,null).as("a").select("a") }], 
     g_io_readXkryoX: [function({g}) { return g.io("data/tinkerpop-modern.kryo").read() }, function({g}) { return g.V() }, function({g}) { return g.E() }], 
     g_io_read_withXreader_gryoX: [function({g}) { return g.io("data/tinkerpop-modern.kryo").with_("~tinkerpop.io.reader","gryo").read() }, function({g}) { return g.V() }, function({g}) { return g.E() }], 
     g_io_readXgraphsonX: [function({g}) { return g.io("data/tinkerpop-modern.json").read() }, function({g}) { return g.V() }, function({g}) { return g.E() }], 
diff --git a/gremlin-python/src/main/python/radish/gremlin.py b/gremlin-python/src/main/python/radish/gremlin.py
index ac17b3b..2a7addd 100644
--- a/gremlin-python/src/main/python/radish/gremlin.py
+++ b/gremlin-python/src/main/python/radish/gremlin.py
@@ -486,6 +486,8 @@ world.gremlins = {
     'g_VX1X_outEXcreatedX_inV_inE_outV_path': [(lambda g, vid1=None:g.V(vid1).outE('created').inV().inE().outV().path())], 
     'g_V_asXaX_out_asXbX_out_asXcX_path_fromXbX_toXcX_byXnameX': [(lambda g:g.V().as_('a').out().as_('b').out().as_('c').path().from_('b').to('c').by('name'))], 
     'g_VX1X_out_path_byXageX': [(lambda g, vid1=None:g.V(vid1).out().path().by('age'))], 
+    'g_injectX1_null_nullX_path': [(lambda g:g.inject(1,None,None).path())], 
+    'g_injectX1_null_nullX_path_dedup': [(lambda g:g.inject(1,None,None).path().dedup())], 
     'g_V_peerPressure_hasXclusterX': [(lambda g:g.withStrategies(*[TraversalStrategy('VertexProgramStrategy',{'graphComputer':'org.apache.tinkerpop.gremlin.process.computer.GraphComputer'}, 'org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.decoration.VertexProgramStrategy')]).V().peerPressure().has('gremlin.peerPressureVertexProgram.cluster')), (lambda g:g.withStrategies(*[TraversalStrategy('VertexProgramStrategy',{'graphComputer':'org.apache.tinkerpop.gremlin.process.com [...]
     'g_V_peerPressure_withXpropertyName_clusterX_withXedges_outEXknowsXX_pageRankX1X_byXrankX_withXedges_outEXknowsX_withXtimes_2X_group_byXclusterX_byXrank_sumX_limitX100X': [(lambda g:g.withStrategies(*[TraversalStrategy('VertexProgramStrategy',{'graphComputer':'org.apache.tinkerpop.gremlin.process.computer.GraphComputer'}, 'org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.decoration.VertexProgramStrategy')]).V().peerPressure().with_('~tinkerpop.peerPressure.propertyNam [...]
     'g_V_hasXname_rippleX_inXcreatedX_peerPressure_withXedges_outEX_withyXpropertyName_clusterX_repeatXunionXidentity__bothX_timesX2X_dedup_valueMapXname_clusterX': [(lambda g:g.withStrategies(*[TraversalStrategy('VertexProgramStrategy',{'graphComputer':'org.apache.tinkerpop.gremlin.process.computer.GraphComputer'}, 'org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.decoration.VertexProgramStrategy')]).V().has('name','ripple').in_('created').peerPressure().with_('~tinkerpo [...]
@@ -700,6 +702,7 @@ world.gremlins = {
     'g_VX1X_valuesXageX_injectXnull_nullX': [(lambda g, xx1=None: g.V(xx1).values('age').inject(None,None))], 
     'g_VX1X_valuesXageX_injectXnullX': [(lambda g, xx1=None:g.V(xx1).age.inject(None))], 
     'g_VX1X_valuesXageX_inject': [(lambda g, xx1=None:g.V(xx1).age.inject())], 
+    'g_injectXnull_1_3_nullX_asXaX_selectXaX': [(lambda g:g.inject(None,1,3,None).as_('a').select('a'))], 
     'g_io_readXkryoX': [(lambda g:g.io('data/tinkerpop-modern.kryo').read()), (lambda g:g.V()), (lambda g:g.E())], 
     'g_io_read_withXreader_gryoX': [(lambda g:g.io('data/tinkerpop-modern.kryo').with_('~tinkerpop.io.reader','gryo').read()), (lambda g:g.V()), (lambda g:g.E())], 
     'g_io_readXgraphsonX': [(lambda g:g.io('data/tinkerpop-modern.json').read()), (lambda g:g.V()), (lambda g:g.E())], 
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 f0d34dc..8b1cfea 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
@@ -123,6 +123,14 @@ import org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComp
         test = "org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest",
         method = "g_V_hasXk_withinXcXX_valuesXkX",
         reason = "Requires some test data initialization with addV()")
+@Graph.OptOut(
+        test = "org.apache.tinkerpop.gremlin.process.traversal.step.map.PathTest",
+        method = "g_injectX1_null_nullX_path",
+        reason = "The inject() step is not supported by GraphComputer")
+@Graph.OptOut(
+        test = "org.apache.tinkerpop.gremlin.process.traversal.step.map.PathTest",
+        method = "g_injectX1_null_nullX_path_dedup",
+        reason = "The inject() step is not supported by GraphComputer")
 @GraphProvider.Descriptor(computer = TinkerGraphComputer.class)
 public class GraphBinaryRemoteGraphComputerProvider extends AbstractRemoteGraphProvider {
 
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 4932dde..68588ce 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
@@ -123,6 +123,14 @@ import org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComp
         test = "org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest",
         method = "g_V_hasXk_withinXcXX_valuesXkX",
         reason = "Requires some test data initialization with addV()")
+@Graph.OptOut(
+        test = "org.apache.tinkerpop.gremlin.process.traversal.step.map.PathTest",
+        method = "g_injectX1_null_nullX_path",
+        reason = "The inject() step is not supported by GraphComputer")
+@Graph.OptOut(
+        test = "org.apache.tinkerpop.gremlin.process.traversal.step.map.PathTest",
+        method = "g_injectX1_null_nullX_path_dedup",
+        reason = "The inject() step is not supported by GraphComputer")
 @GraphProvider.Descriptor(computer = TinkerGraphComputer.class)
 public class GraphSONRemoteGraphComputerProvider extends AbstractRemoteGraphProvider {
     public GraphSONRemoteGraphComputerProvider() {
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 1240741..e7f46ee 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
@@ -124,6 +124,14 @@ import org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComp
         test = "org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest",
         method = "g_V_hasXk_withinXcXX_valuesXkX",
         reason = "Requires some test data initialization with addV()")
+@Graph.OptOut(
+        test = "org.apache.tinkerpop.gremlin.process.traversal.step.map.PathTest",
+        method = "g_injectX1_null_nullX_path",
+        reason = "The inject() step is not supported by GraphComputer")
+@Graph.OptOut(
+        test = "org.apache.tinkerpop.gremlin.process.traversal.step.map.PathTest",
+        method = "g_injectX1_null_nullX_path_dedup",
+        reason = "The inject() step is not supported by GraphComputer")
 @GraphProvider.Descriptor(computer = TinkerGraphComputer.class)
 public class GryoRemoteGraphComputerProvider extends AbstractRemoteGraphProvider {
 
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 924992d..6c5dbe2 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
@@ -126,6 +126,14 @@ import org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComp
         test = "org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasTest",
         method = "g_V_hasXk_withinXcXX_valuesXkX",
         reason = "Requires some test data initialization with addV()")
+@Graph.OptOut(
+        test = "org.apache.tinkerpop.gremlin.process.traversal.step.map.PathTest",
+        method = "g_injectX1_null_nullX_path",
+        reason = "The inject() step is not supported by GraphComputer")
+@Graph.OptOut(
+        test = "org.apache.tinkerpop.gremlin.process.traversal.step.map.PathTest",
+        method = "g_injectX1_null_nullX_path_dedup",
+        reason = "The inject() step is not supported by GraphComputer")
 @GraphProvider.Descriptor(computer = TinkerGraphComputer.class)
 public class GryoRemoteGraphGroovyTranslatorComputerProvider extends GryoRemoteGraphGroovyTranslatorProvider {
     private final int AVAILABLE_PROCESSORS = Runtime.getRuntime().availableProcessors();
diff --git a/gremlin-test/features/filter/Has.feature b/gremlin-test/features/filter/Has.feature
index 753f0dd..8b96c5a 100644
--- a/gremlin-test/features/filter/Has.feature
+++ b/gremlin-test/features/filter/Has.feature
@@ -477,7 +477,7 @@ Feature: Step - has()
       | d[32].i |
       | d[35].i |
 
-  Scenario: g_V_both_dedup_properties_hasKeyXageX_hasValueXgtX30XX_value
+  Scenario: g_V_both_properties_dedup_hasKeyXageX_hasValueXgtX30XX_value
     Given the modern graph
     And the traversal of
     """
diff --git a/gremlin-test/features/map/Path.feature b/gremlin-test/features/map/Path.feature
index 40dcd88..1f49128 100644
--- a/gremlin-test/features/map/Path.feature
+++ b/gremlin-test/features/map/Path.feature
@@ -116,4 +116,29 @@ Feature: Step - path()
       | result |
       | p[d[29].i,null] |
       | p[d[29].i,d[27].i] |
-      | p[d[29].i,d[32].i] |
\ No newline at end of file
+      | p[d[29].i,d[32].i] |
+
+  Scenario: g_injectX1_null_nullX_path
+    Given the empty graph
+    And the traversal of
+      """
+      g.inject(1, null, null).path()
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | p[d[1].i] |
+      | p[null] |
+      | p[null] |
+
+  Scenario: g_injectX1_null_nullX_path_dedup
+    Given the empty graph
+    And the traversal of
+      """
+      g.inject(1, null, null).path().dedup()
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | p[d[1].i] |
+      | p[null] |
\ No newline at end of file
diff --git a/gremlin-test/features/sideEffect/Inject.feature b/gremlin-test/features/sideEffect/Inject.feature
index 49d8569..bf5a23f 100644
--- a/gremlin-test/features/sideEffect/Inject.feature
+++ b/gremlin-test/features/sideEffect/Inject.feature
@@ -177,4 +177,18 @@ Feature: Step - inject()
     When iterated to list
     Then the result should be unordered
       | result |
-      | d[29].i |
\ No newline at end of file
+      | d[29].i |
+
+  Scenario: g_injectXnull_1_3_nullX_asXaX_selectXaX
+    Given the empty graph
+    And the traversal of
+      """
+      g.inject(null, 1, 3, null).as("a").select("a")
+      """
+    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/step/filter/HasTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/HasTest.java
index faa8f59..4ef1376 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/HasTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/HasTest.java
@@ -573,7 +573,7 @@ public abstract class HasTest extends AbstractGremlinProcessTest {
 
     @Test
     @LoadGraphWith(MODERN)
-    public void g_V_both_dedup_properties_hasKeyXageX_hasValueXgtX30XX_value() {
+    public void g_V_both_properties_dedup_hasKeyXageX_hasValueXgtX30XX_value() {
         final Traversal<Vertex, Integer> traversal = get_g_V_both_properties_dedup_hasKeyXageX_hasValueXgtX30XX_value();
         printTraversalForm(traversal);
         checkResults(Arrays.asList(32, 35), traversal);
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PathTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PathTest.java
index 1856677..d60b2cf 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PathTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/PathTest.java
@@ -64,6 +64,10 @@ public abstract class PathTest extends AbstractGremlinProcessTest {
 
     public abstract Traversal<Vertex, Path> get_g_V_asXaX_out_asXbX_out_asXcX_path_fromXbX_toXcX_byXnameX();
 
+    public abstract Traversal<Integer, Path> get_g_injectX1_null_nullX_path();
+
+    public abstract Traversal<Integer, Path> get_g_injectX1_null_nullX_path_dedup();
+
     @Test
     @LoadGraphWith(MODERN)
     public void g_VX1X_name_path() {
@@ -192,6 +196,27 @@ public abstract class PathTest extends AbstractGremlinProcessTest {
                 MutablePath.make().extend("josh", Collections.singleton("b")).extend("ripple", Collections.singleton("c"))), traversal);
     }
 
+    @Test
+    @LoadGraphWith(MODERN)
+    public void g_injectX1_null_nullX_path() {
+        final Traversal<Integer, Path> traversal = get_g_injectX1_null_nullX_path();
+        printTraversalForm(traversal);
+        checkResults(Arrays.asList(
+                MutablePath.make().extend(1, Collections.emptySet()),
+                MutablePath.make().extend(null, Collections.emptySet()),
+                MutablePath.make().extend(null, Collections.emptySet())), traversal);
+    }
+
+    @Test
+    @LoadGraphWith(MODERN)
+    public void g_injectX1_null_nullX_path_dedup() {
+        final Traversal<Integer, Path> traversal = get_g_injectX1_null_nullX_path_dedup();
+        printTraversalForm(traversal);
+        checkResults(Arrays.asList(
+                MutablePath.make().extend(1, Collections.emptySet()),
+                MutablePath.make().extend(null, Collections.emptySet())), traversal);
+    }
+
     public static class Traversals extends PathTest {
         @Override
         public Traversal<Vertex, Path> get_g_VX1X_name_path(final Object v1Id) {
@@ -232,5 +257,15 @@ public abstract class PathTest extends AbstractGremlinProcessTest {
         public Traversal<Vertex, Path> get_g_V_asXaX_out_asXbX_out_asXcX_path_fromXbX_toXcX_byXnameX() {
             return g.V().as("a").out().as("b").out().as("c").path().from("b").to("c").by("name");
         }
+
+        @Override
+        public Traversal<Integer, Path> get_g_injectX1_null_nullX_path() {
+            return g.inject(1, null, null).path();
+        }
+
+        @Override
+        public Traversal<Integer, Path> get_g_injectX1_null_nullX_path_dedup() {
+            return g.inject(1, null, null).path().dedup();
+        }
     }
 }
\ 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 8fb3eb4..c256aa4 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
@@ -68,6 +68,8 @@ public abstract class InjectTest extends AbstractGremlinProcessTest {
 
     public abstract Traversal<Map<String,Object>, Map<String, Object>> get_g_injectXname_marko_age_nullX_selectXname_ageX();
 
+    public abstract Traversal<Integer, Integer> get_g_injectXnull_1_3_nullX_asXaX_selectXaX();
+
     @Test
     @LoadGraphWith(MODERN)
     public void g_VX1X_out_injectXv2X_name() {
@@ -181,6 +183,13 @@ public abstract class InjectTest extends AbstractGremlinProcessTest {
         checkResults(Arrays.asList("ripple", "lop", "lop", "vadas", "josh"), traversal);
     }
 
+    @Test
+    public void g_injectXnull_1_3_nullX_asXaX_selectXaX() {
+        final Traversal<Integer, Integer> traversal = get_g_injectXnull_1_3_nullX_asXaX_selectXaX();
+        printTraversalForm(traversal);
+        checkResults(Arrays.asList(null, 1, 3, null), traversal);
+    }
+
     public static class Traversals extends InjectTest {
 
         @Override
@@ -249,5 +258,10 @@ public abstract class InjectTest extends AbstractGremlinProcessTest {
             m.put("age", null);
             return g.inject(m).select("name","age");
         }
+
+        @Override
+        public Traversal<Integer, Integer> get_g_injectXnull_1_3_nullX_asXaX_selectXaX() {
+            return g.inject(null, 1, 3, null).as("a").select("a");
+        }
     }
 }