Title: Async :many codegen breaks for DML + RETURNING queries (uses .stream() server-side cursor) · Issue #101 · sqlc-dev/sqlc-gen-python · GitHub
Open Graph Title: Async :many codegen breaks for DML + RETURNING queries (uses .stream() server-side cursor) · Issue #101 · sqlc-dev/sqlc-gen-python
X Title: Async :many codegen breaks for DML + RETURNING queries (uses .stream() server-side cursor) · Issue #101 · sqlc-dev/sqlc-gen-python
Description: Summary 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 (DEC...
Open Graph Description: Summary 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()...
X Description: Summary 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.stre...
Opengraph URL: https://github.com/sqlc-dev/sqlc-gen-python/issues/101
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Async :many codegen breaks for DML + RETURNING queries (uses .stream() server-side cursor)","articleBody":"## Summary\n\nFor `: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.\n\nThe sync codepath uses `.execute()` and works correctly for the same SQL — only the async path is affected.\n\n## Reproducer\n\n`schema.sql`\n```sql\nCREATE TABLE widgets (\n id BIGSERIAL PRIMARY KEY,\n counter INT NOT NULL DEFAULT 0\n);\n```\n\n`queries.sql`\n```sql\n-- name: BumpWidgets :many\nWITH selected AS (\n SELECT id FROM widgets WHERE counter \u003c $1 LIMIT $2\n)\nUPDATE widgets\nSET counter = widgets.counter + 1\nFROM selected\nWHERE widgets.id = selected.id\nRETURNING widgets.id, widgets.counter;\n```\n\n`sqlc.yaml`\n```yaml\nversion: \"2\"\nplugins:\n - name: py\n wasm:\n url: https://downloads.sqlc.dev/plugin/sqlc-gen-python_1.3.0.wasm\n sha256: fbedae96b5ecae2380a70fb5b925fd4bff58a6cfb1f3140375d098fbab7b3a3c\nsql:\n - schema: \"schema.sql\"\n queries: \"queries.sql\"\n engine: postgresql\n codegen:\n - plugin: py\n out: gen\n options:\n package: gen\n emit_async_querier: true\n emit_sync_querier: false\n```\n\nGenerated wrapper (abridged):\n```python\nasync def bump_widgets(self, *, threshold: int, limit: int) -\u003e AsyncIterator[models.Widget]:\n result = await self._conn.stream(\n sqlalchemy.text(BUMP_WIDGETS), {\"p1\": threshold, \"p2\": limit}\n )\n async for row in result:\n yield models.Widget(id=row[0], counter=row[1])\n```\n\nCalling it raises:\n```\npsycopg.errors.SyntaxError: syntax error at or near \"UPDATE\"\nLINE 5: UPDATE widgets\n ^\n```\n\n## Expected\n\nAsync `: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.\n\n## Workaround\n\nBypass 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.\n\n## Suggested fix direction\n\nThe 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.\n\nHappy to send a PR if there's interest.\n\n## Environment\n\n- sqlc-gen-python `1.3.0` (downloads.sqlc.dev wasm, sha256 fbedae96…)\n- sqlc CLI `1.31.1`\n- Postgres 18 + psycopg 3 + SQLAlchemy 2.0\n","author":{"url":"https://github.com/jrc2139","@type":"Person","name":"jrc2139"},"datePublished":"2026-05-05T23:34:48.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":1},"url":"https://github.com/101/sqlc-gen-python/issues/101"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:0d39f081-8598-dafb-3526-f832480d5177 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | CE68:2AE5C2:F923B0:168625E:6A54B3D9 |
| html-safe-nonce | 90da4ca65d648173fb039eb4c76d37b55da1534d945fff170d0b10716466268c |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJDRTY4OjJBRTVDMjpGOTIzQjA6MTY4NjI1RTo2QTU0QjNEOSIsInZpc2l0b3JfaWQiOiI1MTMxMTA3NDA3ODAwNjExODAxIiwicmVnaW9uX2VkZ2UiOiJpYWQiLCJyZWdpb25fcmVuZGVyIjoiaWFkIn0= |
| visitor-hmac | 7d126511920952531c96a71cd1c4c07343134e2ca9e181a78b4d8ba297f94940 |
| hovercard-subject-tag | issue:4387720383 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/sqlc-dev/sqlc-gen-python/101/issue_layout |
| twitter:image | https://opengraph.githubassets.com/575403bd239978b0a406481837d9461cf1e333975be7fb5336495ba738b788d5/sqlc-dev/sqlc-gen-python/issues/101 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/575403bd239978b0a406481837d9461cf1e333975be7fb5336495ba738b788d5/sqlc-dev/sqlc-gen-python/issues/101 |
| og:image:alt | Summary 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()... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | jrc2139 |
| hostname | github.com |
| expected-hostname | github.com |
| None | b9a586c06a05a7a86fc7e3f4dbd03e42f6869085879aa184aa6369456dbd50fb |
| turbo-cache-control | no-preview |
| go-import | github.com/sqlc-dev/sqlc-gen-python git https://github.com/sqlc-dev/sqlc-gen-python.git |
| octolytics-dimension-user_id | 136738596 |
| octolytics-dimension-user_login | sqlc-dev |
| octolytics-dimension-repository_id | 548530161 |
| octolytics-dimension-repository_nwo | sqlc-dev/sqlc-gen-python |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 548530161 |
| octolytics-dimension-repository_network_root_nwo | sqlc-dev/sqlc-gen-python |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 5d343e1152573ef294b28f54e4a14fb5fdb3a5a1 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width