Skip to content

fix(search): parenthesize group_id OR-chain in multi-group fulltext queries#1581

Open
Saltasm wants to merge 1 commit into
getzep:mainfrom
Saltasm:fix/fulltext-query-multigroup-parens
Open

fix(search): parenthesize group_id OR-chain in multi-group fulltext queries#1581
Saltasm wants to merge 1 commit into
getzep:mainfrom
Saltasm:fix/fulltext-query-multigroup-parens

Conversation

@Saltasm

@Saltasm Saltasm commented Jun 14, 2026

Copy link
Copy Markdown

Problem

The Neo4j fulltext query builders join group filters with OR, then append the query terms with AND, without parenthesizing the OR-chain:

group_id:"a" OR group_id:"b" OR group_id:"c" AND (terms)

Lucene's classic query parser treats AND as promoting its adjacent clauses to MUST, so this parses as:

clause occur
group_id:"a" SHOULD
group_id:"b" SHOULD
group_id:"c" MUST (promoted by the following AND)
(terms) MUST

A BooleanQuery with any MUST clause requires only the MUST clauses, so this effectively becomes (group_id:"c" AND terms): the leading groups become scoring-only and the query terms are scoped to just the last group_id. A multi-group fulltext search therefore silently returns matches from only the last group passed (and zero rows when that group is empty), while the semantic leg of hybrid search masks the regression.

Fix

Wrap the OR-chain in parens so the terms apply across all groups:

(group_id:"a" OR group_id:"b" OR group_id:"c") AND (terms)

Applied to both Neo4j builders that have the issue:

  • graphiti_core/search/search_utils.pyfulltext_query (neo4j branch)
  • graphiti_core/driver/neo4j/operations/search_ops.py_build_neo4j_fulltext_query

Single-group queries are unaffected (group_id:"a" AND (terms) already parses correctly). FalkorDB and Kuzu builders are unaffected — they already parenthesize / use different syntax.

Verification

With the parenthesized query, an identical multi-group search returns the same cross-group result set regardless of the order group_ids are passed; without it, results collapse to whichever group_id is passed last.

…ueries

The Neo4j fulltext query builders join group filters with OR, then append the
query terms with AND, without parenthesizing the OR-chain:

    group_id:"a" OR group_id:"b" OR group_id:"c" AND (terms)

Lucene's classic query parser treats AND as binding the immediately adjacent
clauses, so this parses as:

    group_id:"a"  SHOULD
    group_id:"b"  SHOULD
    group_id:"c"  MUST   <- promoted by the following AND
    (terms)       MUST

A BooleanQuery with any MUST clause requires only the MUST clauses, so the query
collapses to (group_id:"c" AND terms): groups a and b become scoring-only and the
terms are effectively scoped to just the last group. A search over multiple groups
silently returns matches from only the last group_id passed (and zero rows when
that last group is empty), while the semantic/cosine leg masks it.

Fix: wrap the OR-chain in parens so the terms apply across all groups:

    (group_id:"a" OR group_id:"b" OR group_id:"c") AND (terms)

Applied to both Neo4j builders that have the issue:
- graphiti_core/search/search_utils.py (fulltext_query, neo4j branch)
- graphiti_core/driver/neo4j/operations/search_ops.py (_build_neo4j_fulltext_query)

Single-group queries are unaffected (a AND terms parses correctly); FalkorDB and
Kuzu builders already parenthesize / use different syntax.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
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.

1 participant