From e6ff06ea388d13ba634c61a097e31eac110b6f0d Mon Sep 17 00:00:00 2001 From: legendPei <804141866@qq.com> Date: Thu, 21 May 2026 10:15:43 +0800 Subject: [PATCH 01/10] fix(server): avoid extracting text range filters --- .../traversal/optimize/TraversalUtil.java | 41 +++++++++++++++++++ .../hugegraph/core/CountStrategyCoreTest.java | 25 +++++++++++ 2 files changed, 66 insertions(+) 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 142c95620b..ad1f737d24 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 @@ -171,6 +171,10 @@ public static void extractHasContainer(HugeGraphStep newStep, step = step.getNextStep(); if (step instanceof HasStep) { HasContainerHolder holder = (HasContainerHolder) step; + if (!canExtractHasContainers(TraversalUtil.getGraph(newStep), + holder.getHasContainers())) { + break; + } for (HasContainer has : holder.getHasContainers()) { if (!GraphStep.processHasContainerIds(newStep, has)) { newStep.addHasContainer(has); @@ -188,6 +192,10 @@ public static void extractHasContainer(HugeVertexStep newStep, do { if (step instanceof HasStep) { HasContainerHolder holder = (HasContainerHolder) step; + if (!canExtractHasContainers(TraversalUtil.getGraph(newStep), + holder.getHasContainers())) { + break; + } for (HasContainer has : holder.getHasContainers()) { newStep.addHasContainer(has); } @@ -198,6 +206,39 @@ public static void extractHasContainer(HugeVertexStep newStep, } while (step instanceof HasStep || step instanceof NoOpBarrierStep); } + private static boolean canExtractHasContainers(HugeGraph graph, + List hasContainers) { + for (HasContainer has : hasContainers) { + if (!canExtractHasContainer(graph, has)) { + return false; + } + } + return true; + } + + private static boolean canExtractHasContainer(HugeGraph graph, + HasContainer has) { + if (isSysProp(has.getKey())) { + return true; + } + + PropertyKey pkey = graph.propertyKey(has.getKey()); + if (!pkey.dataType().isText()) { + return true; + } + + List> predicates = new ArrayList<>(); + collectPredicates(predicates, ImmutableList.of(has.getPredicate())); + for (P pred : predicates) { + BiPredicate bp = pred.getBiPredicate(); + if (bp == Compare.gt || bp == Compare.gte || + bp == Compare.lt || bp == Compare.lte) { + return false; + } + } + return true; + } + public static void extractOrder(Step newStep, Traversal.Admin traversal) { Step step = newStep; 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 fdfc9d2e44..834727d476 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 @@ -125,4 +125,29 @@ public void testWhereCountGteNegativeDoesNotBuildInvalidRange() { Assert.assertEquals(4L, count); } + + @Test + public void testRepeatAfterTextRangeFilterWithEmptyResult() { + SchemaManager schema = graph().schema(); + schema.propertyKey("vp4").asText().create(); + schema.vertexLabel("vl1").properties("vp4") + .nullableKeys("vp4").create(); + schema.edgeLabel("el2").link("vl1", "vl1").create(); + + Vertex v1 = graph().addVertex(T.label, "vl1", "vp4", "a"); + Vertex v2 = graph().addVertex(T.label, "vl1", "vp4", "b"); + v1.addEdge("el2", v2); + commitTx(); + + long direct = graph().traversal().V().has("vp4", P.lt("")) + .repeat(__.out("el2")).emit().times(1) + .count().next(); + long viaMatch = graph().traversal().V() + .match(__.as("start").has("vp4", P.lt("")) + .out("el2").as("m")) + .select("m").count().next(); + + Assert.assertEquals(0L, direct); + Assert.assertEquals(viaMatch, direct); + } } From 1446b2b1718b80d9d9071044465328a24bf5adab Mon Sep 17 00:00:00 2001 From: legendPei <804141866@qq.com> Date: Thu, 21 May 2026 10:15:43 +0800 Subject: [PATCH 02/10] fix(server): avoid extracting text range filters --- .../traversal/optimize/TraversalUtil.java | 74 ++++++++++++++++--- .../hugegraph/core/CountStrategyCoreTest.java | 25 +++++++ 2 files changed, 89 insertions(+), 10 deletions(-) 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 142c95620b..1b1f3ecfc5 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 @@ -171,13 +171,10 @@ public static void extractHasContainer(HugeGraphStep newStep, step = step.getNextStep(); if (step instanceof HasStep) { HasContainerHolder holder = (HasContainerHolder) step; - for (HasContainer has : holder.getHasContainers()) { - if (!GraphStep.processHasContainerIds(newStep, has)) { - newStep.addHasContainer(has); - } + if (extractHasContainers(newStep, holder)) { + TraversalHelper.copyLabels(step, step.getPreviousStep(), false); + traversal.removeStep(step); } - TraversalHelper.copyLabels(step, step.getPreviousStep(), false); - traversal.removeStep(step); } } while (step instanceof HasStep || step instanceof NoOpBarrierStep); } @@ -188,16 +185,73 @@ public static void extractHasContainer(HugeVertexStep newStep, do { if (step instanceof HasStep) { HasContainerHolder holder = (HasContainerHolder) step; - for (HasContainer has : holder.getHasContainers()) { - newStep.addHasContainer(has); + if (extractHasContainers(newStep, holder)) { + TraversalHelper.copyLabels(step, step.getPreviousStep(), false); + traversal.removeStep(step); } - TraversalHelper.copyLabels(step, step.getPreviousStep(), false); - traversal.removeStep(step); } step = step.getNextStep(); } while (step instanceof HasStep || step instanceof NoOpBarrierStep); } + private static boolean extractHasContainers(HugeGraphStep newStep, + HasContainerHolder holder) { + HugeGraph graph = TraversalUtil.tryGetGraph(newStep); + Iterator iterator = holder.getHasContainers().iterator(); + while (iterator.hasNext()) { + HasContainer has = iterator.next(); + if (!canExtractHasContainer(graph, has)) { + continue; + } + if (!GraphStep.processHasContainerIds(newStep, has)) { + newStep.addHasContainer(has); + } + iterator.remove(); + } + return holder.getHasContainers().isEmpty(); + } + + private static boolean extractHasContainers(HugeVertexStep newStep, + HasContainerHolder holder) { + HugeGraph graph = TraversalUtil.tryGetGraph(newStep); + Iterator iterator = holder.getHasContainers().iterator(); + while (iterator.hasNext()) { + HasContainer has = iterator.next(); + if (!canExtractHasContainer(graph, has)) { + continue; + } + newStep.addHasContainer(has); + iterator.remove(); + } + return holder.getHasContainers().isEmpty(); + } + + private static boolean canExtractHasContainer(HugeGraph graph, + HasContainer has) { + if (isSysProp(has.getKey())) { + return true; + } + if (graph == null) { + return false; + } + + PropertyKey pkey = graph.propertyKey(has.getKey()); + if (!pkey.dataType().isText()) { + return true; + } + + List> predicates = new ArrayList<>(); + collectPredicates(predicates, ImmutableList.of(has.getPredicate())); + for (P pred : predicates) { + BiPredicate bp = pred.getBiPredicate(); + if (bp == Compare.gt || bp == Compare.gte || + bp == Compare.lt || bp == Compare.lte) { + return false; + } + } + return true; + } + public static void extractOrder(Step newStep, Traversal.Admin traversal) { Step step = newStep; 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 fdfc9d2e44..834727d476 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 @@ -125,4 +125,29 @@ public void testWhereCountGteNegativeDoesNotBuildInvalidRange() { Assert.assertEquals(4L, count); } + + @Test + public void testRepeatAfterTextRangeFilterWithEmptyResult() { + SchemaManager schema = graph().schema(); + schema.propertyKey("vp4").asText().create(); + schema.vertexLabel("vl1").properties("vp4") + .nullableKeys("vp4").create(); + schema.edgeLabel("el2").link("vl1", "vl1").create(); + + Vertex v1 = graph().addVertex(T.label, "vl1", "vp4", "a"); + Vertex v2 = graph().addVertex(T.label, "vl1", "vp4", "b"); + v1.addEdge("el2", v2); + commitTx(); + + long direct = graph().traversal().V().has("vp4", P.lt("")) + .repeat(__.out("el2")).emit().times(1) + .count().next(); + long viaMatch = graph().traversal().V() + .match(__.as("start").has("vp4", P.lt("")) + .out("el2").as("m")) + .select("m").count().next(); + + Assert.assertEquals(0L, direct); + Assert.assertEquals(viaMatch, direct); + } } From a67a8864b813c084ab64ec4ca762750e6e6acb3f Mon Sep 17 00:00:00 2001 From: legendPei <804141866@qq.com> Date: Thu, 21 May 2026 15:19:46 +0800 Subject: [PATCH 03/10] test(server): cover text range filter extraction --- .../traversal/optimize/TraversalUtil.java | 36 ++++++----- .../hugegraph/core/CountStrategyCoreTest.java | 61 +++++++++++++++++-- .../unit/core/TraversalUtilTest.java | 16 +++++ 3 files changed, 94 insertions(+), 19 deletions(-) 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 1b1f3ecfc5..6964d912d7 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 @@ -197,33 +197,39 @@ public static void extractHasContainer(HugeVertexStep newStep, private static boolean extractHasContainers(HugeGraphStep newStep, HasContainerHolder holder) { HugeGraph graph = TraversalUtil.tryGetGraph(newStep); - Iterator iterator = holder.getHasContainers().iterator(); - while (iterator.hasNext()) { - HasContainer has = iterator.next(); - if (!canExtractHasContainer(graph, has)) { - continue; - } + List hasContainers = holder.getHasContainers(); + if (!canExtractHasContainers(graph, hasContainers)) { + return false; + } + for (HasContainer has : hasContainers) { if (!GraphStep.processHasContainerIds(newStep, has)) { newStep.addHasContainer(has); } - iterator.remove(); } - return holder.getHasContainers().isEmpty(); + return true; } private static boolean extractHasContainers(HugeVertexStep newStep, HasContainerHolder holder) { HugeGraph graph = TraversalUtil.tryGetGraph(newStep); - Iterator iterator = holder.getHasContainers().iterator(); - while (iterator.hasNext()) { - HasContainer has = iterator.next(); + List hasContainers = holder.getHasContainers(); + if (!canExtractHasContainers(graph, hasContainers)) { + return false; + } + for (HasContainer has : hasContainers) { + newStep.addHasContainer(has); + } + return true; + } + + private static boolean canExtractHasContainers(HugeGraph graph, + List hasContainers) { + for (HasContainer has : hasContainers) { if (!canExtractHasContainer(graph, has)) { - continue; + return false; } - newStep.addHasContainer(has); - iterator.remove(); } - return holder.getHasContainers().isEmpty(); + return true; } private static boolean canExtractHasContainer(HugeGraph graph, 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 834727d476..bd66dad36a 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 @@ -130,12 +130,13 @@ public void testWhereCountGteNegativeDoesNotBuildInvalidRange() { public void testRepeatAfterTextRangeFilterWithEmptyResult() { SchemaManager schema = graph().schema(); schema.propertyKey("vp4").asText().create(); - schema.vertexLabel("vl1").properties("vp4") - .nullableKeys("vp4").create(); + schema.propertyKey("age").asInt().create(); + schema.vertexLabel("vl1").properties("vp4", "age") + .nullableKeys("vp4", "age").create(); schema.edgeLabel("el2").link("vl1", "vl1").create(); - Vertex v1 = graph().addVertex(T.label, "vl1", "vp4", "a"); - Vertex v2 = graph().addVertex(T.label, "vl1", "vp4", "b"); + Vertex v1 = graph().addVertex(T.label, "vl1", "vp4", "a", "age", 1); + Vertex v2 = graph().addVertex(T.label, "vl1", "vp4", "b", "age", 2); v1.addEdge("el2", v2); commitTx(); @@ -150,4 +151,56 @@ public void testRepeatAfterTextRangeFilterWithEmptyResult() { Assert.assertEquals(0L, direct); Assert.assertEquals(viaMatch, direct); } + + @Test + public void testTextRangeFilterDoesNotStopLaterGraphHasExtraction() { + SchemaManager schema = graph().schema(); + schema.propertyKey("vp4").asText().create(); + schema.propertyKey("age").asInt().create(); + schema.vertexLabel("vl1").properties("vp4", "age") + .nullableKeys("vp4", "age").create(); + + graph().addVertex(T.label, "vl1", "vp4", "a", "age", 1); + graph().addVertex(T.label, "vl1", "vp4", "b", "age", 2); + commitTx(); + + long direct = graph().traversal().V() + .has("vp4", P.lt("")) + .has("age", 1) + .count().next(); + long viaMatch = graph().traversal().V() + .match(__.as("v").has("vp4", P.lt("")) + .has("age", 1)) + .select("v").count().next(); + + Assert.assertEquals(0L, direct); + Assert.assertEquals(viaMatch, direct); + } + + @Test + public void testTextRangeFilterDoesNotStopLaterVertexHasExtraction() { + SchemaManager schema = graph().schema(); + schema.propertyKey("vp4").asText().create(); + schema.propertyKey("age").asInt().create(); + schema.vertexLabel("vl1").properties("vp4", "age") + .nullableKeys("vp4", "age").create(); + schema.edgeLabel("el2").link("vl1", "vl1").create(); + + Vertex v1 = graph().addVertex(T.label, "vl1", "vp4", "a", "age", 1); + Vertex v2 = graph().addVertex(T.label, "vl1", "vp4", "b", "age", 2); + v1.addEdge("el2", v2); + commitTx(); + + long direct = graph().traversal().V(v1.id()).out("el2") + .has("vp4", P.lt("")) + .has("age", 2) + .count().next(); + long viaMatch = graph().traversal().V(v1.id()).out("el2") + .match(__.as("v").has("vp4", P.lt("")) + .has("age", 2)) + .select("v").count().next(); + + Assert.assertEquals(0L, direct); + Assert.assertEquals(viaMatch, direct); + } } diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/TraversalUtilTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/TraversalUtilTest.java index 08322112b7..91c67dced0 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/TraversalUtilTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/TraversalUtilTest.java @@ -17,15 +17,31 @@ package org.apache.hugegraph.unit.core; +import java.lang.reflect.Method; + import org.apache.hugegraph.HugeException; +import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.testutil.Assert; import org.apache.hugegraph.traversal.optimize.TraversalUtil; import org.apache.hugegraph.util.DateUtil; import org.apache.tinkerpop.gremlin.process.traversal.P; +import org.apache.tinkerpop.gremlin.process.traversal.step.util.HasContainer; import org.junit.Test; public class TraversalUtilTest { + @Test + public void testCanExtractHasContainerWithoutGraph() throws Exception { + Method method = TraversalUtil.class.getDeclaredMethod( + "canExtractHasContainer", HugeGraph.class, HasContainer.class); + method.setAccessible(true); + + Assert.assertTrue((Boolean) method.invoke( + null, null, new HasContainer("~label", P.eq("person")))); + Assert.assertFalse((Boolean) method.invoke( + null, null, new HasContainer("name", P.eq("marko")))); + } + @Test public void testParsePredicate() { Assert.assertEquals(P.eq(0), From 1d843688ac13a2a677ab5573a9ccc50a0ab97c3b Mon Sep 17 00:00:00 2001 From: legendPei <804141866@qq.com> Date: Thu, 21 May 2026 15:53:41 +0800 Subject: [PATCH 04/10] fix(server): address text filter review comments --- .../traversal/optimize/TraversalUtil.java | 12 +++--- .../hugegraph/core/CountStrategyCoreTest.java | 39 +++++++++---------- .../optimize/TraversalUtilOptimizeTest.java | 34 ++++++++++++++++ .../unit/core/TraversalUtilTest.java | 16 -------- 4 files changed, 58 insertions(+), 43 deletions(-) create mode 100644 hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/traversal/optimize/TraversalUtilOptimizeTest.java 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 6964d912d7..28e185f8a1 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 @@ -166,9 +166,8 @@ public static void trySetGraph(Step step, HugeGraph graph) { public static void extractHasContainer(HugeGraphStep newStep, Traversal.Admin traversal) { - Step step = newStep; - do { - step = step.getNextStep(); + Step step = newStep.getNextStep(); + while (step instanceof HasStep || step instanceof NoOpBarrierStep) { if (step instanceof HasStep) { HasContainerHolder holder = (HasContainerHolder) step; if (extractHasContainers(newStep, holder)) { @@ -176,7 +175,8 @@ public static void extractHasContainer(HugeGraphStep newStep, traversal.removeStep(step); } } - } while (step instanceof HasStep || step instanceof NoOpBarrierStep); + step = step.getNextStep(); + } } public static void extractHasContainer(HugeVertexStep newStep, @@ -232,8 +232,8 @@ private static boolean canExtractHasContainers(HugeGraph graph, return true; } - private static boolean canExtractHasContainer(HugeGraph graph, - HasContainer has) { + static boolean canExtractHasContainer(HugeGraph graph, + HasContainer has) { if (isSysProp(has.getKey())) { return true; } 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 bd66dad36a..45d1392637 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 @@ -48,6 +48,17 @@ private void initGraph() { commitTx(); } + private void initTextRangeSchema(boolean withEdge) { + SchemaManager schema = graph().schema(); + schema.propertyKey("vp4").asText().create(); + schema.propertyKey("age").asInt().create(); + schema.vertexLabel("vl1").properties("vp4", "age") + .nullableKeys("vp4", "age").create(); + if (withEdge) { + schema.edgeLabel("el2").link("vl1", "vl1").create(); + } + } + @Test public void testWhereCountLtNegativeIsAlwaysFalse() { this.initSchema(); @@ -82,7 +93,7 @@ public void testWhereCountOutsideNegativeKeepsOriginalSemantics() { .count().next(); Assert.assertEquals(1L, direct); - Assert.assertEquals(viaMatch, direct); + Assert.assertEquals(direct, viaMatch); } @Test @@ -128,12 +139,7 @@ public void testWhereCountGteNegativeDoesNotBuildInvalidRange() { @Test public void testRepeatAfterTextRangeFilterWithEmptyResult() { - SchemaManager schema = graph().schema(); - schema.propertyKey("vp4").asText().create(); - schema.propertyKey("age").asInt().create(); - schema.vertexLabel("vl1").properties("vp4", "age") - .nullableKeys("vp4", "age").create(); - schema.edgeLabel("el2").link("vl1", "vl1").create(); + this.initTextRangeSchema(true); Vertex v1 = graph().addVertex(T.label, "vl1", "vp4", "a", "age", 1); Vertex v2 = graph().addVertex(T.label, "vl1", "vp4", "b", "age", 2); @@ -149,16 +155,12 @@ public void testRepeatAfterTextRangeFilterWithEmptyResult() { .select("m").count().next(); Assert.assertEquals(0L, direct); - Assert.assertEquals(viaMatch, direct); + Assert.assertEquals(direct, viaMatch); } @Test public void testTextRangeFilterDoesNotStopLaterGraphHasExtraction() { - SchemaManager schema = graph().schema(); - schema.propertyKey("vp4").asText().create(); - schema.propertyKey("age").asInt().create(); - schema.vertexLabel("vl1").properties("vp4", "age") - .nullableKeys("vp4", "age").create(); + this.initTextRangeSchema(false); graph().addVertex(T.label, "vl1", "vp4", "a", "age", 1); graph().addVertex(T.label, "vl1", "vp4", "b", "age", 2); @@ -174,17 +176,12 @@ public void testTextRangeFilterDoesNotStopLaterGraphHasExtraction() { .select("v").count().next(); Assert.assertEquals(0L, direct); - Assert.assertEquals(viaMatch, direct); + Assert.assertEquals(direct, viaMatch); } @Test public void testTextRangeFilterDoesNotStopLaterVertexHasExtraction() { - SchemaManager schema = graph().schema(); - schema.propertyKey("vp4").asText().create(); - schema.propertyKey("age").asInt().create(); - schema.vertexLabel("vl1").properties("vp4", "age") - .nullableKeys("vp4", "age").create(); - schema.edgeLabel("el2").link("vl1", "vl1").create(); + this.initTextRangeSchema(true); Vertex v1 = graph().addVertex(T.label, "vl1", "vp4", "a", "age", 1); Vertex v2 = graph().addVertex(T.label, "vl1", "vp4", "b", "age", 2); @@ -201,6 +198,6 @@ public void testTextRangeFilterDoesNotStopLaterVertexHasExtraction() { .select("v").count().next(); Assert.assertEquals(0L, direct); - Assert.assertEquals(viaMatch, direct); + Assert.assertEquals(direct, viaMatch); } } 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 new file mode 100644 index 0000000000..167c4b64c6 --- /dev/null +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/traversal/optimize/TraversalUtilOptimizeTest.java @@ -0,0 +1,34 @@ +/* + * 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 org.apache.hugegraph.testutil.Assert; +import org.apache.tinkerpop.gremlin.process.traversal.P; +import org.apache.tinkerpop.gremlin.process.traversal.step.util.HasContainer; +import org.junit.Test; + +public class TraversalUtilOptimizeTest { + + @Test + public void testCanExtractHasContainerWithoutGraph() { + Assert.assertTrue(TraversalUtil.canExtractHasContainer( + null, new HasContainer("~label", P.eq("person")))); + Assert.assertFalse(TraversalUtil.canExtractHasContainer( + null, new HasContainer("name", P.eq("marko")))); + } +} diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/TraversalUtilTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/TraversalUtilTest.java index 91c67dced0..08322112b7 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/TraversalUtilTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/TraversalUtilTest.java @@ -17,31 +17,15 @@ package org.apache.hugegraph.unit.core; -import java.lang.reflect.Method; - import org.apache.hugegraph.HugeException; -import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.testutil.Assert; import org.apache.hugegraph.traversal.optimize.TraversalUtil; import org.apache.hugegraph.util.DateUtil; import org.apache.tinkerpop.gremlin.process.traversal.P; -import org.apache.tinkerpop.gremlin.process.traversal.step.util.HasContainer; import org.junit.Test; public class TraversalUtilTest { - @Test - public void testCanExtractHasContainerWithoutGraph() throws Exception { - Method method = TraversalUtil.class.getDeclaredMethod( - "canExtractHasContainer", HugeGraph.class, HasContainer.class); - method.setAccessible(true); - - Assert.assertTrue((Boolean) method.invoke( - null, null, new HasContainer("~label", P.eq("person")))); - Assert.assertFalse((Boolean) method.invoke( - null, null, new HasContainer("name", P.eq("marko")))); - } - @Test public void testParsePredicate() { Assert.assertEquals(P.eq(0), From b2b784ed1466cb45eb1f7a1b60050f531bbf032a Mon Sep 17 00:00:00 2001 From: legendPei <804141866@qq.com> Date: Thu, 21 May 2026 20:01:08 +0800 Subject: [PATCH 05/10] fix(server): keep has extraction traversal stable --- .../traversal/optimize/TraversalUtil.java | 14 +++++++++++--- .../optimize/TraversalUtilOptimizeTest.java | 13 +++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) 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 28e185f8a1..b36249583e 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 @@ -39,6 +39,7 @@ import org.apache.hugegraph.backend.query.Condition; import org.apache.hugegraph.backend.query.ConditionQuery; import org.apache.hugegraph.backend.query.Query; +import org.apache.hugegraph.exception.NotFoundException; import org.apache.hugegraph.exception.NotSupportException; import org.apache.hugegraph.iterator.FilterIterator; import org.apache.hugegraph.schema.PropertyKey; @@ -168,6 +169,7 @@ public static void extractHasContainer(HugeGraphStep newStep, Traversal.Admin traversal) { Step step = newStep.getNextStep(); while (step instanceof HasStep || step instanceof NoOpBarrierStep) { + Step nextStep = step.getNextStep(); if (step instanceof HasStep) { HasContainerHolder holder = (HasContainerHolder) step; if (extractHasContainers(newStep, holder)) { @@ -175,7 +177,7 @@ public static void extractHasContainer(HugeGraphStep newStep, traversal.removeStep(step); } } - step = step.getNextStep(); + step = nextStep; } } @@ -183,6 +185,7 @@ public static void extractHasContainer(HugeVertexStep newStep, Traversal.Admin traversal) { Step step = newStep; do { + Step nextStep = step.getNextStep(); if (step instanceof HasStep) { HasContainerHolder holder = (HasContainerHolder) step; if (extractHasContainers(newStep, holder)) { @@ -190,7 +193,7 @@ public static void extractHasContainer(HugeVertexStep newStep, traversal.removeStep(step); } } - step = step.getNextStep(); + step = nextStep; } while (step instanceof HasStep || step instanceof NoOpBarrierStep); } @@ -241,7 +244,12 @@ static boolean canExtractHasContainer(HugeGraph graph, return false; } - PropertyKey pkey = graph.propertyKey(has.getKey()); + PropertyKey pkey; + try { + pkey = graph.propertyKey(has.getKey()); + } catch (NotFoundException e) { + return false; + } if (!pkey.dataType().isText()) { return true; } 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 167c4b64c6..ba65a63299 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,10 +17,13 @@ package org.apache.hugegraph.traversal.optimize; +import org.apache.hugegraph.HugeGraph; +import org.apache.hugegraph.exception.NotFoundException; import org.apache.hugegraph.testutil.Assert; import org.apache.tinkerpop.gremlin.process.traversal.P; import org.apache.tinkerpop.gremlin.process.traversal.step.util.HasContainer; import org.junit.Test; +import org.mockito.Mockito; public class TraversalUtilOptimizeTest { @@ -31,4 +34,14 @@ public void testCanExtractHasContainerWithoutGraph() { Assert.assertFalse(TraversalUtil.canExtractHasContainer( null, new HasContainer("name", P.eq("marko")))); } + + @Test + public void testCanExtractHasContainerWithMissingPropertyKey() { + HugeGraph graph = Mockito.mock(HugeGraph.class); + Mockito.when(graph.propertyKey("missing")) + .thenThrow(new NotFoundException("missing")); + + Assert.assertFalse(TraversalUtil.canExtractHasContainer( + graph, new HasContainer("missing", P.eq("marko")))); + } } From c8b4bf06df0c4b3472909d991957278e0d375ec3 Mon Sep 17 00:00:00 2001 From: legendPei <804141866@qq.com> Date: Thu, 21 May 2026 20:30:20 +0800 Subject: [PATCH 06/10] fix(server): extract safe has containers individually --- .../traversal/optimize/TraversalUtil.java | 44 +++++++++++-------- .../hugegraph/core/CountStrategyCoreTest.java | 12 ++++- .../optimize/TraversalUtilOptimizeTest.java | 43 ++++++++++++++++++ 3 files changed, 78 insertions(+), 21 deletions(-) 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 b36249583e..852ef50b7c 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 @@ -42,6 +42,7 @@ import org.apache.hugegraph.exception.NotFoundException; import org.apache.hugegraph.exception.NotSupportException; import org.apache.hugegraph.iterator.FilterIterator; +import org.apache.hugegraph.schema.IndexLabel; import org.apache.hugegraph.schema.PropertyKey; import org.apache.hugegraph.schema.SchemaLabel; import org.apache.hugegraph.structure.HugeElement; @@ -200,39 +201,31 @@ public static void extractHasContainer(HugeVertexStep newStep, private static boolean extractHasContainers(HugeGraphStep newStep, HasContainerHolder holder) { HugeGraph graph = TraversalUtil.tryGetGraph(newStep); - List hasContainers = holder.getHasContainers(); - if (!canExtractHasContainers(graph, hasContainers)) { - return false; - } + List hasContainers = new ArrayList<>(holder.getHasContainers()); for (HasContainer has : hasContainers) { + if (!canExtractHasContainer(graph, has)) { + continue; + } if (!GraphStep.processHasContainerIds(newStep, has)) { newStep.addHasContainer(has); } + holder.removeHasContainer(has); } - return true; + return holder.getHasContainers().isEmpty(); } private static boolean extractHasContainers(HugeVertexStep newStep, HasContainerHolder holder) { HugeGraph graph = TraversalUtil.tryGetGraph(newStep); - List hasContainers = holder.getHasContainers(); - if (!canExtractHasContainers(graph, hasContainers)) { - return false; - } - for (HasContainer has : hasContainers) { - newStep.addHasContainer(has); - } - return true; - } - - private static boolean canExtractHasContainers(HugeGraph graph, - List hasContainers) { + List hasContainers = new ArrayList<>(holder.getHasContainers()); for (HasContainer has : hasContainers) { if (!canExtractHasContainer(graph, has)) { - return false; + continue; } + newStep.addHasContainer(has); + holder.removeHasContainer(has); } - return true; + return holder.getHasContainers().isEmpty(); } static boolean canExtractHasContainer(HugeGraph graph, @@ -250,6 +243,9 @@ static boolean canExtractHasContainer(HugeGraph graph, } catch (NotFoundException e) { return false; } + if (!hasIndexForProperty(graph, pkey)) { + return false; + } if (!pkey.dataType().isText()) { return true; } @@ -266,6 +262,16 @@ static boolean canExtractHasContainer(HugeGraph graph, return true; } + private static boolean hasIndexForProperty(HugeGraph graph, + PropertyKey pkey) { + for (IndexLabel indexLabel : graph.indexLabels()) { + if (indexLabel.indexFields().contains(pkey.id())) { + return true; + } + } + return false; + } + public static void extractOrder(Step newStep, Traversal.Admin traversal) { Step step = newStep; 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 45d1392637..7de5bbe157 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 @@ -49,11 +49,19 @@ private void initGraph() { } private void initTextRangeSchema(boolean withEdge) { + this.initTextRangeSchema(withEdge, false); + } + + private void initTextRangeSchema(boolean withEdge, boolean withAgeIndex) { SchemaManager schema = graph().schema(); schema.propertyKey("vp4").asText().create(); schema.propertyKey("age").asInt().create(); schema.vertexLabel("vl1").properties("vp4", "age") .nullableKeys("vp4", "age").create(); + if (withAgeIndex) { + schema.indexLabel("vl1ByAge").onV("vl1").range() + .by("age").create(); + } if (withEdge) { schema.edgeLabel("el2").link("vl1", "vl1").create(); } @@ -160,7 +168,7 @@ public void testRepeatAfterTextRangeFilterWithEmptyResult() { @Test public void testTextRangeFilterDoesNotStopLaterGraphHasExtraction() { - this.initTextRangeSchema(false); + this.initTextRangeSchema(false, true); graph().addVertex(T.label, "vl1", "vp4", "a", "age", 1); graph().addVertex(T.label, "vl1", "vp4", "b", "age", 2); @@ -181,7 +189,7 @@ public void testTextRangeFilterDoesNotStopLaterGraphHasExtraction() { @Test public void testTextRangeFilterDoesNotStopLaterVertexHasExtraction() { - this.initTextRangeSchema(true); + this.initTextRangeSchema(true, true); Vertex v1 = graph().addVertex(T.label, "vl1", "vp4", "a", "age", 1); Vertex v2 = graph().addVertex(T.label, "vl1", "vp4", "b", "age", 2); 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 ba65a63299..3683ce58a9 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,9 +17,16 @@ package org.apache.hugegraph.traversal.optimize; +import java.util.Collections; + import org.apache.hugegraph.HugeGraph; +import org.apache.hugegraph.backend.id.Id; +import org.apache.hugegraph.backend.id.IdGenerator; import org.apache.hugegraph.exception.NotFoundException; +import org.apache.hugegraph.schema.IndexLabel; +import org.apache.hugegraph.schema.PropertyKey; import org.apache.hugegraph.testutil.Assert; +import org.apache.hugegraph.type.define.DataType; import org.apache.tinkerpop.gremlin.process.traversal.P; import org.apache.tinkerpop.gremlin.process.traversal.step.util.HasContainer; import org.junit.Test; @@ -31,6 +38,8 @@ public class TraversalUtilOptimizeTest { public void testCanExtractHasContainerWithoutGraph() { Assert.assertTrue(TraversalUtil.canExtractHasContainer( null, new HasContainer("~label", P.eq("person")))); + Assert.assertTrue(TraversalUtil.canExtractHasContainer( + null, new HasContainer("~id", P.eq("1")))); Assert.assertFalse(TraversalUtil.canExtractHasContainer( null, new HasContainer("name", P.eq("marko")))); } @@ -44,4 +53,38 @@ public void testCanExtractHasContainerWithMissingPropertyKey() { Assert.assertFalse(TraversalUtil.canExtractHasContainer( graph, new HasContainer("missing", P.eq("marko")))); } + + @Test + public void testCanExtractHasContainerWithoutPropertyIndex() { + HugeGraph graph = Mockito.mock(HugeGraph.class); + PropertyKey age = propertyKey(1L, "age", DataType.INT); + Mockito.when(graph.propertyKey("age")).thenReturn(age); + Mockito.when(graph.indexLabels()).thenReturn(Collections.emptyList()); + + Assert.assertFalse(TraversalUtil.canExtractHasContainer( + graph, new HasContainer("age", P.eq(1)))); + } + + @Test + public void testCanExtractHasContainerWithPropertyIndex() { + HugeGraph graph = Mockito.mock(HugeGraph.class); + PropertyKey age = propertyKey(1L, "age", DataType.INT); + IndexLabel ageIndex = new IndexLabel(null, IdGenerator.of(2L), + "vl1ByAge"); + ageIndex.indexFields(age.id()); + Mockito.when(graph.propertyKey("age")).thenReturn(age); + Mockito.when(graph.indexLabels()) + .thenReturn(Collections.singletonList(ageIndex)); + + Assert.assertTrue(TraversalUtil.canExtractHasContainer( + graph, new HasContainer("age", P.eq(1)))); + } + + private static PropertyKey propertyKey(long id, String name, + DataType dataType) { + Id keyId = IdGenerator.of(id); + PropertyKey key = new PropertyKey(null, keyId, name); + key.dataType(dataType); + return key; + } } From b2ff85b4c673f9de09c0b66556492f649e05198d Mon Sep 17 00:00:00 2001 From: legendPei <804141866@qq.com> Date: Thu, 21 May 2026 20:50:07 +0800 Subject: [PATCH 07/10] fix(server): scope has extraction to label indexes --- .../traversal/optimize/TraversalUtil.java | 114 +++++++++++++++++- .../hugegraph/core/CountStrategyCoreTest.java | 39 +++++- .../optimize/TraversalUtilOptimizeTest.java | 4 +- 3 files changed, 147 insertions(+), 10 deletions(-) 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 852ef50b7c..a68a532a69 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 @@ -20,10 +20,12 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.function.BiPredicate; import java.util.function.Function; import java.util.regex.Matcher; @@ -202,8 +204,11 @@ private static boolean extractHasContainers(HugeGraphStep newStep, HasContainerHolder holder) { HugeGraph graph = TraversalUtil.tryGetGraph(newStep); List hasContainers = new ArrayList<>(holder.getHasContainers()); + HugeType type = newStep.returnsVertex() ? HugeType.VERTEX : HugeType.EDGE; + Set labelIds = collectLabelIds(graph, type, newStep.getHasContainers(), + hasContainers); for (HasContainer has : hasContainers) { - if (!canExtractHasContainer(graph, has)) { + if (!canExtractHasContainer(graph, type, labelIds, has)) { continue; } if (!GraphStep.processHasContainerIds(newStep, has)) { @@ -218,8 +223,11 @@ private static boolean extractHasContainers(HugeVertexStep newStep, HasContainerHolder holder) { HugeGraph graph = TraversalUtil.tryGetGraph(newStep); List hasContainers = new ArrayList<>(holder.getHasContainers()); + HugeType type = newStep.returnsVertex() ? HugeType.VERTEX : HugeType.EDGE; + Set labelIds = collectLabelIds(graph, type, newStep.getHasContainers(), + hasContainers); for (HasContainer has : hasContainers) { - if (!canExtractHasContainer(graph, has)) { + if (!canExtractHasContainer(graph, type, labelIds, has)) { continue; } newStep.addHasContainer(has); @@ -230,6 +238,13 @@ private static boolean extractHasContainers(HugeVertexStep newStep, static boolean canExtractHasContainer(HugeGraph graph, HasContainer has) { + return canExtractHasContainer(graph, HugeType.UNKNOWN, null, has); + } + + private static boolean canExtractHasContainer(HugeGraph graph, + HugeType type, + Set labelIds, + HasContainer has) { if (isSysProp(has.getKey())) { return true; } @@ -243,7 +258,7 @@ static boolean canExtractHasContainer(HugeGraph graph, } catch (NotFoundException e) { return false; } - if (!hasIndexForProperty(graph, pkey)) { + if (!hasIndexForProperty(graph, type, labelIds, pkey, has)) { return false; } if (!pkey.dataType().isText()) { @@ -263,15 +278,102 @@ static boolean canExtractHasContainer(HugeGraph graph, } private static boolean hasIndexForProperty(HugeGraph graph, - PropertyKey pkey) { - for (IndexLabel indexLabel : graph.indexLabels()) { - if (indexLabel.indexFields().contains(pkey.id())) { + HugeType type, + Set labelIds, + PropertyKey pkey, + HasContainer has) { + if (labelIds == null || labelIds.size() != 1) { + return false; + } + + SchemaLabel schemaLabel = schemaLabel(graph, type, labelIds.iterator().next()); + for (Id indexLabelId : schemaLabel.indexLabels()) { + IndexLabel indexLabel = graph.indexLabel(indexLabelId); + if (indexLabel.indexFields().contains(pkey.id()) && + matchIndexType(indexLabel, has)) { return true; } } return false; } + private static SchemaLabel schemaLabel(HugeGraph graph, HugeType type, + Id label) { + if (type.isVertex()) { + return graph.vertexLabel(label); + } else { + assert type.isEdge(); + return graph.edgeLabel(label); + } + } + + private static boolean matchIndexType(IndexLabel indexLabel, + HasContainer has) { + if (indexLabel.indexType().isUnique()) { + return false; + } + + boolean range = false; + boolean search = false; + List> predicates = new ArrayList<>(); + collectPredicates(predicates, ImmutableList.of(has.getPredicate())); + for (P pred : predicates) { + BiPredicate bp = pred.getBiPredicate(); + range |= bp == Compare.gt || bp == Compare.gte || + bp == Compare.lt || bp == Compare.lte; + search |= bp instanceof Condition.RelationType && + ((Condition.RelationType) bp).isSearchType(); + } + + if (search) { + return indexLabel.indexType().isSearch(); + } + if (indexLabel.indexType().isSearch()) { + return false; + } + return !range || indexLabel.indexType().isNumeric(); + } + + @SafeVarargs + private static Set collectLabelIds(HugeGraph graph, HugeType type, + List... containers) { + if (graph == null) { + return null; + } + + Set labelIds = new HashSet<>(); + for (List list : containers) { + for (HasContainer has : list) { + if (token2HugeKey(has.getKey()) != HugeKeys.LABEL) { + continue; + } + if (!collectLabelIds(graph, type, has, labelIds)) { + return null; + } + } + } + return labelIds; + } + + private static boolean collectLabelIds(HugeGraph graph, HugeType type, + HasContainer has, Set labels) { + BiPredicate bp = has.getPredicate().getBiPredicate(); + if (bp == Compare.eq) { + labels.add((Id) convSysValueIfNeeded(graph, type, HugeKeys.LABEL, + has.getValue())); + return true; + } + if (bp == Contains.within) { + Collection values = (Collection) has.getValue(); + for (Object value : values) { + labels.add((Id) convSysValueIfNeeded(graph, type, HugeKeys.LABEL, + value)); + } + return true; + } + return false; + } + public static void extractOrder(Step newStep, Traversal.Admin traversal) { Step step = newStep; 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 7de5bbe157..5d76d80003 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 @@ -175,11 +175,13 @@ public void testTextRangeFilterDoesNotStopLaterGraphHasExtraction() { commitTx(); long direct = graph().traversal().V() + .hasLabel("vl1") .has("vp4", P.lt("")) .has("age", 1) .count().next(); long viaMatch = graph().traversal().V() - .match(__.as("v").has("vp4", P.lt("")) + .match(__.as("v").hasLabel("vl1") + .has("vp4", P.lt("")) .has("age", 1)) .select("v").count().next(); @@ -197,15 +199,48 @@ public void testTextRangeFilterDoesNotStopLaterVertexHasExtraction() { commitTx(); long direct = graph().traversal().V(v1.id()).out("el2") + .hasLabel("vl1") .has("vp4", P.lt("")) .has("age", 2) .count().next(); long viaMatch = graph().traversal().V(v1.id()).out("el2") - .match(__.as("v").has("vp4", P.lt("")) + .match(__.as("v").hasLabel("vl1") + .has("vp4", P.lt("")) .has("age", 2)) .select("v").count().next(); Assert.assertEquals(0L, direct); Assert.assertEquals(direct, viaMatch); } + + @Test + public void testTextRangeFilterDoesNotExtractIndexFromOtherLabel() { + SchemaManager schema = graph().schema(); + schema.propertyKey("vp4").asText().create(); + schema.propertyKey("age").asInt().create(); + schema.vertexLabel("vl1").properties("vp4", "age") + .nullableKeys("vp4", "age").create(); + schema.vertexLabel("vl2").properties("vp4", "age") + .nullableKeys("vp4", "age").create(); + schema.indexLabel("vl1ByAge").onV("vl1").range() + .by("age").create(); + + graph().addVertex(T.label, "vl2", "vp4", "a", "age", 1); + graph().addVertex(T.label, "vl2", "vp4", "b", "age", 2); + commitTx(); + + long direct = graph().traversal().V() + .hasLabel("vl2") + .has("vp4", P.lt("")) + .has("age", 1) + .count().next(); + long viaMatch = graph().traversal().V() + .match(__.as("v").hasLabel("vl2") + .has("vp4", P.lt("")) + .has("age", 1)) + .select("v").count().next(); + + Assert.assertEquals(0L, direct); + Assert.assertEquals(direct, viaMatch); + } } 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 3683ce58a9..696c65eddb 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 @@ -66,7 +66,7 @@ public void testCanExtractHasContainerWithoutPropertyIndex() { } @Test - public void testCanExtractHasContainerWithPropertyIndex() { + public void testCanExtractHasContainerWithoutLabelContext() { HugeGraph graph = Mockito.mock(HugeGraph.class); PropertyKey age = propertyKey(1L, "age", DataType.INT); IndexLabel ageIndex = new IndexLabel(null, IdGenerator.of(2L), @@ -76,7 +76,7 @@ public void testCanExtractHasContainerWithPropertyIndex() { Mockito.when(graph.indexLabels()) .thenReturn(Collections.singletonList(ageIndex)); - Assert.assertTrue(TraversalUtil.canExtractHasContainer( + Assert.assertFalse(TraversalUtil.canExtractHasContainer( graph, new HasContainer("age", P.eq(1)))); } From ddf4671fe0df2666c7126566a0b17da0f9e41ac8 Mon Sep 17 00:00:00 2001 From: legendPei <804141866@qq.com> Date: Thu, 21 May 2026 21:13:44 +0800 Subject: [PATCH 08/10] fix(server): align has extraction with index matching --- .../traversal/optimize/TraversalUtil.java | 11 +++- .../hugegraph/core/CountStrategyCoreTest.java | 55 +++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) 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 a68a532a69..a42f556105 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 @@ -289,7 +289,7 @@ private static boolean hasIndexForProperty(HugeGraph graph, SchemaLabel schemaLabel = schemaLabel(graph, type, labelIds.iterator().next()); for (Id indexLabelId : schemaLabel.indexLabels()) { IndexLabel indexLabel = graph.indexLabel(indexLabelId); - if (indexLabel.indexFields().contains(pkey.id()) && + if (matchIndexField(indexLabel, pkey) && matchIndexType(indexLabel, has)) { return true; } @@ -297,6 +297,12 @@ private static boolean hasIndexForProperty(HugeGraph graph, return false; } + private static boolean matchIndexField(IndexLabel indexLabel, + PropertyKey pkey) { + List fields = indexLabel.indexFields(); + return !fields.isEmpty() && fields.get(0).equals(pkey.id()); + } + private static SchemaLabel schemaLabel(HugeGraph graph, HugeType type, Id label) { if (type.isVertex()) { @@ -319,6 +325,9 @@ private static boolean matchIndexType(IndexLabel indexLabel, collectPredicates(predicates, ImmutableList.of(has.getPredicate())); for (P pred : predicates) { BiPredicate bp = pred.getBiPredicate(); + if (bp == Compare.neq || bp == Contains.without) { + return false; + } range |= bp == Compare.gt || bp == Compare.gte || bp == Compare.lt || bp == Compare.lte; search |= bp instanceof Condition.RelationType && 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 5d76d80003..6fd0474c74 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 @@ -243,4 +243,59 @@ public void testTextRangeFilterDoesNotExtractIndexFromOtherLabel() { Assert.assertEquals(0L, direct); Assert.assertEquals(direct, viaMatch); } + + @Test + public void testTextRangeFilterDoesNotExtractNonPrefixCompositeIndex() { + SchemaManager schema = graph().schema(); + schema.propertyKey("vp4").asText().create(); + schema.propertyKey("age").asInt().create(); + schema.propertyKey("city").asText().create(); + schema.vertexLabel("vl1").properties("vp4", "age", "city") + .nullableKeys("vp4", "age", "city").create(); + schema.indexLabel("vl1ByCityAge").onV("vl1").secondary() + .by("city", "age").create(); + + graph().addVertex(T.label, "vl1", "vp4", "a", "age", 1, + "city", "Beijing"); + graph().addVertex(T.label, "vl1", "vp4", "b", "age", 2, + "city", "Shanghai"); + commitTx(); + + long direct = graph().traversal().V() + .hasLabel("vl1") + .has("vp4", P.lt("")) + .has("age", 1) + .count().next(); + long viaMatch = graph().traversal().V() + .match(__.as("v").hasLabel("vl1") + .has("vp4", P.lt("")) + .has("age", 1)) + .select("v").count().next(); + + Assert.assertEquals(0L, direct); + Assert.assertEquals(direct, viaMatch); + } + + @Test + public void testTextRangeFilterDoesNotExtractNeqPredicate() { + this.initTextRangeSchema(false, true); + + graph().addVertex(T.label, "vl1", "vp4", "a", "age", 1); + graph().addVertex(T.label, "vl1", "vp4", "b", "age", 2); + commitTx(); + + long direct = graph().traversal().V() + .hasLabel("vl1") + .has("vp4", P.lt("")) + .has("age", P.neq(1)) + .count().next(); + long viaMatch = graph().traversal().V() + .match(__.as("v").hasLabel("vl1") + .has("vp4", P.lt("")) + .has("age", P.neq(1))) + .select("v").count().next(); + + Assert.assertEquals(0L, direct); + Assert.assertEquals(direct, viaMatch); + } } From 0b45ebb4fb21e012577df8c709e682242b401d10 Mon Sep 17 00:00:00 2001 From: legendPei <804141866@qq.com> Date: Thu, 21 May 2026 21:55:08 +0800 Subject: [PATCH 09/10] fix(server): narrow text range filter extraction --- .../traversal/optimize/TraversalUtil.java | 159 +++--------------- .../hugegraph/core/CountStrategyCoreTest.java | 101 +---------- .../optimize/TraversalUtilOptimizeTest.java | 23 +-- 3 files changed, 32 insertions(+), 251 deletions(-) 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 a42f556105..8f4f7eb86b 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 @@ -20,12 +20,10 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; import java.util.function.BiPredicate; import java.util.function.Function; import java.util.regex.Matcher; @@ -44,7 +42,6 @@ import org.apache.hugegraph.exception.NotFoundException; import org.apache.hugegraph.exception.NotSupportException; import org.apache.hugegraph.iterator.FilterIterator; -import org.apache.hugegraph.schema.IndexLabel; import org.apache.hugegraph.schema.PropertyKey; import org.apache.hugegraph.schema.SchemaLabel; import org.apache.hugegraph.structure.HugeElement; @@ -203,48 +200,41 @@ public static void extractHasContainer(HugeVertexStep newStep, private static boolean extractHasContainers(HugeGraphStep newStep, HasContainerHolder holder) { HugeGraph graph = TraversalUtil.tryGetGraph(newStep); - List hasContainers = new ArrayList<>(holder.getHasContainers()); - HugeType type = newStep.returnsVertex() ? HugeType.VERTEX : HugeType.EDGE; - Set labelIds = collectLabelIds(graph, type, newStep.getHasContainers(), - hasContainers); - for (HasContainer has : hasContainers) { - if (!canExtractHasContainer(graph, type, labelIds, has)) { - continue; - } + if (!canExtractHasContainers(graph, holder)) { + return false; + } + for (HasContainer has : holder.getHasContainers()) { if (!GraphStep.processHasContainerIds(newStep, has)) { newStep.addHasContainer(has); } - holder.removeHasContainer(has); } - return holder.getHasContainers().isEmpty(); + return true; } private static boolean extractHasContainers(HugeVertexStep newStep, HasContainerHolder holder) { HugeGraph graph = TraversalUtil.tryGetGraph(newStep); - List hasContainers = new ArrayList<>(holder.getHasContainers()); - HugeType type = newStep.returnsVertex() ? HugeType.VERTEX : HugeType.EDGE; - Set labelIds = collectLabelIds(graph, type, newStep.getHasContainers(), - hasContainers); - for (HasContainer has : hasContainers) { - if (!canExtractHasContainer(graph, type, labelIds, has)) { - continue; - } + if (!canExtractHasContainers(graph, holder)) { + return false; + } + for (HasContainer has : holder.getHasContainers()) { newStep.addHasContainer(has); - holder.removeHasContainer(has); } - return holder.getHasContainers().isEmpty(); + return true; } - static boolean canExtractHasContainer(HugeGraph graph, - HasContainer has) { - return canExtractHasContainer(graph, HugeType.UNKNOWN, null, has); + private static boolean canExtractHasContainers(HugeGraph graph, + HasContainerHolder holder) { + for (HasContainer has : holder.getHasContainers()) { + if (!canExtractHasContainer(graph, has)) { + return false; + } + } + return true; } - private static boolean canExtractHasContainer(HugeGraph graph, - HugeType type, - Set labelIds, - HasContainer has) { + static boolean canExtractHasContainer(HugeGraph graph, + HasContainer has) { if (isSysProp(has.getKey())) { return true; } @@ -258,9 +248,6 @@ private static boolean canExtractHasContainer(HugeGraph graph, } catch (NotFoundException e) { return false; } - if (!hasIndexForProperty(graph, type, labelIds, pkey, has)) { - return false; - } if (!pkey.dataType().isText()) { return true; } @@ -277,112 +264,6 @@ private static boolean canExtractHasContainer(HugeGraph graph, return true; } - private static boolean hasIndexForProperty(HugeGraph graph, - HugeType type, - Set labelIds, - PropertyKey pkey, - HasContainer has) { - if (labelIds == null || labelIds.size() != 1) { - return false; - } - - SchemaLabel schemaLabel = schemaLabel(graph, type, labelIds.iterator().next()); - for (Id indexLabelId : schemaLabel.indexLabels()) { - IndexLabel indexLabel = graph.indexLabel(indexLabelId); - if (matchIndexField(indexLabel, pkey) && - matchIndexType(indexLabel, has)) { - return true; - } - } - return false; - } - - private static boolean matchIndexField(IndexLabel indexLabel, - PropertyKey pkey) { - List fields = indexLabel.indexFields(); - return !fields.isEmpty() && fields.get(0).equals(pkey.id()); - } - - private static SchemaLabel schemaLabel(HugeGraph graph, HugeType type, - Id label) { - if (type.isVertex()) { - return graph.vertexLabel(label); - } else { - assert type.isEdge(); - return graph.edgeLabel(label); - } - } - - private static boolean matchIndexType(IndexLabel indexLabel, - HasContainer has) { - if (indexLabel.indexType().isUnique()) { - return false; - } - - boolean range = false; - boolean search = false; - List> predicates = new ArrayList<>(); - collectPredicates(predicates, ImmutableList.of(has.getPredicate())); - for (P pred : predicates) { - BiPredicate bp = pred.getBiPredicate(); - if (bp == Compare.neq || bp == Contains.without) { - return false; - } - range |= bp == Compare.gt || bp == Compare.gte || - bp == Compare.lt || bp == Compare.lte; - search |= bp instanceof Condition.RelationType && - ((Condition.RelationType) bp).isSearchType(); - } - - if (search) { - return indexLabel.indexType().isSearch(); - } - if (indexLabel.indexType().isSearch()) { - return false; - } - return !range || indexLabel.indexType().isNumeric(); - } - - @SafeVarargs - private static Set collectLabelIds(HugeGraph graph, HugeType type, - List... containers) { - if (graph == null) { - return null; - } - - Set labelIds = new HashSet<>(); - for (List list : containers) { - for (HasContainer has : list) { - if (token2HugeKey(has.getKey()) != HugeKeys.LABEL) { - continue; - } - if (!collectLabelIds(graph, type, has, labelIds)) { - return null; - } - } - } - return labelIds; - } - - private static boolean collectLabelIds(HugeGraph graph, HugeType type, - HasContainer has, Set labels) { - BiPredicate bp = has.getPredicate().getBiPredicate(); - if (bp == Compare.eq) { - labels.add((Id) convSysValueIfNeeded(graph, type, HugeKeys.LABEL, - has.getValue())); - return true; - } - if (bp == Contains.within) { - Collection values = (Collection) has.getValue(); - for (Object value : values) { - labels.add((Id) convSysValueIfNeeded(graph, type, HugeKeys.LABEL, - value)); - } - return true; - } - return false; - } - public static void extractOrder(Step newStep, Traversal.Admin traversal) { Step step = newStep; 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 6fd0474c74..a0ee9647ee 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 @@ -49,19 +49,11 @@ private void initGraph() { } private void initTextRangeSchema(boolean withEdge) { - this.initTextRangeSchema(withEdge, false); - } - - private void initTextRangeSchema(boolean withEdge, boolean withAgeIndex) { SchemaManager schema = graph().schema(); schema.propertyKey("vp4").asText().create(); schema.propertyKey("age").asInt().create(); schema.vertexLabel("vl1").properties("vp4", "age") .nullableKeys("vp4", "age").create(); - if (withAgeIndex) { - schema.indexLabel("vl1ByAge").onV("vl1").range() - .by("age").create(); - } if (withEdge) { schema.edgeLabel("el2").link("vl1", "vl1").create(); } @@ -167,8 +159,8 @@ public void testRepeatAfterTextRangeFilterWithEmptyResult() { } @Test - public void testTextRangeFilterDoesNotStopLaterGraphHasExtraction() { - this.initTextRangeSchema(false, true); + public void testTextRangeFilterKeepsMixedGraphHasStep() { + this.initTextRangeSchema(false); graph().addVertex(T.label, "vl1", "vp4", "a", "age", 1); graph().addVertex(T.label, "vl1", "vp4", "b", "age", 2); @@ -190,8 +182,8 @@ public void testTextRangeFilterDoesNotStopLaterGraphHasExtraction() { } @Test - public void testTextRangeFilterDoesNotStopLaterVertexHasExtraction() { - this.initTextRangeSchema(true, true); + public void testTextRangeFilterKeepsMixedVertexHasStep() { + this.initTextRangeSchema(true); Vertex v1 = graph().addVertex(T.label, "vl1", "vp4", "a", "age", 1); Vertex v2 = graph().addVertex(T.label, "vl1", "vp4", "b", "age", 2); @@ -213,89 +205,4 @@ public void testTextRangeFilterDoesNotStopLaterVertexHasExtraction() { Assert.assertEquals(direct, viaMatch); } - @Test - public void testTextRangeFilterDoesNotExtractIndexFromOtherLabel() { - SchemaManager schema = graph().schema(); - schema.propertyKey("vp4").asText().create(); - schema.propertyKey("age").asInt().create(); - schema.vertexLabel("vl1").properties("vp4", "age") - .nullableKeys("vp4", "age").create(); - schema.vertexLabel("vl2").properties("vp4", "age") - .nullableKeys("vp4", "age").create(); - schema.indexLabel("vl1ByAge").onV("vl1").range() - .by("age").create(); - - graph().addVertex(T.label, "vl2", "vp4", "a", "age", 1); - graph().addVertex(T.label, "vl2", "vp4", "b", "age", 2); - commitTx(); - - long direct = graph().traversal().V() - .hasLabel("vl2") - .has("vp4", P.lt("")) - .has("age", 1) - .count().next(); - long viaMatch = graph().traversal().V() - .match(__.as("v").hasLabel("vl2") - .has("vp4", P.lt("")) - .has("age", 1)) - .select("v").count().next(); - - Assert.assertEquals(0L, direct); - Assert.assertEquals(direct, viaMatch); - } - - @Test - public void testTextRangeFilterDoesNotExtractNonPrefixCompositeIndex() { - SchemaManager schema = graph().schema(); - schema.propertyKey("vp4").asText().create(); - schema.propertyKey("age").asInt().create(); - schema.propertyKey("city").asText().create(); - schema.vertexLabel("vl1").properties("vp4", "age", "city") - .nullableKeys("vp4", "age", "city").create(); - schema.indexLabel("vl1ByCityAge").onV("vl1").secondary() - .by("city", "age").create(); - - graph().addVertex(T.label, "vl1", "vp4", "a", "age", 1, - "city", "Beijing"); - graph().addVertex(T.label, "vl1", "vp4", "b", "age", 2, - "city", "Shanghai"); - commitTx(); - - long direct = graph().traversal().V() - .hasLabel("vl1") - .has("vp4", P.lt("")) - .has("age", 1) - .count().next(); - long viaMatch = graph().traversal().V() - .match(__.as("v").hasLabel("vl1") - .has("vp4", P.lt("")) - .has("age", 1)) - .select("v").count().next(); - - Assert.assertEquals(0L, direct); - Assert.assertEquals(direct, viaMatch); - } - - @Test - public void testTextRangeFilterDoesNotExtractNeqPredicate() { - this.initTextRangeSchema(false, true); - - graph().addVertex(T.label, "vl1", "vp4", "a", "age", 1); - graph().addVertex(T.label, "vl1", "vp4", "b", "age", 2); - commitTx(); - - long direct = graph().traversal().V() - .hasLabel("vl1") - .has("vp4", P.lt("")) - .has("age", P.neq(1)) - .count().next(); - long viaMatch = graph().traversal().V() - .match(__.as("v").hasLabel("vl1") - .has("vp4", P.lt("")) - .has("age", P.neq(1))) - .select("v").count().next(); - - Assert.assertEquals(0L, direct); - Assert.assertEquals(direct, viaMatch); - } } 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 696c65eddb..fa8fe879b2 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,13 +17,10 @@ package org.apache.hugegraph.traversal.optimize; -import java.util.Collections; - import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.backend.id.Id; import org.apache.hugegraph.backend.id.IdGenerator; import org.apache.hugegraph.exception.NotFoundException; -import org.apache.hugegraph.schema.IndexLabel; import org.apache.hugegraph.schema.PropertyKey; import org.apache.hugegraph.testutil.Assert; import org.apache.hugegraph.type.define.DataType; @@ -55,29 +52,25 @@ public void testCanExtractHasContainerWithMissingPropertyKey() { } @Test - public void testCanExtractHasContainerWithoutPropertyIndex() { + public void testCanExtractHasContainerWithNonTextProperty() { HugeGraph graph = Mockito.mock(HugeGraph.class); PropertyKey age = propertyKey(1L, "age", DataType.INT); Mockito.when(graph.propertyKey("age")).thenReturn(age); - Mockito.when(graph.indexLabels()).thenReturn(Collections.emptyList()); - Assert.assertFalse(TraversalUtil.canExtractHasContainer( + Assert.assertTrue(TraversalUtil.canExtractHasContainer( graph, new HasContainer("age", P.eq(1)))); } @Test - public void testCanExtractHasContainerWithoutLabelContext() { + public void testCanExtractHasContainerWithTextRangePredicate() { HugeGraph graph = Mockito.mock(HugeGraph.class); - PropertyKey age = propertyKey(1L, "age", DataType.INT); - IndexLabel ageIndex = new IndexLabel(null, IdGenerator.of(2L), - "vl1ByAge"); - ageIndex.indexFields(age.id()); - Mockito.when(graph.propertyKey("age")).thenReturn(age); - Mockito.when(graph.indexLabels()) - .thenReturn(Collections.singletonList(ageIndex)); + PropertyKey name = propertyKey(1L, "name", DataType.TEXT); + Mockito.when(graph.propertyKey("name")).thenReturn(name); Assert.assertFalse(TraversalUtil.canExtractHasContainer( - graph, new HasContainer("age", P.eq(1)))); + graph, new HasContainer("name", P.lt("")))); + Assert.assertTrue(TraversalUtil.canExtractHasContainer( + graph, new HasContainer("name", P.eq("marko")))); } private static PropertyKey propertyKey(long id, String name, From a3616140ca153522e473893906d270d1baf657fe Mon Sep 17 00:00:00 2001 From: legendPei <804141866@qq.com> Date: Fri, 22 May 2026 12:44:09 +0800 Subject: [PATCH 10/10] test(server): cover text range has extraction paths --- .../optimize/TraversalUtilOptimizeTest.java | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) 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 fa8fe879b2..d28769a1c5 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 @@ -25,7 +25,16 @@ import org.apache.hugegraph.testutil.Assert; 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.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; +import org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasStep; +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.util.TraversalHelper; +import org.apache.tinkerpop.gremlin.structure.Vertex; import org.junit.Test; import org.mockito.Mockito; @@ -73,6 +82,72 @@ public void testCanExtractHasContainerWithTextRangePredicate() { graph, new HasContainer("name", P.eq("marko")))); } + @Test + public void testExtractHasContainerKeepsTextRangeGraphHasStep() { + HugeGraph graph = Mockito.mock(HugeGraph.class); + PropertyKey name = propertyKey(1L, "name", DataType.TEXT); + Mockito.when(graph.propertyKey("name")).thenReturn(name); + + Traversal.Admin traversal = traversal(__.V() + .has("name", P.lt("marko")), + graph); + HugeGraphStep newStep = replaceGraphStep(traversal); + + TraversalUtil.extractHasContainer(newStep, traversal); + + Assert.assertTrue(newStep.getHasContainers().isEmpty()); + Assert.assertTrue(hasStepExists(traversal)); + } + + @Test + public void testExtractHasContainerRemovesSafeGraphHasStep() { + HugeGraph graph = Mockito.mock(HugeGraph.class); + PropertyKey age = propertyKey(1L, "age", DataType.INT); + Mockito.when(graph.propertyKey("age")).thenReturn(age); + + Traversal.Admin traversal = traversal(__.V().has("age", 18), + graph); + HugeGraphStep newStep = replaceGraphStep(traversal); + + TraversalUtil.extractHasContainer(newStep, traversal); + + Assert.assertEquals(1, newStep.getHasContainers().size()); + Assert.assertFalse(hasStepExists(traversal)); + } + + @Test + public void testExtractHasContainerKeepsTextRangeVertexHasStep() { + HugeGraph graph = Mockito.mock(HugeGraph.class); + PropertyKey name = propertyKey(1L, "name", DataType.TEXT); + Mockito.when(graph.propertyKey("name")).thenReturn(name); + + Traversal.Admin traversal = traversal(__.V().out() + .has("name", P.lt("marko")), + graph); + HugeVertexStep newStep = replaceVertexStep(traversal); + + TraversalUtil.extractHasContainer(newStep, traversal); + + Assert.assertTrue(newStep.getHasContainers().isEmpty()); + Assert.assertTrue(hasStepExists(traversal)); + } + + @Test + public void testExtractHasContainerRemovesSafeVertexHasStep() { + HugeGraph graph = Mockito.mock(HugeGraph.class); + PropertyKey age = propertyKey(1L, "age", DataType.INT); + Mockito.when(graph.propertyKey("age")).thenReturn(age); + + Traversal.Admin traversal = traversal(__.V().out().has("age", 18), + graph); + HugeVertexStep newStep = replaceVertexStep(traversal); + + TraversalUtil.extractHasContainer(newStep, traversal); + + Assert.assertEquals(1, newStep.getHasContainers().size()); + Assert.assertFalse(hasStepExists(traversal)); + } + private static PropertyKey propertyKey(long id, String name, DataType dataType) { Id keyId = IdGenerator.of(id); @@ -80,4 +155,48 @@ private static PropertyKey propertyKey(long id, String name, key.dataType(dataType); return key; } + + private static Traversal.Admin traversal(GraphTraversal traversal, + HugeGraph graph) { + Traversal.Admin admin = traversal.asAdmin(); + admin.setGraph(graph); + return admin; + } + + private static HugeGraphStep replaceGraphStep(Traversal.Admin traversal) { + GraphStep origin = (GraphStep) traversal.getStartStep(); + HugeGraphStep newStep = new HugeGraphStep<>(origin); + replaceStep(origin, newStep, traversal); + return newStep; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private static HugeVertexStep replaceVertexStep(Traversal.Admin traversal) { + VertexStep origin = null; + for (Step step : traversal.getSteps()) { + if (step instanceof VertexStep) { + origin = (VertexStep) step; + break; + } + } + Assert.assertNotNull(origin); + HugeVertexStep newStep = new HugeVertexStep<>(origin); + replaceStep(origin, newStep, traversal); + return newStep; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private static void replaceStep(Step origin, Step newStep, + Traversal.Admin traversal) { + TraversalHelper.replaceStep((Step) origin, (Step) newStep, traversal); + } + + private static boolean hasStepExists(Traversal.Admin traversal) { + for (Step step : traversal.getSteps()) { + if (step instanceof HasStep) { + return true; + } + } + return false; + } }