From 98fef9a4f248cbce4f856da1760cca75b12f937b Mon Sep 17 00:00:00 2001 From: contrueCT Date: Sun, 12 Apr 2026 15:53:35 +0800 Subject: [PATCH 1/9] fix: support boolean range predicates in condition query --- .../hugegraph/backend/query/Condition.java | 18 +++++++- .../backend/query/ConditionQueryFlatten.java | 3 ++ .../apache/hugegraph/core/EdgeCoreTest.java | 42 ++++++++++++++++++ .../unit/core/ConditionQueryFlattenTest.java | 43 +++++++++++++++++++ .../hugegraph/unit/core/ConditionTest.java | 26 +++++++++++ .../org/apache/hugegraph/query/Condition.java | 18 +++++++- 6 files changed, 148 insertions(+), 2 deletions(-) diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/Condition.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/Condition.java index 09e223e4ca..e65179df55 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/Condition.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/Condition.java @@ -195,7 +195,7 @@ private static boolean equals(final Object first, * * @param first is actual value, might be Number/Date or String, It is * probably that the `first` is serialized to String. - * @param second is value in query condition, must be Number/Date + * @param second is value in query condition, must be Number/Date/Boolean * @return the value 0 if first is numerically equal to second; * a value less than 0 if first is numerically less than * second; and a value greater than 0 if first is @@ -208,6 +208,8 @@ private static int compare(final Object first, final Object second) { (Number) second); } else if (second instanceof Date) { return compareDate(first, (Date) second); + } else if (second instanceof Boolean) { + return compareBoolean(first, (Boolean) second); } throw new IllegalArgumentException(String.format( @@ -230,6 +232,20 @@ private static int compareDate(Object first, Date second) { second, second.getClass().getSimpleName())); } + private static int compareBoolean(Object first, Boolean second) { + if (first == null) { + first = false; + } + if (first instanceof Boolean) { + return Boolean.compare((Boolean) first, second); + } + + throw new IllegalArgumentException(String.format( + "Can't compare between %s(%s) and %s(%s)", + first, first.getClass().getSimpleName(), + second, second.getClass().getSimpleName())); + } + private void checkBaseType(Object value, Class clazz) { if (!clazz.isInstance(value)) { String valueClass = value == null ? "null" : diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/ConditionQueryFlatten.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/ConditionQueryFlatten.java index 83af41b008..5209178ba5 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/ConditionQueryFlatten.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/ConditionQueryFlatten.java @@ -507,6 +507,9 @@ private static int compare(Relation first, Relation second) { return NumericUtil.compareNumber(firstValue, (Number) secondValue); } else if (firstValue instanceof Date && secondValue instanceof Date) { return ((Date) firstValue).compareTo((Date) secondValue); + } else if (firstValue instanceof Boolean && + secondValue instanceof Boolean) { + return Boolean.compare((Boolean) firstValue, (Boolean) secondValue); } else { throw new IllegalArgumentException(String.format("Can't compare between %s and %s", first, second)); diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java index 265d408742..648b6dffd5 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java @@ -7117,6 +7117,48 @@ public void testQueryEdgeWithNullablePropertyInCompositeIndex() { Assert.assertEquals(1, (int) el.get(0).value("id")); } + @Test + public void testQueryEdgeByBooleanRangePredicate() { + HugeGraph graph = graph(); + initStrikeIndex(); + + Vertex louise = graph.addVertex(T.label, "person", "name", "Louise", + "city", "Beijing", "age", 21); + Vertex sean = graph.addVertex(T.label, "person", "name", "Sean", + "city", "Beijing", "age", 23); + long current = System.currentTimeMillis(); + louise.addEdge("strike", sean, "id", 1, + "timestamp", current, "place", "park", + "tool", "shovel", "reason", "jeer", + "arrested", false); + louise.addEdge("strike", sean, "id", 2, + "timestamp", current + 1, "place", "street", + "tool", "shovel", "reason", "jeer", + "arrested", true); + + List hasEdges = graph.traversal().E() + .has("arrested", P.lt(true)) + .toList(); + Assert.assertEquals(1, hasEdges.size()); + Assert.assertEquals(1, (int) hasEdges.get(0).value("id")); + + List whereEdges = graph.traversal().E() + .where(__.has("arrested", P.lt(true))) + .toList(); + Assert.assertEquals(1, whereEdges.size()); + Assert.assertEquals(1, (int) whereEdges.get(0).value("id")); + + List matchEdges = graph.traversal().E() + .as("e") + .match(__.as("e") + .has("arrested", P.lt(true))) + .select("e") + .dedup() + .toList(); + Assert.assertEquals(1, matchEdges.size()); + Assert.assertEquals(1, (int) matchEdges.get(0).value("id")); + } + @Test public void testQueryEdgeByPage() { Assume.assumeTrue("Not support paging", diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/ConditionQueryFlattenTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/ConditionQueryFlattenTest.java index 627dcb29aa..dbe5506480 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/ConditionQueryFlattenTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/ConditionQueryFlattenTest.java @@ -256,5 +256,48 @@ public void testFlattenWithNotIn() { Collection actual = queries.iterator().next().conditions(); Assert.assertEquals(expect, actual); } + + @Test + public void testFlattenWithBooleanRangeUpperBound() { + Id key = IdGenerator.of("c1"); + + ConditionQuery query = new ConditionQuery(HugeType.VERTEX); + query.query(Condition.lt(key, true)); + query.query(Condition.lt(key, false)); + + List queries = ConditionQueryFlatten.flatten(query); + Assert.assertEquals(1, queries.size()); + + Collection actual = queries.iterator().next().conditions(); + Assert.assertEquals(ImmutableList.of(Condition.lt(key, false)), actual); + } + + @Test + public void testFlattenWithBooleanRangeWindow() { + Id key = IdGenerator.of("c1"); + + ConditionQuery query = new ConditionQuery(HugeType.VERTEX); + query.query(Condition.gte(key, false)); + query.query(Condition.lt(key, true)); + + List queries = ConditionQueryFlatten.flatten(query); + Assert.assertEquals(1, queries.size()); + + Collection actual = queries.iterator().next().conditions(); + Assert.assertEquals(ImmutableList.of(Condition.gte(key, false), + Condition.lt(key, true)), actual); + } + + @Test + public void testFlattenWithConflictingBooleanRange() { + Id key = IdGenerator.of("c1"); + + ConditionQuery query = new ConditionQuery(HugeType.VERTEX); + query.query(Condition.gt(key, false)); + query.query(Condition.lt(key, true)); + + List queries = ConditionQueryFlatten.flatten(query); + Assert.assertEquals(0, queries.size()); + } } diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/ConditionTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/ConditionTest.java index e333c7a98b..485c3d6c23 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/ConditionTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/ConditionTest.java @@ -293,6 +293,32 @@ public void testConditionLte() { }); } + @Test + public void testConditionBooleanRange() { + Condition lt = Condition.lt(HugeKeys.ID, true); + Assert.assertTrue(lt.test(false)); + Assert.assertFalse(lt.test(true)); + + Condition lte = Condition.lte(HugeKeys.ID, false); + Assert.assertTrue(lte.test(false)); + Assert.assertFalse(lte.test(true)); + + Condition gt = Condition.gt(HugeKeys.ID, false); + Assert.assertTrue(gt.test(true)); + Assert.assertFalse(gt.test(false)); + + Condition gte = Condition.gte(HugeKeys.ID, true); + Assert.assertTrue(gte.test(true)); + Assert.assertFalse(gte.test(false)); + + Assert.assertThrows(IllegalArgumentException.class, () -> { + Condition.lt(HugeKeys.ID, true).test(1); + }, e -> { + String err = "Can't compare between 1(Integer) and true(Boolean)"; + Assert.assertEquals(err, e.getMessage()); + }); + } + @Test public void testConditionNeq() { Condition c1 = Condition.neq(HugeKeys.ID, 123); diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/query/Condition.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/query/Condition.java index 5c7d3e221c..0b97c95c8a 100644 --- a/hugegraph-struct/src/main/java/org/apache/hugegraph/query/Condition.java +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/query/Condition.java @@ -437,7 +437,7 @@ private static boolean equals(final Object first, * * @param first is actual value, might be Number/Date or String, It is * probably that the `first` is serialized to String. - * @param second is value in query condition, must be Number/Date + * @param second is value in query condition, must be Number/Date/Boolean * @return the value 0 if first is numerically equal to second; * a value less than 0 if first is numerically less than * second; and a value greater than 0 if first is @@ -450,6 +450,8 @@ private static int compare(final Object first, final Object second) { (Number) second); } else if (second instanceof Date) { return compareDate(first, (Date) second); + } else if (second instanceof Boolean) { + return compareBoolean(first, (Boolean) second); } throw new IllegalArgumentException(String.format( @@ -472,6 +474,20 @@ private static int compareDate(Object first, Date second) { second, second.getClass().getSimpleName())); } + private static int compareBoolean(Object first, Boolean second) { + if (first == null) { + first = false; + } + if (first instanceof Boolean) { + return Boolean.compare((Boolean) first, second); + } + + throw new IllegalArgumentException(String.format( + "Can't compare between %s(%s) and %s(%s)", + first, first.getClass().getSimpleName(), + second, second.getClass().getSimpleName())); + } + public static List tokenize(String str) { final ArrayList tokens = new ArrayList<>(); int previous = 0; From dc441b64d756f2daff94fcec2ac4fe336839b7e9 Mon Sep 17 00:00:00 2001 From: contrueCT Date: Sun, 12 Apr 2026 15:58:26 +0800 Subject: [PATCH 2/9] test: cover boolean range query edge cases --- .../backend/query/ConditionQueryFlatten.java | 23 ++++++++++++++++--- .../apache/hugegraph/core/EdgeCoreTest.java | 2 ++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/ConditionQueryFlatten.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/ConditionQueryFlatten.java index 5209178ba5..8b9b7cb1d8 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/ConditionQueryFlatten.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/ConditionQueryFlatten.java @@ -427,9 +427,26 @@ private static boolean validRange(Relation low, Relation high) { if (low == null || high == null) { return true; } - return compare(low, high) < 0 || compare(low, high) == 0 && - low.relation() == Condition.RelationType.GTE && - high.relation() == Condition.RelationType.LTE; + int compared = compare(low, high); + if (compared > 0) { + return false; + } + if (compared == 0) { + return low.relation() == Condition.RelationType.GTE && + high.relation() == Condition.RelationType.LTE; + } + return !emptyBooleanRange(low, high); + } + + private static boolean emptyBooleanRange(Relation low, Relation high) { + if (!(low.value() instanceof Boolean) || + !(high.value() instanceof Boolean)) { + return false; + } + return Boolean.FALSE.equals(low.value()) && + Boolean.TRUE.equals(high.value()) && + low.relation() == Condition.RelationType.GT && + high.relation() == Condition.RelationType.LT; } private static boolean validEq(Relation eq, Relation low, Relation high) { diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java index 648b6dffd5..1c15969449 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java @@ -7121,6 +7121,8 @@ public void testQueryEdgeWithNullablePropertyInCompositeIndex() { public void testQueryEdgeByBooleanRangePredicate() { HugeGraph graph = graph(); initStrikeIndex(); + graph.schema().indexLabel("strikeByArrested").onE("strike").secondary() + .by("arrested").create(); Vertex louise = graph.addVertex(T.label, "person", "name", "Louise", "city", "Beijing", "age", 21); From 4cd2049221918cb2986d8197ef2312f81a2a751b Mon Sep 17 00:00:00 2001 From: contrueCT Date: Sun, 12 Apr 2026 16:06:49 +0800 Subject: [PATCH 3/9] fix(test): align boolean range regression coverage --- .../backend/query/ConditionQueryFlatten.java | 2 +- .../apache/hugegraph/core/EdgeCoreTest.java | 18 +++++------------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/ConditionQueryFlatten.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/ConditionQueryFlatten.java index 8b9b7cb1d8..3208c0549c 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/ConditionQueryFlatten.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/ConditionQueryFlatten.java @@ -265,7 +265,7 @@ private static List flattenRelations(ConditionQuery query) { cq.query(nonRelations); return ImmutableList.of(cq); } - return ImmutableList.of(query); + return ImmutableList.of(); } private static Relations optimizeRelations(Relations relations) { diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java index 1c15969449..c742136a6a 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java @@ -7121,8 +7121,6 @@ public void testQueryEdgeWithNullablePropertyInCompositeIndex() { public void testQueryEdgeByBooleanRangePredicate() { HugeGraph graph = graph(); initStrikeIndex(); - graph.schema().indexLabel("strikeByArrested").onE("strike").secondary() - .by("arrested").create(); Vertex louise = graph.addVertex(T.label, "person", "name", "Louise", "city", "Beijing", "age", 21); @@ -7138,12 +7136,6 @@ public void testQueryEdgeByBooleanRangePredicate() { "tool", "shovel", "reason", "jeer", "arrested", true); - List hasEdges = graph.traversal().E() - .has("arrested", P.lt(true)) - .toList(); - Assert.assertEquals(1, hasEdges.size()); - Assert.assertEquals(1, (int) hasEdges.get(0).value("id")); - List whereEdges = graph.traversal().E() .where(__.has("arrested", P.lt(true))) .toList(); @@ -7151,11 +7143,11 @@ public void testQueryEdgeByBooleanRangePredicate() { Assert.assertEquals(1, (int) whereEdges.get(0).value("id")); List matchEdges = graph.traversal().E() - .as("e") - .match(__.as("e") - .has("arrested", P.lt(true))) - .select("e") - .dedup() + .match(__.as("start") + .where(__.has("arrested", + P.lt(true))) + .as("matched")) + .select("matched") .toList(); Assert.assertEquals(1, matchEdges.size()); Assert.assertEquals(1, (int) matchEdges.get(0).value("id")); From d92050ff93133131518433ae70afd04b96a37605 Mon Sep 17 00:00:00 2001 From: contrueCT Date: Sun, 12 Apr 2026 16:14:43 +0800 Subject: [PATCH 4/9] fix(core): normalize boolean range predicates --- .../traversal/optimize/TraversalUtil.java | 48 ++++++++++++++++--- .../apache/hugegraph/core/EdgeCoreTest.java | 2 + 2 files changed, 44 insertions(+), 6 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 11a5c0cee4..c61504063e 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 @@ -46,6 +46,7 @@ import org.apache.hugegraph.structure.HugeElement; import org.apache.hugegraph.structure.HugeProperty; import org.apache.hugegraph.type.HugeType; +import org.apache.hugegraph.type.define.DataType; import org.apache.hugegraph.type.define.Directions; import org.apache.hugegraph.type.define.HugeKeys; import org.apache.hugegraph.util.CollectionUtil; @@ -419,9 +420,9 @@ public static Condition convOr(HugeGraph graph, return cond; } - private static Condition.Relation convCompare2Relation(HugeGraph graph, - HugeType type, - HasContainer has) { + private static Condition convCompare2Relation(HugeGraph graph, + HugeType type, + HasContainer has) { assert type.isGraph(); BiPredicate bp = has.getPredicate().getBiPredicate(); assert bp instanceof Compare; @@ -459,9 +460,9 @@ private static Condition.Relation convCompare2SyspropRelation(HugeGraph graph, } } - private static Condition.Relation convCompare2UserpropRelation(HugeGraph graph, - HugeType type, - HasContainer has) { + private static Condition convCompare2UserpropRelation(HugeGraph graph, + HugeType type, + HasContainer has) { BiPredicate bp = has.getPredicate().getBiPredicate(); assert bp instanceof Compare; @@ -469,6 +470,11 @@ private static Condition.Relation convCompare2UserpropRelation(HugeGraph graph, PropertyKey pkey = graph.propertyKey(key); Id pkeyId = pkey.id(); Object value = validPropertyValue(has.getValue(), pkey); + if (pkey.dataType() == DataType.BOOLEAN && + value instanceof Boolean) { + return convCompare2BooleanUserpropRelation((Compare) bp, pkeyId, + (Boolean) value); + } switch ((Compare) bp) { case eq: @@ -488,6 +494,36 @@ private static Condition.Relation convCompare2UserpropRelation(HugeGraph graph, } } + private static Condition convCompare2BooleanUserpropRelation(Compare compare, + Id key, + Boolean value) { + switch (compare) { + case eq: + return Condition.eq(key, value); + case neq: + return Condition.neq(key, value); + case gt: + return value ? impossibleBooleanCondition(key) : + Condition.eq(key, true); + case gte: + return value ? Condition.eq(key, true) : + Condition.in(key, ImmutableList.of(false, true)); + case lt: + return value ? Condition.eq(key, false) : + impossibleBooleanCondition(key); + case lte: + return value ? Condition.in(key, ImmutableList.of(false, true)) : + Condition.eq(key, false); + default: + throw new AssertionError(compare); + } + } + + private static Condition impossibleBooleanCondition(Id key) { + return Condition.and(Condition.eq(key, false), + Condition.eq(key, true)); + } + private static Condition convRelationType2Relation(HugeGraph graph, HugeType type, HasContainer has) { diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java index c742136a6a..b71ac5e862 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java @@ -7121,6 +7121,8 @@ public void testQueryEdgeWithNullablePropertyInCompositeIndex() { public void testQueryEdgeByBooleanRangePredicate() { HugeGraph graph = graph(); initStrikeIndex(); + graph.schema().indexLabel("strikeByArrested").onE("strike").secondary() + .by("arrested").create(); Vertex louise = graph.addVertex(T.label, "person", "name", "Louise", "city", "Beijing", "age", 21); From a07f43faf3f29d0a29433be39a2d57693c87fd12 Mon Sep 17 00:00:00 2001 From: contrueCT Date: Sun, 12 Apr 2026 16:22:01 +0800 Subject: [PATCH 5/9] test(core): expand boolean range edge coverage --- .../apache/hugegraph/core/EdgeCoreTest.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java index b71ac5e862..efa3ded95f 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java @@ -7138,6 +7138,12 @@ public void testQueryEdgeByBooleanRangePredicate() { "tool", "shovel", "reason", "jeer", "arrested", true); + List hasLtEdges = graph.traversal().E() + .has("arrested", P.lt(true)) + .toList(); + Assert.assertEquals(1, hasLtEdges.size()); + Assert.assertEquals(1, (int) hasLtEdges.get(0).value("id")); + List whereEdges = graph.traversal().E() .where(__.has("arrested", P.lt(true))) .toList(); @@ -7153,6 +7159,51 @@ public void testQueryEdgeByBooleanRangePredicate() { .toList(); Assert.assertEquals(1, matchEdges.size()); Assert.assertEquals(1, (int) matchEdges.get(0).value("id")); + + List hasLteFalseEdges = graph.traversal().E() + .has("arrested", P.lte(false)) + .toList(); + Assert.assertEquals(1, hasLteFalseEdges.size()); + Assert.assertEquals(1, (int) hasLteFalseEdges.get(0).value("id")); + + List hasGtFalseEdges = graph.traversal().E() + .has("arrested", P.gt(false)) + .toList(); + Assert.assertEquals(1, hasGtFalseEdges.size()); + Assert.assertEquals(2, (int) hasGtFalseEdges.get(0).value("id")); + + List hasGteTrueEdges = graph.traversal().E() + .has("arrested", P.gte(true)) + .toList(); + Assert.assertEquals(1, hasGteTrueEdges.size()); + Assert.assertEquals(2, (int) hasGteTrueEdges.get(0).value("id")); + + List hasGteFalseEdges = graph.traversal().E() + .has("arrested", P.gte(false)) + .toList(); + Assert.assertEquals(2, hasGteFalseEdges.size()); + Set gteFalseIds = new HashSet<>(); + for (Edge edge : hasGteFalseEdges) { + gteFalseIds.add(edge.value("id")); + } + Assert.assertEquals(ImmutableSet.of(1, 2), gteFalseIds); + + List hasLteTrueEdges = graph.traversal().E() + .has("arrested", P.lte(true)) + .toList(); + Assert.assertEquals(2, hasLteTrueEdges.size()); + Set lteTrueIds = new HashSet<>(); + for (Edge edge : hasLteTrueEdges) { + lteTrueIds.add(edge.value("id")); + } + Assert.assertEquals(ImmutableSet.of(1, 2), lteTrueIds); + + Assert.assertEquals(0, graph.traversal().E() + .has("arrested", P.lt(false)) + .toList().size()); + Assert.assertEquals(0, graph.traversal().E() + .has("arrested", P.gt(true)) + .toList().size()); } @Test From ce8a4bbc17e2e9d1747f5ebd77087c0dc8c51d77 Mon Sep 17 00:00:00 2001 From: contrueCT Date: Mon, 27 Apr 2026 19:29:30 +0800 Subject: [PATCH 6/9] fix(core): address boolean predicate review feedback --- .../backend/query/ConditionQueryFlatten.java | 2 +- .../traversal/optimize/TraversalUtil.java | 9 +--- .../apache/hugegraph/core/EdgeCoreTest.java | 51 +++++++++++++++++++ .../unit/core/ConditionQueryFlattenTest.java | 21 +++++++- .../apache/hugegraph/query/ConditionTest.java | 50 ++++++++++++++++++ 5 files changed, 123 insertions(+), 10 deletions(-) create mode 100644 hugegraph-struct/src/test/java/org/apache/hugegraph/query/ConditionTest.java diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/ConditionQueryFlatten.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/ConditionQueryFlatten.java index 3208c0549c..8b9b7cb1d8 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/ConditionQueryFlatten.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/ConditionQueryFlatten.java @@ -265,7 +265,7 @@ private static List flattenRelations(ConditionQuery query) { cq.query(nonRelations); return ImmutableList.of(cq); } - return ImmutableList.of(); + return ImmutableList.of(query); } private static Relations optimizeRelations(Relations relations) { 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 c61504063e..8d0e7e48ab 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 @@ -503,14 +503,14 @@ private static Condition convCompare2BooleanUserpropRelation(Compare compare, case neq: return Condition.neq(key, value); case gt: - return value ? impossibleBooleanCondition(key) : + return value ? Condition.in(key, ImmutableList.of()) : Condition.eq(key, true); case gte: return value ? Condition.eq(key, true) : Condition.in(key, ImmutableList.of(false, true)); case lt: return value ? Condition.eq(key, false) : - impossibleBooleanCondition(key); + Condition.in(key, ImmutableList.of()); case lte: return value ? Condition.in(key, ImmutableList.of(false, true)) : Condition.eq(key, false); @@ -519,11 +519,6 @@ private static Condition convCompare2BooleanUserpropRelation(Compare compare, } } - private static Condition impossibleBooleanCondition(Id key) { - return Condition.and(Condition.eq(key, false), - Condition.eq(key, true)); - } - private static Condition convRelationType2Relation(HugeGraph graph, HugeType type, HasContainer has) { diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java index efa3ded95f..a528e19fcc 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java @@ -7206,6 +7206,57 @@ public void testQueryEdgeByBooleanRangePredicate() { .toList().size()); } + @Test + public void testQueryEdgeByBooleanRangePredicateWithoutNullableProperty() { + HugeGraph graph = graph(); + initStrikeIndex(); + graph.schema().indexLabel("strikeByHurt").onE("strike").secondary() + .by("hurt").create(); + + Vertex louise = graph.addVertex(T.label, "person", "name", "Louise", + "city", "Beijing", "age", 21); + Vertex sean = graph.addVertex(T.label, "person", "name", "Sean", + "city", "Beijing", "age", 23); + long current = System.currentTimeMillis(); + louise.addEdge("strike", sean, "id", 1, + "timestamp", current, "place", "park", + "tool", "shovel", "reason", "jeer", + "hurt", false); + louise.addEdge("strike", sean, "id", 2, + "timestamp", current + 1, "place", "street", + "tool", "shovel", "reason", "jeer", + "hurt", true); + louise.addEdge("strike", sean, "id", 3, + "timestamp", current + 2, "place", "mall", + "tool", "shovel", "reason", "jeer"); + + List gteFalseEdges = graph.traversal().E() + .has("hurt", P.gte(false)) + .toList(); + Assert.assertEquals(2, gteFalseEdges.size()); + Set gteFalseIds = new HashSet<>(); + for (Edge edge : gteFalseEdges) { + gteFalseIds.add(edge.value("id")); + } + Assert.assertEquals(ImmutableSet.of(1, 2), gteFalseIds); + + List lteTrueEdges = graph.traversal().E() + .has("hurt", P.lte(true)) + .toList(); + Assert.assertEquals(2, lteTrueEdges.size()); + Set lteTrueIds = new HashSet<>(); + for (Edge edge : lteTrueEdges) { + lteTrueIds.add(edge.value("id")); + } + Assert.assertEquals(ImmutableSet.of(1, 2), lteTrueIds); + + List lteFalseEdges = graph.traversal().E() + .has("hurt", P.lte(false)) + .toList(); + Assert.assertEquals(1, lteFalseEdges.size()); + Assert.assertEquals(1, (int) lteFalseEdges.get(0).value("id")); + } + @Test public void testQueryEdgeByPage() { Assume.assumeTrue("Not support paging", diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/ConditionQueryFlattenTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/ConditionQueryFlattenTest.java index dbe5506480..b74183f9a5 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/ConditionQueryFlattenTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/ConditionQueryFlattenTest.java @@ -293,11 +293,28 @@ public void testFlattenWithConflictingBooleanRange() { Id key = IdGenerator.of("c1"); ConditionQuery query = new ConditionQuery(HugeType.VERTEX); - query.query(Condition.gt(key, false)); - query.query(Condition.lt(key, true)); + query.query(Condition.gt(key, false).and(Condition.lt(key, true))); List queries = ConditionQueryFlatten.flatten(query); Assert.assertEquals(0, queries.size()); } + + @Test + public void testFlattenWithConflictingNumericRangeKeepsQuery() { + Id key = IdGenerator.of("c1"); + + Condition gt = Condition.gt(key, 10); + Condition eq = Condition.eq(key, 9); + + ConditionQuery query = new ConditionQuery(HugeType.VERTEX); + query.query(gt); + query.query(eq); + + List queries = ConditionQueryFlatten.flatten(query); + Assert.assertEquals(1, queries.size()); + + Collection actual = queries.iterator().next().conditions(); + Assert.assertEquals(ImmutableList.of(gt, eq), actual); + } } diff --git a/hugegraph-struct/src/test/java/org/apache/hugegraph/query/ConditionTest.java b/hugegraph-struct/src/test/java/org/apache/hugegraph/query/ConditionTest.java new file mode 100644 index 0000000000..fad8a37b01 --- /dev/null +++ b/hugegraph-struct/src/test/java/org/apache/hugegraph/query/ConditionTest.java @@ -0,0 +1,50 @@ +/* + * 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.query; + +import org.apache.hugegraph.type.define.HugeKeys; +import org.junit.Assert; +import org.junit.Test; + +public class ConditionTest { + + @Test + public void testConditionBooleanRange() { + Condition lt = Condition.lt(HugeKeys.ID, true); + Assert.assertTrue(lt.test(false)); + Assert.assertFalse(lt.test(true)); + + Condition lte = Condition.lte(HugeKeys.ID, false); + Assert.assertTrue(lte.test(false)); + Assert.assertFalse(lte.test(true)); + + Condition gt = Condition.gt(HugeKeys.ID, false); + Assert.assertTrue(gt.test(true)); + Assert.assertFalse(gt.test(false)); + + Condition gte = Condition.gte(HugeKeys.ID, true); + Assert.assertTrue(gte.test(true)); + Assert.assertFalse(gte.test(false)); + + IllegalArgumentException exception = Assert.assertThrows( + IllegalArgumentException.class, + () -> Condition.lt(HugeKeys.ID, true).test(1)); + Assert.assertEquals("Can't compare between 1(Integer) and true(Boolean)", + exception.getMessage()); + } +} From 9679866d857f27798388443f2a600d8ee169ec33 Mon Sep 17 00:00:00 2001 From: contrueCT Date: Mon, 27 Apr 2026 19:35:23 +0800 Subject: [PATCH 7/9] test(core): fix nullable boolean predicate coverage --- .../src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java index a528e19fcc..de3447385e 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java @@ -7228,7 +7228,8 @@ public void testQueryEdgeByBooleanRangePredicateWithoutNullableProperty() { "hurt", true); louise.addEdge("strike", sean, "id", 3, "timestamp", current + 2, "place", "mall", - "tool", "shovel", "reason", "jeer"); + "tool", "shovel", "reason", "jeer", + "arrested", false); List gteFalseEdges = graph.traversal().E() .has("hurt", P.gte(false)) From bec076d0315c582a41a909844f5a4c1175bfcac0 Mon Sep 17 00:00:00 2001 From: contrueCT Date: Mon, 27 Apr 2026 19:38:18 +0800 Subject: [PATCH 8/9] test(core): complete nullable boolean edge fixtures --- .../src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java index de3447385e..280c76000d 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java @@ -7221,11 +7221,11 @@ public void testQueryEdgeByBooleanRangePredicateWithoutNullableProperty() { louise.addEdge("strike", sean, "id", 1, "timestamp", current, "place", "park", "tool", "shovel", "reason", "jeer", - "hurt", false); + "hurt", false, "arrested", false); louise.addEdge("strike", sean, "id", 2, "timestamp", current + 1, "place", "street", "tool", "shovel", "reason", "jeer", - "hurt", true); + "hurt", true, "arrested", true); louise.addEdge("strike", sean, "id", 3, "timestamp", current + 2, "place", "mall", "tool", "shovel", "reason", "jeer", From 3abba15d94fcfbb12d4b1a8b18fa13797eea3bb7 Mon Sep 17 00:00:00 2001 From: contrueCT Date: Mon, 27 Apr 2026 22:01:00 +0800 Subject: [PATCH 9/9] fix(core): refine boolean predicate handling --- .../hugegraph/backend/query/Condition.java | 6 ++-- .../traversal/optimize/TraversalUtil.java | 2 +- .../apache/hugegraph/core/EdgeCoreTest.java | 18 ++++++++++++ .../apache/hugegraph/core/VertexCoreTest.java | 28 +++++++++++++++++++ .../hugegraph/unit/core/ConditionTest.java | 6 ++++ .../org/apache/hugegraph/query/Condition.java | 6 ++-- .../apache/hugegraph/query/ConditionTest.java | 6 ++++ 7 files changed, 63 insertions(+), 9 deletions(-) diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/Condition.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/Condition.java index e65179df55..8d9ecd4332 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/Condition.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/Condition.java @@ -233,16 +233,14 @@ private static int compareDate(Object first, Date second) { } private static int compareBoolean(Object first, Boolean second) { - if (first == null) { - first = false; - } if (first instanceof Boolean) { return Boolean.compare((Boolean) first, second); } throw new IllegalArgumentException(String.format( "Can't compare between %s(%s) and %s(%s)", - first, first.getClass().getSimpleName(), + first, first == null ? null : + first.getClass().getSimpleName(), second, second.getClass().getSimpleName())); } 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 8d0e7e48ab..142c95620b 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 @@ -501,7 +501,7 @@ private static Condition convCompare2BooleanUserpropRelation(Compare compare, case eq: return Condition.eq(key, value); case neq: - return Condition.neq(key, value); + return Condition.eq(key, !value); case gt: return value ? Condition.in(key, ImmutableList.of()) : Condition.eq(key, true); diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java index 280c76000d..842787c096 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java @@ -7160,6 +7160,18 @@ public void testQueryEdgeByBooleanRangePredicate() { Assert.assertEquals(1, matchEdges.size()); Assert.assertEquals(1, (int) matchEdges.get(0).value("id")); + List hasNeqTrueEdges = graph.traversal().E() + .has("arrested", P.neq(true)) + .toList(); + Assert.assertEquals(1, hasNeqTrueEdges.size()); + Assert.assertEquals(1, (int) hasNeqTrueEdges.get(0).value("id")); + + List hasNeqFalseEdges = graph.traversal().E() + .has("arrested", P.neq(false)) + .toList(); + Assert.assertEquals(1, hasNeqFalseEdges.size()); + Assert.assertEquals(2, (int) hasNeqFalseEdges.get(0).value("id")); + List hasLteFalseEdges = graph.traversal().E() .has("arrested", P.lte(false)) .toList(); @@ -7256,6 +7268,12 @@ public void testQueryEdgeByBooleanRangePredicateWithoutNullableProperty() { .toList(); Assert.assertEquals(1, lteFalseEdges.size()); Assert.assertEquals(1, (int) lteFalseEdges.get(0).value("id")); + + List neqTrueEdges = graph.traversal().E() + .has("hurt", P.neq(true)) + .toList(); + Assert.assertEquals(1, neqTrueEdges.size()); + Assert.assertEquals(1, (int) neqTrueEdges.get(0).value("id")); } @Test diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/VertexCoreTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/VertexCoreTest.java index a329de3afb..c12cff6b6b 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/VertexCoreTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/VertexCoreTest.java @@ -6470,6 +6470,34 @@ public void testQueryVertexByPropertyWithEmptyString() { Assert.assertEquals("", vertex.value("city")); } + @Test + public void testQueryVertexByBooleanNeqPredicate() { + HugeGraph graph = graph(); + graph.schema().indexLabel("languageByDynamic").onV("language") + .secondary().by("dynamic").create(); + + graph.addVertex(T.label, "language", "name", "java", + "dynamic", true); + graph.addVertex(T.label, "language", "name", "rust", + "dynamic", false); + graph.addVertex(T.label, "language", "name", "c"); + this.commitTx(); + + List neqTrueVertices = graph.traversal().V() + .hasLabel("language") + .has("dynamic", P.neq(true)) + .toList(); + Assert.assertEquals(1, neqTrueVertices.size()); + Assert.assertEquals("rust", neqTrueVertices.get(0).value("name")); + + List neqFalseVertices = graph.traversal().V() + .hasLabel("language") + .has("dynamic", P.neq(false)) + .toList(); + Assert.assertEquals(1, neqFalseVertices.size()); + Assert.assertEquals("java", neqFalseVertices.get(0).value("name")); + } + @Test public void testQueryVertexBeforeAfterUpdateMultiPropertyWithIndex() { HugeGraph graph = graph(); diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/ConditionTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/ConditionTest.java index 485c3d6c23..ba4b09dcab 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/ConditionTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/ConditionTest.java @@ -317,6 +317,12 @@ public void testConditionBooleanRange() { String err = "Can't compare between 1(Integer) and true(Boolean)"; Assert.assertEquals(err, e.getMessage()); }); + Assert.assertThrows(IllegalArgumentException.class, () -> { + Condition.lt(HugeKeys.ID, true).test((Object) null); + }, e -> { + String err = "Can't compare between null(null) and true(Boolean)"; + Assert.assertEquals(err, e.getMessage()); + }); } @Test diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/query/Condition.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/query/Condition.java index 0b97c95c8a..0d1b7ad05b 100644 --- a/hugegraph-struct/src/main/java/org/apache/hugegraph/query/Condition.java +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/query/Condition.java @@ -475,16 +475,14 @@ private static int compareDate(Object first, Date second) { } private static int compareBoolean(Object first, Boolean second) { - if (first == null) { - first = false; - } if (first instanceof Boolean) { return Boolean.compare((Boolean) first, second); } throw new IllegalArgumentException(String.format( "Can't compare between %s(%s) and %s(%s)", - first, first.getClass().getSimpleName(), + first, first == null ? null : + first.getClass().getSimpleName(), second, second.getClass().getSimpleName())); } diff --git a/hugegraph-struct/src/test/java/org/apache/hugegraph/query/ConditionTest.java b/hugegraph-struct/src/test/java/org/apache/hugegraph/query/ConditionTest.java index fad8a37b01..b34fa4e735 100644 --- a/hugegraph-struct/src/test/java/org/apache/hugegraph/query/ConditionTest.java +++ b/hugegraph-struct/src/test/java/org/apache/hugegraph/query/ConditionTest.java @@ -46,5 +46,11 @@ public void testConditionBooleanRange() { () -> Condition.lt(HugeKeys.ID, true).test(1)); Assert.assertEquals("Can't compare between 1(Integer) and true(Boolean)", exception.getMessage()); + + exception = Assert.assertThrows(IllegalArgumentException.class, + () -> Condition.lt(HugeKeys.ID, true) + .test((Object) null)); + Assert.assertEquals("Can't compare between null(null) and true(Boolean)", + exception.getMessage()); } }