Skip to content

Commit 835d4f9

Browse files
giulio-leoneCopilot
andcommitted
fix(search): wrap group_ids OR clause in parentheses for BM25 queries
Without parentheses around the OR clause, Lucene operator precedence causes AND to bind tighter than OR, so only the last group_id is effectively filtered when multiple group_ids are provided. Before: group_id:"g1" OR group_id:"g2" AND (query) → parsed as: g1 OR (g2 AND query) After: (group_id:"g1" OR group_id:"g2") AND (query) → correctly filters by all group_ids Fixes #1249 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 2099603 commit 835d4f9

2 files changed

Lines changed: 38 additions & 3 deletions

File tree

graphiti_core/search/search_utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ def fulltext_query(query: str, group_ids: list[str] | None, driver: GraphDriver)
9898
for f in group_ids_filter_list:
9999
group_ids_filter += f if not group_ids_filter else f' OR {f}'
100100

101-
group_ids_filter += ' AND ' if group_ids_filter else ''
101+
if group_ids_filter:
102+
group_ids_filter = '(' + group_ids_filter + ') AND '
102103

103104
lucene_query = lucene_sanitize(query)
104105
# If the lucene query is too long return no query

tests/utils/search/search_utils_test.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
from unittest.mock import AsyncMock, patch
1+
from unittest.mock import AsyncMock, MagicMock, patch
22

33
import pytest
44

55
from graphiti_core.nodes import EntityNode
66
from graphiti_core.search.search_filters import SearchFilters
7-
from graphiti_core.search.search_utils import hybrid_node_search
7+
from graphiti_core.search.search_utils import fulltext_query, hybrid_node_search
88

99

1010
@pytest.mark.asyncio
@@ -161,3 +161,37 @@ async def test_hybrid_node_search_with_limit_and_duplicates():
161161
mock_similarity_search.assert_called_with(
162162
mock_driver, [0.1, 0.2, 0.3], SearchFilters(), ['1'], 4
163163
)
164+
165+
166+
class TestFulltextQueryGroupIds:
167+
"""Regression tests for group_ids filter parenthesization (issue #1249)."""
168+
169+
@staticmethod
170+
def _neo4j_driver() -> MagicMock:
171+
from graphiti_core.driver.driver import GraphProvider
172+
173+
driver = MagicMock()
174+
driver.provider = GraphProvider.NEO4J
175+
driver.fulltext_syntax = ''
176+
return driver
177+
178+
def test_single_group_id(self):
179+
driver = self._neo4j_driver()
180+
result = fulltext_query('hello', ['g1'], driver)
181+
assert result == '(group_id:"g1") AND (hello)'
182+
183+
def test_multiple_group_ids_parenthesized(self):
184+
"""The OR clause must be wrapped in parentheses so AND binds correctly."""
185+
driver = self._neo4j_driver()
186+
result = fulltext_query('hello', ['g1', 'g2', 'g3'], driver)
187+
assert result == '(group_id:"g1" OR group_id:"g2" OR group_id:"g3") AND (hello)'
188+
189+
def test_no_group_ids(self):
190+
driver = self._neo4j_driver()
191+
result = fulltext_query('hello', None, driver)
192+
assert result == '(hello)'
193+
194+
def test_empty_group_ids(self):
195+
driver = self._neo4j_driver()
196+
result = fulltext_query('hello', [], driver)
197+
assert result == '(hello)'

0 commit comments

Comments
 (0)