Skip to content

fix(postgrest): normalize boolean values in is_() to lowercase literals#1535

Open
winklemad wants to merge 1 commit into
supabase:mainfrom
winklemad:fix/postgrest-is-boolean-literal
Open

fix(postgrest): normalize boolean values in is_() to lowercase literals#1535
winklemad wants to merge 1 commit into
supabase:mainfrom
winklemad:fix/postgrest-is-boolean-literal

Conversation

@winklemad

Copy link
Copy Markdown

No linked issue — found by reading the postgrest filter builder.

What kind of change does this PR introduce?

Bug fix.

What is the current behavior?

is_() normalizes Python None to the PostgREST literal null, but does nothing for Python booleans. A boolean therefore falls through to filter(), which formats it into the query string as is.True / is.False:

client.table("t").select("*").is_("active", True)
# -> ?active=is.True

PostgREST's is operator is case-sensitive and only accepts the lowercase trilean literals null, true, false, unknown (see PostgREST/postgrest#2077). is.True is rejected by the server, so .is_(col, True) / .is_(col, False) — the natural way to filter a boolean column against IS TRUE / IS FALSE — produce a failing request.

What is the new behavior?

is_() converts True/False to "true"/"false", using is True / is False identity checks so only real booleans are touched — strings such as "unknown" / "not_null" and any other value pass through unchanged. This matches supabase-js, whose .is('col', true) emits is.true.

client.table("t").select("*").is_("active", True)   # -> ?active=is.true
client.table("t").select("*").is_("active", False)  # -> ?active=is.false
client.table("t").select("*").is_("active", None)   # -> ?active=is.null   (unchanged)

Additional context

The fix lives in the shared base_request_builder.py, so it covers both the sync and async clients. Added test_is_true / test_is_false / test_is_none to the postgrest filter-builder tests (_async and _sync). The full postgrest unit suite passes and ruff check + format are clean.

`is_()` already converts Python `None` to the PostgREST literal `"null"`,
but Python booleans fell through to `filter()`, which formats them as
`is.True` / `is.False`. PostgREST's `is` operator is case-sensitive and
only accepts lowercase `null`/`true`/`false`/`unknown`, so those requests
are rejected by the server. Convert `True`/`False` to `"true"`/`"false"`
(matching supabase-js, which emits `is.true`).
@winklemad
winklemad requested review from a team and o-santi as code owners July 10, 2026 14:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant