diff --git a/python/packages/azure-cosmos/agent_framework_azure_cosmos/_vector_store.py b/python/packages/azure-cosmos/agent_framework_azure_cosmos/_vector_store.py index 13141c589df..15e8a044fd4 100644 --- a/python/packages/azure-cosmos/agent_framework_azure_cosmos/_vector_store.py +++ b/python/packages/azure-cosmos/agent_framework_azure_cosmos/_vector_store.py @@ -1004,7 +1004,7 @@ def _translate_filter( return "false" if operator == "eq" else f"IS_DEFINED({access})" parameter = _add_parameter(parameters, value) if operator == "eq": - return f"(IS_DEFINED({access}) AND {access} = {parameter})" + return f"(IS_DEFINED({access}) AND NOT IS_NULL({access}) AND {access} = {parameter})" return f"(IS_DEFINED({access}) AND (IS_NULL({access}) OR {access} != {parameter}))" if operator in ("gt", "gte", "lt", "lte", "between"): if field.type_ not in ("str", "int", "float"): diff --git a/python/packages/azure-cosmos/tests/azure_cosmos/test_vector_store.py b/python/packages/azure-cosmos/tests/azure_cosmos/test_vector_store.py index 108b12f3788..8819799ba9d 100644 --- a/python/packages/azure-cosmos/tests/azure_cosmos/test_vector_store.py +++ b/python/packages/azure-cosmos/tests/azure_cosmos/test_vector_store.py @@ -691,9 +691,14 @@ async def test_full_batch_item_size_and_json_preflight() -> None: ), ( Filter("text", "eq", "O'Reilly"), - '(IS_DEFINED(c["content"]) AND c["content"] = @filter_0)', + '(IS_DEFINED(c["content"]) AND NOT IS_NULL(c["content"]) AND c["content"] = @filter_0)', ["O'Reilly"], ), + ( + FilterGroup("not", [Filter("optional", "eq", "value")]), + '(NOT (IS_DEFINED(c["optional"]) AND NOT IS_NULL(c["optional"]) AND c["optional"] = @filter_0))', + ["value"], + ), ( Filter("text", "ne", "x"), '(IS_DEFINED(c["content"]) AND (IS_NULL(c["content"]) OR c["content"] != @filter_0))', @@ -765,7 +770,7 @@ async def test_full_batch_item_size_and_json_preflight() -> None: ], ) def test_filter_translation_matches_portable_semantics( - expression: Filter, + expression: Filter | FilterGroup, clause: str, values: list[Any], ) -> None: @@ -790,7 +795,7 @@ def test_filter_groups_and_rejections() -> None: clause, parameters = collection._prepare_filter(group) assert clause == ( '((IS_DEFINED(c["count"]) AND NOT IS_NULL(c["count"]) AND c["count"] >= @filter_0) ' - 'AND (NOT (IS_DEFINED(c["active"]) AND c["active"] = @filter_1)))' + 'AND (NOT (IS_DEFINED(c["active"]) AND NOT IS_NULL(c["active"]) AND c["active"] = @filter_1)))' ) assert [item["value"] for item in parameters] == [1, False] for expression, exception in ( @@ -958,7 +963,7 @@ async def test_filtered_get_query_is_parameterized_and_bounded() -> None: kwargs = container.query_items.call_args.kwargs query = kwargs["query"] assert "hello" not in query - assert 'WHERE (IS_DEFINED(c["content"]) AND c["content"] = @filter_0)' in query + assert 'WHERE (IS_DEFINED(c["content"]) AND NOT IS_NULL(c["content"]) AND c["content"] = @filter_0)' in query assert 'ORDER BY c["count"] DESC OFFSET @skip LIMIT @top' in query assert kwargs["parameters"] == [ {"name": "@filter_0", "value": "hello"}, diff --git a/python/packages/azure-cosmos/tests/azure_cosmos/test_vector_store_integration.py b/python/packages/azure-cosmos/tests/azure_cosmos/test_vector_store_integration.py index b51ffe849d8..bd1b5e510a1 100644 --- a/python/packages/azure-cosmos/tests/azure_cosmos/test_vector_store_integration.py +++ b/python/packages/azure-cosmos/tests/azure_cosmos/test_vector_store_integration.py @@ -269,6 +269,8 @@ async def test_disposable_vector_database_crud_filter_and_search() -> None: } assert await default_collection.get(filter=Filter("count", "eq", True)) == [] assert await default_collection.get(filter=Filter("active", "eq", 1)) == [] + not_value = FilterGroup("not", [Filter("optional", "eq", "value")]) + assert {item["key"] for item in await default_collection.get(filter=not_value)} == {"one", "three"} missing_optional = {**multi_records[0], "id": "missing"} missing_optional.pop("optional")