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 e960cc5ec fix(server): skip unsafe count optm for nested predicates
(#3100)
e960cc5ec is described below
commit e960cc5ec202ab608f8845b4617480fbd672f4a8
Author: lokidundun <[email protected]>
AuthorDate: Thu Jul 23 11:21:24 2026 +0800
fix(server): skip unsafe count optm for nested predicates (#3100)
---
.../traversal/optimize/HugeCountStrategy.java | 23 ++++
.../hugegraph/core/CountStrategyCoreTest.java | 129 +++++++++++++++++++++
2 files changed, 152 insertions(+)
diff --git
a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugeCountStrategy.java
b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugeCountStrategy.java
index ea6de76ac..c3a1542f8 100644
---
a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugeCountStrategy.java
+++
b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugeCountStrategy.java
@@ -175,6 +175,11 @@ public final class HugeCountStrategy
}
}
+ if (isStepPredicate instanceof ConnectiveP) {
+ useNotStep = false;
+ dismissCountIs = false;
+ }
+
/*
* HugeGraph extracts RangeGlobalStep into backend queries. A
* negative upper bound is never useful for count(), and would
@@ -272,10 +277,28 @@ public final class HugeCountStrategy
return false;
}
+ final P<?> predicate = ((IsStep<?>) step.getNextStep()).getPredicate();
+ if (this.hasNestedConnectivePredicate(predicate)) {
+ return false;
+ }
+
final Step parent = step.getTraversal().getParent().asStep();
return (parent instanceof FilterStep || parent.getLabels().isEmpty())
&&
!(parent.getNextStep() instanceof MatchStep.MatchEndStep &&
((MatchStep.MatchEndStep) parent.getNextStep())
.getMatchKey().isPresent());
}
+
+ private boolean hasNestedConnectivePredicate(P<?> predicate) {
+ if (!(predicate instanceof ConnectiveP)) {
+ return false;
+ }
+
+ for (P<?> child : ((ConnectiveP<?>) predicate).getPredicates()) {
+ if (child instanceof ConnectiveP) {
+ return true;
+ }
+ }
+ return false;
+ }
}
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 660c2e040..f00918050 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
@@ -23,11 +23,14 @@ import org.apache.hugegraph.testutil.Assert;
import org.apache.hugegraph.traversal.optimize.HugeGraphStep;
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.HasContainerHolder;
import org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasStep;
+import
org.apache.tinkerpop.gremlin.process.traversal.step.filter.RangeGlobalStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.HasContainer;
+import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
import org.apache.tinkerpop.gremlin.structure.Edge;
import org.apache.tinkerpop.gremlin.structure.T;
import org.apache.tinkerpop.gremlin.structure.Vertex;
@@ -209,6 +212,132 @@ public class CountStrategyCoreTest extends BaseCoreTest {
Assert.assertEquals(4L, count);
}
+ @Test
+ public void testWhereCountNestedConnectivePredicate() {
+ this.initSchema();
+ Vertex source = graph().addVertex(T.label, "person", "name", "source");
+ Vertex target = graph().addVertex(T.label, "person", "name", "target");
+ source.addEdge("knows", target);
+ commitTx();
+
+ long count = graph().traversal().V(source.id())
+ .where(__.both("knows").count()
+ .is(P.<Long>outside(1L, 18L)
+ .and(P.gte(0L))))
+ .count().next();
+
+ Assert.assertEquals(0L, count);
+ }
+
+ @Test
+ public void testWhereCountNegatedNestedConnectivePredicate() {
+ this.initSchema();
+ Vertex source = graph().addVertex(T.label, "person", "name", "source");
+ Vertex target = graph().addVertex(T.label, "person", "name", "target");
+ source.addEdge("knows", target);
+ commitTx();
+
+ long count = graph().traversal().V(source.id())
+ .where(__.both("knows").count()
+ .is(P.not(P.<Long>outside(1L, 18L)
+ .and(P.gte(0L)))))
+ .count().next();
+
+ Assert.assertEquals(1L, count);
+ }
+
+ @Test
+ public void testWhereCountFlatAndContradictionEmpty() {
+ this.initSchema();
+ Vertex source = graph().addVertex(T.label, "person", "name", "source");
+ commitTx();
+
+ long count = graph().traversal().V(source.id())
+ .where(__.both("knows").count()
+ .is(P.<Long>eq(0L).and(P.neq(0L))))
+ .count().next();
+
+ Assert.assertEquals(0L, count);
+ }
+
+ @Test
+ public void testWhereCountFlatAndContradictionNonEmpty() {
+ this.initSchema();
+ Vertex source = graph().addVertex(T.label, "person", "name", "source");
+ Vertex target = graph().addVertex(T.label, "person", "name", "target");
+ source.addEdge("knows", target);
+ commitTx();
+
+ long count = graph().traversal().V(source.id())
+ .where(__.both("knows").count()
+ .is(P.<Long>eq(0L).and(P.neq(0L))))
+ .count().next();
+
+ Assert.assertEquals(0L, count);
+ }
+
+ @Test
+ public void testWhereCountFlatOrTautologyEmpty() {
+ this.initSchema();
+ Vertex source = graph().addVertex(T.label, "person", "name", "source");
+ commitTx();
+
+ long count = graph().traversal().V(source.id())
+ .where(__.both("knows").count()
+ .is(P.<Long>eq(0L).or(P.neq(0L))))
+ .count().next();
+
+ Assert.assertEquals(1L, count);
+ }
+
+ @Test
+ public void testWhereCountFlatOrTautologyNonEmpty() {
+ this.initSchema();
+ Vertex source = graph().addVertex(T.label, "person", "name", "source");
+ Vertex target = graph().addVertex(T.label, "person", "name", "target");
+ source.addEdge("knows", target);
+ commitTx();
+
+ long count = graph().traversal().V(source.id())
+ .where(__.both("knows").count()
+ .is(P.<Long>eq(0L).or(P.neq(0L))))
+ .count().next();
+
+ Assert.assertEquals(1L, count);
+ }
+
+ @Test
+ public void testWhereCountFlatConnectiveStillGetsRangeBound() {
+ this.initSchema();
+ this.initGraph();
+
+ GraphTraversal<Vertex, Long> traversal = graph().traversal().V()
+ .where(__.out().count()
+
.is(P.between(1, 18)))
+ .count();
+ traversal.asAdmin().applyStrategies();
+
+ boolean foundRangeStep = false;
+ for (Step<?, ?> step : traversal.asAdmin().getSteps()) {
+ if (step instanceof TraversalParent) {
+ for (Traversal.Admin<?, ?> inner :
+ ((TraversalParent) step).getLocalChildren()) {
+ for (Step<?, ?> innerStep : inner.getSteps()) {
+ if (innerStep instanceof RangeGlobalStep) {
+ foundRangeStep = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ Assert.assertTrue("Expected RangeGlobalStep for flat ConnectiveP " +
+ "between(1,18)", foundRangeStep);
+
+ long count = traversal.next();
+ Assert.assertEquals(1L, count);
+ }
+
@Test
public void testRepeatAfterTextRangeFilterWithEmptyResult() {
this.initTextRangeSchema(true);