Skip to content

Commit a436181

Browse files
committed
TINKERPOP-2971 Fixed bug in value traversal to group()
Ensured that reducing/supplying barriers that follow collecting barriers get some respect.
1 parent 53008f1 commit a436181

26 files changed

Lines changed: 545 additions & 70 deletions

File tree

CHANGELOG.asciidoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ This release also includes changes from <<release-3-7-XXX, 3.7.XXX>>.
4444
* Added static `instance()` method to `ElementIdStrategy` to an instance with the default configuration.
4545
* Updated `ElementIdStrategy.getConfiguration()` to help with serialization.
4646
* Changed type for `ReservedKeysVerificationStrategy.keys` in .NET to take a `Set<string>` rather than `List<string>`.
47+
* Fixed bug in `group()` value traversal of the second `by()` where a `CollectingBarrierStep` could produce an unexpected filtering effect when `ReducingBarrierStep` or `SupplyingBarrierStep` instances were not taken into account.
48+
* Changed `DetachedFactory` to special case the handling of `ComputerAdjacentVertex` which doesn't carry properties but still needs to be detachable for OLAP cases.
49+
* Deprecated `ProfilingAware.prepareForProfiling` method preferring to simply `resetBarrierFromValueTraversal` from the `Grouping` interface after strategy application.
4750
4851
== TinkerPop 3.7.0 (Gremfir Master of the Pan Flute)
4952

docs/src/upgrade/release-3.8.x.asciidoc

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,42 @@ Integer overflows caused by addition and multiplication operations will throw an
174174
skipped with incorrect result.
175175
176176
==== Gremlin Lang Float Literals Default to Double
177+
177178
The `GremlinLangScriptEngine` has been modified to treat float literals without explicit type suffixes (like 'm', 'f',
178179
or 'd') as Double by default. Users who need BigDecimal precision can still use the 'm' suffix (e.g., 1.0m).
179180
`GremlinGroovyScriptEngine` will still default to BigDecimal for float literals.
180181
182+
==== group() Value Traversal Semantics
183+
184+
The `group()` step takes two `by()` modulators. The first defines the key for the grouping and the second acts upon the
185+
values that were grouped to each key. The latter is referred to as the "value traversal". In earlier versions of
186+
TinkerPop, using `order()` in the value traversal could produce an unexpected result if combined with a step like
187+
`fold()`.
188+
189+
[source,text]
190+
----
191+
gremlin> g.V().has("person","name",P.within("vadas","peter")).group().by().by(__.out().fold())
192+
==>[v[2]:[],v[6]:[v[3]]]
193+
gremlin> g.V().has("person","name",P.within("vadas","peter")).group().by().by(__.out().order().fold())
194+
==>[v[6]:[v[3]]]
195+
----
196+
197+
The example above shows that `v[2]` gets filtered away when `order()` is included. Obviously, this was not expected
198+
behavior. The problem can be more generally explained as an issue where a `Barrier` like `order()` can return an empty
199+
result. If this step is followed by another `Barrier` that always produces an output like `sum()`, `count()` or `fold()`
200+
then the empty result would not feed through to that following step. This issue has now been fixed and the two
201+
traversals from the previous example now return the same results.
202+
203+
[source,text]
204+
----
205+
gremlin> g.V().has("person","name",P.within("vadas","peter")).group().by().by(__.out().fold())
206+
==>[v[2]:[],v[6]:[v[3]]]
207+
gremlin> g.V().has("person","name",P.within("vadas","peter")).group().by().by(__.out().order().fold())
208+
==>[v[2]:[],v[6]:[v[3]]]
209+
----
210+
211+
See: link:https://issues.apache.org/jira/browse/TINKERPOP-2971[TINKERPOP-2971]
212+
181213
=== Upgrading for Providers
182214
183215
==== Graph System Providers

gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/util/ComputerGraph.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
*/
5050
public final class ComputerGraph implements Graph {
5151

52-
private enum State {VERTEX_PROGRAM, MAP_REDUCE}
52+
public enum State {VERTEX_PROGRAM, MAP_REDUCE}
5353

5454
private ComputerVertex starVertex;
5555
private final Set<String> computeKeys;

gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/Barrier.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,11 @@ public interface Barrier<B> extends MemoryComputing<B> {
7373
public default void done() {
7474

7575
}
76+
77+
/**
78+
* If a barrier is unproductive then provide an empty object suitable to the implementation which can be used
79+
* to represent that state. This is important for cases like {@code by(out().order().fold())} where the
80+
* {@code order()} might filter but the {@code fold()} means the traversal is productive.
81+
*/
82+
public B getEmptyBarrier();
7683
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.tinkerpop.gremlin.process.traversal.step;
20+
21+
import org.apache.tinkerpop.gremlin.process.traversal.step.filter.RangeGlobalStep;
22+
23+
/**
24+
* Marker interface for a {@link Barrier} that can behave as a filter, like {@link RangeGlobalStep}
25+
*/
26+
public interface FilteringBarrier<S> extends Barrier<S> {
27+
}

gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/Grouping.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ public default boolean hasBarrierInValueTraversal() {
5656
return null != determineBarrierStep(getValueTraversal());
5757
}
5858

59+
/**
60+
* The first non-local {@link Barrier} step ascertained by calls to {@link #determineBarrierStep(Traversal.Admin)}
61+
* is cached for a {@code Grouping}. After strategy application it is important that the cache be reset to ensure
62+
* that any modifications to the child traversal are accounted for.
63+
*/
64+
public void resetBarrierFromValueTraversal();
65+
5966
/**
6067
* Determines the first non-local {@link Barrier} step in the provided traversal. This method is used by {@link GroupStep}
6168
* and {@link GroupSideEffectStep} to ultimately determine the reducing bi-operator.

gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/LocalBarrier.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,8 @@ public default MemoryComputeKey getMemoryComputeKey() {
3333
return MemoryComputeKey.of(((Step) this).getId(), Operator.and, false, true);
3434
}
3535

36+
@Override
37+
default TraverserSet<S> getEmptyBarrier() {
38+
return new TraverserSet<>();
39+
}
3640
}

gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/ProfilingAware.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,13 @@ public interface ProfilingAware {
4040

4141
/**
4242
* Prepares the step for any internal changes that might help ensure that profiling will work as expected.
43+
* @deprecated As of release 3.8.0, not directly replaced because a simple call to {@link Step#reset()} at traversal
44+
* lock can more generally do what is needed by this much more specific API.
4345
*/
44-
public void prepareForProfiling();
46+
@Deprecated
47+
public default void prepareForProfiling() {
48+
49+
}
4550

4651
/**
4752
* A helper class which holds a {@link Barrier} and it's related {@link ProfileStep} so that the latter can have
@@ -85,6 +90,11 @@ public void addBarrier(final Object barrier) {
8590
this.barrier.addBarrier(barrier);
8691
}
8792

93+
@Override
94+
public Object getEmptyBarrier() {
95+
return new Object();
96+
}
97+
8898
@Override
8999
public MemoryComputeKey getMemoryComputeKey() {
90100
return this.barrier.getMemoryComputeKey();

gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/DedupGlobalStep.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,13 @@
2323
import org.apache.tinkerpop.gremlin.process.traversal.Pop;
2424
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
2525
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
26-
import org.apache.tinkerpop.gremlin.process.traversal.step.Barrier;
2726
import org.apache.tinkerpop.gremlin.process.traversal.step.ByModulating;
27+
import org.apache.tinkerpop.gremlin.process.traversal.step.FilteringBarrier;
2828
import org.apache.tinkerpop.gremlin.process.traversal.step.GraphComputing;
2929
import org.apache.tinkerpop.gremlin.process.traversal.step.PathProcessor;
3030
import org.apache.tinkerpop.gremlin.process.traversal.step.Scoping;
3131
import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
3232
import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
33-
import org.apache.tinkerpop.gremlin.process.traversal.util.FastNoSuchElementException;
3433
import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalProduct;
3534
import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil;
3635
import org.apache.tinkerpop.gremlin.structure.Property;
@@ -53,7 +52,8 @@
5352
/**
5453
* @author Marko A. Rodriguez (http://markorodriguez.com)
5554
*/
56-
public final class DedupGlobalStep<S> extends FilterStep<S> implements TraversalParent, Scoping, GraphComputing, Barrier<Map<Object, Traverser.Admin<S>>>, ByModulating, PathProcessor {
55+
public final class DedupGlobalStep<S> extends FilterStep<S> implements TraversalParent, Scoping, GraphComputing,
56+
FilteringBarrier<Map<Object, Traverser.Admin<S>>>, ByModulating, PathProcessor {
5757

5858
private Traversal.Admin<S, Object> dedupTraversal = null;
5959
private Set<Object> duplicateSet = new HashSet<>();
@@ -191,6 +191,11 @@ public void processAllStarts() {
191191

192192
}
193193

194+
@Override
195+
public Map<Object, Traverser.Admin<S>> getEmptyBarrier() {
196+
return new HashMap<>();
197+
}
198+
194199
@Override
195200
public boolean hasNextBarrier() {
196201
return null != this.barrier || this.starts.hasNext();

gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/RangeGlobalStep.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,12 @@
2121
import org.apache.tinkerpop.gremlin.process.computer.MemoryComputeKey;
2222
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
2323
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
24-
import org.apache.tinkerpop.gremlin.process.traversal.step.Barrier;
2524
import org.apache.tinkerpop.gremlin.process.traversal.step.Bypassing;
25+
import org.apache.tinkerpop.gremlin.process.traversal.step.FilteringBarrier;
2626
import org.apache.tinkerpop.gremlin.process.traversal.step.Ranging;
2727
import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
2828
import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.TraverserSet;
2929
import org.apache.tinkerpop.gremlin.process.traversal.util.FastNoSuchElementException;
30-
import org.apache.tinkerpop.gremlin.structure.util.CloseableIterator;
3130
import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
3231
import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
3332

@@ -42,7 +41,7 @@
4241
* @author Bob Briody (http://bobbriody.com)
4342
* @author Marko A. Rodriguez (http://markorodriguez.com)
4443
*/
45-
public final class RangeGlobalStep<S> extends FilterStep<S> implements Ranging, Bypassing, Barrier<TraverserSet<S>> {
44+
public final class RangeGlobalStep<S> extends FilterStep<S> implements Ranging, Bypassing, FilteringBarrier<TraverserSet<S>> {
4645

4746
private long low;
4847
private final long high;
@@ -155,6 +154,11 @@ public void processAllStarts() {
155154

156155
}
157156

157+
@Override
158+
public TraverserSet<S> getEmptyBarrier() {
159+
return new TraverserSet<>();
160+
}
161+
158162
@Override
159163
public boolean hasNextBarrier() {
160164
return this.starts.hasNext();

0 commit comments

Comments
 (0)