Skip to content

Group AND/OR under NOT in Neo4jVectorFilterExpressionConverter - #7222

Open
Rishavbhattarai wants to merge 1 commit into
spring-projects:mainfrom
Rishavbhattarai:neo4j-not-parentheses
Open

Rishavbhattarai wants to merge 1 commit into
spring-projects:mainfrom
Rishavbhattarai:neo4j-not-parentheses

Conversation

@Rishavbhattarai

Copy link
Copy Markdown

Neo4jVectorFilterExpressionConverter.doNot() prefixes the negated expression with NOT but adds no parentheses. When the operand is an AND or OR that isn't wrapped in a Group, the Cypher means something else:

Filter Cypher before Neo4j reads it as
b.not(b.and(b.eq("genre", "drama"), b.gt("year", 2020))) NOT node.genre = "drama" AND node.year > 2020 (NOT genre = "drama") AND year > 2020
b.not(b.or(b.eq("genre", "drama"), b.gt("year", 2020))) NOT node.genre = "drama" OR node.year > 2020 (NOT genre = "drama") OR year > 2020

(metadata. prefixes shortened.) Against Neo4j 5.24 with four documents, the OR filter returned 3 documents where SimpleVectorStore returns 1, and the AND filter returned 1 where it returns 3. The other converters avoid this because the default doNot() pushes the negation down into the terms. Neo4j overrides it.

The fix wraps an AND or OR operand in a Group before adding NOT, so it emits NOT (a AND b). Other operands are unchanged, so NIN still produces NOT node.x IN [...].

Tests:

  • Two converter tests in Neo4jVectorFilterExpressionConverterTests for NOT over an ungrouped AND and OR.
  • searchWithNegatedCompoundFilter in Neo4jVectorStoreIT runs both filters through FilterExpressionBuilder. That class needs OPENAI_API_KEY, so I ran it locally with a FixedDimensionEmbeddingModel swapped in (not part of this PR): it passes with the change and fails without it, and searchWithFilters still passes.
  • ./mvnw -pl vector-stores/spring-ai-neo4j-store test passes (31 tests).

Related: #6818 changes the parser so that NOT binds tighter than AND / OR in text filters. This PR is about the expression tree and fixes the builder case either way.

Neo4jVectorFilterExpressionConverter.doNot() wrote the negated
expression without parentheses. For NOT(a AND b) it emitted
`NOT a AND b`, which Cypher reads as `(NOT a) AND b`, so the filter
returned the wrong documents. The same happened for OR.

Wrap an AND or OR operand in a group before prefixing it with NOT.
Other operands, such as the IN used for NIN, are unchanged.

Add converter tests for NOT over AND and OR, and an integration test
that runs both filters against Neo4j.

Signed-off-by: Rishavbhattarai <bhattarai.rishav@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants