From 5f610b91f8c0343caa68fde9a911ca63fd4651a9 Mon Sep 17 00:00:00 2001 From: tgolob <34978067+tgolob@users.noreply.github.com> Date: Sat, 22 Aug 2026 10:02:50 -0400 Subject: [PATCH] fix(postgres): compose vector filters as SQL --- python/semantic_kernel/connectors/postgres.py | 7 ++-- .../connectors/memory/test_postgres_store.py | 34 ++++++++++++++++++- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/python/semantic_kernel/connectors/postgres.py b/python/semantic_kernel/connectors/postgres.py index 5a7e66c15880..38e5f8d5411b 100644 --- a/python/semantic_kernel/connectors/postgres.py +++ b/python/semantic_kernel/connectors/postgres.py @@ -794,11 +794,12 @@ def _construct_vector_query( ) if where_clauses := self._build_filter(options.filter): # type: ignore - query += ( - sql.SQL("WHERE {clause}").format(clause=sql.SQL(" AND ").join(where_clauses)) + clause = ( + sql.SQL(" AND ").join(sql.SQL(where_clause) for where_clause in where_clauses) if isinstance(where_clauses, list) - else sql.SQL("WHERE {clause}").format(clause=where_clauses) + else sql.SQL(where_clauses) ) + query += sql.SQL(" WHERE {clause}").format(clause=clause) query += sql.SQL(" ORDER BY {dist_col} LIMIT {limit}").format( dist_col=sql.Identifier(self._distance_column_name), diff --git a/python/tests/unit/connectors/memory/test_postgres_store.py b/python/tests/unit/connectors/memory/test_postgres_store.py index 8f417cf7036b..439656e4d014 100644 --- a/python/tests/unit/connectors/memory/test_postgres_store.py +++ b/python/tests/unit/connectors/memory/test_postgres_store.py @@ -17,7 +17,13 @@ PostgresSettings, PostgresStore, ) -from semantic_kernel.data.vector import DistanceFunction, IndexKind, VectorStoreField, vectorstoremodel +from semantic_kernel.data.vector import ( + DistanceFunction, + IndexKind, + VectorSearchOptions, + VectorStoreField, + vectorstoremodel, +) @fixture(scope="function") @@ -322,6 +328,32 @@ def model_post_init(self, context: Any) -> None: assert statement_str == expected_statement +@pytest.mark.parametrize( + "filter_, expected_clause", + [ + ("lambda x: x.id == 1", 'WHERE "id" = 1'), + (["lambda x: x.id == 1", "lambda x: x.id > 0"], 'WHERE "id" = 1 AND "id" > 0'), + ], +) +def test_vector_search_filter_is_composed_as_sql(filter_, expected_clause) -> None: + """Filter predicates must remain SQL fragments instead of quoted string literals.""" + pool = AsyncConnectionPool(open=False) + collection = PostgresCollection( + collection_name="test_collection", + record_type=SimpleDataModel, + connection_pool=pool, + ) + + query, _, _ = collection._construct_vector_query( + vector=[1.0, 2.0, 3.0], + options=VectorSearchOptions(filter=filter_, top=3), + ) + + query_string = query.as_string() + assert f'FROM "public"."test_collection" {expected_clause} ORDER BY' in query_string + assert '\'"id"' not in query_string + + async def test_model_post_init_conflicting_distance_column_name(vector_store: PostgresStore) -> None: @vectorstoremodel @dataclass