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/12/14 19:08:51 UTC
[tinkerpop] branch TINKERPOP-2667 created (now 81b40bf)
This is an automated email from the ASF dual-hosted git repository.
spmallette pushed a change to branch TINKERPOP-2667
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git.
at 81b40bf TINKERPOP-2667 Allowed fold()/addAll to merge Map objects
This branch includes the following new commits:
new 81b40bf TINKERPOP-2667 Allowed fold()/addAll to merge Map objects
The 1 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.
[tinkerpop] 01/01: TINKERPOP-2667 Allowed fold()/addAll to merge Map objects
Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
spmallette pushed a commit to branch TINKERPOP-2667
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 81b40bfa4fa5d7cb7f0b39d8f434bea74fc5a021
Author: Stephen Mallette <st...@amazon.com>
AuthorDate: Tue Dec 14 13:45:33 2021 -0500
TINKERPOP-2667 Allowed fold()/addAll to merge Map objects
---
CHANGELOG.asciidoc | 1 +
docs/src/reference/the-traversal.asciidoc | 2 ++
docs/src/upgrade/release-3.5.x.asciidoc | 28 ++++++++++++++++++++++
.../gremlin/process/traversal/Operator.java | 2 ++
.../traversal/dsl/graph/GraphTraversal.java | 13 +++++-----
.../process/traversal/step/map/FoldStep.java | 3 ++-
.../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs | 2 ++
.../gremlin-javascript/test/cucumber/gremlin.js | 2 ++
gremlin-python/src/main/python/radish/gremlin.py | 2 ++
gremlin-test/features/map/Fold.feature | 21 ++++++++++++++++
10 files changed, 69 insertions(+), 7 deletions(-)
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 3dca819..f3443a1 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -36,6 +36,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
* Deprecated `JsonBuilder` serialization for GraphSON and Gryo.
* Added `ProductiveByStrategy` to ensure consistency of `by()` modulator behaviors when child traversal arguments contained no elements.
* Changed drivers to once again send the previously deprecated and removed "close" message for sessions.
+* Modified `fold()` to merge `Map` instances with `addAll`.
* Allowed `null` string values in the Gremlin grammar.
* Fixed support for `SeedStrategy` in the Gremlin Grammar.
* Fixed bug in `Translator` of `gremlin-javascript` around array translation.
diff --git a/docs/src/reference/the-traversal.asciidoc b/docs/src/reference/the-traversal.asciidoc
index adec52b..517805b 100644
--- a/docs/src/reference/the-traversal.asciidoc
+++ b/docs/src/reference/the-traversal.asciidoc
@@ -1241,6 +1241,7 @@ g.V(1).out('knows').values('name').fold(0) {a,b -> a + b.length()} <3>
g.V().values('age').fold(0) {a,b -> a + b} <4>
g.V().values('age').fold(0, sum) <5>
g.V().values('age').sum() <6>
+g.inject([a: 1],[b:2]).fold([], addAll) <7>
----
<1> A parameterless `fold()` will aggregate all the objects into a list and then emit the list.
@@ -1249,6 +1250,7 @@ g.V().values('age').sum() <6>
<4> What is the total age of the people in the graph?
<5> The same as before, but using a built-in bi-function.
<6> The same as before, but using the <<sum-step,`sum()`-step>>.
+<7> A mechanism for merging `Map` instances. If a key occurs in more than a single `Map`, the later occurrence will replace the earlier.
*Additional References*
diff --git a/docs/src/upgrade/release-3.5.x.asciidoc b/docs/src/upgrade/release-3.5.x.asciidoc
index 882c8c1..5908821 100644
--- a/docs/src/upgrade/release-3.5.x.asciidoc
+++ b/docs/src/upgrade/release-3.5.x.asciidoc
@@ -68,6 +68,34 @@ From a Gremlin language perspective there are no functions that can be called on
See:link:https://issues.apache.org/jira/browse/TINKERPOP-2596[TINKERPOP-2596]
+==== fold() Map
+
+The `fold()` step has long been used for reducing the traversal stream to a `List` as in:
+
+```text
+gremlin> g.V().values('age').fold()
+==>[29,27,32,35]
+gremlin> g.inject([a: 1],[b:2]).fold()
+==>[[a:1],[b:2]]
+```
+
+This step has always had a lesser known second signature though which has two arguments of a "seed" and "foldFunction":
+
+```text
+gremlin> g.inject([a: 1],[b:2]).fold([], addAll)
+==>[[a:1],[b:2]]
+```
+
+There was a bit of incomplete (or perhaps incorrect) logic that did not allow the folding of `Map` and as of this
+version, that issue has been resolved which enables `Map` entry merges:
+
+```text
+gremlin> g.inject([a: 1],[b:2]).fold([:], addAll)
+==>[a:1,b:2]
+```
+
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-2667[TINKERPOP-2667]
+
==== Refinements to null
Release 3.5.0 introduce the ability for there to be traversers that contained a `null` value. Since that time it has
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 ea84f2d..b4d3cb2 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
@@ -170,6 +170,8 @@ public enum Operator implements BinaryOperator<Object> {
* 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}.
+ * <p/>
+ * The semantics described above for {@code Collection} are the same when applied to a {@code Map}.
*
* @since 3.2.0-incubating
*/
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 9a45300..c3cf64d 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
@@ -854,7 +854,7 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
* Rolls up objects in the stream into an aggregate value as defined by a {@code seed} and {@code BiFunction}.
*
* @param seed the value to provide as the first argument to the {@code foldFunction}
- * @param foldFunction the function to fold by where the first argument is the {@code seed} or the value returned from subsequent calss and
+ * @param foldFunction the function to fold by where the first argument is the {@code seed} or the value returned from subsequent class and
* the second argument is the value from the stream
* @return the traversal with an appended {@link FoldStep}
* @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#fold-step" target="_blank">Reference Documentation - Fold Step</a>
@@ -862,7 +862,7 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
*/
public default <E2> GraphTraversal<S, E2> fold(final E2 seed, final BiFunction<E2, E, E2> foldFunction) {
this.asAdmin().getBytecode().addStep(Symbols.fold, seed, foldFunction);
- return this.asAdmin().addStep(new FoldStep<>(this.asAdmin(), new ConstantSupplier<>(seed), foldFunction)); // TODO: User should provide supplier?
+ return this.asAdmin().addStep(new FoldStep<>(this.asAdmin(), new ConstantSupplier<>(seed), foldFunction));
}
/**
@@ -2984,15 +2984,16 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
/**
* This step modifies {@link #choose(Function)} to specifies the available choices that might be executed.
*
- * @param pickToken the token that would trigger this option
+ * @param pick the token that would trigger this option which may be a {@link TraversalOptionParent.Pick},
+ * a {@link Traversal}, {@link Predicate}, or object depending on the step being modulated.
* @param traversalOption the option as a traversal
* @return the traversal with the modulated step
* @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#choose-step" target="_blank">Reference Documentation - Choose Step</a>
* @since 3.0.0-incubating
*/
- public default <M, E2> GraphTraversal<S, E> option(final M pickToken, final Traversal<?, E2> traversalOption) {
- this.asAdmin().getBytecode().addStep(Symbols.option, pickToken, traversalOption);
- ((TraversalOptionParent<M, E, E2>) this.asAdmin().getEndStep()).addGlobalChildOption(pickToken, (Traversal.Admin<E, E2>) traversalOption.asAdmin());
+ public default <M, E2> GraphTraversal<S, E> option(final M pick, final Traversal<?, E2> traversalOption) {
+ this.asAdmin().getBytecode().addStep(Symbols.option, pick, traversalOption);
+ ((TraversalOptionParent<M, E, E2>) this.asAdmin().getEndStep()).addGlobalChildOption(pick, (Traversal.Admin<E, E2>) traversalOption.asAdmin());
return this;
}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/FoldStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/FoldStep.java
index 2e8d97e..cc0d983 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/FoldStep.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/FoldStep.java
@@ -27,6 +27,7 @@ import org.apache.tinkerpop.gremlin.util.function.ArrayListSupplier;
import java.io.Serializable;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
@@ -48,7 +49,7 @@ public final class FoldStep<S, E> extends ReducingBarrierStep<S, E> {
public FoldStep(final Traversal.Admin traversal, final Supplier<E> seed, final BiFunction<E, S, E> foldFunction) {
super(traversal);
- this.listFold = Operator.addAll.equals(foldFunction);
+ this.listFold = seed.get() instanceof Collection;
this.setSeedSupplier(seed);
this.setReducingBiOperator(new FoldBiOperator<>(foldFunction));
}
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index ec08c96..d4bf4f3 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -381,6 +381,8 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
{"g_V_fold", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Fold()}},
{"g_V_fold_unfold", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Fold().Unfold<object>()}},
{"g_V_age_foldX0_plusX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("age").Fold<object>(0,Operator.Sum)}},
+ {"g_injectXa1_b2X_foldXm_addAllX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(new Dictionary<object,object> {{"a", 1}},new Dictionary<object,object> {{"b", 2}}).Fold<object>(new Dictionary<object,object> {},Operator.AddAll)}},
+ {"g_injectXa1_b2_b4X_foldXm_addAllX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(new Dictionary<object,object> {{"a", 1}},new Dictionary<object,object> {{"b", 2}},new Dictionary<object,object> {{"b", 4}}).Fold<object>(new Dictionary<object,object> {},Operator.AddAll)}},
{"g_VX1X_V_valuesXnameX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).V().Values<object>("name")}},
{"g_V_outXknowsX_V_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Out("knows").V().Values<object>("name")}},
{"g_V_hasXname_GarciaX_inXsungByX_asXsongX_V_hasXname_Willie_DixonX_inXwrittenByX_whereXeqXsongXX_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("artist","name","Garcia").In("sungBy").As("song").V().Has("artist","name","Willie_Dixon").In("writtenBy").Where(P.Eq("song")).Values<object>("name")}},
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 fd477b9..dc01b00 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
@@ -369,6 +369,8 @@ const gremlins = {
g_V_fold: [function({g}) { return g.V().fold() }],
g_V_fold_unfold: [function({g}) { return g.V().fold().unfold() }],
g_V_age_foldX0_plusX: [function({g}) { return g.V().values("age").fold(0,Operator.sum) }],
+ g_injectXa1_b2X_foldXm_addAllX: [function({g}) { return g.inject(new Map([["a",1]]),new Map([["b",2]])).fold(new Map([]),Operator.addAll) }],
+ g_injectXa1_b2_b4X_foldXm_addAllX: [function({g}) { return g.inject(new Map([["a",1]]),new Map([["b",2]]),new Map([["b",4]])).fold(new Map([]),Operator.addAll) }],
g_VX1X_V_valuesXnameX: [function({g, vid1}) { return g.V(vid1).V().values("name") }],
g_V_outXknowsX_V_name: [function({g}) { return g.V().out("knows").V().values("name") }],
g_V_hasXname_GarciaX_inXsungByX_asXsongX_V_hasXname_Willie_DixonX_inXwrittenByX_whereXeqXsongXX_name: [function({g}) { return g.V().has("artist","name","Garcia").in_("sungBy").as("song").V().has("artist","name","Willie_Dixon").in_("writtenBy").where(P.eq("song")).values("name") }],
diff --git a/gremlin-python/src/main/python/radish/gremlin.py b/gremlin-python/src/main/python/radish/gremlin.py
index 30ab552..a41354e 100644
--- a/gremlin-python/src/main/python/radish/gremlin.py
+++ b/gremlin-python/src/main/python/radish/gremlin.py
@@ -354,6 +354,8 @@ world.gremlins = {
'g_V_fold': [(lambda g:g.V().fold())],
'g_V_fold_unfold': [(lambda g:g.V().fold().unfold())],
'g_V_age_foldX0_plusX': [(lambda g:g.V().age.fold(0,Operator.sum_))],
+ 'g_injectXa1_b2X_foldXm_addAllX': [(lambda g:g.inject({'a':1},{'b':2}).fold({},Operator.addAll))],
+ 'g_injectXa1_b2_b4X_foldXm_addAllX': [(lambda g:g.inject({'a':1},{'b':2},{'b':4}).fold({},Operator.addAll))],
'g_VX1X_V_valuesXnameX': [(lambda g, vid1=None:g.V(vid1).V().name)],
'g_V_outXknowsX_V_name': [(lambda g:g.V().out('knows').V().name)],
'g_V_hasXname_GarciaX_inXsungByX_asXsongX_V_hasXname_Willie_DixonX_inXwrittenByX_whereXeqXsongXX_name': [(lambda g:g.V().has('artist','name','Garcia').in_('sungBy').as_('song').V().has('artist','name','Willie_Dixon').in_('writtenBy').where(P.eq('song')).name)],
diff --git a/gremlin-test/features/map/Fold.feature b/gremlin-test/features/map/Fold.feature
index c4c0f80..d0ca7f6 100644
--- a/gremlin-test/features/map/Fold.feature
+++ b/gremlin-test/features/map/Fold.feature
@@ -60,3 +60,24 @@ Feature: Step - fold()
| result |
| d[123].i |
+ Scenario: g_injectXa1_b2X_foldXm_addAllX
+ Given the empty graph
+ And the traversal of
+ """
+ g.inject(["a":1],["b":2]).fold([:], Operator.addAll)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | m[{"a":"d[1].i", "b":"d[2].i"}] |
+
+ Scenario: g_injectXa1_b2_b4X_foldXm_addAllX
+ Given the empty graph
+ And the traversal of
+ """
+ g.inject(["a":1], ["b":2], ["b":4]).fold([:], Operator.addAll)
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | m[{"a":"d[1].i", "b":"d[4].i"}] |
\ No newline at end of file