fix(vector_stores/opensearch): translate the '*' wildcard to an exists query for every key (#6522)
This commit is contained in:
@@ -35,8 +35,16 @@ def _build_filter_clauses(filters):
|
||||
for key, value in (filters or {}).items():
|
||||
if value is None:
|
||||
continue
|
||||
if key not in _IDENTITY_FILTER_KEYS and (not isinstance(value, (str, int, float, bool)) or value == "*"):
|
||||
logger.debug(f"Ignoring non-scalar or wildcard filter value for key {key!r}")
|
||||
if value == "*":
|
||||
# "Any value" wildcard (a documented Platform pattern): match
|
||||
# documents where the field exists — as opensearch.ts already
|
||||
# does for every key — instead of a literal, near-always-empty
|
||||
# term match on the string "*".
|
||||
_validate_filter(key, value)
|
||||
filter_clauses.append({"exists": {"field": f"payload.{key}"}})
|
||||
continue
|
||||
if key not in _IDENTITY_FILTER_KEYS and not isinstance(value, (str, int, float, bool)):
|
||||
logger.debug(f"Ignoring non-scalar filter value for key {key!r}")
|
||||
continue
|
||||
_validate_filter(key, value)
|
||||
field = f"payload.{key}.keyword" if isinstance(value, str) else f"payload.{key}"
|
||||
|
||||
@@ -643,13 +643,49 @@ class TestOpenSearchCustomFilters(TestOpenSearchFilterValidation):
|
||||
clauses = self._search_body()["query"]["bool"]["filter"]
|
||||
self.assertEqual(clauses, [{"term": {"payload.user_id.keyword": "alice"}}])
|
||||
|
||||
def test_search_ignores_wildcard_filter_value(self):
|
||||
def test_search_translates_wildcard_filter_value_to_exists(self):
|
||||
self.os_db.search(query="", vectors=[[0.1] * 1536], filters={"user_id": "alice", "category": "*"})
|
||||
clauses = self._search_body()["query"]["bool"]["filter"]
|
||||
self.assertEqual(clauses, [{"term": {"payload.user_id.keyword": "alice"}}])
|
||||
self.assertEqual(
|
||||
clauses,
|
||||
[
|
||||
{"term": {"payload.user_id.keyword": "alice"}},
|
||||
{"exists": {"field": "payload.category"}},
|
||||
],
|
||||
)
|
||||
|
||||
def test_list_ignores_or_operator_filter(self):
|
||||
self.os_db.list(filters={"user_id": "alice", "$or": [{"a": 1}]})
|
||||
self.client_mock.search.assert_called_once()
|
||||
clauses = self._search_body()["query"]["bool"]["filter"]
|
||||
self.assertEqual(clauses, [{"term": {"payload.user_id.keyword": "alice"}}])
|
||||
|
||||
|
||||
class TestOpenSearchWildcardFilters(TestOpenSearchFilterValidation):
|
||||
"""The "*" wildcard means "any value" (a documented Platform pattern) and
|
||||
must build an exists query — as opensearch.ts does — for every key,
|
||||
including the identity keys, instead of a literal term match on "*"."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.client_mock.search.return_value = {"hits": {"hits": []}}
|
||||
|
||||
def _search_body(self):
|
||||
return self.client_mock.search.call_args[1]["body"]
|
||||
|
||||
def test_identity_key_wildcard_builds_exists_query(self):
|
||||
self.os_db.search(query="", vectors=[[0.1] * 1536], filters={"agent_id": "*"})
|
||||
clauses = self._search_body()["query"]["bool"]["filter"]
|
||||
self.assertIn({"exists": {"field": "payload.agent_id"}}, clauses)
|
||||
self.assertNotIn({"term": {"payload.agent_id.keyword": "*"}}, clauses)
|
||||
|
||||
def test_custom_key_wildcard_builds_exists_query(self):
|
||||
self.os_db.list(filters={"category": "*"})
|
||||
clauses = self._search_body()["query"]["bool"]["filter"]
|
||||
self.assertIn({"exists": {"field": "payload.category"}}, clauses)
|
||||
|
||||
def test_wildcard_combines_with_term_filters(self):
|
||||
self.os_db.keyword_search(query="q", filters={"user_id": "u1", "topic": "*"})
|
||||
clauses = self._search_body()["query"]["bool"]["filter"]
|
||||
self.assertIn({"term": {"payload.user_id.keyword": "u1"}}, clauses)
|
||||
self.assertIn({"exists": {"field": "payload.topic"}}, clauses)
|
||||
|
||||
Reference in New Issue
Block a user