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 57486a4079..b9cdb4fd67 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.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 @@ private static void registerPrivateActions() { "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 @@ private static void genRegisterPrivateActions() { 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 deaa458c23..8fcd76d39b 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.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 @@ static void registerTraversalStrategies(Class clazz) { .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 0000000000..4a78ed2523 --- /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 + 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> visited = + Collections.newSetFromMap(new IdentityHashMap<>()); + markConnectiveLabelSteps(traversal, visited); + } + + @Override + public Set> applyPost() { + return Collections.singleton(InlineFilterStrategy.class); + } + + public static HugeConnectiveLabelStepStrategy instance() { + return INSTANCE; + } + + private static void markConnectiveLabelSteps(Traversal.Admin traversal, + Set> 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 7b68f71778..e6a56027a1 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.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 @@ 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 static void extractHasContainer(HugeGraphStep newStep, 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 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 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 @@ private static boolean followedByMatchStep(Step step) { 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 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 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 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 extractLabelHasContainers( + HugeGraphStep step, HasContainerHolder holder) { + List 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 static void extractHasContainer(HugeVertexStep newStep, 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 static void extractCount(Step newStep, 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 6e31679014..660c2e040c 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 @@ private void initTextRangeSchema(boolean withEdge) { } } + 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 void testTextRangeFilterKeepsEdgeGraphHasStep() { 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 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(__.as("start1") + .and(__.hasLabel("el2")) + .as("m1")) + .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 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 c3208a0b99..c79db5056f 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.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 void testExtractHasContainerKeepsMatchRangeWithoutGraph() { 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 void testExtractHasContainerRemovesSafeVertexHasStep() { 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> 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 @@ private static void replaceStep(Step origin, Step newStep, 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 @@ private static boolean hasStepExists(Traversal.Admin traversal) { } 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; + } }