This is an automated email from the ASF dual-hosted git repository.
colegreer pushed a commit to branch 3.8-dev
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
The following commit(s) were added to refs/heads/3.8-dev by this push:
new a97db03618 TINKERPOP-3148 Prevent multiple by modulators from being
used for the dedup step by throwing IllegalStateException if modulateBy is
called more than once. (#3105)
a97db03618 is described below
commit a97db03618fa2fd7c17d24e331ff62adfe9c8df4
Author: andreachild <[email protected]>
AuthorDate: Mon May 5 10:00:36 2025 -0700
TINKERPOP-3148 Prevent multiple by modulators from being used for the dedup
step by throwing IllegalStateException if modulateBy is called more than once.
(#3105)
---
CHANGELOG.asciidoc | 2 +-
.../process/traversal/step/filter/DedupGlobalStep.java | 3 +++
.../process/traversal/step/filter/DedupGlobalStepTest.java | 6 ++++++
.../test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs | 1 +
gremlin-go/driver/cucumber/gremlin.go | 1 +
.../javascript/gremlin-javascript/test/cucumber/gremlin.js | 1 +
gremlin-python/src/main/python/radish/gremlin.py | 1 +
.../tinkerpop/gremlin/test/features/filter/Dedup.feature | 11 ++++++++++-
8 files changed, 24 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 6219bf76bf..7e0ac31f06 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -66,7 +66,7 @@ This release also includes changes from <<release-3-7-XXX,
3.7.XXX>>.
* Added missing strategies in `gremlin-go`, updated certain strategies to take
varargs and updated `GoTranslatorVisitor` for corresponding translations.
* Fixed `BigInt` and `BigDecimal` parsing in `gremlin-go` cucumber test suite,
fixed `UnscaledValue` type in `BigDecimal` struct and added `ParseBigDecimal`
method.
* Added validation to `valueMap()`/`propertyMap()`/`groupCount()` to prevent
an invalid usage of multiple `by()` modulators.
-* Added validation to `groupCount()` and `sack()` to prevent an invalid usage
of multiple `by()` modulators.
+* Added validation to `groupCount()`, `sack()` and `dedup()` to prevent an
invalid usage of multiple `by()` modulators.
* Deprecated `ProcessLimitedStandardSuite` and `ProcessLimitedComputerSuite`
in favor of `ProcessEmbeddedStandardSuite` and `ProcessEmbeddedComputerSuite`
respectively.
* Deprecated `ProcessStandardSuite` and the `ProcessComputerSuite` in favor of
Gherkin testing and the `ProcessEmbeddedStandardSuite` and
`ProcessEmbeddedComputerSuite` for testing JVM-specific Gremlin behaviors.
* Removed lambda oriented Gremlin testing from Gherkin test suite.
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/DedupGlobalStep.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/DedupGlobalStep.java
index 16d7ee4657..ad4a762fe0 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/DedupGlobalStep.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/DedupGlobalStep.java
@@ -122,6 +122,9 @@ public final class DedupGlobalStep<S> extends FilterStep<S>
implements Traversal
@Override
public void modulateBy(final Traversal.Admin<?, ?> dedupTraversal) {
+ if (this.dedupTraversal != null) {
+ throw new IllegalStateException("Dedup step can only have one by
modulator");
+ }
this.dedupTraversal = this.integrateChild(dedupTraversal);
}
diff --git
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/DedupGlobalStepTest.java
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/DedupGlobalStepTest.java
index 67331c2a88..d7beabe555 100644
---
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/DedupGlobalStepTest.java
+++
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/DedupGlobalStepTest.java
@@ -24,6 +24,7 @@ import
org.apache.tinkerpop.gremlin.process.traversal.step.StepTest;
import java.util.Arrays;
import java.util.List;
+import org.junit.Test;
/**
* @author Daniel Kuppitz (http://gremlin.guru)
@@ -37,4 +38,9 @@ public class DedupGlobalStepTest extends StepTest {
__.dedup().by("name")
);
}
+
+ @Test(expected = IllegalStateException.class)
+ public void shouldThrowForMultipleByModulators() {
+ __.dedup().by("name").by("age");
+ }
}
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index ea2ce560b8..0ec2b51e13 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -200,6 +200,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
{"g_VX1X_valuesXageX_dedupXlocalX_unfold", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p)
=>g.V(p["vid1"]).Values<object>("age").Dedup(Scope.Local).Unfold<object>()}},
{"g_V_properties_dedup_count", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.AddV("person").Property("name",
"josh").AddV("person").Property("name", "josh").AddV("person").Property("name",
"josh"), (g,p) =>g.V().Properties<object>("name").Dedup().Count()}},
{"g_V_properties_dedup_byXvalueX_count", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.AddV("person").Property("name",
"josh").AddV("person").Property("name", "josh").AddV("person").Property("name",
"josh"), (g,p)
=>g.V().Properties<object>("name").Dedup().By(T.Value).Count()}},
+ {"g_V_both_hasXlabel_softwareX_dedup_byXlangX_byXnameX_name",
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.V().Both().Has(T.Label,
"software").Dedup().By("lang").By("name").Values<object>("name")}},
{"g_V_drop", new List<Func<GraphTraversalSource,
IDictionary<string, object>, ITraversal>> {(g,p)
=>g.AddV().As("a").AddV().As("b").AddE("knows").To("a"), (g,p) =>g.V().Drop(),
(g,p) =>g.V(), (g,p) =>g.E()}},
{"g_V_outE_drop", new List<Func<GraphTraversalSource,
IDictionary<string, object>, ITraversal>> {(g,p)
=>g.AddV().As("a").AddV().As("b").AddE("knows").To("a"), (g,p)
=>g.V().OutE().Drop(), (g,p) =>g.V(), (g,p) =>g.E()}},
{"g_V_properties_drop", new List<Func<GraphTraversalSource,
IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV().Property("name",
"bob").AddV().Property("name", "alice"), (g,p)
=>g.V().Properties<object>().Drop(), (g,p) =>g.V(), (g,p)
=>g.V().Properties<object>()}},
diff --git a/gremlin-go/driver/cucumber/gremlin.go
b/gremlin-go/driver/cucumber/gremlin.go
index 5e5564dc6d..be5821231c 100644
--- a/gremlin-go/driver/cucumber/gremlin.go
+++ b/gremlin-go/driver/cucumber/gremlin.go
@@ -169,6 +169,7 @@ var translationMap = map[string][]func(g
*gremlingo.GraphTraversalSource, p map[
"g_VX1X_valuesXageX_dedupXlocalX_unfold": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.V(p["vid1"]).Values("age").Dedup(gremlingo.Scope.Local).Unfold()}},
"g_V_properties_dedup_count": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return
g.AddV("person").Property("name", "josh").AddV("person").Property("name",
"josh").AddV("person").Property("name", "josh")}, func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.V().Properties("name").Dedup().Count()}},
"g_V_properties_dedup_byXvalueX_count": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.AddV("person").Property("name",
"josh").AddV("person").Property("name", "josh").AddV("person").Property("name",
"josh")}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.V().Properties("name").Dedup().By(gremlingo.T.Value).Count()}},
+ "g_V_both_hasXlabel_softwareX_dedup_byXlangX_byXnameX_name": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.V().Both().Has(gremlingo.T.Label,
"software").Dedup().By("lang").By("name").Values("name")}},
"g_V_drop": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return
g.AddV().As("a").AddV().As("b").AddE("knows").To("a")}, func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.V().Drop()}, func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.V()}, func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {ret [...]
"g_V_outE_drop": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return
g.AddV().As("a").AddV().As("b").AddE("knows").To("a")}, func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.V().OutE().Drop()}, func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.V()}, func(g
*gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTr
[...]
"g_V_properties_drop": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return
g.AddV().Property("name", "bob").AddV().Property("name", "alice")}, func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.V().Properties().Drop()}, func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.V()}, func(g
*gremlingo.GraphTraversalSource, p map[string]interfa [...]
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 074fb918ab..2d6e42b706 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
@@ -199,6 +199,7 @@ const gremlins = {
g_VX1X_valuesXageX_dedupXlocalX_unfold: [function({g, vid1}) { return
g.V(vid1).values("age").dedup(Scope.local).unfold() }],
g_V_properties_dedup_count: [function({g}) { return
g.addV("person").property("name", "josh").addV("person").property("name",
"josh").addV("person").property("name", "josh") }, function({g}) { return
g.V().properties("name").dedup().count() }],
g_V_properties_dedup_byXvalueX_count: [function({g}) { return
g.addV("person").property("name", "josh").addV("person").property("name",
"josh").addV("person").property("name", "josh") }, function({g}) { return
g.V().properties("name").dedup().by(T.value).count() }],
+ g_V_both_hasXlabel_softwareX_dedup_byXlangX_byXnameX_name: [function({g})
{ return g.V().both().has(T.label,
"software").dedup().by("lang").by("name").values("name") }],
g_V_drop: [function({g}) { return
g.addV().as("a").addV().as("b").addE("knows").to("a") }, function({g}) { return
g.V().drop() }, function({g}) { return g.V() }, function({g}) { return g.E()
}],
g_V_outE_drop: [function({g}) { return
g.addV().as("a").addV().as("b").addE("knows").to("a") }, function({g}) { return
g.V().outE().drop() }, function({g}) { return g.V() }, function({g}) { return
g.E() }],
g_V_properties_drop: [function({g}) { return g.addV().property("name",
"bob").addV().property("name", "alice") }, function({g}) { return
g.V().properties().drop() }, function({g}) { return g.V() }, function({g}) {
return g.V().properties() }],
diff --git a/gremlin-python/src/main/python/radish/gremlin.py
b/gremlin-python/src/main/python/radish/gremlin.py
index e01425c3ae..97fa2afdcd 100644
--- a/gremlin-python/src/main/python/radish/gremlin.py
+++ b/gremlin-python/src/main/python/radish/gremlin.py
@@ -172,6 +172,7 @@ world.gremlins = {
'g_VX1X_valuesXageX_dedupXlocalX_unfold': [(lambda g,
vid1=None:g.V(vid1).values('age').dedup(Scope.local).unfold())],
'g_V_properties_dedup_count': [(lambda
g:g.add_v('person').property('name', 'josh').add_v('person').property('name',
'josh').add_v('person').property('name', 'josh')), (lambda
g:g.V().properties('name').dedup().count())],
'g_V_properties_dedup_byXvalueX_count': [(lambda
g:g.add_v('person').property('name', 'josh').add_v('person').property('name',
'josh').add_v('person').property('name', 'josh')), (lambda
g:g.V().properties('name').dedup().by(T.value).count())],
+ 'g_V_both_hasXlabel_softwareX_dedup_byXlangX_byXnameX_name': [(lambda
g:g.V().both().has(T.label,
'software').dedup().by('lang').by('name').values('name'))],
'g_V_drop': [(lambda
g:g.add_v().as_('a').add_v().as_('b').add_e('knows').to('a')), (lambda
g:g.V().drop()), (lambda g:g.V()), (lambda g:g.E())],
'g_V_outE_drop': [(lambda
g:g.add_v().as_('a').add_v().as_('b').add_e('knows').to('a')), (lambda
g:g.V().out_e().drop()), (lambda g:g.V()), (lambda g:g.E())],
'g_V_properties_drop': [(lambda g:g.add_v().property('name',
'bob').add_v().property('name', 'alice')), (lambda
g:g.V().properties().drop()), (lambda g:g.V()), (lambda g:g.V().properties())],
diff --git
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Dedup.feature
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Dedup.feature
index ebcc571ad3..af88f48ee5 100644
---
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Dedup.feature
+++
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Dedup.feature
@@ -421,4 +421,13 @@ Feature: Step - dedup()
When iterated to list
Then the result should be unordered
| result |
- | d[1].l |
\ No newline at end of file
+ | d[1].l |
+
+ Scenario: g_V_both_hasXlabel_softwareX_dedup_byXlangX_byXnameX_name
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().both().has(T.label,
"software").dedup().by("lang").by("name").values("name")
+ """
+ When iterated to list
+ Then the traversal will raise an error with message containing text of
"Dedup step can only have one by modulator"
\ No newline at end of file