Skip to content

feat(opencypher): enable filter pushdown for list predicates (any/all/none/single) in WHERE clause#3891

Merged
lvca merged 4 commits intoArcadeData:mainfrom
subha0319:main
Apr 22, 2026
Merged

feat(opencypher): enable filter pushdown for list predicates (any/all/none/single) in WHERE clause#3891
lvca merged 4 commits intoArcadeData:mainfrom
subha0319:main

Conversation

@subha0319
Copy link
Copy Markdown
Contributor

@subha0319 subha0319 commented Apr 17, 2026

What does this PR do?

Enables filter pushdown optimization for any(), all(), none(), and single() list predicates when used in WHERE clauses.

Previously, WhereClause.collectExpressionVariables() had no branch for ListPredicateExpression, so outer variables referenced inside these predicates (e.g. p in p.name) were never collected. This prevented the query optimizer from recognizing which variables these predicates depend on, causing it to skip them as pushdown candidates entirely.

This PR adds the missing branch, correctly collecting outer variables while excluding the loop-scoped iterator variable (e.g. x in any(x IN list WHERE x = p.name)). The same iterator-exclusion logic is also applied consistently to the existing ListComprehensionExpression branch, which had the same gap.

Files changed:

  • engine/src/main/java/com/arcadedb/query/opencypher/WhereClause.java: adds ListPredicateExpression branch and fixes ListComprehensionExpression branch in collectExpressionVariables()
  • engine/src/test/java/com/arcadedb/query/opencypher/OpenCypherWhereClauseTest.java: adds ListPredicateFilterPushdownTest nested class with 5 tests covering any, NOT any, all, none, single

Testing:

  • All 5 new tests pass: testAnyPredicateInWhere, testNotAnyPredicateInWhere, testAllPredicateInWhere, testNonePredicateInWhere, testSinglePredicateInWhere
  • Full OpenCypher test suite passes.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements variable collection for list predicate expressions in OpenCypher WHERE clauses, ensuring that variables within predicates like 'any()' are correctly identified. Feedback was provided regarding the placement and implementation of the new test cases. Specifically, the tests were added to an inappropriate nested class lacking the necessary vertex type setup, used inconsistent indentation, and utilized JUnit assertions instead of the project's standard AssertJ library. An unnecessary JUnit import should also be removed.

Comment on lines 934 to 967
@Test
public void testAnyPredicateInWhere() {
// Cleanup first to ensure a clean state
database.command("opencypher", "MATCH (n:Person) DETACH DELETE n");

database.command("opencypher",
"CREATE (:Person {name:'Alice'}), (:Person {name:'Bob'}), (:Person {name:'Charlie'})");

ResultSet rs = database.query("opencypher",
"MATCH (p:Person) WHERE any(x IN ['Alice'] WHERE x = p.name) RETURN p.name AS name ORDER BY name");

List<String> names = new ArrayList<>();
while (rs.hasNext()) names.add(rs.next().getProperty("name"));

assertEquals(List.of("Alice"), names);
}

@Test
public void testNotAnyPredicateInWhere() {
// Cleanup first to ensure a clean state
database.command("opencypher", "MATCH (n:Person) DETACH DELETE n");

database.command("opencypher",
"CREATE (:Person {name:'Alice'}), (:Person {name:'Bob'}), (:Person {name:'Charlie'})");

ResultSet rs = database.query("opencypher",
"MATCH (p:Person) WHERE NOT any(x IN ['Alice'] WHERE x = p.name) RETURN p.name AS name ORDER BY name");

List<String> names = new ArrayList<>();
while (rs.hasNext()) names.add(rs.next().getProperty("name"));

assertEquals(List.of("Bob", "Charlie"), names);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

These tests are logically misplaced inside the NotOperatorParenthesesRegression nested class, which is intended for a different set of regression tests. Furthermore, the setUp method of this nested class does not create the Person vertex type, which will cause these tests to fail. Moving them to the main OpenCypherWhereClauseTest class ensures they use the correct database setup and follow the file's 2-space indentation and AssertJ usage style.

  }

  @Test
  void testAnyPredicateInWhere() {
    database.command("opencypher", "MATCH (n:Person) DETACH DELETE n");
    database.command("opencypher",
        "CREATE (:Person {name:'Alice'}), (:Person {name:'Bob'}), (:Person {name:'Charlie'})");

    ResultSet rs = database.query("opencypher",
        "MATCH (p:Person) WHERE any(x IN ['Alice'] WHERE x = p.name) RETURN p.name AS name ORDER BY name");

    List<String> names = new ArrayList<>();
    while (rs.hasNext()) names.add(rs.next().getProperty("name"));

    assertThat(names).containsExactly("Alice");
  }

  @Test
  void testNotAnyPredicateInWhere() {
    database.command("opencypher", "MATCH (n:Person) DETACH DELETE n");
    database.command("opencypher",
        "CREATE (:Person {name:'Alice'}), (:Person {name:'Bob'}), (:Person {name:'Charlie'})");

    ResultSet rs = database.query("opencypher",
        "MATCH (p:Person) WHERE NOT any(x IN ['Alice'] WHERE x = p.name) RETURN p.name AS name ORDER BY name");

    List<String> names = new ArrayList<>();
    while (rs.hasNext()) names.add(rs.next().getProperty("name"));

    assertThat(names).containsExactly("Bob", "Charlie");
  }


import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertEquals;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This import is unnecessary if we stick to AssertJ (assertThat) which is used throughout the rest of the file. It's better to maintain consistency in the testing framework used.

@codacy-production
Copy link
Copy Markdown

codacy-production Bot commented Apr 17, 2026

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes. Give us feedback

@lvca
Copy link
Copy Markdown
Member

lvca commented Apr 17, 2026

Thanks for the PR! Here are my findings after reviewing the change and reproducing locally.

Critical

1. The new tests pass without the fix.
I checked out current main (PR fix NOT applied), cherry-picked only the test file from this PR, and ran OpenCypherWhereClauseTest: all 48 tests pass, including testAnyPredicateInWhere and testNotAnyPredicateInWhere. I also wrote an independent reproduction at the outer-class level (with the proper Person schema) and it also passes without the fix. The described symptom (query returning Alice, Bob, Charlie) is not reproducible on current main, so these are not valid regression tests.

2. Tests are placed in the wrong nested class.
They were appended at the end of the file, which puts them inside NotOperatorParenthesesRegression. That nested class has its own @BeforeEach creating DOCUMENT/CHUNK/IMAGE only - there is no Person vertex type. The tests only work by implicit auto-creation on CREATE (:Person ...), which is fragile and unrelated to what the inner class is testing. Please either move them to the outer class or add a new nested class (e.g. AnyPredicateInWhereRegression) with a proper setUp that creates Person.

3. Project conventions not followed.
Uses JUnit assertEquals instead of AssertJ assertThat(...).containsExactly(...). The codebase (and CLAUDE.md) mandate AssertJ. The added import static org.junit.jupiter.api.Assertions.assertEquals; should be removed.

Style

  • WhereClause.java:182-184: indentation is broken. The else if sits on a separate line after the previous }, and the first body line is 4-space indented while the rest uses 6-space. Please join } else if (...) onto one line and normalize indentation to match the surrounding branches.
  • The new test methods use inconsistent indentation (8 vs 4 spaces) instead of the project's 2-space standard.

Design / completeness

What's good

  • Diff is minimal and non-invasive.
  • The fix logic (collect outer variables, exclude the loop-bound iterator) is semantically correct and matches Neo4j behavior.
  • OpenCypherListPredicateTest (20 tests) and the rest of OpenCypherWhereClauseTest still pass with the fix applied.

Requested follow-up

Because the tests don't distinguish "with fix" from "without fix" on main, could you:

  1. Provide a failing query/test on a specific main SHA that actually exercises the bug (possibly only via the HTTP API serializer: studio path mentioned in the issue), or
  2. Reframe the change on optimization grounds (enabling filter pushdown for list predicates) rather than as a correctness fix?

Once the points above are addressed, I'm happy to take another look.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 18, 2026

Codecov Report

❌ Patch coverage is 27.77778% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.83%. Comparing base (7656946) to head (d442472).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
...com/arcadedb/query/opencypher/ast/WhereClause.java 27.77% 10 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3891      +/-   ##
==========================================
- Coverage   63.87%   63.83%   -0.05%     
==========================================
  Files        1591     1591              
  Lines      118934   118948      +14     
  Branches    25275    25277       +2     
==========================================
- Hits        75968    75926      -42     
- Misses      32489    32548      +59     
+ Partials    10477    10474       -3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@subha0319 subha0319 marked this pull request as draft April 18, 2026 07:51
@subha0319 subha0319 changed the title fix(opencypher): collect variables from ListPredicateExpression in WHERE clause feat(opencypher): enable filter pushdown for list predicates (any/all/none/single) in WHERE clause Apr 22, 2026
@subha0319
Copy link
Copy Markdown
Contributor Author

@lvca

Thanks for the thorough review. I've addressed all points.

On the bug reproduction: I reproduced via ArcadeDB Studio using the OpenCypher HTTP API with the exact queries from issue #3888. On current main, both any(...) and NOT any(...) return correct results. The bug does not reproduce, confirming your finding. I've reframed this PR as an optimization as you suggested.

Changes made:

  1. Tests moved into a new ListPredicateFilterPushdownTest nested class with its own @BeforeEach that creates a proper Person schema: no longer inside NotOperatorParenthesesRegression
  2. Replaced assertEquals with AssertJ assertThat(...).containsExactly(...) and removed the JUnit import
  3. Fixed indentation in WhereClause.java: } else if joined onto one line, normalized to 2-space indent
  4. Added coverage for all four list predicates: any, NOT any, all, none, single
  5. Applied the same iterator-exclusion fix to the ListComprehensionExpression branch for consistency, as you suggested

Full OpenCypher suite passes with 0 failures.

Happy to make any further adjustments.

@subha0319 subha0319 marked this pull request as ready for review April 22, 2026 12:22
@lvca
Copy link
Copy Markdown
Member

lvca commented Apr 22, 2026

Perfect, thanks!!!

@lvca lvca merged commit e240704 into ArcadeData:main Apr 22, 2026
13 of 17 checks passed
@lvca lvca self-requested a review April 22, 2026 14:21
@lvca lvca added this to the 26.4.1 milestone Apr 22, 2026
lvca added a commit that referenced this pull request Apr 22, 2026
…h list predicates

Locks in the PR #3891 fix by asserting that collectVariables() returns the
correct outer variables for any/all/none/single predicates used in a
comparison, and excludes the loop-scoped iterator from list comprehensions.
All six tests fail on the pre-fix WhereClause implementation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants