This is an automated email from the ASF dual-hosted git repository.

imbajin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hugegraph.git


The following commit(s) were added to refs/heads/master by this push:
     new beb30eee6 fix(server): avoid extracting text range filters (#3034)
beb30eee6 is described below

commit beb30eee6278d69bb531f532d47508458d797e6b
Author: legendpei <[email protected]>
AuthorDate: Tue Jun 2 12:40:49 2026 +0800

    fix(server): avoid extracting text range filters (#3034)
---
 .../traversal/optimize/TraversalUtil.java          |  96 +++++++--
 .../hugegraph/core/CountStrategyCoreTest.java      | 104 ++++++++-
 .../optimize/TraversalUtilOptimizeTest.java        | 237 +++++++++++++++++++++
 .../org/apache/hugegraph/unit/UnitTestSuite.java   |   2 +
 4 files changed, 423 insertions(+), 16 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 142c95620..8f4f7eb86 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.Aggregate;
 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;
@@ -166,38 +167,103 @@ public final class TraversalUtil {
 
     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) {
+            Step<?, ?> nextStep = 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);
+            step = nextStep;
+        }
     }
 
     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;
-                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();
+            step = nextStep;
         } while (step instanceof HasStep || step instanceof NoOpBarrierStep);
     }
 
+    private static boolean extractHasContainers(HugeGraphStep<?, ?> newStep,
+                                                HasContainerHolder holder) {
+        HugeGraph graph = TraversalUtil.tryGetGraph(newStep);
+        if (!canExtractHasContainers(graph, holder)) {
+            return false;
+        }
+        for (HasContainer has : holder.getHasContainers()) {
+            if (!GraphStep.processHasContainerIds(newStep, has)) {
+                newStep.addHasContainer(has);
+            }
+        }
+        return true;
+    }
+
+    private static boolean extractHasContainers(HugeVertexStep<?> newStep,
+                                                HasContainerHolder holder) {
+        HugeGraph graph = TraversalUtil.tryGetGraph(newStep);
+        if (!canExtractHasContainers(graph, holder)) {
+            return false;
+        }
+        for (HasContainer has : holder.getHasContainers()) {
+            newStep.addHasContainer(has);
+        }
+        return true;
+    }
+
+    private static boolean canExtractHasContainers(HugeGraph graph,
+                                                   HasContainerHolder holder) {
+        for (HasContainer has : holder.getHasContainers()) {
+            if (!canExtractHasContainer(graph, has)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    static boolean canExtractHasContainer(HugeGraph graph,
+                                          HasContainer has) {
+        if (isSysProp(has.getKey())) {
+            return true;
+        }
+        if (graph == null) {
+            return false;
+        }
+
+        PropertyKey pkey;
+        try {
+            pkey = graph.propertyKey(has.getKey());
+        } catch (NotFoundException e) {
+            return false;
+        }
+        if (!pkey.dataType().isText()) {
+            return true;
+        }
+
+        List<P<Object>> predicates = new ArrayList<>();
+        collectPredicates(predicates, ImmutableList.of(has.getPredicate()));
+        for (P<Object> 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 fdfc9d2e4..212c2bc04 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,19 @@ public class CountStrategyCoreTest extends BaseCoreTest {
         commitTx();
     }
 
+    private void initTextRangeSchema(boolean withEdge) {
+        SchemaManager schema = graph().schema();
+        schema.propertyKey("vp4").asText().create();
+        schema.propertyKey("ep4").asText().create();
+        schema.propertyKey("age").asInt().create();
+        schema.vertexLabel("vl1").properties("vp4", "age")
+              .nullableKeys("vp4", "age").create();
+        if (withEdge) {
+            schema.edgeLabel("el2").properties("ep4")
+                  .nullableKeys("ep4").link("vl1", "vl1").create();
+        }
+    }
+
     @Test
     public void testWhereCountLtNegativeIsAlwaysFalse() {
         this.initSchema();
@@ -82,7 +95,7 @@ public class CountStrategyCoreTest extends BaseCoreTest {
                              .count().next();
 
         Assert.assertEquals(1L, direct);
-        Assert.assertEquals(viaMatch, direct);
+        Assert.assertEquals(direct, viaMatch);
     }
 
     @Test
@@ -125,4 +138,93 @@ public class CountStrategyCoreTest extends BaseCoreTest {
 
         Assert.assertEquals(4L, count);
     }
+
+    @Test
+    public void testRepeatAfterTextRangeFilterWithEmptyResult() {
+        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);
+        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(direct, viaMatch);
+    }
+
+    @Test
+    public void testTextRangeFilterKeepsMixedGraphHasStep() {
+        this.initTextRangeSchema(false);
+
+        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", 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 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);
+        v1.addEdge("el2", v2);
+        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").hasLabel("vl1")
+                                      .has("vp4", P.lt(""))
+                                      .has("age", 2))
+                             .select("v").count().next();
+
+        Assert.assertEquals(0L, direct);
+        Assert.assertEquals(direct, viaMatch);
+    }
+
+    @Test
+    public void testTextRangeFilterKeepsEdgeGraphHasStep() {
+        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);
+        v1.addEdge("el2", v2, "ep4", "a");
+        commitTx();
+
+        long direct = graph().traversal().E()
+                           .has("ep4", P.lt(""))
+                           .count().next();
+        long viaMatch = graph().traversal().E()
+                             .match(__.as("e").has("ep4", P.lt("")))
+                             .select("e").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
new file mode 100644
index 000000000..cdf6addfc
--- /dev/null
+++ 
b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/traversal/optimize/TraversalUtilOptimizeTest.java
@@ -0,0 +1,237 @@
+/*
+ * 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.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.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;
+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;
+
+public class TraversalUtilOptimizeTest {
+
+    @Test
+    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"))));
+    }
+
+    @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"))));
+    }
+
+    @Test
+    public void testCanExtractHasContainerWithNonTextProperty() {
+        HugeGraph graph = Mockito.mock(HugeGraph.class);
+        PropertyKey age = propertyKey(1L, "age", DataType.INT);
+        Mockito.when(graph.propertyKey("age")).thenReturn(age);
+
+        Assert.assertTrue(TraversalUtil.canExtractHasContainer(
+                graph, new HasContainer("age", P.eq(1))));
+    }
+
+    @Test
+    public void testCanExtractHasContainerWithTextRangePredicate() {
+        HugeGraph graph = Mockito.mock(HugeGraph.class);
+        PropertyKey name = propertyKey(1L, "name", DataType.TEXT);
+        Mockito.when(graph.propertyKey("name")).thenReturn(name);
+
+        Assert.assertFalse(TraversalUtil.canExtractHasContainer(
+                graph, new HasContainer("name", P.lt(""))));
+        Assert.assertFalse(TraversalUtil.canExtractHasContainer(
+                graph, new HasContainer("name", P.gte("marko"))));
+        Assert.assertFalse(TraversalUtil.canExtractHasContainer(
+                graph, new HasContainer("name", P.between("josh", "marko"))));
+        Assert.assertTrue(TraversalUtil.canExtractHasContainer(
+                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 testExtractHasContainerKeepsTextRangeWithoutGraph() {
+        Traversal.Admin<?, ?> traversal = __.V()
+                                           .has("name", P.lt("marko"))
+                                           .asAdmin();
+        HugeGraphStep<?, ?> newStep = replaceGraphStep(traversal);
+
+        TraversalUtil.extractHasContainer(newStep, traversal);
+
+        Assert.assertTrue(newStep.getHasContainers().isEmpty());
+        Assert.assertTrue(hasStepExists(traversal));
+    }
+
+    @Test
+    public void testExtractHasContainerKeepsTextBetweenGraphHasStep() {
+        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.between(
+                                                             "josh", "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);
+        PropertyKey key = new PropertyKey(null, keyId, 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<Vertex> 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;
+    }
+}
diff --git 
a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java
 
b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java
index 0780b03a6..cb34e3922 100644
--- 
a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java
+++ 
b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java
@@ -19,6 +19,7 @@ package org.apache.hugegraph.unit;
 
 import org.apache.hugegraph.core.RoleElectionStateMachineTest;
 import org.apache.hugegraph.meta.MetaManagerSchemaCacheClearEventTest;
+import org.apache.hugegraph.traversal.optimize.TraversalUtilOptimizeTest;
 import org.apache.hugegraph.unit.api.filter.LoadDetectFilterTest;
 import org.apache.hugegraph.unit.api.filter.PathFilterTest;
 import org.apache.hugegraph.unit.api.gremlin.GremlinQueryAPITest;
@@ -126,6 +127,7 @@ import org.junit.runners.Suite;
         ExceptionTest.class,
         BackendStoreInfoTest.class,
         TraversalUtilTest.class,
+        TraversalUtilOptimizeTest.class,
         PageStateTest.class,
         SystemSchemaStoreTest.class,
         ServerInfoManagerTest.class,

Reply via email to