Python: Fix Postgres filter SQL composition#14018
Conversation
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 91%
✓ Correctness
The PR correctly migrates Postgres filter SQL construction from plain f-strings to psycopg's
sql.Composabletypes (sql.SQL,sql.Identifier,sql.Literal), fixing both the missing space before WHERE and the risk of filter predicates being embedded as quoted string literals instead of SQL expressions. The logic is preserved faithfully across all comparison, boolean, unary, attribute, constant, and list/tuple AST node cases. The_build_filterreturn contract (single composable vs list of composables) is properly handled by the existingisinstance(where_clauses, list)check. No correctness issues found.
✓ Security Reliability
This PR correctly migrates Postgres filter construction from manual string interpolation to psycopg's sql.Composable system (sql.Identifier for field names, sql.Literal for constants, sql.SQL for operators). The old code's manual escaping (e.g., replace("'", "'")) was fragile and led to the bug described in the PR rationale where filter predicates were treated as string literals rather than SQL expressions. The fix also addresses a missing space before WHERE. No new security or reliability issues are introduced.
✓ Test Coverage
The PR adds two well-structured regression tests that verify the core bug fix (filter predicates embedded as proper SQL composables with correct WHERE spacing). However, several changed code paths remain untested: the newly added ast.Tuple() handler, None/boolean literal rendering via sql.Literal (behavioral change from explicit string formatting), OR boolean operations, and chain comparisons. These represent moderate coverage gaps for a security-sensitive change (SQL composition).
✗ Design Approach
The composable SQL rewrite fixes the quoted-predicate bug, but it still leaves one supported filter shape semantically incorrect: null equality/inequality. Because
ast.Constant()now acceptsNoneas a literal whileast.Eqandast.NotEqstill always emit=and<>, a filter likelambda record: record.content_type == Nonecompiles to"content_type" = NULL, which silently filters out matching rows in Postgres.
Flagged Issues
- python/semantic_kernel/connectors/postgres.py:874-876 and 916 still compose null comparisons as
= NULL/<> NULL, so supported filters such aslambda record: record.content_type == Noneor!= Nonewill never behave correctly in Postgres. Null equality should be special-cased to emitIS NULL/IS NOT NULL.
Automated review by pragnyanramtha's agents
Motivation and Context
Fixes #13595.
Postgres vector-search lambda filters were parsed into plain strings and then passed through
psycopg.sql.SQL.format(). psycopg treats non-composable values as SQL literals, so filter predicates could be embedded as quoted strings instead of SQL expressions. The filtered query also missed a leading space beforeWHERE.Description
psycopg.sqlcomposables instead of plain stringssql.Identifierfor field names andsql.Literalfor constantsAND,OR,NOT,IN, and chained comparisons as SQL fragmentsContribution Checklist
Validation
From
python/:.venv/bin/python -m pytest tests/unit/connectors/memory/test_postgres_store.py -q-> 22 passed.venv/bin/ruff check semantic_kernel/connectors/postgres.py tests/unit/connectors/memory/test_postgres_store.py-> passed.venv/bin/ruff format --check semantic_kernel/connectors/postgres.py tests/unit/connectors/memory/test_postgres_store.py-> passedgit diff --check-> passed