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

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

commit b668a0974ace241b55309cc9737fb1f311a7cf38
Author: Stephen Mallette <[email protected]>
AuthorDate: Wed Feb 19 15:45:23 2025 -0500

    TINKERPOP-3137 Allowed null as a Map value in mergeV/E
---
 CHANGELOG.asciidoc                                 |  3 +-
 .../process/traversal/step/map/MergeStep.java      |  9 +++--
 .../traversal/step/map/MergeVertexStepTest.java    | 15 ++++++--
 .../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs |  4 ++
 gremlin-go/driver/cucumber/gremlin.go              |  4 ++
 .../gremlin-javascript/test/cucumber/gremlin.js    |  4 ++
 gremlin-python/src/main/python/radish/gremlin.py   |  4 ++
 .../gremlin/test/features/map/AddVertex.feature    |  1 -
 .../gremlin/test/features/map/MergeEdge.feature    | 43 +++++++++++++++++++++-
 .../gremlin/test/features/map/MergeVertex.feature  | 37 ++++++++++++++++++-
 10 files changed, 112 insertions(+), 12 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index ff1b7ceb48..40fd19e1b9 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -35,7 +35,8 @@ 
image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 * Fixed issue in `gremlin-console` where it couldn't accept plugin files that 
included empty lines or invalid plugin names.
 * Modified grammar to make `none()` usage more consistent as a filter step 
where it can now be used to chain additional traversal steps and be used 
anonymously.
 * Added missing anonymous support for `disjunct()` in Python and Javascript.
-* Deprecated gremlin_python.process.__.has_key_ in favor of 
gremlin_python.process.__.has_key.
+* Deprecated `gremlin_python.process.__.has_key_` in favor of 
`gremlin_python.process.__.has_key`.
+* Allowed `mergeV()` and `mergeE()` to supply `null` in `Map` values.
 
 [[release-3-7-3]]
 === TinkerPop 3.7.3 (October 23, 2024)
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeStep.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeStep.java
index 9b0f12d1d6..5168dc950b 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeStep.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeStep.java
@@ -271,10 +271,6 @@ public abstract class MergeStep<S, E, C> extends 
FlatMapStep<S, E>
             final Object k = e.getKey();
             final Object v = e.getValue();
 
-            if (v == null) {
-                throw new IllegalArgumentException(String.format("%s() does 
not allow null Map values - check: %s", op, k));
-            }
-
             if (ignoreTokens) {
                 if (!(k instanceof String)) {
                     throw new 
IllegalArgumentException(String.format("option(onMatch) expects keys in Map to 
be of String - check: %s", k));
@@ -282,6 +278,11 @@ public abstract class MergeStep<S, E, C> extends 
FlatMapStep<S, E>
                     ElementHelper.validateProperty((String) k, v);
                 }
             } else {
+                // don't allow null values for enums: T, Direction, Merge
+                if (k instanceof Enum && v == null) {
+                    throw new IllegalArgumentException(String.format("%s() 
does not allow null Map values - check: %s", op, k));
+                }
+
                 if (!(k instanceof String) && !allowedTokens.contains(k)) {
                     throw new IllegalArgumentException(String.format(
                             "%s() and option(onCreate) args expect keys in Map 
to be either String or %s - check: %s",
diff --git 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStepTest.java
 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStepTest.java
index ca2feaeaf0..a612735e48 100644
--- 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStepTest.java
+++ 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStepTest.java
@@ -124,16 +124,23 @@ public class MergeVertexStepTest {
     }
 
     @Test(expected = IllegalArgumentException.class)
-    public void shouldFailToValidateWithObjectAsLabelValue() {
+    public void shouldFailToValidateWithNullIdValue() {
         final Map<Object,Object> m = CollectionUtil.asMap("k", "v",
-                T.label, new Object());
+                T.id, null);
         MergeVertexStep.validateMapInput(m, false);
     }
 
     @Test(expected = IllegalArgumentException.class)
-    public void shouldFailToValidateWithNullIdValue() {
+    public void shouldFailToValidateWithNullMergeValue() {
         final Map<Object,Object> m = CollectionUtil.asMap("k", "v",
-                T.id, null);
+                Merge.inV, null);
+        MergeVertexStep.validateMapInput(m, false);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void shouldFailToValidateWithObjectAsLabelValue() {
+        final Map<Object,Object> m = CollectionUtil.asMap("k", "v",
+                T.label, new Object());
         MergeVertexStep.validateMapInput(m, false);
     }
 
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs 
b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index a97e660a2c..521f7fb11b 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -921,6 +921,8 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                
{"g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonMatch_sideEffectXpropertyXweight_0XX_constantXemptyXX",
 new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) 
=>g.AddV("person").Property("name","marko").As("a").AddV("person").Property("name","vadas").As("b").AddE("knows").Property("weight",1).From("a").To("b"),
 (g,p) =>g.MergeE((IDictionary<object,object>) p["xx1"]).Option(Merge.OnMatch, 
(ITraversal) __.SideEffect(__.Property("weight",0)). [...]
                
{"g_injectXlist1_list2X_mergeEXlimitXlocal_1XX_optionXonCreate_rangeXlocal_1_2XX_optionXonMatch_tailXlocalXX_to_match",
 new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) 
=>g.AddV("person").Property("name","marko").Property("age",29).As("marko").AddV("person").Property("name","vadas").Property("age",27).As("vadas").AddV("software").Property("name","lop").Property("lang","java").As("lop").AddV("person").Property("name","josh").Property("age
 [...]
                
{"g_injectXlist1_list2X_mergeEXlimitXlocal_1XX_optionXonCreate_rangeXlocal_1_2XX_optionXonMatch_tailXlocalXX_to_create",
 new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) 
=>g.AddV("person").Property("name","marko").Property("age",29).As("marko").AddV("person").Property("name","vadas").Property("age",27).As("vadas").AddV("software").Property("name","lop").Property("lang","java").As("lop").AddV("person").Property("name","josh").Property("ag
 [...]
+               
{"g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonMatch_weight_nullX_allowed",
 new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) 
=>g.AddV("person").Property("name","marko").As("a").AddV("person").Property("name","vadas").As("b").AddE("knows").From("a").To("b").Property("weight",1.0),
 (g,p) =>g.MergeE((IDictionary<object,object>) p["xx1"]).Option(Merge.OnMatch, 
(IDictionary<object,object>) p["xx2"]), (g,p) =>g.V(), (g,p) =>g.E().HasLabel 
[...]
+               
{"g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonMatch_weight_nullX", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) 
=>g.AddV("person").Property("name","marko").As("a").AddV("person").Property("name","vadas").As("b").AddE("knows").From("a").To("b").Property("weight",1.0),
 (g,p) =>g.MergeE((IDictionary<object,object>) p["xx1"]).Option(Merge.OnMatch, 
(IDictionary<object,object>) p["xx2"]), (g,p) =>g.V(), (g,p) 
=>g.E().HasLabel("knows" [...]
                {"g_mergeVXemptyX_optionXonMatch_nullX", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.AddV("person").Property("name","marko").Property("age",29), (g,p) 
=>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> 
{}).Option(Merge.OnMatch, (IDictionary<object,object>) null), (g,p) 
=>g.V().Has("person","name","marko").Has("age",29)}}, 
                {"g_V_mergeVXemptyX_optionXonMatch_nullX", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.AddV("person").Property("name","marko").Property("age",29), (g,p) 
=>g.V().MergeV((IDictionary<object,object>) new Dictionary<object,object> 
{}).Option(Merge.OnMatch, (IDictionary<object,object>) null), (g,p) 
=>g.V().Has("person","name","marko").Has("age",29)}}, 
                {"g_mergeVXnullX_optionXonCreate_label_null_name_markoX", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.AddV("person").Property("name","marko").Property("age",29), (g,p) 
=>g.MergeV((IDictionary<object,object>) p["xx1"])}}, 
@@ -982,6 +984,8 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                {"g_mergeV_hidden_label_key_onMatch_matched_prohibited", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.MergeV((IDictionary<object,object>) new Dictionary<object,object> 
{}).Option(Merge.OnMatch, (IDictionary<object,object>) p["xx1"])}}, 
                
{"g_injectXlist1_list2X_mergeVXlimitXlocal_1XX_optionXonCreate_rangeXlocal_1_2X_optionXonMatch_tailXlocalXX_to_match",
 new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.AddV("person").Property("name","marko").Property("age",29), (g,p) 
=>g.Inject(p["xx1"],p["xx1"],p["xx2"]).Fold().MergeV((ITraversal) 
__.Limit<object>(Scope.Local,1)).Option(Merge.OnCreate, (ITraversal) 
__.Range<object>(Scope.Local,1,2)).Option(Merge.OnMatch, (ITraversal [...]
                
{"g_injectXlist1_list2X_mergeVXlimitXlocal_1XX_optionXonCreate_rangeXlocal_1_2X_optionXonMatch_tailXlocalXX_to_create",
 new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.AddV("person").Property("name","marko").Property("age",29), (g,p) 
=>g.Inject(p["xx1"],p["xx1"],p["xx2"]).Fold().MergeV((ITraversal) 
__.Limit<object>(Scope.Local,1)).Option(Merge.OnCreate, (ITraversal) 
__.Range<object>(Scope.Local,1,2)).Option(Merge.OnMatch, (ITraversa [...]
+               
{"g_mergeVXlabel_person_name_marko_age_29X_optionXonMatch_age_nullX_allowed", 
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.AddV("person").Property("name","marko").Property("age",29), (g,p) 
=>g.MergeV((IDictionary<object,object>) p["xx1"]).Option(Merge.OnMatch, 
(IDictionary<object,object>) p["xx2"]), (g,p) =>g.V(), (g,p) 
=>g.V().Has("person","name","marko").Has("age",(object) null)}}, 
+               
{"g_mergeVXlabel_person_name_marko_age_29X_optionXonMatch_age_nullX", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.AddV("person").Property("name","marko").Property("age",29), (g,p) 
=>g.MergeV((IDictionary<object,object>) p["xx1"]).Option(Merge.OnMatch, 
(IDictionary<object,object>) p["xx2"]), (g,p) =>g.V(), (g,p) 
=>g.V().Has("person","name","marko").Has("age")}}, 
                {"g_V_age_min", new List<Func<GraphTraversalSource, 
IDictionary<string, object>, ITraversal>> {(g,p) 
=>g.V().Values<object>("age").Min<object>()}}, 
                {"g_V_foo_min", new List<Func<GraphTraversalSource, 
IDictionary<string, object>, ITraversal>> {(g,p) 
=>g.V().Values<object>("foo").Min<object>()}}, 
                {"g_V_name_min", new List<Func<GraphTraversalSource, 
IDictionary<string, object>, ITraversal>> {(g,p) 
=>g.V().Values<object>("name").Min<object>()}}, 
diff --git a/gremlin-go/driver/cucumber/gremlin.go 
b/gremlin-go/driver/cucumber/gremlin.go
index f4f3263fd7..d2867d7f88 100644
--- a/gremlin-go/driver/cucumber/gremlin.go
+++ b/gremlin-go/driver/cucumber/gremlin.go
@@ -892,6 +892,8 @@ var translationMap = map[string][]func(g 
*gremlingo.GraphTraversalSource, p map[
     
"g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonMatch_sideEffectXpropertyXweight_0XX_constantXemptyXX":
 {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.AddV("person").Property("name", 
"marko").As("a").AddV("person").Property("name", 
"vadas").As("b").AddE("knows").Property("weight", 1).From("a").To("b")}, func(g 
*gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.MergeE(p["xx1"]).Optio [...]
     
"g_injectXlist1_list2X_mergeEXlimitXlocal_1XX_optionXonCreate_rangeXlocal_1_2XX_optionXonMatch_tailXlocalXX_to_match":
 {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.AddV("person").Property("name", 
"marko").Property("age", 29).As("marko").AddV("person").Property("name", 
"vadas").Property("age", 27).As("vadas").AddV("software").Property("name", 
"lop").Property("lang", "java").As("lop").AddV("person").Property("name", 
"josh").Prop [...]
     
"g_injectXlist1_list2X_mergeEXlimitXlocal_1XX_optionXonCreate_rangeXlocal_1_2XX_optionXonMatch_tailXlocalXX_to_create":
 {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.AddV("person").Property("name", 
"marko").Property("age", 29).As("marko").AddV("person").Property("name", 
"vadas").Property("age", 27).As("vadas").AddV("software").Property("name", 
"lop").Property("lang", "java").As("lop").AddV("person").Property("name", 
"josh").Pro [...]
+    
"g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonMatch_weight_nullX_allowed": 
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.AddV("person").Property("name", 
"marko").As("a").AddV("person").Property("name", 
"vadas").As("b").AddE("knows").From("a").To("b").Property("weight", 1.0)}, 
func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return 
g.MergeE(p["xx1"]).Option(gremlingo.Merge.OnMatch [...]
+    "g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonMatch_weight_nullX": 
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.AddV("person").Property("name", 
"marko").As("a").AddV("person").Property("name", 
"vadas").As("b").AddE("knows").From("a").To("b").Property("weight", 1.0)}, 
func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return 
g.MergeE(p["xx1"]).Option(gremlingo.Merge.OnMatch, p["xx2 [...]
     "g_mergeVXemptyX_optionXonMatch_nullX": {func(g 
*gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.AddV("person").Property("name", 
"marko").Property("age", 29)}, func(g *gremlingo.GraphTraversalSource, p 
map[string]interface{}) *gremlingo.GraphTraversal {return 
g.MergeV(map[interface{}]interface{}{}).Option(gremlingo.Merge.OnMatch, nil)}, 
func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.V [...]
     "g_V_mergeVXemptyX_optionXonMatch_nullX": {func(g 
*gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.AddV("person").Property("name", 
"marko").Property("age", 29)}, func(g *gremlingo.GraphTraversalSource, p 
map[string]interface{}) *gremlingo.GraphTraversal {return 
g.V().MergeV(map[interface{}]interface{}{}).Option(gremlingo.Merge.OnMatch, 
nil)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {retu [...]
     "g_mergeVXnullX_optionXonCreate_label_null_name_markoX": {func(g 
*gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.AddV("person").Property("name", 
"marko").Property("age", 29)}, func(g *gremlingo.GraphTraversalSource, p 
map[string]interface{}) *gremlingo.GraphTraversal {return g.MergeV(p["xx1"])}}, 
@@ -953,6 +955,8 @@ var translationMap = map[string][]func(g 
*gremlingo.GraphTraversalSource, p map[
     "g_mergeV_hidden_label_key_onMatch_matched_prohibited": {func(g 
*gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return 
g.MergeV(map[interface{}]interface{}{}).Option(gremlingo.Merge.OnMatch, 
p["xx1"])}}, 
     
"g_injectXlist1_list2X_mergeVXlimitXlocal_1XX_optionXonCreate_rangeXlocal_1_2X_optionXonMatch_tailXlocalXX_to_match":
 {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.AddV("person").Property("name", 
"marko").Property("age", 29)}, func(g *gremlingo.GraphTraversalSource, p 
map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"], 
p["xx1"], p["xx2"]).Fold().MergeV(gremlingo.T__.Limit(gremlingo.Scope.Local, 
1)).Opti [...]
     
"g_injectXlist1_list2X_mergeVXlimitXlocal_1XX_optionXonCreate_rangeXlocal_1_2X_optionXonMatch_tailXlocalXX_to_create":
 {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.AddV("person").Property("name", 
"marko").Property("age", 29)}, func(g *gremlingo.GraphTraversalSource, p 
map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"], 
p["xx1"], p["xx2"]).Fold().MergeV(gremlingo.T__.Limit(gremlingo.Scope.Local, 
1)).Opt [...]
+    
"g_mergeVXlabel_person_name_marko_age_29X_optionXonMatch_age_nullX_allowed": 
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.AddV("person").Property("name", 
"marko").Property("age", 29)}, func(g *gremlingo.GraphTraversalSource, p 
map[string]interface{}) *gremlingo.GraphTraversal {return 
g.MergeV(p["xx1"]).Option(gremlingo.Merge.OnMatch, p["xx2"])}, func(g 
*gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.Graph 
[...]
+    "g_mergeVXlabel_person_name_marko_age_29X_optionXonMatch_age_nullX": 
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.AddV("person").Property("name", 
"marko").Property("age", 29)}, func(g *gremlingo.GraphTraversalSource, p 
map[string]interface{}) *gremlingo.GraphTraversal {return 
g.MergeV(p["xx1"]).Option(gremlingo.Merge.OnMatch, p["xx2"])}, func(g 
*gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversa [...]
     "g_V_age_min": {func(g *gremlingo.GraphTraversalSource, p 
map[string]interface{}) *gremlingo.GraphTraversal {return 
g.V().Values("age").Min()}}, 
     "g_V_foo_min": {func(g *gremlingo.GraphTraversalSource, p 
map[string]interface{}) *gremlingo.GraphTraversal {return 
g.V().Values("foo").Min()}}, 
     "g_V_name_min": {func(g *gremlingo.GraphTraversalSource, p 
map[string]interface{}) *gremlingo.GraphTraversal {return 
g.V().Values("name").Min()}}, 
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 be9ba42240..d93e55473a 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
@@ -912,6 +912,8 @@ const gremlins = {
     
g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonMatch_sideEffectXpropertyXweight_0XX_constantXemptyXX:
 [function({g, xx1}) { return 
g.addV("person").property("name","marko").as("a").addV("person").property("name","vadas").as("b").addE("knows").property("weight",1).from_("a").to("b")
 }, function({g, xx1}) { return 
g.mergeE(xx1).option(Merge.onMatch,__.sideEffect(__.property("weight",0)).constant(new
 Map([]))) }, function({g, xx1}) { return g.V() }, function({g, xx1}) { return 
g.E(). [...]
     
g_injectXlist1_list2X_mergeEXlimitXlocal_1XX_optionXonCreate_rangeXlocal_1_2XX_optionXonMatch_tailXlocalXX_to_match:
 [function({g, xx1, xx2}) { return 
g.addV("person").property("name","marko").property("age",29).as("marko").addV("person").property("name","vadas").property("age",27).as("vadas").addV("software").property("name","lop").property("lang","java").as("lop").addV("person").property("name","josh").property("age",32).as("josh").addV("software").property("name","ripple").propert
 [...]
     
g_injectXlist1_list2X_mergeEXlimitXlocal_1XX_optionXonCreate_rangeXlocal_1_2XX_optionXonMatch_tailXlocalXX_to_create:
 [function({g, xx1, xx2}) { return 
g.addV("person").property("name","marko").property("age",29).as("marko").addV("person").property("name","vadas").property("age",27).as("vadas").addV("software").property("name","lop").property("lang","java").as("lop").addV("person").property("name","josh").property("age",32).as("josh").addV("software").property("name","ripple").proper
 [...]
+    
g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonMatch_weight_nullX_allowed: 
[function({g, xx1, xx2}) { return 
g.addV("person").property("name","marko").as("a").addV("person").property("name","vadas").as("b").addE("knows").from_("a").to("b").property("weight",1.0)
 }, function({g, xx1, xx2}) { return g.mergeE(xx1).option(Merge.onMatch,xx2) }, 
function({g, xx1, xx2}) { return g.V() }, function({g, xx1, xx2}) { return 
g.E().hasLabel("knows") }, function({g, xx1, xx2}) { return g.E().ha [...]
+    g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonMatch_weight_nullX: 
[function({g, xx1, xx2}) { return 
g.addV("person").property("name","marko").as("a").addV("person").property("name","vadas").as("b").addE("knows").from_("a").to("b").property("weight",1.0)
 }, function({g, xx1, xx2}) { return g.mergeE(xx1).option(Merge.onMatch,xx2) }, 
function({g, xx1, xx2}) { return g.V() }, function({g, xx1, xx2}) { return 
g.E().hasLabel("knows") }, function({g, xx1, xx2}) { return g.E().hasLabel(" 
[...]
     g_mergeVXemptyX_optionXonMatch_nullX: [function({g}) { return 
g.addV("person").property("name","marko").property("age",29) }, function({g}) { 
return g.mergeV(new Map([])).option(Merge.onMatch,null) }, function({g}) { 
return g.V().has("person","name","marko").has("age",29) }], 
     g_V_mergeVXemptyX_optionXonMatch_nullX: [function({g}) { return 
g.addV("person").property("name","marko").property("age",29) }, function({g}) { 
return g.V().mergeV(new Map([])).option(Merge.onMatch,null) }, function({g}) { 
return g.V().has("person","name","marko").has("age",29) }], 
     g_mergeVXnullX_optionXonCreate_label_null_name_markoX: [function({g, xx1}) 
{ return g.addV("person").property("name","marko").property("age",29) }, 
function({g, xx1}) { return g.mergeV(xx1) }], 
@@ -973,6 +975,8 @@ const gremlins = {
     g_mergeV_hidden_label_key_onMatch_matched_prohibited: [function({g, xx1}) 
{ return g.mergeV(new Map([])).option(Merge.onMatch,xx1) }], 
     
g_injectXlist1_list2X_mergeVXlimitXlocal_1XX_optionXonCreate_rangeXlocal_1_2X_optionXonMatch_tailXlocalXX_to_match:
 [function({g, xx1, xx2}) { return 
g.addV("person").property("name","marko").property("age",29) }, function({g, 
xx1, xx2}) { return 
g.inject(xx1,xx1,xx2).fold().mergeV(__.limit(Scope.local,1)).option(Merge.onCreate,__.range(Scope.local,1,2)).option(Merge.onMatch,__.tail(Scope.local))
 }, function({g, xx1, xx2}) { return 
g.V().has("person","name","marko").has("created","N" [...]
     
g_injectXlist1_list2X_mergeVXlimitXlocal_1XX_optionXonCreate_rangeXlocal_1_2X_optionXonMatch_tailXlocalXX_to_create:
 [function({g, xx1, xx2}) { return 
g.addV("person").property("name","marko").property("age",29) }, function({g, 
xx1, xx2}) { return 
g.inject(xx1,xx1,xx2).fold().mergeV(__.limit(Scope.local,1)).option(Merge.onCreate,__.range(Scope.local,1,2)).option(Merge.onMatch,__.tail(Scope.local))
 }, function({g, xx1, xx2}) { return 
g.V().has("person","name","stephen").hasNot("create [...]
+    g_mergeVXlabel_person_name_marko_age_29X_optionXonMatch_age_nullX_allowed: 
[function({g, xx1, xx2}) { return 
g.addV("person").property("name","marko").property("age",29) }, function({g, 
xx1, xx2}) { return g.mergeV(xx1).option(Merge.onMatch,xx2) }, function({g, 
xx1, xx2}) { return g.V() }, function({g, xx1, xx2}) { return 
g.V().has("person","name","marko").has("age",null) }], 
+    g_mergeVXlabel_person_name_marko_age_29X_optionXonMatch_age_nullX: 
[function({g, xx1, xx2}) { return 
g.addV("person").property("name","marko").property("age",29) }, function({g, 
xx1, xx2}) { return g.mergeV(xx1).option(Merge.onMatch,xx2) }, function({g, 
xx1, xx2}) { return g.V() }, function({g, xx1, xx2}) { return 
g.V().has("person","name","marko").has("age") }], 
     g_V_age_min: [function({g}) { return g.V().values("age").min() }], 
     g_V_foo_min: [function({g}) { return g.V().values("foo").min() }], 
     g_V_name_min: [function({g}) { return g.V().values("name").min() }], 
diff --git a/gremlin-python/src/main/python/radish/gremlin.py 
b/gremlin-python/src/main/python/radish/gremlin.py
index 17a290b6d8..c01921d018 100644
--- a/gremlin-python/src/main/python/radish/gremlin.py
+++ b/gremlin-python/src/main/python/radish/gremlin.py
@@ -894,6 +894,8 @@ world.gremlins = {
     
'g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonMatch_sideEffectXpropertyXweight_0XX_constantXemptyXX':
 [(lambda g, 
xx1=None:g.addV('person').property('name','marko').as_('a').addV('person').property('name','vadas').as_('b').addE('knows').property('weight',1).from_('a').to('b')),
 (lambda g, 
xx1=None:g.merge_e(xx1).option(Merge.on_match,__.sideEffect(__.property('weight',0)).constant({}))),
 (lambda g, xx1=None:g.V()), (lambda g, 
xx1=None:g.E().hasLabel('knows').has('weight',1)), (l [...]
     
'g_injectXlist1_list2X_mergeEXlimitXlocal_1XX_optionXonCreate_rangeXlocal_1_2XX_optionXonMatch_tailXlocalXX_to_match':
 [(lambda g, 
xx1=None,xx2=None:g.addV('person').property('name','marko').property('age',29).as_('marko').addV('person').property('name','vadas').property('age',27).as_('vadas').addV('software').property('name','lop').property('lang','java').as_('lop').addV('person').property('name','josh').property('age',32).as_('josh').addV('software').property('name','ripple').prope
 [...]
     
'g_injectXlist1_list2X_mergeEXlimitXlocal_1XX_optionXonCreate_rangeXlocal_1_2XX_optionXonMatch_tailXlocalXX_to_create':
 [(lambda g, 
xx1=None,xx2=None:g.addV('person').property('name','marko').property('age',29).as_('marko').addV('person').property('name','vadas').property('age',27).as_('vadas').addV('software').property('name','lop').property('lang','java').as_('lop').addV('person').property('name','josh').property('age',32).as_('josh').addV('software').property('name','ripple').prop
 [...]
+    
'g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonMatch_weight_nullX_allowed': 
[(lambda g, 
xx1=None,xx2=None:g.addV('person').property('name','marko').as_('a').addV('person').property('name','vadas').as_('b').addE('knows').from_('a').to('b').property('weight',float(1.0))),
 (lambda g, xx1=None,xx2=None:g.merge_e(xx1).option(Merge.on_match,xx2)), 
(lambda g, xx1=None,xx2=None:g.V()), (lambda g, 
xx1=None,xx2=None:g.E().hasLabel('knows')), (lambda g, 
xx1=None,xx2=None:g.E().hasLabel('kno [...]
+    'g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonMatch_weight_nullX': 
[(lambda g, 
xx1=None,xx2=None:g.addV('person').property('name','marko').as_('a').addV('person').property('name','vadas').as_('b').addE('knows').from_('a').to('b').property('weight',float(1.0))),
 (lambda g, xx1=None,xx2=None:g.merge_e(xx1).option(Merge.on_match,xx2)), 
(lambda g, xx1=None,xx2=None:g.V()), (lambda g, 
xx1=None,xx2=None:g.E().hasLabel('knows')), (lambda g, 
xx1=None,xx2=None:g.E().hasLabel('knows').has [...]
     'g_mergeVXemptyX_optionXonMatch_nullX': [(lambda 
g:g.addV('person').property('name','marko').property('age',29)), (lambda 
g:g.merge_v({}).option(Merge.on_match,None)), (lambda 
g:g.V().has('person','name','marko').has('age',29))], 
     'g_V_mergeVXemptyX_optionXonMatch_nullX': [(lambda 
g:g.addV('person').property('name','marko').property('age',29)), (lambda 
g:g.V().merge_v({}).option(Merge.on_match,None)), (lambda 
g:g.V().has('person','name','marko').has('age',29))], 
     'g_mergeVXnullX_optionXonCreate_label_null_name_markoX': [(lambda g, 
xx1=None:g.addV('person').property('name','marko').property('age',29)), (lambda 
g, xx1=None:g.merge_v(xx1))], 
@@ -955,6 +957,8 @@ world.gremlins = {
     'g_mergeV_hidden_label_key_onMatch_matched_prohibited': [(lambda g, 
xx1=None:g.merge_v({}).option(Merge.on_match,xx1))], 
     
'g_injectXlist1_list2X_mergeVXlimitXlocal_1XX_optionXonCreate_rangeXlocal_1_2X_optionXonMatch_tailXlocalXX_to_match':
 [(lambda g, 
xx1=None,xx2=None:g.addV('person').property('name','marko').property('age',29)),
 (lambda g, 
xx1=None,xx2=None:g.inject(xx1,xx1,xx2).fold().merge_v(__.limit(Scope.local,1)).option(Merge.on_create,__.range_(Scope.local,1,2)).option(Merge.on_match,__.tail(Scope.local))),
 (lambda g, 
xx1=None,xx2=None:g.V().has('person','name','marko').has('created','N')), (lam 
[...]
     
'g_injectXlist1_list2X_mergeVXlimitXlocal_1XX_optionXonCreate_rangeXlocal_1_2X_optionXonMatch_tailXlocalXX_to_create':
 [(lambda g, 
xx1=None,xx2=None:g.addV('person').property('name','marko').property('age',29)),
 (lambda g, 
xx1=None,xx2=None:g.inject(xx1,xx1,xx2).fold().merge_v(__.limit(Scope.local,1)).option(Merge.on_create,__.range_(Scope.local,1,2)).option(Merge.on_match,__.tail(Scope.local))),
 (lambda g, 
xx1=None,xx2=None:g.V().has('person','name','stephen').hasNot('created')), (l 
[...]
+    
'g_mergeVXlabel_person_name_marko_age_29X_optionXonMatch_age_nullX_allowed': 
[(lambda g, 
xx1=None,xx2=None:g.addV('person').property('name','marko').property('age',29)),
 (lambda g, xx1=None,xx2=None:g.merge_v(xx1).option(Merge.on_match,xx2)), 
(lambda g, xx1=None,xx2=None:g.V()), (lambda g, 
xx1=None,xx2=None:g.V().has('person','name','marko').has('age',None))], 
+    'g_mergeVXlabel_person_name_marko_age_29X_optionXonMatch_age_nullX': 
[(lambda g, 
xx1=None,xx2=None:g.addV('person').property('name','marko').property('age',29)),
 (lambda g, xx1=None,xx2=None:g.merge_v(xx1).option(Merge.on_match,xx2)), 
(lambda g, xx1=None,xx2=None:g.V()), (lambda g, 
xx1=None,xx2=None:g.V().has('person','name','marko').has('age'))], 
     'g_V_age_min': [(lambda g:g.V().age.min_())], 
     'g_V_foo_min': [(lambda g:g.V().foo.min_())], 
     'g_V_name_min': [(lambda g:g.V().name.min_())], 
diff --git 
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AddVertex.feature
 
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AddVertex.feature
index 6b124e2cf6..004794b4a0 100644
--- 
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AddVertex.feature
+++ 
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AddVertex.feature
@@ -527,7 +527,6 @@ Feature: Step - addV()
     Then the result should have a count of 1
     And the graph should return 0 for count of 
"g.V().hasLabel(\"person\").values()"
 
-
   @AllowNullPropertyValues
   Scenario: g_addVXpersonX_propertyXname_joshX_propertyXage_nullX
     Given the empty graph
diff --git 
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeEdge.feature
 
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeEdge.feature
index cea0550187..05ad52ca14 100644
--- 
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeEdge.feature
+++ 
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeEdge.feature
@@ -947,4 +947,45 @@ Feature: Step - mergeE()
     And the graph should return 7 for count of "g.E()"
     And the graph should return 7 for count of "g.E().hasNot(\"created\")"
     And the graph should return 1 for count of 
"g.V().has(\"person\",\"name\",\"marko\").outE(\"knows\").hasNot(\"created\").inV().has(\"person\",\"name\",\"vadas\")"
-    And the graph should return 1 for count of 
"g.V().has(\"person\",\"name\",\"vadas\").outE(\"self\").hasNot('weight').inV().has(\"person\",\"name\",\"vadas\")"
\ No newline at end of file
+    And the graph should return 1 for count of 
"g.V().has(\"person\",\"name\",\"vadas\").outE(\"self\").hasNot('weight').inV().has(\"person\",\"name\",\"vadas\")"
+
+  @AllowNullPropertyValues
+  Scenario: 
g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonMatch_weight_nullX_allowed
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").as("a").
+        addV("person").property("name", "vadas").as("b").
+        addE("knows").from("a").to("b").property("weight",1.0)
+      """
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"knows\", 
\"D[OUT]\":\"v[marko]\", \"D[IN]\":\"v[vadas]\"}]"
+    And using the parameter xx2 defined as "m[{\"weight\":null}]"
+    And the traversal of
+      """
+      g.mergeE(xx1).option(Merge.onMatch,xx2)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 2 for count of "g.V()"
+    And the graph should return 1 for count of "g.E().hasLabel(\"knows\")"
+    And the graph should return 1 for count of 
"g.E().hasLabel(\"knows\").has(\"weight\",null)"
+
+  Scenario: 
g_mergeEXlabel_knows_out_marko_in_vadasX_optionXonMatch_weight_nullX
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").as("a").
+        addV("person").property("name", "vadas").as("b").
+        addE("knows").from("a").to("b").property("weight",1.0)
+      """
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"knows\", 
\"D[OUT]\":\"v[marko]\", \"D[IN]\":\"v[vadas]\"}]"
+    And using the parameter xx2 defined as "m[{\"weight\":null}]"
+    And the traversal of
+      """
+      g.mergeE(xx1).option(Merge.onMatch,xx2)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 2 for count of "g.V()"
+    And the graph should return 1 for count of "g.E().hasLabel(\"knows\")"
+    And the graph should return 0 for count of 
"g.E().hasLabel(\"knows\").has(\"weight\")"
\ No newline at end of file
diff --git 
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeVertex.feature
 
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeVertex.feature
index a9a61f4a8b..16b0b32883 100644
--- 
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeVertex.feature
+++ 
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/MergeVertex.feature
@@ -1048,4 +1048,39 @@ Feature: Step - mergeV()
     When iterated to list
     Then the result should have a count of 1
     And the graph should return 1 for count of 
"g.V().has(\"person\",\"name\",\"stephen\").hasNot(\"created\")"
-    And the graph should return 2 for count of "g.V()"
\ No newline at end of file
+    And the graph should return 2 for count of "g.V()"
+
+  @AllowNullPropertyValues
+  Scenario: 
g_mergeVXlabel_person_name_marko_age_29X_optionXonMatch_age_nullX_allowed
+    Given the empty graph
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", 
\"name\":\"marko\"}]"
+    And using the parameter xx2 defined as "m[{\"age\": null}]"
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property("age", 29)
+      """
+    And the traversal of
+      """
+      g.mergeV(xx1).option(Merge.onMatch, xx2)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 1 for count of "g.V()"
+    And the graph should return 1 for count of 
"g.V().has(\"person\",\"name\",\"marko\").has(\"age\",null)"
+
+  Scenario: g_mergeVXlabel_person_name_marko_age_29X_optionXonMatch_age_nullX
+    Given the empty graph
+    And using the parameter xx1 defined as "m[{\"t[label]\": \"person\", 
\"name\":\"marko\"}]"
+    And using the parameter xx2 defined as "m[{\"age\": null}]"
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "marko").property("age", 29)
+      """
+    And the traversal of
+      """
+      g.mergeV(xx1).option(Merge.onMatch, xx2)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 1 for count of "g.V()"
+    And the graph should return 0 for count of 
"g.V().has(\"person\",\"name\",\"marko\").has(\"age\")"
\ No newline at end of file


Reply via email to