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():
|
for key, value in (filters or {}).items():
|
||||||
if value is None:
|
if value is None:
|
||||||
continue
|
continue
|
||||||
if key not in _IDENTITY_FILTER_KEYS and (not isinstance(value, (str, int, float, bool)) or value == "*"):
|
if value == "*":
|
||||||
logger.debug(f"Ignoring non-scalar or wildcard filter value for key {key!r}")
|
# "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
|
continue
|
||||||
_validate_filter(key, value)
|
_validate_filter(key, value)
|
||||||
field = f"payload.{key}.keyword" if isinstance(value, str) else f"payload.{key}"
|
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"]
|
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"}}])
|
||||||
|
|
||||||
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": "*"})
|
self.os_db.search(query="", vectors=[[0.1] * 1536], filters={"user_id": "alice", "category": "*"})
|
||||||
clauses = self._search_body()["query"]["bool"]["filter"]
|
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):
|
def test_list_ignores_or_operator_filter(self):
|
||||||
self.os_db.list(filters={"user_id": "alice", "$or": [{"a": 1}]})
|
self.os_db.list(filters={"user_id": "alice", "$or": [{"a": 1}]})
|
||||||
self.client_mock.search.assert_called_once()
|
self.client_mock.search.assert_called_once()
|
||||||
clauses = self._search_body()["query"]["bool"]["filter"]
|
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"}}])
|
||||||
|
|
||||||
|
|
||||||
|
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