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
