Skip to content

feat(postgrest): return a select builder from insert, upsert, update and delete - #1558

Open
karpovantonme wants to merge 1 commit into
supabase:mainfrom
karpovantonme:feat/postgrest-select-builder-after-write
Open

feat(postgrest): return a select builder from insert, upsert, update and delete#1558
karpovantonme wants to merge 1 commit into
supabase:mainfrom
karpovantonme:feat/postgrest-select-builder-after-write

Conversation

@karpovantonme

Copy link
Copy Markdown

Closes #1553.

Problem

insert() and upsert() return a query builder, update() and delete() return a filter builder. Neither type exposes single(), maybe_single(), csv(), explain(), order(), limit() or range(), so a chain that reads naturally in the JS client has no equivalent in Python:

client.from_("users").insert({"name": "n"}).select().single().execute()
#                                            ^^^^^^ returns self, so .single() does not exist

select() is defined on the query builder and returns self, which means the chain ends on a builder that never had single() to begin with. In supabase-js all four of these methods return a PostgrestFilterBuilder, and the transform methods hang off it — this is the parity gap the issue describes.

Fix

Return SelectRequestBuilder from insert(), upsert(), update() and delete().

SelectRequestBuilder already derives from the query builder and from the filter builder, so this only widens the surface. Every method that worked before still works, on the same object, with the same request state — filters after update() and delete() are unaffected, and nothing is removed or renamed. Callers who only use .execute() see no difference.

single() on a write is what PostgREST already supports: Prefer: return=representation is set by select(), and single() adds Accept: application/vnd.pgrst.object+json. The added tests assert exactly that, together with the HTTP method staying POST / PATCH / DELETE.

Note on the sync package

src/postgrest/src/postgrest/_sync/request_builder.py is edited by hand here rather than regenerated with make build-sync. On main, the generated output differs from the committed _sync in ways unrelated to this change — most visibly send_with_retry comes out with asyncio.sleep where the committed file has time.sleep, plus import ordering. Regenerating would have pulled that into this PR, so the same twelve lines were applied to _async and _sync instead. Happy to raise that separately if it is news to you.

Verification

  • make postgrest.pytest — 306 passed (8 new tests, spread across _async and _sync).
  • make postgrest.mypy — no issues in 30 source files.
  • uv run ruff check and uv run ruff format --check — clean.

…and delete

`insert()` and `upsert()` returned a query builder and `update()` and `delete()`
returned a filter builder. Neither exposes `single()`, `maybe_single()`, `csv()`,
`explain()`, `order()`, `limit()` or `range()`, so a chain that reads naturally
in the JS client has no equivalent in Python:

    client.from_("users").insert({...}).select().single().execute()

`select()` on those builders returns `self`, so the chain stops at a builder that
never had `single()` to begin with. In supabase-js all four of these methods
return a `PostgrestFilterBuilder`, which carries the transform methods.

Return `SelectRequestBuilder` from all four instead. It already derives from the
query builder and from the filter builder, so this only widens the surface: every
method that worked before still works, on the same object, with the same request
state. Filters after `update()` and `delete()` are unaffected.

The sync package is edited by hand rather than through `make build-sync`. The
generated output currently differs from the committed `_sync` in ways unrelated
to this change (`asyncio.sleep` in place of `time.sleep` in `send_with_retry`,
among others), so regenerating would have pulled unrelated breakage into this PR.

Closes supabase#1553
@karpovantonme
karpovantonme requested review from a team and o-santi as code owners August 2, 2026 21:15
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.

Allow selecting single row after INSERT

1 participant