INCREASED_OFFSET_SCALAR_PREDICATES =
- Set.of(Compare.eq, Compare.neq, Compare.lte, Compare.gt, new NotP.NotPBiPredicate(Compare.eq), new NotP.NotPBiPredicate(Compare.neq), new NotP.NotPBiPredicate(Compare.lt), new NotP.NotPBiPredicate(Compare.gt));
+ Set.of(Compare.eq, Compare.neq, Compare.lte, Compare.gt, new NotP.NotPBiPredicate(Compare.eq), new NotP.NotPBiPredicate(Compare.neq), new NotP.NotPBiPredicate(Compare.lt), new NotP.NotPBiPredicate(Compare.gt), new NotP.NotPBiPredicate(Compare.lte));
private static final CountStrategy INSTANCE = new CountStrategy();
@@ -196,7 +203,13 @@ public void apply(final Traversal.Admin, ?> traversal) {
}
}
} else {
- TraversalHelper.insertBeforeStep(new RangeGlobalStep<>(traversal, 0L, highRange < 0 ? 0 : highRange), curr, traversal);
+ final long limit = highRange < 0 ? 0 : highRange;
+ final Long chainLimit;
+ if (isLimitSafe(traversal, isStep, isStepPredicate, limit)) {
+ TraversalHelper.insertBeforeStep(new RangeGlobalStep<>(traversal, 0L, limit), curr, traversal);
+ } else if ((chainLimit = getIsChainLimit(traversal, isStep, limit)) != null) {
+ TraversalHelper.insertBeforeStep(new RangeGlobalStep<>(traversal, 0L, chainLimit), curr, traversal);
+ }
}
i++;
}
@@ -205,6 +218,107 @@ public void apply(final Traversal.Admin, ?> traversal) {
}
}
+ /**
+ * Determines if a {@code limit(n)} can be placed in front of {@code count().is(predicate)} without changing the
+ * result. The limit caps the count at {@code n}, which is enough for the {@link IsStep} to decide, but if the
+ * predicate accepts {@code n} then the capped value passes through in place of the real count. That is only
+ * acceptable when nothing downstream observes the count value (e.g. a trailing {@code is()} in
+ * {@code count().is(gt(1)).is(lt(3))}, the root traversal or a {@code map()} child) and the child is only used as
+ * a filter.
+ */
+ private static boolean isLimitSafe(final Traversal.Admin, ?> traversal, final IsStepContract isStep,
+ final P isStepPredicate, final long limit) {
+ // if the capped count cannot pass the predicate, any count that does pass is exact
+ return !isStepPredicate.test(limit) || !isCountValueObserved(traversal, isStep);
+ }
+
+ /**
+ * Determines if the value emitted by {@code count().is(predicate)} is visible beyond a simple filter decision.
+ */
+ private static boolean isCountValueObserved(final Traversal.Admin, ?> traversal, final IsStepContract isStep) {
+ // a later step can access the count value. this includes the WhereEndStep that where() appends when its
+ // child ends with a label, as that compares the value to the path. labels elsewhere in a filter child can't
+ // be read from outside of it, so they don't matter here.
+ if (!(isStep.getNextStep() instanceof EmptyStep))
+ return true;
+
+ // the repeat() body always ends with a RepeatEndStep so it was already handled above, which leaves until()
+ // and emit() where the result is only used as a condition
+ if (traversal.getParent() instanceof RepeatStep) return false;
+
+ final TraversalParent parent = traversal.getParent();
+ // only parents that use the child traversal purely to test for the existence of a result. other FilterStep
+ // or SideEffectStep parents (e.g. dedup().by() or aggregate().by()) consume the value itself.
+ return !(parent instanceof TraversalFilterStep || parent instanceof NotStep
+ || parent instanceof ConnectiveStep || parent instanceof TraversalSideEffectStep);
+ }
+
+ /**
+ * Determines a limit for {@code count().is(a).is(b)...} that takes every {@code is()} in the chain into account.
+ * Each predicate gives the same result for every count of its limit or more, so with the largest of those limits
+ * the capped count passes the whole chain exactly when the real count does. If the chain then rejects the cap, any
+ * count that passes is exact; otherwise the limit is only safe when the count value is not observed afterwards.
+ *
+ * @return the limit to use or {@code null} if the chain can't be limited safely
+ */
+ private static Long getIsChainLimit(final Traversal.Admin, ?> traversal, final IsStepContract firstIsStep,
+ final long firstLimit) {
+ final List predicates = new ArrayList<>();
+ predicates.add(firstIsStep.getPredicate());
+ final Long firstHighRange = getHighRange(firstIsStep.getPredicate());
+ if (firstHighRange == null) return null;
+ long limit = Math.max(firstLimit, firstHighRange);
+ IsStepContract lastIsStep = firstIsStep;
+ while (lastIsStep.getNextStep() instanceof IsStepContract) {
+ lastIsStep = (IsStepContract) lastIsStep.getNextStep();
+ final P predicate = lastIsStep.getPredicate();
+ if (predicate.hasTraversal()) return null;
+ final Long candidate = getHighRange(predicate);
+ if (candidate == null) return null;
+ limit = Math.max(limit, candidate);
+ predicates.add(predicate);
+ }
+
+ if (predicates.size() == 1) return null;
+
+ final long cap = limit;
+ final boolean capPasses = predicates.stream().allMatch(p -> p.test(cap));
+ return !capPasses || !isCountValueObserved(traversal, lastIsStep) ? cap : null;
+ }
+
+ /**
+ * Gets the smallest count from which {@code predicate} gives the same result for every larger count, or
+ * {@code null} if that can't be determined.
+ */
+ private static Long getHighRange(final P> predicate) {
+ try {
+ if (predicate instanceof ConnectiveP) {
+ long highRange = 0L;
+ for (final P> p : ((ConnectiveP>) predicate).getPredicates()) {
+ final Long candidate = getHighRange(p);
+ if (candidate == null) return null;
+ highRange = Math.max(highRange, candidate);
+ }
+ return highRange;
+ }
+
+ final Object value = predicate.getValue();
+ final BiPredicate biPredicate = predicate.getBiPredicate();
+ if (value instanceof Number) {
+ final long offset = INCREASED_OFFSET_SCALAR_PREDICATES.contains(biPredicate) ? 1L : 0L;
+ return Math.max(0L, (long) Math.ceil(((Number) value).doubleValue()) + offset);
+ }
+ final Long offset = RANGE_PREDICATES.get(biPredicate);
+ if (value instanceof Collection && !((Collection>) value).isEmpty() && offset != null) {
+ final Object high = Collections.max((Collection) value);
+ if (high instanceof Number) return Math.max(0L, ((Number) high).longValue() + offset);
+ }
+ } catch (final ClassCastException ex) {
+ // mixed value types in within()/without() can't be compared, so the chain can't be reasoned about
+ }
+ return null;
+ }
+
/**
* The {@code traversal} argument was marked as one that could be converted to {@code not()}, so wrap it inside
* that step and copy the labels.
@@ -227,6 +341,9 @@ private boolean doStrategy(final Step step) {
final Step parent = step.getTraversal().getParent().asStep();
return (parent instanceof FilterStep || parent.getLabels().isEmpty()) && // if the parent is labeled, then the count matters
!(parent.getNextStep() instanceof MatchStep.MatchEndStep && // if this is in a pattern match, then don't do it.
- ((MatchStep.MatchEndStep) parent.getNextStep()).getMatchKey().isPresent());
+ ((MatchStep.MatchEndStep) parent.getNextStep()).getMatchKey().isPresent()) &&
+ // a traversal in the predicate (e.g. is(lt(__.constant(3))) or is(lte(__.select("a")))) is only
+ // resolved at runtime, so no limit can be derived that is guaranteed not to change the result
+ !((IsStepContract>) step.getNextStep()).getPredicate().hasTraversal();
}
}
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/CountStrategyTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/CountStrategyTest.java
index 77e02634289..63ff1aad94a 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/CountStrategyTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/CountStrategyTest.java
@@ -68,10 +68,14 @@ public static Iterable generateTestParameters() {
{__.count().is(1), __.limit(2).count().is(1)},
{__.count().is(-1), __.limit(0).count().is(-1)}, // -1 for RangeGlobalStep means no upper limit
{__.count().is(-2), __.limit(0).count().is(-2)},
+ // the limit is kept at the root when the capped count can't pass the predicate: a count of 0 fails
+ // is(-2023) and between() excludes its upper bound of 10, so any count that does pass is exact
{__.count().is(-2023), __.limit(0).count().is(-2023)},
{__.count().is(P.between(0, 10)), __.limit(10).count().is(P.between(0, 10))},
{__.count().is(P.between(5, 10)), __.limit(10).count().is(P.between(5, 10))},
- {__.count().is(P.gt(10).or(lt(5))), __.limit(11).count().is(P.gt(10).or(lt(5)))},
+ // limit(11) would cap e.g. 20 at 11, which passes gt(10) and would be returned in place of 20
+ {__.count().is(P.gt(10).or(lt(5))), __.count().is(P.gt(10).or(lt(5)))},
+ {__.where(__.count().is(P.gt(10).or(lt(5)))), __.where(__.limit(11).count().is(P.gt(10).or(lt(5))))},
{__.out().count().is(0), __.out().limit(1).count().is(0)},
{__.outE().count().is(lt(1)), __.outE().limit(1).count().is(lt(1))},
{__.both().count().is(lte(0)), __.both().limit(1).count().is(lte(0))},
@@ -84,15 +88,75 @@ public static Iterable generateTestParameters() {
{__.filter(__.out().out().count().is(0)), __.not(__.out().out())},
{__.local(aggregate("x")).count().is(0).as("a"), __.local(aggregate("x")).limit(1).count().is(0).as("a")},
{__.out().count().as("a").is(0), __.out().limit(1).count().as("a").is(0)},
- {__.out().count().is(neq(4)), __.out().limit(5).count().is(neq(4))},
+ {__.out().count().is(neq(4)), __.out().count().is(neq(4))}, // root: the count value is returned
+ {__.where(__.out().count().is(neq(4))), __.where(__.out().limit(5).count().is(neq(4)))},
+ {__.out().where(__.count().is(neq(4))), __.out().where(__.limit(5).count().is(neq(4)))},
{__.out().count().is(lte(3)), __.out().limit(4).count().is(lte(3))},
{__.out().count().is(lt(3)), __.out().limit(3).count().is(lt(3))},
- {__.out().count().is(gt(2)), __.out().limit(3).count().is(gt(2))},
- {__.out().count().is(gte(2)), __.out().limit(2).count().is(gte(2))},
+ {__.out().count().is(gt(2)), __.out().count().is(gt(2))}, // root: the count value is returned
+ {__.filter(__.out().count().is(gt(2))), __.filter(__.out().limit(3).count().is(gt(2)))},
+ {__.out().count().is(gte(2)), __.out().count().is(gte(2))}, // root: the count value is returned
+ {__.filter(__.out().count().is(gte(2))), __.filter(__.out().limit(2).count().is(gte(2)))},
{__.out().count().is(inside(2, 4)), __.out().limit(4).count().is(inside(2, 4))},
- {__.out().count().is(outside(2, 4)), __.out().limit(5).count().is(outside(2, 4))},
+ {__.out().count().is(outside(2, 4)), __.out().count().is(outside(2, 4))}, // root: the count value is returned
+ {__.where(__.out().count().is(outside(2, 4))), __.where(__.out().limit(5).count().is(outside(2, 4)))},
{__.out().count().is(within(2, 6, 4)), __.out().limit(7).count().is(within(2, 6, 4))},
- {__.out().count().is(without(2, 6, 4)), __.out().limit(6).count().is(without(2, 6, 4))},
+ {__.out().count().is(without(2, 6, 4)), __.out().count().is(without(2, 6, 4))}, // root: limit(7) passes and is returned
+ // without() only gives the same result for every count above its largest value, so the limit is max + 1
+ {__.where(__.out().count().is(without(2, 6, 4))), __.where(__.out().limit(7).count().is(without(2, 6, 4)))},
+ // not(lte(2)) is gt(2), which needs a limit of 3 like gt(2) does
+ {__.out().count().is(P.not(lte(2))), __.out().count().is(P.not(lte(2)))},
+ {__.where(__.out().count().is(P.not(lte(2)))), __.where(__.out().limit(3).count().is(P.not(lte(2))))},
+ // a chain of is() steps is limited by the largest limit of its predicates. if the chain rejects that
+ // cap, any count that passes is exact
+ {__.where(__.out().count().is(gt(1)).is(lt(3))), __.where(__.out().limit(3).count().is(gt(1)).is(lt(3)))},
+ {__.where(__.out().count().is(gte(2)).is(lte(2))), __.where(__.out().limit(3).count().is(gte(2)).is(lte(2)))},
+ {__.filter(__.out().count().is(neq(1)).is(lt(5))), __.filter(__.out().limit(5).count().is(neq(1)).is(lt(5)))},
+ {__.out().count().is(gt(1)).is(lt(3)), __.out().limit(3).count().is(gt(1)).is(lt(3))},
+ {__.map(__.out().count().is(gt(1)).is(lt(3))), __.map(__.out().limit(3).count().is(gt(1)).is(lt(3)))},
+ // if the chain accepts the cap, it can only be limited when the count isn't observed afterwards
+ {__.where(__.out().count().is(gt(1)).is(neq(2))), __.where(__.out().limit(3).count().is(gt(1)).is(neq(2)))},
+ {__.out().count().is(gt(1)).is(neq(2)), __.out().count().is(gt(1)).is(neq(2))},
+ {__.map(__.out().count().is(gt(1)).is(gt(2))), __.map(__.out().count().is(gt(1)).is(gt(2)))},
+ {__.where(__.out().count().is(gt(1)).is(gt(2)).as("a")), __.where(__.out().count().is(gt(1)).is(gt(2)).as("a"))},
+ // a traversal anywhere in the chain means the chain can't be reasoned about
+ {__.where(__.out().count().is(gt(1)).is(lt(__.constant(3L)))), __.where(__.out().count().is(gt(1)).is(lt(__.constant(3L))))},
+ // a capped count that passes is() must not be observed by later steps or the parent
+ // a label at the end of the where() child turns it into a WhereTraversalStep, which compares the
+ // count to the path, so the capped value would be observed
+ {__.where(__.out().count().is(gt(1)).as("a")), __.where(__.out().count().is(gt(1)).as("a"))},
+ // other labels in a filter-only child can't be read outside of it, so the limit is safe
+ {__.where(__.out().count().as("a").is(gt(1))), __.where(__.out().limit(2).count().as("a").is(gt(1)))},
+ {__.filter(__.out().count().as("a").is(gt(1))), __.filter(__.out().limit(2).count().as("a").is(gt(1)))},
+ {__.filter(__.out().count().is(gt(1)).as("a")), __.filter(__.out().limit(2).count().is(gt(1)).as("a"))},
+ {__.repeat(__.out()).until(__.out().count().is(gt(1)).is(lt(3))), __.repeat(__.out()).until(__.out().limit(3).count().is(gt(1)).is(lt(3)))},
+ {__.map(__.out().count().is(gt(1))), __.map(__.out().count().is(gt(1)))},
+ {__.flatMap(__.out().count().is(gte(2))), __.flatMap(__.out().count().is(gte(2)))},
+ {__.local(__.out().count().is(gt(1))), __.local(__.out().count().is(gt(1)))},
+ {__.project("a").by(__.out().count().is(gt(1))), __.project("a").by(__.out().count().is(gt(1)))},
+ {__.dedup().by(__.out().count().is(gt(1))), __.dedup().by(__.out().count().is(gt(1)))},
+ {__.aggregate("x").by(__.out().count().is(gt(1))), __.aggregate("x").by(__.out().count().is(gt(1)))},
+ {__.repeat(__.out().count().is(gt(1))).times(1), __.repeat(__.out().count().is(gt(1))).times(1)},
+ // traversal-valued predicates are resolved at runtime, so no safe limit can be derived, even in
+ // a filter-only child and even when the traversal reads a label on count()
+ {__.filter(__.out().count().is(gt(1).and(lt(__.constant(3L))))), __.filter(__.out().count().is(gt(1).and(lt(__.constant(3L)))))},
+ {__.filter(__.out().count().as("a").is(gt(1).and(lte(__.select("a"))))), __.filter(__.out().count().as("a").is(gt(1).and(lte(__.select("a")))))},
+ {__.out().count().is(gt(1).and(lte(__.constant(5L)))), __.out().count().is(gt(1).and(lte(__.constant(5L))))},
+ {__.filter(__.out().count().is(lt(__.constant(1L)))), __.filter(__.out().count().is(lt(__.constant(1L))))},
+ {__.filter(__.out().count().is(P.not(gt(__.constant(1L))))), __.filter(__.out().count().is(P.not(gt(__.constant(1L)))))},
+ // still optimized: only exact counts below the limit can pass the first is()
+ {__.where(__.out().count().is(lt(3)).is(gt(1))), __.where(__.out().limit(3).count().is(lt(3)).is(gt(1)))},
+ {__.out().count().is(lt(3)).is(gt(1)), __.out().limit(3).count().is(lt(3)).is(gt(1))},
+ {__.map(__.out().count().is(lte(3))), __.map(__.out().limit(4).count().is(lte(3)))},
+ // still optimized: the parent only checks whether anything passed
+ {__.where(__.out().count().is(gt(2))), __.where(__.out().limit(3).count().is(gt(2)))},
+ {__.not(__.out().count().is(gte(2))), __.not(__.out().limit(2).count().is(gte(2)))},
+ {__.sideEffect(__.out().count().is(gt(2))), __.sideEffect(__.out().limit(3).count().is(gt(2)))},
+ {__.repeat(__.out()).until(__.out().count().is(gt(2))), __.repeat(__.out()).until(__.out().limit(3).count().is(gt(2)))},
+ {__.repeat(__.out()).emit(__.out().count().is(gte(2))), __.repeat(__.out()).emit(__.out().limit(2).count().is(gte(2)))},
+ {__.out().until(__.out().count().is(gt(2))).repeat(__.out()), __.out().until(__.out().limit(3).count().is(gt(2))).repeat(__.out())},
+ {__.and(__.out().count().is(gt(1)), __.has("name")), __.and(__.out().limit(2).count().is(gt(1)), __.has("name"))},
+ {__.or(__.out().count().is(gte(3)), __.has("name")), __.or(__.out().limit(3).count().is(gte(3)), __.has("name"))},
{__.map(__.count().is(0)), __.map(__.limit(1).count().is(0))},
{__.flatMap(__.count().is(0)), __.flatMap(__.limit(1).count().is(0))},
{__.flatMap(__.count().is(0)).as("a"), __.flatMap(__.count().is(0)).as("a")}, // no change
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/LazyBarrierStrategyTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/LazyBarrierStrategyTest.java
index 032c7e29f50..fd6134729e1 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/LazyBarrierStrategyTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/LazyBarrierStrategyTest.java
@@ -78,7 +78,8 @@ public static Iterable generateTestParameters() {
{__.out().out().out().count(), __.out().out().barrier(LAZY_SIZE).out().count(), Collections.emptyList()},
{__.out().out().out().out().count(), __.out().out().barrier(LAZY_SIZE).out().barrier(LAZY_SIZE).out().count(), Collections.emptyList()},
{__.out().out().out().count(), __.out().out().barrier(LAZY_SIZE).outE().count(), Arrays.asList(CountStrategy.instance(), AdjacentToIncidentStrategy.instance(), GValueReductionStrategy.instance())},
- {__.out().out().out().count().is(P.gt(10)), __.out().out().barrier(LAZY_SIZE).outE().limit(11).count().is(P.gt(10)), Arrays.asList(CountStrategy.instance(), AdjacentToIncidentStrategy.instance(), GValueReductionStrategy.instance())},
+ {__.out().out().out().count().is(P.gt(10)), __.out().out().barrier(LAZY_SIZE).outE().count().is(P.gt(10)), Arrays.asList(CountStrategy.instance(), AdjacentToIncidentStrategy.instance(), GValueReductionStrategy.instance())},
+ {__.where(__.out().out().out().count().is(P.gt(10))), __.where(__.out().out().barrier(LAZY_SIZE).outE().limit(11).count().is(P.gt(10))), Arrays.asList(CountStrategy.instance(), AdjacentToIncidentStrategy.instance(), GValueReductionStrategy.instance())},
{__.outE().inV().outE().inV().outE().inV().groupCount(), __.outE().inV().outE().inV().barrier(LAZY_SIZE).outE().inV().groupCount(), Collections.emptyList()},
{__.outE().inV().outE().inV().outE().inV().groupCount(), __.out().out().barrier(LAZY_SIZE).out().groupCount(), List.of(IncidentToAdjacentStrategy.instance(), GValueReductionStrategy.instance())},
{__.out().out().has("age", 32).out().count(), __.out().out().barrier(LAZY_SIZE).has("age", 32).out().count(), Collections.emptyList()},
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/util/TraversalExplanationTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/util/TraversalExplanationTest.java
index e911fb72996..e3c7f0ca865 100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/util/TraversalExplanationTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/util/TraversalExplanationTest.java
@@ -108,7 +108,9 @@ public void shouldApplyStrategiesCorrectly() {
traversal.setStrategies(TraversalStrategies.GlobalCache.getStrategies(Graph.class).clone());
checkTraversalExplanation(traversal.explain(), List.of(new ExplainExpectation(AdjacentToIncidentStrategy.class, "[VertexStepPlaceholder(IN,edge)")));
///
- traversal = __.outE().inV().group().by(__.inE().outV().groupCount().by(__.both().count().is(P.gt(2)))).asAdmin();
+ // lt(3) so that CountStrategy can safely limit: the count is a by() key, so a predicate that accepts the cap
+ // (e.g. gt(2)) must be left unlimited
+ traversal = __.outE().inV().group().by(__.inE().outV().groupCount().by(__.both().count().is(P.lt(3)))).asAdmin();
traversal.setStrategies(TraversalStrategies.GlobalCache.getStrategies(Graph.class).clone().removeStrategies(ProductiveByStrategy.class));
checkTraversalExplanation(traversal.explain(), List.of(new ExplainExpectation(IncidentToAdjacentStrategy.class, "[VertexStepPlaceholder(IN,vertex)"),
new ExplainExpectation(IncidentToAdjacentStrategy.class, "[VertexStepPlaceholder(OUT,vertex)"),
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index 26d7bfdc7e3..299660d46c3 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -826,6 +826,14 @@ private static IDictionary, ITraversal>> {(g,p) =>g.WithoutStrategies(typeof(ConnectiveStrategy)).V().Has("name", "marko").Or().Where(__.In("knows").Has("name", "marko"))}},
{"g_withStrategiesXCountStrategyX_V_whereXoutE_count_isX0XX", new List, ITraversal>> {(g,p) =>g.WithStrategies(new CountStrategy()).V().Where(__.OutE().Count().Is(0))}},
{"g_withoutStrategiesXCountStrategyX_V_whereXoutE_count_isX0XX", new List, ITraversal>> {(g,p) =>g.WithoutStrategies(typeof(CountStrategy)).V().Where(__.OutE().Count().Is(0))}},
+ {"g_withStrategiesXCountStrategyX_V_whereXoutE_count_isXgtX1XX_isXltX3XXX", new List, ITraversal>> {(g,p) =>g.WithStrategies(new CountStrategy()).V().Where(__.OutE().Count().Is(P.Gt(1)).Is(P.Lt(3)))}},
+ {"g_withStrategiesXCountStrategyX_V_whereXoutE_count_isXltX3XX_isXgtX1XXX", new List, ITraversal>> {(g,p) =>g.WithStrategies(new CountStrategy()).V().Where(__.OutE().Count().Is(P.Lt(3)).Is(P.Gt(1)))}},
+ {"g_withStrategiesXCountStrategyX_V_outE_count_isXgtX2XX", new List, ITraversal>> {(g,p) =>g.WithStrategies(new CountStrategy()).V().OutE().Count().Is(P.Gt(2))}},
+ {"g_withStrategiesXCountStrategyX_V_mapXoutE_count_isXgtX1XXX", new List, ITraversal>> {(g,p) =>g.WithStrategies(new CountStrategy()).V().Map(__.OutE().Count().Is(P.Gt(1)))}},
+ {"g_withStrategiesXCountStrategyX_V_filterXoutE_count_isXgtX1X_andXltXconstantX3XXXXX_name", new List, ITraversal>> {(g,p) =>g.WithStrategies(new CountStrategy()).V().Filter(__.OutE().Count().Is(P.Gt(1).And(P.Lt(__.Constant(3l))))).Values("name")}},
+ {"g_withStrategiesXCountStrategyX_V_outE_count_asXaX_isXgtX1X_andXlteXselectXaXXXX", new List, ITraversal>> {(g,p) =>g.WithStrategies(new CountStrategy()).V().OutE().Count().As("a").Is(P.Gt(1).And(P.Lte(__.Select("a"))))}},
+ {"g_withStrategiesXCountStrategyX_V_unionXidentity_hasXname_markoXX_count_isXwithoutX2_6_4XX", new List, ITraversal>> {(g,p) =>g.WithStrategies(new CountStrategy()).V().Union(__.Identity(), __.Has("name", "marko")).Count().Is(P.Without(2, 6, 4))}},
+ {"g_withStrategiesXCountStrategyX_V_count_isXnotXlteX2XXX", new List, ITraversal>> {(g,p) =>g.WithStrategies(new CountStrategy()).V().Count().Is(P.Not(P.Lte(2)))}},
{"g_withStrategiesXEarlyLimitStrategyX_V_out_order_byXnameX_valueMap_limitX3X_selectXnameX", new List, ITraversal>> {(g,p) =>g.WithStrategies(new EarlyLimitStrategy()).V().Out().Order().By("name").ValueMap().Limit(3).Select("name")}},
{"g_withoutStrategiesXEarlyLimitStrategyX_V_out_order_byXnameX_valueMap_limitX3X_selectXnameX", new List, ITraversal>> {(g,p) =>g.WithoutStrategies(typeof(EarlyLimitStrategy)).V().Out().Order().By("name").ValueMap().Limit(3).Select("name")}},
{"g_withStrategiesXEdgeLabelVerificationStrategyXthrowException_true_logWarning_falseXX_V", new List, ITraversal>> {(g,p) =>g.WithStrategies(new EdgeLabelVerificationStrategy(throwException: true, logWarning: false)).V().Out()}},
@@ -3185,6 +3193,14 @@ private static IDictionary, ITraversal>> {(g,p) =>g.WithoutStrategies(typeof(ConnectiveStrategy)).V().Has("name", "marko").Or().Where(__.In("knows").Has("name", "marko"))}},
{"g_withStrategiesXCountStrategyX_V_whereXoutE_count_isX0XX", new List, ITraversal>> {(g,p) =>g.WithStrategies(new CountStrategy()).V().Where(__.OutE().Count().Is(0))}},
{"g_withoutStrategiesXCountStrategyX_V_whereXoutE_count_isX0XX", new List, ITraversal>> {(g,p) =>g.WithoutStrategies(typeof(CountStrategy)).V().Where(__.OutE().Count().Is(0))}},
+ {"g_withStrategiesXCountStrategyX_V_whereXoutE_count_isXgtX1XX_isXltX3XXX", new List, ITraversal>> {(g,p) =>g.WithStrategies(new CountStrategy()).V().Where(__.OutE().Count().Is(P.Gt(1)).Is(P.Lt(3)))}},
+ {"g_withStrategiesXCountStrategyX_V_whereXoutE_count_isXltX3XX_isXgtX1XXX", new List, ITraversal>> {(g,p) =>g.WithStrategies(new CountStrategy()).V().Where(__.OutE().Count().Is(P.Lt(3)).Is(P.Gt(1)))}},
+ {"g_withStrategiesXCountStrategyX_V_outE_count_isXgtX2XX", new List, ITraversal>> {(g,p) =>g.WithStrategies(new CountStrategy()).V().OutE().Count().Is(P.Gt(2))}},
+ {"g_withStrategiesXCountStrategyX_V_mapXoutE_count_isXgtX1XXX", new List, ITraversal>> {(g,p) =>g.WithStrategies(new CountStrategy()).V().Map(__.OutE().Count().Is(P.Gt(1)))}},
+ {"g_withStrategiesXCountStrategyX_V_filterXoutE_count_isXgtX1X_andXltXconstantX3XXXXX_name", new List, ITraversal>> {(g,p) =>g.WithStrategies(new CountStrategy()).V().Filter(__.OutE().Count().Is(P.Gt(1).And(P.Lt(__.Constant(3l))))).Values("name")}},
+ {"g_withStrategiesXCountStrategyX_V_outE_count_asXaX_isXgtX1X_andXlteXselectXaXXXX", new List, ITraversal>> {(g,p) =>g.WithStrategies(new CountStrategy()).V().OutE().Count().As("a").Is(P.Gt(1).And(P.Lte(__.Select("a"))))}},
+ {"g_withStrategiesXCountStrategyX_V_unionXidentity_hasXname_markoXX_count_isXwithoutX2_6_4XX", new List, ITraversal>> {(g,p) =>g.WithStrategies(new CountStrategy()).V().Union(__.Identity(), __.Has("name", "marko")).Count().Is(P.Without(2, 6, 4))}},
+ {"g_withStrategiesXCountStrategyX_V_count_isXnotXlteX2XXX", new List, ITraversal>> {(g,p) =>g.WithStrategies(new CountStrategy()).V().Count().Is(P.Not(P.Lte(2)))}},
{"g_withStrategiesXEarlyLimitStrategyX_V_out_order_byXnameX_valueMap_limitX3X_selectXnameX", new List, ITraversal>> {(g,p) =>g.WithStrategies(new EarlyLimitStrategy()).V().Out().Order().By("name").ValueMap().Limit(3).Select("name")}},
{"g_withoutStrategiesXEarlyLimitStrategyX_V_out_order_byXnameX_valueMap_limitX3X_selectXnameX", new List, ITraversal>> {(g,p) =>g.WithoutStrategies(typeof(EarlyLimitStrategy)).V().Out().Order().By("name").ValueMap().Limit(3).Select("name")}},
{"g_withStrategiesXEdgeLabelVerificationStrategyXthrowException_true_logWarning_falseXX_V", new List, ITraversal>> {(g,p) =>g.WithStrategies(new EdgeLabelVerificationStrategy(throwException: true, logWarning: false)).V().Out()}},
diff --git a/gremlin-go/driver/cucumber/gremlin.go b/gremlin-go/driver/cucumber/gremlin.go
index 023693f603f..15dc7f4fe62 100644
--- a/gremlin-go/driver/cucumber/gremlin.go
+++ b/gremlin-go/driver/cucumber/gremlin.go
@@ -779,6 +779,14 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[
"g_withoutStrategiesXConnectiveStrategyX_V_hasXname_markoX_or_whereXinXknowsX_hasXname_markoXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.WithoutStrategies(gremlingo.ConnectiveStrategy()).V().Has("name", "marko").Or().Where(gremlingo.T__.In("knows").Has("name", "marko"))}},
"g_withStrategiesXCountStrategyX_V_whereXoutE_count_isX0XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.WithStrategies(gremlingo.CountStrategy()).V().Where(gremlingo.T__.OutE().Count().Is(0))}},
"g_withoutStrategiesXCountStrategyX_V_whereXoutE_count_isX0XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.WithoutStrategies(gremlingo.CountStrategy()).V().Where(gremlingo.T__.OutE().Count().Is(0))}},
+ "g_withStrategiesXCountStrategyX_V_whereXoutE_count_isXgtX1XX_isXltX3XXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.WithStrategies(gremlingo.CountStrategy()).V().Where(gremlingo.T__.OutE().Count().Is(gremlingo.P.Gt(1)).Is(gremlingo.P.Lt(3)))}},
+ "g_withStrategiesXCountStrategyX_V_whereXoutE_count_isXltX3XX_isXgtX1XXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.WithStrategies(gremlingo.CountStrategy()).V().Where(gremlingo.T__.OutE().Count().Is(gremlingo.P.Lt(3)).Is(gremlingo.P.Gt(1)))}},
+ "g_withStrategiesXCountStrategyX_V_outE_count_isXgtX2XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.WithStrategies(gremlingo.CountStrategy()).V().OutE().Count().Is(gremlingo.P.Gt(2))}},
+ "g_withStrategiesXCountStrategyX_V_mapXoutE_count_isXgtX1XXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.WithStrategies(gremlingo.CountStrategy()).V().Map(gremlingo.T__.OutE().Count().Is(gremlingo.P.Gt(1)))}},
+ "g_withStrategiesXCountStrategyX_V_filterXoutE_count_isXgtX1X_andXltXconstantX3XXXXX_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.WithStrategies(gremlingo.CountStrategy()).V().Filter(gremlingo.T__.OutE().Count().Is(gremlingo.P.Gt(1).And(gremlingo.P.Lt(gremlingo.T__.Constant(int64(3)))))).Values("name")}},
+ "g_withStrategiesXCountStrategyX_V_outE_count_asXaX_isXgtX1X_andXlteXselectXaXXXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.WithStrategies(gremlingo.CountStrategy()).V().OutE().Count().As("a").Is(gremlingo.P.Gt(1).And(gremlingo.P.Lte(gremlingo.T__.Select("a"))))}},
+ "g_withStrategiesXCountStrategyX_V_unionXidentity_hasXname_markoXX_count_isXwithoutX2_6_4XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.WithStrategies(gremlingo.CountStrategy()).V().Union(gremlingo.T__.Identity(), gremlingo.T__.Has("name", "marko")).Count().Is(gremlingo.P.Without(2, 6, 4))}},
+ "g_withStrategiesXCountStrategyX_V_count_isXnotXlteX2XXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.WithStrategies(gremlingo.CountStrategy()).V().Count().Is(gremlingo.P.Not(gremlingo.P.Lte(2)))}},
"g_withStrategiesXEarlyLimitStrategyX_V_out_order_byXnameX_valueMap_limitX3X_selectXnameX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.WithStrategies(gremlingo.EarlyLimitStrategy()).V().Out().Order().By("name").ValueMap().Limit(3).Select("name")}},
"g_withoutStrategiesXEarlyLimitStrategyX_V_out_order_byXnameX_valueMap_limitX3X_selectXnameX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.WithoutStrategies(gremlingo.EarlyLimitStrategy()).V().Out().Order().By("name").ValueMap().Limit(3).Select("name")}},
"g_withStrategiesXEdgeLabelVerificationStrategyXthrowException_true_logWarning_falseXX_V": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.WithStrategies(gremlingo.EdgeLabelVerificationStrategy(gremlingo.EdgeLabelVerificationStrategyConfig{ThrowException: true, LogWarning: false})).V().Out()}},
diff --git a/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js b/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js
index 961dd41f264..1f1d23a691d 100644
--- a/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js
+++ b/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js
@@ -810,6 +810,14 @@ const gremlins = {
g_withoutStrategiesXConnectiveStrategyX_V_hasXname_markoX_or_whereXinXknowsX_hasXname_markoXX: [function({g}) { return g.withoutStrategies(ConnectiveStrategy).V().has("name", "marko").or().where(__.in_("knows").has("name", "marko")) }],
g_withStrategiesXCountStrategyX_V_whereXoutE_count_isX0XX: [function({g}) { return g.withStrategies(new CountStrategy()).V().where(__.outE().count().is(0)) }],
g_withoutStrategiesXCountStrategyX_V_whereXoutE_count_isX0XX: [function({g}) { return g.withoutStrategies(CountStrategy).V().where(__.outE().count().is(0)) }],
+ g_withStrategiesXCountStrategyX_V_whereXoutE_count_isXgtX1XX_isXltX3XXX: [function({g}) { return g.withStrategies(new CountStrategy()).V().where(__.outE().count().is(P.gt(1)).is(P.lt(3))) }],
+ g_withStrategiesXCountStrategyX_V_whereXoutE_count_isXltX3XX_isXgtX1XXX: [function({g}) { return g.withStrategies(new CountStrategy()).V().where(__.outE().count().is(P.lt(3)).is(P.gt(1))) }],
+ g_withStrategiesXCountStrategyX_V_outE_count_isXgtX2XX: [function({g}) { return g.withStrategies(new CountStrategy()).V().outE().count().is(P.gt(2)) }],
+ g_withStrategiesXCountStrategyX_V_mapXoutE_count_isXgtX1XXX: [function({g}) { return g.withStrategies(new CountStrategy()).V().map(__.outE().count().is(P.gt(1))) }],
+ g_withStrategiesXCountStrategyX_V_filterXoutE_count_isXgtX1X_andXltXconstantX3XXXXX_name: [function({g}) { return g.withStrategies(new CountStrategy()).V().filter(__.outE().count().is(P.gt(1).and(P.lt(__.constant(3))))).values("name") }],
+ g_withStrategiesXCountStrategyX_V_outE_count_asXaX_isXgtX1X_andXlteXselectXaXXXX: [function({g}) { return g.withStrategies(new CountStrategy()).V().outE().count().as("a").is(P.gt(1).and(P.lte(__.select("a")))) }],
+ g_withStrategiesXCountStrategyX_V_unionXidentity_hasXname_markoXX_count_isXwithoutX2_6_4XX: [function({g}) { return g.withStrategies(new CountStrategy()).V().union(__.identity(), __.has("name", "marko")).count().is(P.without(2, 6, 4)) }],
+ g_withStrategiesXCountStrategyX_V_count_isXnotXlteX2XXX: [function({g}) { return g.withStrategies(new CountStrategy()).V().count().is(P.not(P.lte(2))) }],
g_withStrategiesXEarlyLimitStrategyX_V_out_order_byXnameX_valueMap_limitX3X_selectXnameX: [function({g}) { return g.withStrategies(new EarlyLimitStrategy()).V().out().order().by("name").valueMap().limit(3).select("name") }],
g_withoutStrategiesXEarlyLimitStrategyX_V_out_order_byXnameX_valueMap_limitX3X_selectXnameX: [function({g}) { return g.withoutStrategies(EarlyLimitStrategy).V().out().order().by("name").valueMap().limit(3).select("name") }],
g_withStrategiesXEdgeLabelVerificationStrategyXthrowException_true_logWarning_falseXX_V: [function({g}) { return g.withStrategies(new EdgeLabelVerificationStrategy({throwException: true, logWarning: false})).V().out() }],
diff --git a/gremlin-python/src/main/python/tests/feature/gremlin.py b/gremlin-python/src/main/python/tests/feature/gremlin.py
index 3a00f1ddda9..dc5cb815185 100644
--- a/gremlin-python/src/main/python/tests/feature/gremlin.py
+++ b/gremlin-python/src/main/python/tests/feature/gremlin.py
@@ -784,6 +784,14 @@
'g_withoutStrategiesXConnectiveStrategyX_V_hasXname_markoX_or_whereXinXknowsX_hasXname_markoXX': [(lambda g:g.without_strategies(ConnectiveStrategy).V().has('name', 'marko').or_().where(__.in_('knows').has('name', 'marko')))],
'g_withStrategiesXCountStrategyX_V_whereXoutE_count_isX0XX': [(lambda g:g.with_strategies(CountStrategy()).V().where(__.out_e().count().is_(0)))],
'g_withoutStrategiesXCountStrategyX_V_whereXoutE_count_isX0XX': [(lambda g:g.without_strategies(CountStrategy).V().where(__.out_e().count().is_(0)))],
+ 'g_withStrategiesXCountStrategyX_V_whereXoutE_count_isXgtX1XX_isXltX3XXX': [(lambda g:g.with_strategies(CountStrategy()).V().where(__.out_e().count().is_(P.gt(1)).is_(P.lt(3))))],
+ 'g_withStrategiesXCountStrategyX_V_whereXoutE_count_isXltX3XX_isXgtX1XXX': [(lambda g:g.with_strategies(CountStrategy()).V().where(__.out_e().count().is_(P.lt(3)).is_(P.gt(1))))],
+ 'g_withStrategiesXCountStrategyX_V_outE_count_isXgtX2XX': [(lambda g:g.with_strategies(CountStrategy()).V().out_e().count().is_(P.gt(2)))],
+ 'g_withStrategiesXCountStrategyX_V_mapXoutE_count_isXgtX1XXX': [(lambda g:g.with_strategies(CountStrategy()).V().map(__.out_e().count().is_(P.gt(1))))],
+ 'g_withStrategiesXCountStrategyX_V_filterXoutE_count_isXgtX1X_andXltXconstantX3XXXXX_name': [(lambda g:g.with_strategies(CountStrategy()).V().filter_(__.out_e().count().is_(P.gt(1).and_(P.lt(__.constant(long(3)))))).values('name'))],
+ 'g_withStrategiesXCountStrategyX_V_outE_count_asXaX_isXgtX1X_andXlteXselectXaXXXX': [(lambda g:g.with_strategies(CountStrategy()).V().out_e().count().as_('a').is_(P.gt(1).and_(P.lte(__.select('a')))))],
+ 'g_withStrategiesXCountStrategyX_V_unionXidentity_hasXname_markoXX_count_isXwithoutX2_6_4XX': [(lambda g:g.with_strategies(CountStrategy()).V().union(__.identity(), __.has('name', 'marko')).count().is_(P.without(2, 6, 4)))],
+ 'g_withStrategiesXCountStrategyX_V_count_isXnotXlteX2XXX': [(lambda g:g.with_strategies(CountStrategy()).V().count().is_(P.not_(P.lte(2))))],
'g_withStrategiesXEarlyLimitStrategyX_V_out_order_byXnameX_valueMap_limitX3X_selectXnameX': [(lambda g:g.with_strategies(EarlyLimitStrategy()).V().out().order().by('name').value_map().limit(3).select('name'))],
'g_withoutStrategiesXEarlyLimitStrategyX_V_out_order_byXnameX_valueMap_limitX3X_selectXnameX': [(lambda g:g.without_strategies(EarlyLimitStrategy).V().out().order().by('name').value_map().limit(3).select('name'))],
'g_withStrategiesXEdgeLabelVerificationStrategyXthrowException_true_logWarning_falseXX_V': [(lambda g:g.with_strategies(EdgeLabelVerificationStrategy(throw_exception=True, log_warning=False)).V().out())],
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json
index c20718dabe8..ea245920b44 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json
@@ -14000,6 +14000,142 @@
}
]
},
+ {
+ "scenario": "g_withStrategiesXCountStrategyX_V_whereXoutE_count_isXgtX1XX_isXltX3XXX",
+ "traversals": [
+ {
+ "original": "g.withStrategies(CountStrategy).V().where(outE().count().is(gt(1)).is(lt(3)))",
+ "language": "g.withStrategies(CountStrategy).V().where(__.outE().count().is(P.gt(1)).is(P.lt(3)))",
+ "canonical": "g.withStrategies(CountStrategy).V().where(__.outE().count().is(P.gt(1)).is(P.lt(3)))",
+ "anonymized": "g.withStrategies(CountStrategy).V().where(__.outE().count().is(P.gt(number0)).is(P.lt(number1)))",
+ "dotnet": "g.WithStrategies(new CountStrategy()).V().Where(__.OutE().Count().Is(P.Gt(1)).Is(P.Lt(3)))",
+ "go": "g.WithStrategies(gremlingo.CountStrategy()).V().Where(gremlingo.T__.OutE().Count().Is(gremlingo.P.Gt(1)).Is(gremlingo.P.Lt(3)))",
+ "groovy": "g.withStrategies(CountStrategy).V().where(__.outE().count().is(P.gt(1)).is(P.lt(3)))",
+ "java": "g.withStrategies(CountStrategy.instance()).V().where(__.outE().count().is(P.gt(1)).is(P.lt(3)))",
+ "javascript": "g.withStrategies(new CountStrategy()).V().where(__.outE().count().is(P.gt(1)).is(P.lt(3)))",
+ "python": "g.with_strategies(CountStrategy()).V().where(__.out_e().count().is_(P.gt(1)).is_(P.lt(3)))"
+ }
+ ]
+ },
+ {
+ "scenario": "g_withStrategiesXCountStrategyX_V_whereXoutE_count_isXltX3XX_isXgtX1XXX",
+ "traversals": [
+ {
+ "original": "g.withStrategies(CountStrategy).V().where(outE().count().is(lt(3)).is(gt(1)))",
+ "language": "g.withStrategies(CountStrategy).V().where(__.outE().count().is(P.lt(3)).is(P.gt(1)))",
+ "canonical": "g.withStrategies(CountStrategy).V().where(__.outE().count().is(P.lt(3)).is(P.gt(1)))",
+ "anonymized": "g.withStrategies(CountStrategy).V().where(__.outE().count().is(P.lt(number0)).is(P.gt(number1)))",
+ "dotnet": "g.WithStrategies(new CountStrategy()).V().Where(__.OutE().Count().Is(P.Lt(3)).Is(P.Gt(1)))",
+ "go": "g.WithStrategies(gremlingo.CountStrategy()).V().Where(gremlingo.T__.OutE().Count().Is(gremlingo.P.Lt(3)).Is(gremlingo.P.Gt(1)))",
+ "groovy": "g.withStrategies(CountStrategy).V().where(__.outE().count().is(P.lt(3)).is(P.gt(1)))",
+ "java": "g.withStrategies(CountStrategy.instance()).V().where(__.outE().count().is(P.lt(3)).is(P.gt(1)))",
+ "javascript": "g.withStrategies(new CountStrategy()).V().where(__.outE().count().is(P.lt(3)).is(P.gt(1)))",
+ "python": "g.with_strategies(CountStrategy()).V().where(__.out_e().count().is_(P.lt(3)).is_(P.gt(1)))"
+ }
+ ]
+ },
+ {
+ "scenario": "g_withStrategiesXCountStrategyX_V_outE_count_isXgtX2XX",
+ "traversals": [
+ {
+ "original": "g.withStrategies(CountStrategy).V().outE().count().is(gt(2))",
+ "language": "g.withStrategies(CountStrategy).V().outE().count().is(P.gt(2))",
+ "canonical": "g.withStrategies(CountStrategy).V().outE().count().is(P.gt(2))",
+ "anonymized": "g.withStrategies(CountStrategy).V().outE().count().is(P.gt(number0))",
+ "dotnet": "g.WithStrategies(new CountStrategy()).V().OutE().Count().Is(P.Gt(2))",
+ "go": "g.WithStrategies(gremlingo.CountStrategy()).V().OutE().Count().Is(gremlingo.P.Gt(2))",
+ "groovy": "g.withStrategies(CountStrategy).V().outE().count().is(P.gt(2))",
+ "java": "g.withStrategies(CountStrategy.instance()).V().outE().count().is(P.gt(2))",
+ "javascript": "g.withStrategies(new CountStrategy()).V().outE().count().is(P.gt(2))",
+ "python": "g.with_strategies(CountStrategy()).V().out_e().count().is_(P.gt(2))"
+ }
+ ]
+ },
+ {
+ "scenario": "g_withStrategiesXCountStrategyX_V_mapXoutE_count_isXgtX1XXX",
+ "traversals": [
+ {
+ "original": "g.withStrategies(CountStrategy).V().map(outE().count().is(gt(1)))",
+ "language": "g.withStrategies(CountStrategy).V().map(__.outE().count().is(P.gt(1)))",
+ "canonical": "g.withStrategies(CountStrategy).V().map(__.outE().count().is(P.gt(1)))",
+ "anonymized": "g.withStrategies(CountStrategy).V().map(__.outE().count().is(P.gt(number0)))",
+ "dotnet": "g.WithStrategies(new CountStrategy()).V().Map(__.OutE().Count().Is(P.Gt(1)))",
+ "go": "g.WithStrategies(gremlingo.CountStrategy()).V().Map(gremlingo.T__.OutE().Count().Is(gremlingo.P.Gt(1)))",
+ "groovy": "g.withStrategies(CountStrategy).V().map(__.outE().count().is(P.gt(1)))",
+ "java": "g.withStrategies(CountStrategy.instance()).V().map(__.outE().count().is(P.gt(1)))",
+ "javascript": "g.withStrategies(new CountStrategy()).V().map(__.outE().count().is(P.gt(1)))",
+ "python": "g.with_strategies(CountStrategy()).V().map(__.out_e().count().is_(P.gt(1)))"
+ }
+ ]
+ },
+ {
+ "scenario": "g_withStrategiesXCountStrategyX_V_filterXoutE_count_isXgtX1X_andXltXconstantX3XXXXX_name",
+ "traversals": [
+ {
+ "original": "g.withStrategies(CountStrategy).V().filter(outE().count().is(P.gt(1).and(P.lt(__.constant(3l))))).values(\"name\")",
+ "language": "g.withStrategies(CountStrategy).V().filter(__.outE().count().is(P.gt(1).and(P.lt(__.constant(3l))))).values(\"name\")",
+ "canonical": "g.withStrategies(CountStrategy).V().filter(__.outE().count().is(P.gt(1).and(P.lt(__.constant(3l))))).values(\"name\")",
+ "anonymized": "g.withStrategies(CountStrategy).V().filter(__.outE().count().is(P.gt(number0).and(P.lt(__.constant(long0))))).values(string0)",
+ "dotnet": "g.WithStrategies(new CountStrategy()).V().Filter(__.OutE().Count().Is(P.Gt(1).And(P.Lt(__.Constant(3l))))).Values(\"name\")",
+ "go": "g.WithStrategies(gremlingo.CountStrategy()).V().Filter(gremlingo.T__.OutE().Count().Is(gremlingo.P.Gt(1).And(gremlingo.P.Lt(gremlingo.T__.Constant(int64(3)))))).Values(\"name\")",
+ "groovy": "g.withStrategies(CountStrategy).V().filter(__.outE().count().is(P.gt(1).and(P.lt(__.constant(3l))))).values(\"name\")",
+ "java": "g.withStrategies(CountStrategy.instance()).V().filter(__.outE().count().is(P.gt(1).and(P.lt(__.constant(3l))))).values(\"name\")",
+ "javascript": "g.withStrategies(new CountStrategy()).V().filter(__.outE().count().is(P.gt(1).and(P.lt(__.constant(3))))).values(\"name\")",
+ "python": "g.with_strategies(CountStrategy()).V().filter_(__.out_e().count().is_(P.gt(1).and_(P.lt(__.constant(long(3)))))).values('name')"
+ }
+ ]
+ },
+ {
+ "scenario": "g_withStrategiesXCountStrategyX_V_outE_count_asXaX_isXgtX1X_andXlteXselectXaXXXX",
+ "traversals": [
+ {
+ "original": "g.withStrategies(CountStrategy).V().outE().count().as(\"a\").is(P.gt(1).and(P.lte(__.select(\"a\"))))",
+ "language": "g.withStrategies(CountStrategy).V().outE().count().as(\"a\").is(P.gt(1).and(P.lte(__.select(\"a\"))))",
+ "canonical": "g.withStrategies(CountStrategy).V().outE().count().as(\"a\").is(P.gt(1).and(P.lte(__.select(\"a\"))))",
+ "anonymized": "g.withStrategies(CountStrategy).V().outE().count().as(string0).is(P.gt(number0).and(P.lte(__.select(string0))))",
+ "dotnet": "g.WithStrategies(new CountStrategy()).V().OutE().Count().As(\"a\").Is(P.Gt(1).And(P.Lte(__.Select(\"a\"))))",
+ "go": "g.WithStrategies(gremlingo.CountStrategy()).V().OutE().Count().As(\"a\").Is(gremlingo.P.Gt(1).And(gremlingo.P.Lte(gremlingo.T__.Select(\"a\"))))",
+ "groovy": "g.withStrategies(CountStrategy).V().outE().count().as(\"a\").is(P.gt(1).and(P.lte(__.select(\"a\"))))",
+ "java": "g.withStrategies(CountStrategy.instance()).V().outE().count().as(\"a\").is(P.gt(1).and(P.lte(__.select(\"a\"))))",
+ "javascript": "g.withStrategies(new CountStrategy()).V().outE().count().as(\"a\").is(P.gt(1).and(P.lte(__.select(\"a\"))))",
+ "python": "g.with_strategies(CountStrategy()).V().out_e().count().as_('a').is_(P.gt(1).and_(P.lte(__.select('a'))))"
+ }
+ ]
+ },
+ {
+ "scenario": "g_withStrategiesXCountStrategyX_V_unionXidentity_hasXname_markoXX_count_isXwithoutX2_6_4XX",
+ "traversals": [
+ {
+ "original": "g.withStrategies(CountStrategy).V().union(identity(), has(\"name\", \"marko\")).count().is(P.without(2, 6, 4))",
+ "language": "g.withStrategies(CountStrategy).V().union(__.identity(), __.has(\"name\", \"marko\")).count().is(P.without(2, 6, 4))",
+ "canonical": "g.withStrategies(CountStrategy).V().union(__.identity(), __.has(\"name\", \"marko\")).count().is(P.without(2, 6, 4))",
+ "anonymized": "g.withStrategies(CountStrategy).V().union(__.identity(), __.has(string0, string1)).count().is(P.without(number0, number1, number2))",
+ "dotnet": "g.WithStrategies(new CountStrategy()).V().Union(__.Identity(), __.Has(\"name\", \"marko\")).Count().Is(P.Without(2, 6, 4))",
+ "go": "g.WithStrategies(gremlingo.CountStrategy()).V().Union(gremlingo.T__.Identity(), gremlingo.T__.Has(\"name\", \"marko\")).Count().Is(gremlingo.P.Without(2, 6, 4))",
+ "groovy": "g.withStrategies(CountStrategy).V().union(__.identity(), __.has(\"name\", \"marko\")).count().is(P.without(2, 6, 4))",
+ "java": "g.withStrategies(CountStrategy.instance()).V().union(__.identity(), __.has(\"name\", \"marko\")).count().is(P.without(2, 6, 4))",
+ "javascript": "g.withStrategies(new CountStrategy()).V().union(__.identity(), __.has(\"name\", \"marko\")).count().is(P.without(2, 6, 4))",
+ "python": "g.with_strategies(CountStrategy()).V().union(__.identity(), __.has('name', 'marko')).count().is_(P.without(2, 6, 4))"
+ }
+ ]
+ },
+ {
+ "scenario": "g_withStrategiesXCountStrategyX_V_count_isXnotXlteX2XXX",
+ "traversals": [
+ {
+ "original": "g.withStrategies(CountStrategy).V().count().is(P.not(P.lte(2)))",
+ "language": "g.withStrategies(CountStrategy).V().count().is(P.not(P.lte(2)))",
+ "canonical": "g.withStrategies(CountStrategy).V().count().is(P.not(P.lte(2)))",
+ "anonymized": "g.withStrategies(CountStrategy).V().count().is(P.not(P.lte(number0)))",
+ "dotnet": "g.WithStrategies(new CountStrategy()).V().Count().Is(P.Not(P.Lte(2)))",
+ "go": "g.WithStrategies(gremlingo.CountStrategy()).V().Count().Is(gremlingo.P.Not(gremlingo.P.Lte(2)))",
+ "groovy": "g.withStrategies(CountStrategy).V().count().is(P.not(P.lte(2)))",
+ "java": "g.withStrategies(CountStrategy.instance()).V().count().is(P.not(P.lte(2)))",
+ "javascript": "g.withStrategies(new CountStrategy()).V().count().is(P.not(P.lte(2)))",
+ "python": "g.with_strategies(CountStrategy()).V().count().is_(P.not_(P.lte(2)))"
+ }
+ ]
+ },
{
"scenario": "g_withStrategiesXEarlyLimitStrategyX_V_out_order_byXnameX_valueMap_limitX3X_selectXnameX",
"traversals": [
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/integrated/CountStrategy.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/integrated/CountStrategy.feature
index 7df1f3fc2ca..fb268a70ca8 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/integrated/CountStrategy.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/integrated/CountStrategy.feature
@@ -44,3 +44,98 @@ Feature: Step - CountStrategy
| v[vadas] |
| v[lop] |
| v[ripple] |
+
+ # a following is() must see the real count, not the value capped by the limit() CountStrategy injects
+ Scenario: g_withStrategiesXCountStrategyX_V_whereXoutE_count_isXgtX1XX_isXltX3XXX
+ Given the modern graph
+ And the traversal of
+ """
+ g.withStrategies(CountStrategy).V().where(outE().count().is(gt(1)).is(lt(3)))
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | v[josh] |
+
+ # the first is() only passes counts below the limit, so the limit is safe and the count seen downstream is exact
+ Scenario: g_withStrategiesXCountStrategyX_V_whereXoutE_count_isXltX3XX_isXgtX1XXX
+ Given the modern graph
+ And the traversal of
+ """
+ g.withStrategies(CountStrategy).V().where(outE().count().is(lt(3)).is(gt(1)))
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | v[josh] |
+
+ Scenario: g_withStrategiesXCountStrategyX_V_outE_count_isXgtX2XX
+ Given the modern graph
+ And the traversal of
+ """
+ g.withStrategies(CountStrategy).V().outE().count().is(gt(2))
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | d[6].l |
+
+ Scenario: g_withStrategiesXCountStrategyX_V_mapXoutE_count_isXgtX1XXX
+ Given the modern graph
+ And the traversal of
+ """
+ g.withStrategies(CountStrategy).V().map(outE().count().is(gt(1)))
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | d[3].l |
+ | d[2].l |
+
+ # the lt() bound is a traversal, so the strategy can't know it and must not cap the count
+ Scenario: g_withStrategiesXCountStrategyX_V_filterXoutE_count_isXgtX1X_andXltXconstantX3XXXXX_name
+ Given the modern graph
+ And the traversal of
+ """
+ g.withStrategies(CountStrategy).V().filter(outE().count().is(P.gt(1).and(P.lt(__.constant(3l))))).values("name")
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | josh |
+
+ # the predicate traversal reads the label on count(), so it must see the real count
+ Scenario: g_withStrategiesXCountStrategyX_V_outE_count_asXaX_isXgtX1X_andXlteXselectXaXXXX
+ Given the modern graph
+ And the traversal of
+ """
+ g.withStrategies(CountStrategy).V().outE().count().as("a").is(P.gt(1).and(P.lte(__.select("a"))))
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | d[6].l |
+
+ # without() accepts every count above its largest value, so a count of 7 must not be capped at 6
+ Scenario: g_withStrategiesXCountStrategyX_V_unionXidentity_hasXname_markoXX_count_isXwithoutX2_6_4XX
+ Given the modern graph
+ And the traversal of
+ """
+ g.withStrategies(CountStrategy).V().union(identity(), has("name", "marko")).count().is(P.without(2, 6, 4))
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | d[7].l |
+
+ # not(lte(2)) is gt(2), so the count must not be capped at 2
+ Scenario: g_withStrategiesXCountStrategyX_V_count_isXnotXlteX2XXX
+ Given the modern graph
+ And the traversal of
+ """
+ g.withStrategies(CountStrategy).V().count().is(P.not(P.lte(2)))
+ """
+ When iterated to list
+ Then the result should be unordered
+ | result |
+ | d[6].l |