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
{{ message }}
Repository navigation
Ability to return an iterator on a "many" query #720
Hi, when a query return a myriad of objects, the slice based solution is not very efficient, it would be great if it was possible to have a way to return some kind of iterator on the rows, something like sql.Rows but type safe.
The first solution that comes to mind is to add a new type of function in the sql queries builder.
Instead of writing -- name: Name :many we would write -- name: Name :rows or another relevant keyword
and for the generated code, it would look something like this
CREATETABLEpilots (
id integerNOT NULL,
name textNOT NULL
);
ALTERTABLE pilots ADD CONSTRAINT pilot_pkey PRIMARY KEY (id);
@encendre thanks for the feature request. You're suggestion fixes a few of the issues we discussed when this first came up in #155. I'd ask you the same question I asked in that thread: can you use some type of paging to work through this query? It's not usually a good idea to request thousands of rows from a single query.
@encendre@kyleconroy In #155 I linked to a poc for streaming via an iterator func. It's used in a project that needs to stream out alot of entries. Paging should oc be preferred, but sometimes its not an option.
My team and I are considering using sqlc for our services and having this feature would be a huge pro for going for sqlc. Anyway, how could we help with this?
@pcriv I changed the generator to produce code like #155 (comment) - so you pass in a iterator func that handles the streaming. If the func returns error the stream is aborted. You tell me if its a nice way or not :P
That one looks good! but I assume you are using your fork for now. I was hoping there was a nice way of extending sqlc without having to rely on a fork 😅
Yes, I'm just using the fork. I could create a PR if @kyleconroy pre-approves the fundamentals in the approach. The PR would also need to have some documentation, and maybe also generate similar for Kotlin..? 😅
Hi @kyleconroy , the mentioned proposal has been accepted already. Would it be possible to go ahead with the requested feature? We also really need it for the case of exporting data in the streaming mode and where it is not convenient to chunk the data by running multiple queries.
We're also quite interested in start using the range over func for these queries - do you know if they'll be available any time soon? Or if there's a workaround to configure the generator to use these?
I don't think I've seen this pattern anywhere. However, passing a predicate into the function creating the iter.Seq seems like it aligns with the spirit of iterators.
Range over func will be enabled by default on Go 1.23, so we're now unblocked from the Go side of things.
Any updates regarding this feature, since there's no limitations from Go now?
Reacted by Christopher Hunter, Igor Agapie, KasperOmsK, Ruslan Cheshko, Yannay Hammer, Yuki Maeno, Jason Kingsbury, Lars Martens, Sjors Gielen, Mauro Pinto and 2 more
Proposal: opt-in iterator generation for :many queries (reviving #720)
Following up on the Feb 2026 ask for updates, and cross-linking #4464 — there is no open PR for this today (#3631 was closed without merge). Go 1.23+ iterators are stable; the remaining gap is an agreed API and config surface.
Below is a consolidated proposal that tries to resolve the open design threads from #3631 while keeping zero breaking changes.
Problem
sqlc's :many queries always materialize into []T. That is great DX for small result sets, but for exports, sync jobs, ETL, and backfills it forces O(n) heap allocation even when callers only need row-by-row processing. Workarounds (manual paging, forks, skipping sqlc on hot paths) all sacrifice type safety or maintenance.
Go 1.23 shipped iter.Seq / iter.Seq2. Kyle noted that this unblocks native iterator generation. The community need is still very much alive (#720, #4464, production patterns shared by @MatthiasKunnen).
Design principles
Do not change default :many behavior — ListAuthors(ctx) ([]Author, error) stays as-is.
Opt-in via config (and optionally per-query annotation).
Go 1.23 native idioms — primary output is iter.Seq2[T, error].
Lazy by default — the DB query runs on the first iteration step, not at method call (avoids dangling queries if the iterator is never consumed).
Correct lifecycle — defer rows.Close() inside the iterator closure (addresses gbarr's concern in #3631).
This aligns with Kyle's later preference in #3631 for direct for x, err := range q.Method(ctx) — no wrapper type in the common case, hard to misuse, early break still closes rows via defer.
Alternate styles (configurable, not all generated by default)
iterator_style
Generated method
Notes
seq2 (default)
IterAuthors(ctx) iter.Seq2[Author, error]
Recommended
callback
EachAuthor(ctx, func(Author) error) error
Optional sugar; can also be a one-liner at call sites
rows
*IterAuthorsRows with All() / Err() / Close()
Compatibility with #720 original request and #3631
Not proposing three methods per query by default — List* + Iter* covers slice vs stream; Each* is optional config.
iterator_start: eager (alternative): return (iter.Seq2[T, error], error) so connection errors surface before iteration — useful for some pgx setups, but lazy should be the default.
Parameterized queries
Same pattern — iterators mirror :many signatures:
-- name: ListAuthorsByIDs :manySELECT id, name, bio FROM authors WHERE id = ANY($1::int[]);
Config vs annotation: ship both emit_iterators: global and :stream explicit mode?
Eager mode: v1 or defer?
pgx: same PR or immediate follow-up?
Offer to implement
Happy to put together a minimal PoC PR once there is sign-off on the config surface and default API (seq2 + lazy + Iter prefix). Can start with one :many query, one engine, and an end-to-end test.
One-liner: sqlc already generates excellent type-safe Go; an opt-in emit_iterators flag would add lazy iter.Seq2[T, error] companions for :many — same type safety, O(1) memory per row, no breaking changes.
Includes emit_iterators config, lazy iter.Seq2[T, error] companions for :many, :stream / :many:stream annotations, stdlib + pgx/v5 templates, PRD doc, and runtime + codegen tests. Looking for maintainer sign-off on API/config before polishing toward a production PR.
For what it's worth, I've been using a iter.Seq heavy API library which uses this format:
objects, errf := c.GetObjects(ctx)
for object := range objects {
// ....
}
if err := errf(); err != nil {
// ...
}
In my experience working with this, this has been quite natural, any final GetObjects error is not inside the loop, and the Go linter gives an error when errf is not referenced (just like err is when not explicitly checked).
Activity
kyleconroy commentedon Oct 3, 2020
@encendre thanks for the feature request. You're suggestion fixes a few of the issues we discussed when this first came up in #155. I'd ask you the same question I asked in that thread: can you use some type of paging to work through this query? It's not usually a good idea to request thousands of rows from a single query.
eivindr commentedon Nov 6, 2020
@encendre @kyleconroy In #155 I linked to a poc for streaming via an iterator func. It's used in a project that needs to stream out alot of entries. Paging should oc be preferred, but sometimes its not an option.
pcriv commentedon Feb 12, 2021
My team and I are considering using sqlc for our services and having this feature would be a huge pro for going for sqlc. Anyway, how could we help with this?
Edit: We also need this for streaming records
pcriv commentedon Mar 17, 2021
@eivindr Did you find a nice way to extend sqlc to support the iterator?
eivindr commentedon Mar 17, 2021
@pcriv I changed the generator to produce code like #155 (comment) - so you pass in a iterator func that handles the streaming. If the func returns error the stream is aborted. You tell me if its a nice way or not :P
pcriv commentedon Mar 17, 2021
That one looks good! but I assume you are using your fork for now. I was hoping there was a nice way of extending sqlc without having to rely on a fork 😅
eivindr commentedon Mar 17, 2021
Yes, I'm just using the fork. I could create a PR if @kyleconroy pre-approves the fundamentals in the approach. The PR would also need to have some documentation, and maybe also generate similar for Kotlin..? 😅
pcriv commentedon Mar 17, 2021
Having this in sqlc would be a huge win for us :) If I can help somehow please let me know
pcriv commentedon May 4, 2021
@eivindr maybe you can just open the PR and we can continue the discussion there?
kyleconroy commentedon Sep 22, 2023
We're planning on adding support for this if the "range over func" proposal is accepted: golang/go#61405
dskarataev commentedon May 17, 2024
Hi @kyleconroy , the mentioned proposal has been accepted already. Would it be possible to go ahead with the requested feature? We also really need it for the case of exporting data in the streaming mode and where it is not convenient to chunk the data by running multiple queries.
Thanks a lot in advance!
kyleconroy commentedon May 17, 2024
Range over func will be enabled by default on Go 1.23, so we're now unblocked from the Go side of things.
tgmendes commentedon Sep 9, 2024
Hi @kyleconroy 👋
We're also quite interested in start using the range over func for these queries - do you know if they'll be available any time soon? Or if there's a workaround to configure the generator to use these?
Thanks!
viewsharp commentedon Sep 26, 2024
Hi @kyleconroy
Can I help you with implementation of this feature?
For me, the following implementation of iteration is preferable:
kyleconroy commentedon Nov 4, 2024
I found this article really helpful in understanding how to range over functions in Go 1.23 https://eli.thegreenplace.net/2024/ranging-over-functions-in-go-123/
crhntr commentedon Dec 21, 2024
I don't think I've seen this pattern anywhere. However, passing a predicate into the function creating the iter.Seq seems like it aligns with the spirit of iterators.
https://go.dev/play/p/7es2uIvtUqI
Here is a variation with the error handling outside the loop: https://go.dev/play/p/GN0bjrx4_Pl
MatthiasKunnen commentedon Nov 18, 2025
I am using the following in production at the moment:
How to use
Generated code
I still have to look into whether context cancellation not being handled by pgx is expected.
e-gloo commentedon Feb 25, 2026
Any updates regarding this feature, since there's no limitations from Go now?
Tilzen commentedon Jun 14, 2026
Proposal: opt-in iterator generation for
:manyqueries (reviving #720)Following up on the Feb 2026 ask for updates, and cross-linking #4464 — there is no open PR for this today (#3631 was closed without merge). Go 1.23+ iterators are stable; the remaining gap is an agreed API and config surface.
Below is a consolidated proposal that tries to resolve the open design threads from #3631 while keeping zero breaking changes.
Problem
sqlc's
:manyqueries always materialize into[]T. That is great DX for small result sets, but for exports, sync jobs, ETL, and backfills it forces O(n) heap allocation even when callers only need row-by-row processing. Workarounds (manual paging, forks, skipping sqlc on hot paths) all sacrifice type safety or maintenance.Go 1.23 shipped
iter.Seq/iter.Seq2. Kyle noted that this unblocks native iterator generation. The community need is still very much alive (#720, #4464, production patterns shared by @MatthiasKunnen).Design principles
:manybehavior —ListAuthors(ctx) ([]Author, error)stays as-is.iter.Seq2[T, error].defer rows.Close()inside the iterator closure (addresses gbarr's concern in #3631).Proposed
sqlc.yamloptionsScope modes:
emit_iterators: true+iterator_scope: global:manyquery gets a streaming companioniterator_scope: explicit_only-- name: ListAuthors :many:streamor-- name: StreamAuthors :stream(#4464)Teams that want simple DX flip one flag; teams that want fine-grained control use annotations.
Generated API (recommended default:
seq2+lazy)SQL — unchanged:
Go — existing + new companion:
Usage:
This aligns with Kyle's later preference in #3631 for direct
for x, err := range q.Method(ctx)— no wrapper type in the common case, hard to misuse, earlybreakstill closes rows viadefer.Alternate styles (configurable, not all generated by default)
iterator_styleseq2(default)IterAuthors(ctx) iter.Seq2[Author, error]callbackEachAuthor(ctx, func(Author) error) errorrows*IterAuthorsRowswithAll()/Err()/Close()Not proposing three methods per query by default —
List*+Iter*covers slice vs stream;Each*is optional config.iterator_start: eager(alternative): return(iter.Seq2[T, error], error)so connection errors surface before iteration — useful for some pgx setups, but lazy should be the default.Parameterized queries
Same pattern — iterators mirror
:manysignatures:Suggested v1 scope
database/sqldriver path first (pgx follow-up)emit_iterators: true(document in release notes)Open questions for @kyleconroy / maintainers
ItervsStream?emit_iterators: globaland:streamexplicit mode?Offer to implement
Happy to put together a minimal PoC PR once there is sign-off on the config surface and default API (
seq2+ lazy +Iterprefix). Can start with one:manyquery, one engine, and an end-to-end test.One-liner: sqlc already generates excellent type-safe Go; an opt-in
emit_iteratorsflag would add lazyiter.Seq2[T, error]companions for:many— same type safety, O(1) memory per row, no breaking changes.Related: #4464, #4108, closed reference #3631.
Tilzen commentedon Jun 14, 2026
Opened a proof-of-concept PR (not merge-ready) implementing the proposal above for early feedback:
#4488
PoC branch / reference: https://github.com/Tilzen/sqlc/tree/feat/emit-iterators-poc
Includes
emit_iteratorsconfig, lazyiter.Seq2[T, error]companions for:many,:stream/:many:streamannotations, stdlib + pgx/v5 templates, PRD doc, and runtime + codegen tests. Looking for maintainer sign-off on API/config before polishing toward a production PR.sgielen commentedon Jul 7, 2026
For what it's worth, I've been using a iter.Seq heavy API library which uses this format:
In my experience working with this, this has been quite natural, any final GetObjects error is not inside the loop, and the Go linter gives an error when
errfis not referenced (just likeerris when not explicitly checked).iitsdani commentedon Jul 8, 2026
Agreed with @sgielen - the smart pointer approach also mimics what
sql.Rowsdoes (where.Next()is replaced by theiter.Seqpattern).