This is an automated email from the ASF dual-hosted git repository.
imbajin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hugegraph.git
The following commit(s) were added to refs/heads/master by this push:
new 03e6b8e9e fix(core): keep unsafe range filters local (#3068)
03e6b8e9e is described below
commit 03e6b8e9efbd62f1374fc926edef681ef07a1b4d
Author: legendpei <[email protected]>
AuthorDate: Mon Jun 29 12:56:27 2026 +0800
fix(core): keep unsafe range filters local (#3068)
---
.../hugegraph/auth/HugeFactoryAuthProxy.java | 6 +
.../main/java/org/apache/hugegraph/HugeGraph.java | 4 +-
.../optimize/HugeConnectiveLabelStepStrategy.java | 132 ++++++++++++
.../traversal/optimize/TraversalUtil.java | 234 +++++++++++++++++++--
.../hugegraph/core/CountStrategyCoreTest.java | 123 +++++++++++
.../optimize/TraversalUtilOptimizeTest.java | 222 +++++++++++++++++++
6 files changed, 703 insertions(+), 18 deletions(-)
diff --git
a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/auth/HugeFactoryAuthProxy.java
b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/auth/HugeFactoryAuthProxy.java
index 57486a407..b9cdb4fd6 100644
---
a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/auth/HugeFactoryAuthProxy.java
+++
b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/auth/HugeFactoryAuthProxy.java
@@ -54,6 +54,7 @@ import org.apache.hugegraph.task.StandardTaskScheduler;
import org.apache.hugegraph.task.TaskCallable;
import org.apache.hugegraph.task.TaskCallable.SysTaskCallable;
import org.apache.hugegraph.task.TaskManager;
+import org.apache.hugegraph.traversal.optimize.HugeConnectiveLabelStepStrategy;
import org.apache.hugegraph.traversal.optimize.HugeCountStepStrategy;
import org.apache.hugegraph.traversal.optimize.HugeGraphStepStrategy;
import org.apache.hugegraph.traversal.optimize.HugeVertexStepStrategy;
@@ -468,6 +469,10 @@ public final class HugeFactoryAuthProxy {
"createDefaultExecutor",
"lambda$start$0", "start");
Reflection.registerFieldsToFilter(JsonSerializer.class, "LBUF_SIZE",
"INSTANCE");
Reflection.registerMethodsToFilter(JsonSerializer.class,
"writeIterator", "instance");
+
Reflection.registerFieldsToFilter(HugeConnectiveLabelStepStrategy.class,
+ "serialVersionUID", "INSTANCE");
+
Reflection.registerMethodsToFilter(HugeConnectiveLabelStepStrategy.class,
+ "instance");
Reflection.registerFieldsToFilter(HugeVertexStepStrategy.class,
"serialVersionUID",
"INSTANCE");
Reflection.registerMethodsToFilter(HugeVertexStepStrategy.class,
"instance");
@@ -559,6 +564,7 @@ public final class HugeFactoryAuthProxy {
registerPrivateActions(LockManager.class);
registerPrivateActions(ServerReporter.class);
registerPrivateActions(JsonSerializer.class);
+ registerPrivateActions(HugeConnectiveLabelStepStrategy.class);
registerPrivateActions(HugeVertexStepStrategy.class);
registerPrivateActions(HugeGraphStepStrategy.class);
registerPrivateActions(HugeCountStepStrategy.class);
diff --git
a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/HugeGraph.java
b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/HugeGraph.java
index deaa458c2..8fcd76d39 100644
---
a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/HugeGraph.java
+++
b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/HugeGraph.java
@@ -49,6 +49,7 @@ import org.apache.hugegraph.schema.SchemaManager;
import org.apache.hugegraph.schema.VertexLabel;
import org.apache.hugegraph.structure.HugeFeatures;
import org.apache.hugegraph.task.TaskScheduler;
+import org.apache.hugegraph.traversal.optimize.HugeConnectiveLabelStepStrategy;
import org.apache.hugegraph.traversal.optimize.HugeCountStrategy;
import org.apache.hugegraph.traversal.optimize.HugeCountStepStrategy;
import org.apache.hugegraph.traversal.optimize.HugeGraphStepStrategy;
@@ -381,7 +382,8 @@ public interface HugeGraph extends Graph {
.getStrategies(Graph.class)
.clone();
strategies.removeStrategies(CountStrategy.class);
- strategies.addStrategies(HugeCountStrategy.instance(),
+ strategies.addStrategies(HugeConnectiveLabelStepStrategy.instance(),
+ HugeCountStrategy.instance(),
HugeVertexStepStrategy.instance(),
HugeGraphStepStrategy.instance(),
HugeCountStepStrategy.instance(),
diff --git
a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugeConnectiveLabelStepStrategy.java
b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugeConnectiveLabelStepStrategy.java
new file mode 100644
index 000000000..4a78ed252
--- /dev/null
+++
b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugeConnectiveLabelStepStrategy.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hugegraph.traversal.optimize;
+
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Set;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Step;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
+import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
+import org.apache.tinkerpop.gremlin.process.traversal.step.filter.AndStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.filter.OrStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.HasContainer;
+import
org.apache.tinkerpop.gremlin.process.traversal.strategy.AbstractTraversalStrategy;
+import
org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.InlineFilterStrategy;
+import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper;
+
+public final class HugeConnectiveLabelStepStrategy
+ extends
AbstractTraversalStrategy<TraversalStrategy.OptimizationStrategy>
+ implements TraversalStrategy.OptimizationStrategy {
+
+ private static final long serialVersionUID = 2532355470697047377L;
+
+ private static final HugeConnectiveLabelStepStrategy INSTANCE =
+ new HugeConnectiveLabelStepStrategy();
+
+ private HugeConnectiveLabelStepStrategy() {
+ // pass
+ }
+
+ @Override
+ public void apply(Traversal.Admin<?, ?> traversal) {
+ Set<Traversal.Admin<?, ?>> visited =
+ Collections.newSetFromMap(new IdentityHashMap<>());
+ markConnectiveLabelSteps(traversal, visited);
+ }
+
+ @Override
+ public Set<Class<? extends TraversalStrategy.OptimizationStrategy>>
applyPost() {
+ return Collections.singleton(InlineFilterStrategy.class);
+ }
+
+ public static HugeConnectiveLabelStepStrategy instance() {
+ return INSTANCE;
+ }
+
+ private static void markConnectiveLabelSteps(Traversal.Admin<?, ?>
traversal,
+ Set<Traversal.Admin<?, ?>>
visited) {
+ if (!visited.add(traversal)) {
+ return;
+ }
+
+ for (AndStep<?> step : TraversalHelper.getStepsOfClass(AndStep.class,
+ traversal)) {
+ markConnectiveLabelChildren(step, step);
+ }
+ for (OrStep<?> step : TraversalHelper.getStepsOfClass(OrStep.class,
+ traversal)) {
+ markConnectiveLabelChildren(step, step);
+ }
+
+ for (Step<?, ?> step : traversal.getSteps()) {
+ if (!(step instanceof TraversalParent)) {
+ continue;
+ }
+ TraversalParent parent = (TraversalParent) step;
+ for (Traversal.Admin<?, ?> child : parent.getLocalChildren()) {
+ markConnectiveLabelSteps(child, visited);
+ }
+ for (Traversal.Admin<?, ?> child : parent.getGlobalChildren()) {
+ markConnectiveLabelSteps(child, visited);
+ }
+ }
+ }
+
+ private static void markConnectiveLabelChildren(Step<?, ?> step,
+ TraversalParent parent) {
+ if (!(step.getPreviousStep() instanceof HasStep)) {
+ return;
+ }
+ for (Traversal.Admin<?, ?> child : parent.getLocalChildren()) {
+ markPositiveLabelOnlyTraversal(child);
+ }
+ }
+
+ private static void markPositiveLabelOnlyTraversal(
+ Traversal.Admin<?, ?> traversal) {
+ for (Step<?, ?> step : traversal.getSteps()) {
+ if (!(step instanceof HasStep)) {
+ return;
+ }
+ HasStep<?> hasStep = (HasStep<?>) step;
+ if (!hasOnlyPositiveLabelContainers(hasStep)) {
+ return;
+ }
+ }
+
+ for (Step<?, ?> step : traversal.getSteps()) {
+ TraversalUtil.markConnectiveLabelStep(step);
+ }
+ }
+
+ private static boolean hasOnlyPositiveLabelContainers(HasStep<?> step) {
+ if (step.getHasContainers().isEmpty()) {
+ return false;
+ }
+ for (HasContainer has : step.getHasContainers()) {
+ if (!TraversalUtil.isPositiveLabelContainer(has)) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git
a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/TraversalUtil.java
b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/TraversalUtil.java
index 7b68f7177..e6a56027a 100644
---
a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/TraversalUtil.java
+++
b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/TraversalUtil.java
@@ -66,6 +66,7 @@ import
org.apache.tinkerpop.gremlin.process.traversal.step.HasContainerHolder;
import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
import org.apache.tinkerpop.gremlin.process.traversal.step.filter.FilterStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.filter.OrStep;
import
org.apache.tinkerpop.gremlin.process.traversal.step.filter.RangeGlobalStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.CountGlobalStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.GraphStep;
@@ -98,6 +99,9 @@ import com.google.common.collect.ImmutableList;
public final class TraversalUtil {
+ private static final String CONNECTIVE_LABEL_STEP =
+ "~hugegraph.connective-label-step";
+
public static final String P_CALL = "P.";
public static HugeGraph getGraph(Step<?, ?> step) {
@@ -174,26 +178,60 @@ public final class TraversalUtil {
Step<?, ?> nextStep = step.getNextStep();
if (step instanceof HasStep) {
HasContainerHolder holder = (HasContainerHolder) step;
+ boolean connectiveLabelStep =
+ removeConnectiveLabelStep(step);
/*
- * Range/neq predicates before match() may trigger a no-index
- * query after MatchStep reorders filters. Keep known-indexed
- * boolean predicates pushed down, and leave the rest for
- * TinkerPop to evaluate.
+ * Range/neq predicates before match()/connective label filters
+ * may trigger a no-index query after nested filters add
+ * labels. Keep known-indexed predicates pushed down, and leave
+ * the rest for TinkerPop to evaluate.
*/
- if (followedByMatchStep(step) &&
- hasUnusableMatchPredicate(newStep, holder)) {
- List<HasContainer> extracted =
- extractUsableHasContainers(newStep, holder);
- for (HasContainer has : extracted) {
- holder.removeHasContainer(has);
+ boolean followedByMatch = followedByMatchStep(step);
+ Step<?, ?> afterPositiveLabelOrStep = null;
+ if (hasMatchIndexSensitivePredicate(holder)) {
+ afterPositiveLabelOrStep =
+ extractPositiveLabelOnlyOrStep(newStep,
+ traversal,
+ step);
+ if (afterPositiveLabelOrStep != null) {
+ nextStep = afterPositiveLabelOrStep;
}
- if (holder.getHasContainers().isEmpty()) {
- TraversalHelper.copyLabels(step,
step.getPreviousStep(),
- false);
- traversal.removeStep(step);
+ }
+ if (hasUnusableMatchPredicate(newStep, holder)) {
+ List<HasContainer> extracted;
+ if (followedByMatch) {
+ extracted = extractUsableHasContainers(newStep,
holder);
+ } else if (connectiveLabelStep &&
+ hasLabelAfterUnusablePredicate(newStep,
holder)) {
+ extracted = extractLabelHasContainers(newStep, holder);
+ } else {
+ if (afterPositiveLabelOrStep != null) {
+ step = nextStep;
+ continue;
+ }
+ if (hasUnsupportedLabelContainer(holder)) {
+ step = nextStep;
+ continue;
+ }
+ extracted = ImmutableList.of();
+ }
+ if (!extracted.isEmpty()) {
+ for (HasContainer has : extracted) {
+ holder.removeHasContainer(has);
+ }
+ if (holder.getHasContainers().isEmpty()) {
+ TraversalHelper.copyLabels(step,
+ step.getPreviousStep(),
+ false);
+ traversal.removeStep(step);
+ }
+ step = nextStep;
+ continue;
+ }
+ if (followedByMatch) {
+ step = nextStep;
+ continue;
}
- step = nextStep;
- continue;
}
if (extractHasContainers(newStep, holder)) {
TraversalHelper.copyLabels(step, step.getPreviousStep(),
false);
@@ -214,6 +252,167 @@ public final class TraversalUtil {
return next instanceof MatchStep;
}
+ private static Step<?, ?> extractPositiveLabelOnlyOrStep(
+ HugeGraphStep<?, ?> newStep, Traversal.Admin<?, ?> traversal,
+ Step<?, ?> step) {
+ OrStep<?> orStep = positiveLabelOnlyOrStepAfter(step);
+ if (orStep == null) {
+ return null;
+ }
+
+ List<Object> labels = new ArrayList<>();
+ for (Traversal.Admin<?, ?> child : orStep.getLocalChildren()) {
+ if (!collectPositiveLabelValues(child, labels)) {
+ return null;
+ }
+ }
+ if (labels.isEmpty()) {
+ return null;
+ }
+
+ HasContainer has = new HasContainer(T.label.getAccessor(),
+ P.within(labels));
+ if (!GraphStep.processHasContainerIds(newStep, has)) {
+ newStep.addHasContainer(has);
+ }
+
+ Step<?, ?> next = orStep.getNextStep();
+ TraversalHelper.copyLabels(orStep, orStep.getPreviousStep(), false);
+ traversal.removeStep(orStep);
+ return next;
+ }
+
+ private static OrStep<?> positiveLabelOnlyOrStepAfter(Step<?, ?> step) {
+ Step<?, ?> next = step.getNextStep();
+ while (next instanceof NoOpBarrierStep ||
+ next instanceof IdentityStep) {
+ next = next.getNextStep();
+ }
+ if (!(next instanceof OrStep)) {
+ return null;
+ }
+ return (OrStep<?>) next;
+ }
+
+ private static boolean collectPositiveLabelValues(
+ Traversal.Admin<?, ?> traversal, List<Object> labels) {
+ if (traversal.getSteps().size() != 1) {
+ return false;
+ }
+ Step<?, ?> step = traversal.getStartStep();
+ if (!(step instanceof HasStep)) {
+ return false;
+ }
+ HasStep<?> hasStep = (HasStep<?>) step;
+ if (hasStep.getHasContainers().size() != 1) {
+ return false;
+ }
+ HasContainer has = hasStep.getHasContainers().get(0);
+ if (!isPositiveLabelContainer(has)) {
+ return false;
+ }
+ addPositiveLabelValues(has, labels);
+ return true;
+ }
+
+ private static void addPositiveLabelValues(HasContainer has,
+ List<Object> labels) {
+ P<?> predicate = has.getPredicate();
+ BiPredicate<?, ?> bp = predicate.getBiPredicate();
+ if (bp == Compare.eq) {
+ labels.add(predicate.getValue());
+ } else {
+ assert bp == Contains.within;
+ labels.addAll((Collection<?>) predicate.getValue());
+ }
+ }
+
+ private static boolean hasLabelAfterUnusablePredicate(HugeGraphStep<?, ?>
step,
+ HasContainerHolder
holder) {
+ HugeGraph graph = tryGetGraph(step);
+ boolean seenUnusablePredicate = false;
+ for (HasContainer has : holder.getHasContainers()) {
+ if (isPositiveLabelContainer(has)) {
+ return seenUnusablePredicate;
+ }
+ if (hasMatchIndexSensitivePredicate(has) &&
+ (graph == null || !hasUsableMatchIndex(graph, step, has))) {
+ seenUnusablePredicate = true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean hasUnsupportedLabelContainer(
+ HasContainerHolder holder) {
+ for (HasContainer has : holder.getHasContainers()) {
+ if (isLabelContainer(has) && !isPositiveLabelContainer(has)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static void markConnectiveLabelStep(Step<?, ?> step) {
+ step.addLabel(CONNECTIVE_LABEL_STEP);
+ }
+
+ private static boolean removeConnectiveLabelStep(Step<?, ?> step) {
+ boolean hasMarker = step.getLabels().contains(CONNECTIVE_LABEL_STEP);
+ if (hasMarker) {
+ step.removeLabel(CONNECTIVE_LABEL_STEP);
+ }
+ return hasMarker;
+ }
+
+ private static List<HasContainer> extractLabelHasContainers(
+ HugeGraphStep<?, ?> step, HasContainerHolder holder) {
+ List<HasContainer> extracted = new ArrayList<>();
+ for (HasContainer has : holder.getHasContainers()) {
+ if (!isPositiveLabelContainer(has)) {
+ continue;
+ }
+ if (!GraphStep.processHasContainerIds(step, has)) {
+ step.addHasContainer(has);
+ }
+ extracted.add(has);
+ }
+ return extracted;
+ }
+
+ private static boolean isLabelContainer(HasContainer has) {
+ return T.label.getAccessor().equals(has.getKey());
+ }
+
+ static boolean isPositiveLabelContainer(HasContainer has) {
+ if (!isLabelContainer(has)) {
+ return false;
+ }
+
+ P<?> predicate = has.getPredicate();
+ BiPredicate<?, ?> bp = predicate.getBiPredicate();
+ if (bp == Compare.eq) {
+ return true;
+ }
+ if (bp != Contains.within) {
+ return false;
+ }
+
+ Object value = predicate.getValue();
+ return value instanceof Collection &&
+ !((Collection<?>) value).isEmpty();
+ }
+
+ private static boolean hasMatchIndexSensitivePredicate(
+ HasContainerHolder holder) {
+ for (HasContainer has : holder.getHasContainers()) {
+ if (hasMatchIndexSensitivePredicate(has)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private static boolean hasUnusableMatchPredicate(HugeGraphStep<?, ?> step,
HasContainerHolder
holder) {
HugeGraph graph = tryGetGraph(step);
@@ -407,6 +606,7 @@ public final class TraversalUtil {
do {
Step<?, ?> nextStep = step.getNextStep();
if (step instanceof HasStep) {
+ removeConnectiveLabelStep(step);
HasContainerHolder holder = (HasContainerHolder) step;
if (extractHasContainers(newStep, holder)) {
TraversalHelper.copyLabels(step, step.getPreviousStep(),
false);
@@ -552,7 +752,7 @@ public final class TraversalUtil {
holder.setCount();
}
} while (step instanceof CountGlobalStep ||
- step instanceof FilterStep ||
+ (step instanceof FilterStep && !(step instanceof HasStep)) ||
step instanceof IdentityStep ||
step instanceof NoOpBarrierStep);
}
diff --git
a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/CountStrategyCoreTest.java
b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/CountStrategyCoreTest.java
index 6e3167901..660c2e040 100644
---
a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/CountStrategyCoreTest.java
+++
b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/CountStrategyCoreTest.java
@@ -17,6 +17,7 @@
package org.apache.hugegraph.core;
+import org.apache.hugegraph.exception.NoIndexException;
import org.apache.hugegraph.schema.SchemaManager;
import org.apache.hugegraph.testutil.Assert;
import org.apache.hugegraph.traversal.optimize.HugeGraphStep;
@@ -120,6 +121,16 @@ public class CountStrategyCoreTest extends BaseCoreTest {
}
}
+ private void initConnectiveRangeNoIndexSchema() {
+ SchemaManager schema = graph().schema();
+ schema.propertyKey("ep4").asFloat().create();
+ schema.vertexLabel("vl1").create();
+ schema.edgeLabel("el2").properties("ep4")
+ .nullableKeys("ep4").link("vl1", "vl1").create();
+ schema.edgeLabel("el3").properties("ep4")
+ .nullableKeys("ep4").link("vl1", "vl1").create();
+ }
+
@Test
public void testWhereCountLtNegativeIsAlwaysFalse() {
this.initSchema();
@@ -286,6 +297,118 @@ public class CountStrategyCoreTest extends BaseCoreTest {
Assert.assertEquals(direct, viaMatch);
}
+ @Test
+ public void testConnectiveLabelAfterNoIndexRangeMatchesMatchTraversal() {
+ this.initConnectiveRangeNoIndexSchema();
+
+ Vertex v1 = graph().addVertex(T.label, "vl1");
+ Vertex v2 = graph().addVertex(T.label, "vl1");
+ Vertex v3 = graph().addVertex(T.label, "vl1");
+ v1.addEdge("el2", v2, "ep4", 0.1F);
+ v1.addEdge("el2", v3, "ep4", 0.5F);
+ v1.addEdge("el3", v2, "ep4", 0.1F);
+ commitTx();
+
+ Assert.assertEquals(2L, graph().traversal().E()
+ .hasLabel("el2").count().next());
+
+ GraphTraversal<Edge, Long> directTraversal = graph().traversal().E()
+ .has("ep4",
+
P.lt(0.32696354F))
+
.and(__.hasLabel("el2"))
+ .count();
+ HugeGraphStep<?, ?> graphStep = applyAndGetGraphStep(directTraversal);
+ Assert.assertEquals(1, graphStep.getHasContainers().size());
+ Assert.assertEquals(T.label.getAccessor(),
+ graphStep.getHasContainers().get(0).getKey());
+ Assert.assertTrue(hasRemainingHasStep(directTraversal, "ep4"));
+ long direct = directTraversal.next();
+ long viaMatch = graph().traversal().E()
+ .has("ep4", P.lt(0.32696354F))
+ .match(__.<Edge>as("start1")
+ .and(__.hasLabel("el2"))
+ .as("m1"))
+ .<Edge>select("m1").count().next();
+
+ Assert.assertEquals(1L, direct);
+ Assert.assertEquals(direct, viaMatch);
+ }
+
+ @Test
+ public void testOrConnectiveLabelsAfterNoIndexRangeMatchesLabelTraversal()
{
+ this.initConnectiveRangeNoIndexSchema();
+
+ Vertex v1 = graph().addVertex(T.label, "vl1");
+ Vertex v2 = graph().addVertex(T.label, "vl1");
+ Vertex v3 = graph().addVertex(T.label, "vl1");
+ v1.addEdge("el2", v2, "ep4", 0.1F);
+ v1.addEdge("el2", v3, "ep4", 0.5F);
+ v1.addEdge("el3", v2, "ep4", 0.1F);
+ commitTx();
+
+ long count = graph().traversal().E()
+ .has("ep4", P.lt(0.32696354F))
+ .or(__.hasLabel("el2"), __.hasLabel("el3"))
+ .count().next();
+
+ Assert.assertEquals(2L, count);
+ }
+
+ @Test
+ public void testPropertyBeforeLabelNoIndexRangeStillThrows() {
+ this.initConnectiveRangeNoIndexSchema();
+
+ Vertex v1 = graph().addVertex(T.label, "vl1");
+ Vertex v2 = graph().addVertex(T.label, "vl1");
+ v1.addEdge("el2", v2, "ep4", 0.1F);
+ commitTx();
+
+ Assert.assertThrows(NoIndexException.class, () -> {
+ graph().traversal().E()
+ .has("ep4", P.lt(0.32696354F))
+ .hasLabel("el2")
+ .count().next();
+ });
+ }
+
+ @Test
+ public void testNonLabelConnectiveAfterNoIndexRangeStillThrows() {
+ this.initConnectiveRangeNoIndexSchema();
+
+ Vertex v1 = graph().addVertex(T.label, "vl1");
+ Vertex v2 = graph().addVertex(T.label, "vl1");
+ v1.addEdge("el2", v2, "ep4", 0.1F);
+ commitTx();
+
+ Assert.assertThrows(NoIndexException.class, () -> {
+ graph().traversal().E()
+ .has("ep4", P.lt(0.32696354F))
+ .and(__.has("ep4", P.gt(0.0F)))
+ .count().next();
+ });
+ }
+
+ @Test
+ public void testNegativeConnectiveLabelAfterNoIndexRangeStaysLocal() {
+ this.initConnectiveRangeNoIndexSchema();
+
+ Vertex v1 = graph().addVertex(T.label, "vl1");
+ Vertex v2 = graph().addVertex(T.label, "vl1");
+ v1.addEdge("el2", v2, "ep4", 0.1F);
+ commitTx();
+
+ GraphTraversal<Edge, Edge> traversal = graph().traversal().E()
+ .has("ep4",
+ P.lt(0.32696354F))
+
.and(__.hasLabel(P.neq("el2")));
+
+ HugeGraphStep<?, ?> graphStep = applyAndGetGraphStep(traversal);
+ for (HasContainer has : graphStep.getHasContainers()) {
+ Assert.assertNotEquals(T.label.getAccessor(), has.getKey());
+ }
+ Assert.assertTrue(hasRemainingHasStep(traversal,
T.label.getAccessor()));
+ }
+
@Test
public void testMatchWithNoIndexConditionMatchesDirectTraversal() {
this.initMatchNoIndexSchema();
diff --git
a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/traversal/optimize/TraversalUtilOptimizeTest.java
b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/traversal/optimize/TraversalUtilOptimizeTest.java
index c3208a0b9..c79db5056 100644
---
a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/traversal/optimize/TraversalUtilOptimizeTest.java
+++
b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/traversal/optimize/TraversalUtilOptimizeTest.java
@@ -17,6 +17,9 @@
package org.apache.hugegraph.traversal.optimize;
+import java.util.Collections;
+import java.util.Set;
+
import org.apache.hugegraph.HugeGraph;
import org.apache.hugegraph.backend.id.Id;
import org.apache.hugegraph.backend.id.IdGenerator;
@@ -27,13 +30,19 @@ import org.apache.hugegraph.type.define.DataType;
import org.apache.tinkerpop.gremlin.process.traversal.P;
import org.apache.tinkerpop.gremlin.process.traversal.Step;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
+import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
+import org.apache.tinkerpop.gremlin.process.traversal.step.filter.AndStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.filter.OrStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.GraphStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.VertexStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.HasContainer;
+import
org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.InlineFilterStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper;
+import org.apache.tinkerpop.gremlin.structure.T;
import org.apache.tinkerpop.gremlin.structure.Vertex;
import org.junit.Test;
import org.mockito.Mockito;
@@ -140,6 +149,86 @@ public class TraversalUtilOptimizeTest {
Assert.assertTrue(hasStepExists(traversal));
}
+ @Test
+ public void testExtractHasContainerExtractsPositiveLabelOnlyOrStep() {
+ Traversal.Admin<?, ?> traversal = __.V()
+ .has("age", P.gt(18))
+ .or(__.hasLabel("person"),
+
__.hasLabel(P.within("software")))
+ .asAdmin();
+ HugeGraphStep<?, ?> newStep = replaceGraphStep(traversal);
+
+ TraversalUtil.extractHasContainer(newStep, traversal);
+
+ Assert.assertTrue(hasContainer(newStep, T.label.getAccessor()));
+ Assert.assertTrue(hasStepExists(traversal, "age"));
+ Assert.assertFalse(stepExists(traversal, OrStep.class));
+ }
+
+ @Test
+ public void testExtractHasContainerKeepsUnsupportedOrLabelLocal() {
+ Traversal.Admin<?, ?> traversal = __.V()
+ .has("age", P.gt(18))
+ .or(__.hasLabel(P.neq("person")),
+ __.hasLabel("software"))
+ .asAdmin();
+ HugeGraphStep<?, ?> newStep = replaceGraphStep(traversal);
+
+ TraversalUtil.extractHasContainer(newStep, traversal);
+
+ Assert.assertFalse(hasContainer(newStep, T.label.getAccessor()));
+ Assert.assertTrue(hasStepExists(traversal, "age"));
+ Assert.assertTrue(stepExists(traversal, OrStep.class));
+ }
+
+ @Test
+ public void testExtractHasContainerKeepsNonLabelOrLocal() {
+ Traversal.Admin<?, ?> traversal = __.V()
+ .has("age", P.gt(18))
+ .or(__.has("name", "marko"),
+ __.hasLabel("software"))
+ .asAdmin();
+ HugeGraphStep<?, ?> newStep = replaceGraphStep(traversal);
+
+ TraversalUtil.extractHasContainer(newStep, traversal);
+
+ Assert.assertFalse(hasContainer(newStep, T.label.getAccessor()));
+ Assert.assertTrue(hasStepExists(traversal, "age"));
+ Assert.assertTrue(stepExists(traversal, OrStep.class));
+ }
+
+ @Test
+ public void testExtractHasContainerRequiresSingleLabelOnlyOrChild() {
+ Traversal.Admin<?, ?> traversal = __.V()
+ .has("age", P.gt(18))
+ .or(__.hasLabel("person")
+ .has("name", "marko"),
+ __.hasLabel("software"))
+ .asAdmin();
+ HugeGraphStep<?, ?> newStep = replaceGraphStep(traversal);
+
+ TraversalUtil.extractHasContainer(newStep, traversal);
+
+ Assert.assertFalse(hasContainer(newStep, T.label.getAccessor()));
+ Assert.assertTrue(hasStepExists(traversal, "age"));
+ Assert.assertTrue(stepExists(traversal, OrStep.class));
+ }
+
+ @Test
+ public void testExtractHasContainerSkipsOrWhenPredicateIsNotSensitive() {
+ Traversal.Admin<?, ?> traversal = __.V()
+ .has("name", "marko")
+ .or(__.hasLabel("person"),
+ __.hasLabel("software"))
+ .asAdmin();
+ HugeGraphStep<?, ?> newStep = replaceGraphStep(traversal);
+
+ TraversalUtil.extractHasContainer(newStep, traversal);
+
+ Assert.assertFalse(hasContainer(newStep, T.label.getAccessor()));
+ Assert.assertTrue(stepExists(traversal, OrStep.class));
+ }
+
@Test
public void testExtractHasContainerKeepsTextBetweenGraphHasStep() {
HugeGraph graph = Mockito.mock(HugeGraph.class);
@@ -207,6 +296,86 @@ public class TraversalUtilOptimizeTest {
Assert.assertFalse(hasStepExists(traversal));
}
+ @Test
+ public void testIsPositiveLabelContainer() {
+ Assert.assertTrue(TraversalUtil.isPositiveLabelContainer(
+ new HasContainer(T.label.getAccessor(), P.eq("person"))));
+ Assert.assertTrue(TraversalUtil.isPositiveLabelContainer(
+ new HasContainer(T.label.getAccessor(),
+ P.within("person", "software"))));
+
+ Assert.assertFalse(TraversalUtil.isPositiveLabelContainer(
+ new HasContainer("name", P.eq("person"))));
+ Assert.assertFalse(TraversalUtil.isPositiveLabelContainer(
+ new HasContainer(T.label.getAccessor(), P.neq("person"))));
+ Assert.assertFalse(TraversalUtil.isPositiveLabelContainer(
+ new HasContainer(T.label.getAccessor(),
+ P.without("person"))));
+ Assert.assertFalse(TraversalUtil.isPositiveLabelContainer(
+ new HasContainer(T.label.getAccessor(),
+ P.within(Collections.emptyList()))));
+ }
+
+ @Test
+ public void testConnectiveLabelStepStrategyApplyPost() {
+ Set<Class<? extends TraversalStrategy.OptimizationStrategy>> post =
+ HugeConnectiveLabelStepStrategy.instance().applyPost();
+
+ Assert.assertEquals(Collections.singleton(InlineFilterStrategy.class),
+ post);
+ }
+
+ @Test
+ public void testConnectiveLabelStepStrategyMarksAndChildren() {
+ Traversal.Admin<?, ?> traversal = __.V()
+ .has("age", P.gt(18))
+ .and(__.hasLabel("person"))
+ .asAdmin();
+
+ HugeConnectiveLabelStepStrategy.instance().apply(traversal);
+
+ Assert.assertTrue(hasMarkedLocalChild(traversal, AndStep.class));
+ }
+
+ @Test
+ public void testConnectiveLabelStepStrategyMarksOrChildren() {
+ Traversal.Admin<?, ?> traversal = __.V()
+ .has("age", P.gt(18))
+ .or(__.hasLabel("software"),
+ __.hasLabel(P.within("person")))
+ .asAdmin();
+
+ HugeConnectiveLabelStepStrategy.instance().apply(traversal);
+
+ Assert.assertTrue(hasMarkedLocalChild(traversal, OrStep.class));
+ }
+
+ @Test
+ public void testConnectiveLabelStepStrategySkipsWithoutPreviousHasStep() {
+ Traversal.Admin<?, ?> traversal = __.V()
+ .and(__.hasLabel("person"))
+ .asAdmin();
+
+ HugeConnectiveLabelStepStrategy.instance().apply(traversal);
+
+ Assert.assertFalse(hasMarkedLocalChild(traversal, AndStep.class));
+ }
+
+ @Test
+ public void testConnectiveLabelStepStrategySkipsUnsupportedChildren() {
+ Traversal.Admin<?, ?> traversal = __.V()
+ .has("age", P.gt(18))
+ .and(__.has("name", "marko"))
+
.or(__.hasLabel(P.without("person")),
+ __.has("name", "marko"))
+ .asAdmin();
+
+ HugeConnectiveLabelStepStrategy.instance().apply(traversal);
+
+ Assert.assertFalse(hasMarkedLocalChild(traversal, AndStep.class));
+ Assert.assertFalse(hasMarkedLocalChild(traversal, OrStep.class));
+ }
+
private static PropertyKey propertyKey(long id, String name,
DataType dataType) {
Id keyId = IdGenerator.of(id);
@@ -250,6 +419,15 @@ public class TraversalUtilOptimizeTest {
TraversalHelper.replaceStep((Step) origin, (Step) newStep, traversal);
}
+ private static boolean hasContainer(HugeGraphStep<?, ?> step, String key) {
+ for (HasContainer has : step.getHasContainers()) {
+ if (key.equals(has.getKey())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private static boolean hasStepExists(Traversal.Admin<?, ?> traversal) {
for (Step<?, ?> step : traversal.getSteps()) {
if (step instanceof HasStep) {
@@ -258,4 +436,48 @@ public class TraversalUtilOptimizeTest {
}
return false;
}
+
+ private static boolean hasStepExists(Traversal.Admin<?, ?> traversal,
+ String key) {
+ for (Step<?, ?> step : traversal.getSteps()) {
+ if (!(step instanceof HasStep)) {
+ continue;
+ }
+ HasStep<?> hasStep = (HasStep<?>) step;
+ for (HasContainer has : hasStep.getHasContainers()) {
+ if (key.equals(has.getKey())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private static boolean stepExists(Traversal.Admin<?, ?> traversal,
+ Class<?> clazz) {
+ for (Step<?, ?> step : traversal.getSteps()) {
+ if (clazz.isInstance(step)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean hasMarkedLocalChild(Traversal.Admin<?, ?> traversal,
+ Class<?> clazz) {
+ for (Step<?, ?> step : traversal.getSteps()) {
+ if (!clazz.isInstance(step)) {
+ continue;
+ }
+ TraversalParent parent = (TraversalParent) step;
+ for (Traversal.Admin<?, ?> child : parent.getLocalChildren()) {
+ for (Step<?, ?> childStep : child.getSteps()) {
+ if (!childStep.getLabels().isEmpty()) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
}