You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
For :many queries that contain DML + RETURNING (e.g. WITH cte AS (...) UPDATE ... RETURNING ...), the async querier emits await self._conn.stream(...). SQLAlchemy's AsyncConnection.stream() opens a psycopg server-side cursor (DECLARE ... CURSOR FOR ...), and Postgres rejects DML inside a DECLARE CURSOR with syntax error at or near "UPDATE". The query is unrunnable from the generated wrapper.
The sync codepath uses .execute() and works correctly for the same SQL — only the async path is affected.
-- name: BumpWidgets :many
WITH selected AS (
SELECT id FROM widgets WHERE counter < $1LIMIT $2
)
UPDATE widgets
SET counter =widgets.counter+1FROM selected
WHEREwidgets.id=selected.id
RETURNING widgets.id, widgets.counter;
psycopg.errors.SyntaxError: syntax error at or near "UPDATE"
LINE 5: UPDATE widgets
^
Expected
Async :many should work for the same SQL the sync :many path handles correctly today. The CTE + UPDATE + RETURNING pattern is canonical for any batched-update workload.
Workaround
Bypass the generated wrapper and run the SQL through conn.execute(...) from app code, importing the SQL constant from the generated module. Survives sqlc generate because the generated file is left untouched.
Suggested fix direction
The bug is isolated to the async :many branch in internal/gen.go (~lines 1022-1040). Sync :many already uses execute correctly. A minimal patch swaps stream → execute and AsyncFor → For over an awaited Result. Behavioral change: rows are buffered into the result before iteration rather than streamed — same memory profile as the sync path. For very large result sets, an opt-in keeps streaming via a per-query annotation or a config option, but defaulting to execute matches sync behavior and avoids the silent DML breakage.
Activity
Make the async code match the sync code, keep the asyncIterator so in…
borissmidt commentedon Jun 1, 2026
FYI you can use my fork for the meantime using:
It was an easy change.
Ideally i think sqlc could have an 'upgrade' with a
:streamsetting especially now that golang has iterators.