Skip to content

Commit 207d573

Browse files
committed
fix: Cypher -> allReduce(...) may evaluate true cases as false
Fixed issue #4043 The predicate is now only evaluated inside each iteration where the iterator variable is bound. Empty lists yield vacuous true, matching Neo4j/Cypher 25 semantics.
1 parent 9f01ab6 commit 207d573

2 files changed

Lines changed: 30 additions & 7 deletions

File tree

engine/src/main/java/com/arcadedb/query/opencypher/ast/AllReduceExpression.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,9 @@ else if (listValue.getClass().isArray())
8282
else
8383
throw new IllegalArgumentException("allReduce() requires a list, got: " + listValue.getClass().getSimpleName());
8484

85-
// Check initial accumulator against predicate
86-
final ResultInternal initResult = createIterationResult(result, null, accumulator);
87-
final Object initCheck = OpenCypherQueryEngine.getExpressionEvaluator().evaluate(predicateExpression, initResult, context);
88-
if (!Boolean.TRUE.equals(initCheck))
89-
return false;
90-
91-
// Iterate over each element, updating the accumulator and checking the predicate
85+
// Iterate over each element, updating the accumulator and checking the predicate.
86+
// The predicate is only evaluated within each iteration where the iterator variable is bound;
87+
// an empty list yields vacuous truth (true). Matches Neo4j/Cypher 25 semantics.
9288
for (final Object item : iterable) {
9389
final ResultInternal iterResult = createIterationResult(result, item, accumulator);
9490

engine/src/test/java/com/arcadedb/query/opencypher/CypherBuiltInFunctionsTest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1385,6 +1385,33 @@ void allReducePredicateFails() {
13851385
});
13861386
}
13871387

1388+
@Test
1389+
void issue4043_allReducePredicateOnIteratorVariable() {
1390+
// Issue #4043: predicate referencing the iterator variable returned false even
1391+
// when every element satisfied it. Neo4j returns true for all three queries below.
1392+
database.transaction(() -> {
1393+
ResultSet rs = database.query("opencypher",
1394+
"RETURN allReduce(acc = 0, x IN [1, 2, 3] | acc + x, x > 0) AS result");
1395+
assertThat(rs.hasNext()).isTrue();
1396+
assertThat((boolean) rs.next().getProperty("result")).isTrue();
1397+
1398+
rs = database.query("opencypher",
1399+
"RETURN allReduce(acc = 0, x IN [1, 2, 3] | acc + x, x > 10) AS result");
1400+
assertThat(rs.hasNext()).isTrue();
1401+
assertThat((boolean) rs.next().getProperty("result")).isFalse();
1402+
1403+
rs = database.query("opencypher",
1404+
"RETURN allReduce(acc = 0, x IN [] | acc + x, x > 0) AS result");
1405+
assertThat(rs.hasNext()).isTrue();
1406+
assertThat((boolean) rs.next().getProperty("result")).isTrue();
1407+
1408+
rs = database.query("opencypher",
1409+
"RETURN allReduce(acc = 0, x IN [1, 2, 3] | acc + x, acc >= 0) AS result");
1410+
assertThat(rs.hasNext()).isTrue();
1411+
assertThat((boolean) rs.next().getProperty("result")).isTrue();
1412+
});
1413+
}
1414+
13881415
// Note: Integration tests for Cypher queries with built-in functions
13891416
// should be placed in a test class that has access to the Cypher query engine.
13901417
// The tests above verify the function implementations directly.

0 commit comments

Comments
 (0)