Skip to main content
Status: AcceptedArea: BackendDate: 2026-08-12

Context

SQLAlchemy hybrid properties often expand to comparisons such as payment_lock_acquired_at <= cutoff or deleted_at IS NOT NULL. Wrapping such an expression in .is_(True) or .is_(False) produces an outer IS TRUE or IS FALSE node; PostgreSQL’s limited predicate implication logic may then fail to see the underlying indexable condition, particularly when matching a partial index, and choose a sequential scan instead.

Decision

In SQL predicate positions, write a boolean expression directly for truth and negate it with ~expression or sqlalchemy.not_(expression) for falsehood. Do not use .is_(True) or .is_(False) unless SQL identity semantics are intentionally required.
Identity tests remain valid when their three-valued behavior is the requirement. For example, expression.is_not(True) means “false or unknown” and therefore includes NULL, while ~expression means strict falsehood and excludes NULL. The rule does not apply to .is_(None) / .is_not(None), which express SQL null checks.

Consequences

  • PostgreSQL can see comparisons and null checks inside hybrid properties, making ordinary and partial indexes available to the planner.
  • Predicate code consistently uses expression for true and ~expression for false.
  • Every replacement must preserve the intended handling of NULL; IS NOT TRUE, projections, constraints, and other contexts where UNKNOWN is distinct require explicit review.
  • We accept that identity tests remain in exceptional cases and should make the required null semantics evident from their context or a short comment.

References