From dd2f77c11f67888ce2d79908d1c2e74a4fd537ba Mon Sep 17 00:00:00 2001 From: Tilzen Date: Sun, 14 Jun 2026 16:09:01 -0300 Subject: [PATCH 1/2] feat(codegen/go): add opt-in iter.Seq2 companions for :many queries Implement emit_iterators PoC from docs/emit-iterators-prd.md: lazy iter.Seq2[T, error] methods alongside existing slice APIs, with global or explicit_only scope and :stream / :many:stream query annotations. Includes stdlib and pgx templates, config options, end-to-end testdata, and codegen integration tests. --- docs/emit-iterators-prd.md | 363 ++++++++++++++++++ internal/cmd/shim.go | 11 +- internal/codegen/golang/gen.go | 7 + internal/codegen/golang/imports.go | 4 + internal/codegen/golang/iterator.go | 82 ++++ internal/codegen/golang/iterator_test.go | 82 ++++ internal/codegen/golang/opts/options.go | 38 +- internal/codegen/golang/query.go | 20 +- internal/codegen/golang/result.go | 20 +- .../golang/templates/pgx/queryCode.tmpl | 31 ++ .../golang/templates/stdlib/queryCode.tmpl | 27 ++ internal/compiler/parse.go | 7 +- internal/config/v_one.go | 10 + .../endtoend/emit_iterators_codegen_test.go | 91 +++++ .../testdata/emit_iterators/stdlib/go/db.go | 31 ++ .../emit_iterators/stdlib/go/models.go | 15 + .../emit_iterators/stdlib/go/query.sql.go | 73 ++++ .../testdata/emit_iterators/stdlib/query.sql | 5 + .../testdata/emit_iterators/stdlib/schema.sql | 5 + .../testdata/emit_iterators/stdlib/sqlc.json | 14 + .../emit_iterators_explicit/stdlib/go/db.go | 31 ++ .../stdlib/go/models.go | 10 + .../stdlib/go/query.sql.go | 89 +++++ .../emit_iterators_explicit/stdlib/query.sql | 5 + .../emit_iterators_explicit/stdlib/schema.sql | 4 + .../emit_iterators_explicit/stdlib/sqlc.json | 14 + internal/metadata/meta.go | 26 +- internal/metadata/meta_test.go | 29 +- 28 files changed, 1110 insertions(+), 34 deletions(-) create mode 100644 docs/emit-iterators-prd.md create mode 100644 internal/codegen/golang/iterator.go create mode 100644 internal/codegen/golang/iterator_test.go create mode 100644 internal/endtoend/emit_iterators_codegen_test.go create mode 100644 internal/endtoend/testdata/emit_iterators/stdlib/go/db.go create mode 100644 internal/endtoend/testdata/emit_iterators/stdlib/go/models.go create mode 100644 internal/endtoend/testdata/emit_iterators/stdlib/go/query.sql.go create mode 100644 internal/endtoend/testdata/emit_iterators/stdlib/query.sql create mode 100644 internal/endtoend/testdata/emit_iterators/stdlib/schema.sql create mode 100644 internal/endtoend/testdata/emit_iterators/stdlib/sqlc.json create mode 100644 internal/endtoend/testdata/emit_iterators_explicit/stdlib/go/db.go create mode 100644 internal/endtoend/testdata/emit_iterators_explicit/stdlib/go/models.go create mode 100644 internal/endtoend/testdata/emit_iterators_explicit/stdlib/go/query.sql.go create mode 100644 internal/endtoend/testdata/emit_iterators_explicit/stdlib/query.sql create mode 100644 internal/endtoend/testdata/emit_iterators_explicit/stdlib/schema.sql create mode 100644 internal/endtoend/testdata/emit_iterators_explicit/stdlib/sqlc.json diff --git a/docs/emit-iterators-prd.md b/docs/emit-iterators-prd.md new file mode 100644 index 0000000000..e1d7f48137 --- /dev/null +++ b/docs/emit-iterators-prd.md @@ -0,0 +1,363 @@ +# PRD: Opt-in Iterator Generation for `:many` Queries + +**Status:** Proposal +**Target repo:** [sqlc-dev/sqlc](https://github.com/sqlc-dev/sqlc) +**Related issues:** [#720](https://github.com/sqlc-dev/sqlc/issues/720), [#4464](https://github.com/sqlc-dev/sqlc/issues/4464), [#4108](https://github.com/sqlc-dev/sqlc/issues/4108) +**Related PR (closed, reference only):** [#3631](https://github.com/sqlc-dev/sqlc/pull/3631) +**Author:** Community proposal (reviving stalled discussion) +**Last updated:** 2026-06-14 + +--- + +## 1. Problem Statement + +sqlc generates excellent type-safe Go code, but its default API for `:many` queries **always materializes the full result set into a slice**: + +```go +func (q *Queries) ListAuthors(ctx context.Context) ([]Author, error) +``` + +For large result sets (exports, sync jobs, ETL pipelines, backfills), this forces **O(n) heap allocation** even when the caller only needs to process rows one at a time. Alternatives today: + +| Workaround | Drawback | +|------------|----------| +| Manual paging with `LIMIT`/`OFFSET` | Extra query complexity; offset cost at scale; not always expressible | +| Fork sqlc or post-process generated code | Maintenance burden; loses upstream improvements | +| Skip sqlc for streaming paths | Loses type safety on the hot path | + +Go 1.23 shipped **range-over-function iterators** (`iter.Seq`, `iter.Seq2`). sqlc maintainers [noted in #720](https://github.com/sqlc-dev/sqlc/issues/720) that this unblocks native iterator generation. As of June 2026, **no implementation has merged**; [PR #3631](https://github.com/sqlc-dev/sqlc/pull/3631) was closed without merge after API design remained unresolved. + +--- + +## 2. Goals + +1. **Zero breaking changes** — existing `:many` → `[]T` APIs remain the default. +2. **Opt-in streaming** — callers choose slice vs iterator via config or query annotation. +3. **Native Go 1.23 idioms** — generate `iter.Seq2[T, error]` (primary) with optional alternate styles. +4. **Lazy evaluation** — query execution begins on first iteration, not at method call (configurable). +5. **Correct resource lifecycle** — `rows.Close()` on normal completion, early break, panic (via `defer`), and error paths. +6. **Incremental rollout** — Go + `database/sql` first; pgx/stdlib variants and other languages follow. + +## 3. Non-Goals (v1) + +- Replacing or changing default `:many` behavior. +- Automatic streaming for `:one`, `:exec`, or `:copyfrom`. +- Memory pooling / object reuse (future enhancement; see #3631 discussion). +- Server-side PostgreSQL cursors (`DECLARE`/`FETCH`) — separate feature ([#1517](https://github.com/sqlc-dev/sqlc/issues/1517)). +- Python/Kotlin generators in the initial PR (coordinate separately; see #4464). + +--- + +## 4. Proposed Configuration + +### 4.1 `sqlc.yaml` options + +```yaml +version: "2" +sql: + - schema: schema.sql + queries: queries.sql + engine: postgresql +gen: + go: + package: db + out: internal/db + + # --- Iterator options (all opt-in, defaults shown) --- + + emit_iterators: false + # When true, generate a streaming companion method for each :many query. + + iterator_scope: global + # global — all :many queries get a streaming method + # explicit_only — only queries annotated with :many:stream (or :stream) + + iterator_method_prefix: "Iter" + # ListAuthors → IterAuthors + # Set to "Stream" for StreamAuthors if preferred. + + iterator_style: seq2 + # seq2 — iter.Seq2[T, error] (recommended default) + # callback — EachAuthors(ctx, func(Author) error) error + # rows — *AuthorsRows with Next()/Scan()/Close()/Err() (legacy #720 style) + + iterator_start: lazy + # lazy — DB query runs on first iteration step (recommended) + # eager — DB query runs at method call; returns (seq, error) or (*Rows, error) +``` + +### 4.2 Query-level override (optional, for `iterator_scope: explicit_only`) + +```sql +-- name: ListAuthors :many:stream +SELECT id, name, bio FROM authors ORDER BY name; +``` + +Alternatively, a dedicated query kind (as proposed in #4464): + +```sql +-- name: StreamAuthors :stream +SELECT id, name, bio FROM authors ORDER BY name; +``` + +**Recommendation:** support **both** `emit_iterators: global` and `explicit_only` + `:stream` annotation so teams can choose DX vs fine-grained control. + +--- + +## 5. Generated API + +### 5.1 Default: `seq2` + `lazy` (recommended) + +**SQL (unchanged):** + +```sql +-- name: ListAuthors :many +SELECT id, name, bio FROM authors ORDER BY name; +``` + +**Generated Go:** + +```go +import "iter" + +const listAuthors = `-- name: ListAuthors :many +SELECT id, name, bio FROM authors ORDER BY name +` + +// Existing — unchanged +func (q *Queries) ListAuthors(ctx context.Context) ([]Author, error) { + // ... current implementation ... +} + +// New — opt-in via emit_iterators +func (q *Queries) IterAuthors(ctx context.Context) iter.Seq2[Author, error] { + return func(yield func(Author, error) bool) { + rows, err := q.db.QueryContext(ctx, listAuthors) + if err != nil { + yield(Author{}, err) + return + } + defer rows.Close() + + for rows.Next() { + var i Author + if err := rows.Scan(&i.ID, &i.Name, &i.Bio); err != nil { + yield(Author{}, err) + return + } + if !yield(i, nil) { + return // early break; defer closes rows + } + } + if err := rows.Err(); err != nil { + yield(Author{}, err) + } + } +} +``` + +**Caller usage:** + +```go +for author, err := range q.IterAuthors(ctx) { + if err != nil { + return fmt.Errorf("list authors: %w", err) + } + if err := process(author); err != nil { + return err + } +} +return nil +``` + +**Properties:** + +- **Lazy:** no DB round-trip until `range` begins. +- **No wrapper type** for the common case — aligns with Kyle's [later preference](https://github.com/sqlc-dev/sqlc/pull/3631) for `for x, err := range q.Method(ctx)`. +- **Errors in-band** via `Seq2` — familiar Go 1.23 pattern. +- **`break` / `return` safe:** `defer rows.Close()` runs on all exit paths. + +### 5.2 Alternate: `seq2` + `eager` + +For callers who want connection errors before iteration: + +```go +func (q *Queries) IterAuthors(ctx context.Context) (iter.Seq2[Author, error], error) { + rows, err := q.db.QueryContext(ctx, listAuthors) + if err != nil { + return nil, err + } + return func(yield func(Author, error) bool) { + defer rows.Close() + // ... same loop ... + }, nil +} +``` + +### 5.3 Alternate: `callback` style + +Sugar for callers who prefer a single error return: + +```go +func (q *Queries) EachAuthor(ctx context.Context, fn func(Author) error) error { + for author, err := range q.IterAuthors(ctx) { + if err != nil { + return err + } + if err := fn(author); err != nil { + return err + } + } + return nil +} +``` + +**Note:** `Each*` can be generated optionally or left as a one-liner at call sites. Generating **both** `Iter*` and `Each*` for every query adds API surface without much benefit — recommend **`seq2` only** in v1, with `callback` as an opt-in `iterator_style`. + +### 5.4 Alternate: `rows` style (compatibility with #720 / #3631) + +For teams migrating from manual `sql.Rows` patterns: + +```go +type IterAuthorsRows struct { /* rows, err */ } +func (q *Queries) IterAuthors(ctx context.Context) *IterAuthorsRows +func (r *IterAuthorsRows) All() iter.Seq2[Author, error] // or Rows(), Items() +func (r *IterAuthorsRows) Err() error +func (r *IterAuthorsRows) Close() error +``` + +Useful when lazy start + separate error channel is required; more boilerplate than `seq2`. + +--- + +## 6. Design Decisions & Rationale + +| Decision | Choice | Rationale | +|----------|--------|-----------| +| Break `:many` default? | **No** | Maintainer consensus (#720, #4464, Kyle) | +| Keyword vs yaml flag | **Both** | Global flag for DX; `:stream` for explicit control | +| Primary iterator type | `iter.Seq2[T, error]` | Go 1.23 stdlib idiom; Kyle referenced [Thibaut Rousseau's iterator post](https://blog.thibaut-rousseau.com/blog/writing-testing-a-paginated-api-iterator/) | +| Lazy vs eager default | **Lazy** | Avoids dangling queries if iterator is never consumed; matches pierrre/sgielen feedback in #3631 | +| Method naming | `Iter*` default, `Stream*` configurable | `Iter` matches PR #3631; `Stream` matches Kyle's early examples and #4464 | +| Generate 3 methods per query? | **No (v1)** | `List*` + `Iter*` sufficient; `Each*` is optional sugar | +| Min Go version | **1.23+** when `emit_iterators: true` | Required for `iter` package; document in release notes | +| pgx vs database/sql | **database/sql first** | Match existing codegen paths; pgx in follow-up | + +--- + +## 7. Error Handling Semantics + +### 7.1 `seq2` lazy mode + +| Event | Behavior | +|-------|----------| +| Query fails | First `yield(zero, err)`; iteration ends | +| Scan fails | `yield(zero, err)`; iteration ends | +| `rows.Err()` after loop | Final `yield(zero, err)` | +| Caller `break` / `yield` returns false | Loop stops; `defer rows.Close()` | +| Panic in caller loop body | `defer rows.Close()` still runs | + +### 7.2 Close-on-break concern (from #3631) + +[gbarr noted](https://github.com/sqlc-dev/sqlc/pull/3631) that without `defer rows.Close()` inside the iterator closure, a recovered panic leaves the connection mid-fetch. **All generated iterators MUST use `defer rows.Close()`** inside the `Seq2` closure. + +### 7.3 Context cancellation + +Callers may cancel via `ctx`. Behavior depends on driver: + +- `database/sql`: `rows.Next()` may block until cancel (driver-dependent). +- **v1:** pass `ctx` to `QueryContext`; document that full cancel propagation requires driver support. +- **Future:** optional `select { case <-ctx.Done(): ... }` in loop (as MatthiasKunnen uses with pgx). + +--- + +## 8. Parameterized Queries + +Iterators work identically for parameterized `:many` queries: + +```sql +-- name: ListAuthorsByIDs :many +SELECT id, name, bio FROM authors WHERE id = ANY($1::int[]); +``` + +```go +func (q *Queries) IterAuthorsByIDs(ctx context.Context, ids []int32) iter.Seq2[Author, error] +func (q *Queries) ListAuthorsByIDs(ctx context.Context, ids []int32) ([]Author, error) +``` + +Same SQL constant, same prepared statement wiring — only the result consumption differs. + +--- + +## 9. Implementation Plan + +### Phase 1 — Design sign-off (this proposal) + +- [ ] Post proposal to #720; cross-link #4464 +- [ ] Maintainer confirmation on: naming, lazy default, global vs explicit scope +- [ ] Agree v1 scope: Go + `database/sql` + PostgreSQL example + +### Phase 2 — PoC PR + +- [ ] Add config parsing in `internal/codegen/golang/opts` +- [ ] Extend `:many` code generation in `internal/codegen/golang/query.go` (or templates) +- [ ] Generate `Iter*` method alongside existing `List*` +- [ ] End-to-end test in `examples/` (pattern from #3631) +- [ ] Document in sqlc.dev docs + +### Phase 3 — Expand coverage + +- [ ] MySQL, SQLite engines +- [ ] pgx/v5 driver variant +- [ ] `iterator_style: rows` and `callback` if requested +- [ ] Python generator (coordinate with borissmidt / sqlc-gen-python) + +--- + +## 10. Testing Requirements + +1. **Unit:** generated code compiles with `go test` under Go 1.23+. +2. **Integration:** iterator returns all rows; early `break` closes rows (verify via connection pool or mock). +3. **Error paths:** query error, scan error, `rows.Err()` — each yields exactly one error and stops. +4. **Parity:** for same fixture, `List*` and collecting `Iter*` produce identical slices. +5. **Opt-out:** `emit_iterators: false` produces byte-identical output to today (regression). + +--- + +## 11. Open Questions for Maintainers + +1. **Preferred method prefix:** `Iter` vs `Stream`? +2. **Lazy default:** agree lazy is correct for v1? +3. **Global flag vs `:stream` only:** ship both? +4. **Eager mode:** worth exposing in v1 or defer? +5. **pgx:** same PR or immediate follow-up? +6. **Min Go version bump:** gate behind `emit_iterators` or raise global minimum? + +--- + +## 12. One-Line Pitch + +> sqlc generates type-safe Go that materializes every `:many` query into a slice; with Go 1.23, an opt-in `emit_iterators` flag can generate lazy `iter.Seq2[T, error]` companions — same type safety, O(1) memory per row, zero breaking changes. + +--- + +## Appendix A: Comparison with Closed PR #3631 + +| Aspect | PR #3631 | This proposal | +|--------|----------|---------------| +| Trigger | `:iter` query annotation | `emit_iterators` yaml + optional `:stream` | +| API | Wrapper type + `Iterate()` + `Err()` | `iter.Seq2` direct range (default) | +| Lazy start | Unclear / eager in PoC | Explicit `iterator_start: lazy` default | +| Config surface | None | Full yaml options | +| Status | Closed, not merged | — | + +This proposal incorporates #3631's implementation lessons and resolves the API debates raised in its review thread. + +## Appendix B: References + +- [#720 — Ability to return an iterator on a "many" query](https://github.com/sqlc-dev/sqlc/issues/720) +- [#4464 — add :stream keyword](https://github.com/sqlc-dev/sqlc/issues/4464) +- [#4108 — low-level prepare/bind helpers for streaming](https://github.com/sqlc-dev/sqlc/issues/4108) +- [PR #3631 — Ability to return an iterator on rows (closed)](https://github.com/sqlc-dev/sqlc/pull/3631) +- [Go 1.23 — range-over-func](https://go.dev/doc/go1.23#range-over-function) +- [Eli Bendersky — Ranging over functions in Go 1.23](https://eli.thegreenplace.net/2024/ranging-over-functions-in-go-123/) diff --git a/internal/cmd/shim.go b/internal/cmd/shim.go index 654500429a..8d656da89f 100644 --- a/internal/cmd/shim.go +++ b/internal/cmd/shim.go @@ -5,6 +5,7 @@ import ( "github.com/sqlc-dev/sqlc/internal/config" "github.com/sqlc-dev/sqlc/internal/config/convert" "github.com/sqlc-dev/sqlc/internal/info" + "github.com/sqlc-dev/sqlc/internal/metadata" "github.com/sqlc-dev/sqlc/internal/plugin" "github.com/sqlc-dev/sqlc/internal/sql/catalog" ) @@ -156,7 +157,7 @@ func pluginQueries(r *compiler.Result) []*plugin.Query { Name: q.Metadata.Name, Cmd: q.Metadata.Cmd, Text: q.SQL, - Comments: q.Metadata.Comments, + Comments: pluginQueryComments(q.Metadata), Columns: columns, Params: params, Filename: q.Metadata.Filename, @@ -166,6 +167,14 @@ func pluginQueries(r *compiler.Result) []*plugin.Query { return out } +func pluginQueryComments(md metadata.Metadata) []string { + comments := md.Comments + if md.Stream { + comments = append(comments, metadata.StreamAnnotationComment) + } + return comments +} + func pluginQueryColumn(c *compiler.Column) *plugin.Column { l := -1 if c.Length != nil { diff --git a/internal/codegen/golang/gen.go b/internal/codegen/golang/gen.go index 332fe2400a..a6de65ec5a 100644 --- a/internal/codegen/golang/gen.go +++ b/internal/codegen/golang/gen.go @@ -36,6 +36,9 @@ type tmplCtx struct { EmitPreparedQueries bool EmitInterface bool EmitEmptySlices bool + EmitIterators bool + IteratorStyle string + LazyIteratorStart bool EmitMethodsWithDBArgument bool EmitEnumValidMethod bool EmitAllEnumValues bool @@ -180,6 +183,9 @@ func generate(req *plugin.GenerateRequest, options *opts.Options, enums []Enum, EmitDBTags: options.EmitDbTags, EmitPreparedQueries: options.EmitPreparedQueries, EmitEmptySlices: options.EmitEmptySlices, + EmitIterators: options.EmitIterators && options.IteratorStyle == IteratorStyleSeq2, + IteratorStyle: options.IteratorStyle, + LazyIteratorStart: options.IteratorStart == IteratorStartLazy, EmitMethodsWithDBArgument: options.EmitMethodsWithDbArgument, EmitEnumValidMethod: options.EmitEnumValidMethod, EmitAllEnumValues: options.EmitAllEnumValues, @@ -226,6 +232,7 @@ func generate(req *plugin.GenerateRequest, options *opts.Options, enums []Enum, "emitPreparedQueries": tctx.codegenEmitPreparedQueries, "queryMethod": tctx.codegenQueryMethod, "queryRetval": tctx.codegenQueryRetval, + "zeroValue": func(v QueryValue) string { return v.zeroValue() }, } tmpl := template.Must( diff --git a/internal/codegen/golang/imports.go b/internal/codegen/golang/imports.go index 76964248ef..8dba210fb6 100644 --- a/internal/codegen/golang/imports.go +++ b/internal/codegen/golang/imports.go @@ -417,6 +417,10 @@ func (i *importer) queryImports(filename string) fileImports { std["fmt"] = struct{}{} } + if i.Options.EmitIterators && i.Options.IteratorStyle == IteratorStyleSeq2 { + std["iter"] = struct{}{} + } + return sortedImports(std, pkg) } diff --git a/internal/codegen/golang/iterator.go b/internal/codegen/golang/iterator.go new file mode 100644 index 0000000000..a29f98c651 --- /dev/null +++ b/internal/codegen/golang/iterator.go @@ -0,0 +1,82 @@ +package golang + +import ( + "strings" + + "github.com/sqlc-dev/sqlc/internal/codegen/golang/opts" + "github.com/sqlc-dev/sqlc/internal/metadata" +) + +const ( + IteratorScopeGlobal = "global" + IteratorScopeExplicitOnly = "explicit_only" + + IteratorStyleSeq2 = "seq2" + IteratorStyleCallback = "callback" + IteratorStyleRows = "rows" + + IteratorStartLazy = "lazy" + IteratorStartEager = "eager" +) + +func queryStreamAnnotated(comments []string) bool { + for _, c := range comments { + if c == metadata.StreamAnnotationComment || commentHasStreamAnnotation(c) { + return true + } + } + return false +} + +func filterStreamAnnotationComments(comments []string) []string { + if len(comments) == 0 { + return comments + } + out := make([]string, 0, len(comments)) + for _, c := range comments { + if c == metadata.StreamAnnotationComment { + continue + } + out = append(out, c) + } + return out +} + +func commentHasStreamAnnotation(s string) bool { + return strings.Contains(s, metadata.CmdStream) || + strings.Contains(s, metadata.CmdManyStream) +} + +func shouldEmitIterator(options *opts.Options, cmd string, comments []string) bool { + if !options.EmitIterators || cmd != metadata.CmdMany { + return false + } + switch options.IteratorScope { + case "", IteratorScopeGlobal: + return true + case IteratorScopeExplicitOnly: + return queryStreamAnnotated(comments) + default: + return false + } +} + +func iteratorMethodName(methodName, prefix string) string { + if prefix == "" { + prefix = "Iter" + } + for _, p := range []string{"List", "Get", "Find", "Select", "Fetch", "Stream"} { + if strings.HasPrefix(methodName, p) { + return prefix + strings.TrimPrefix(methodName, p) + } + } + return prefix + methodName +} + +func (v QueryValue) zeroValue() string { + t := v.DefineType() + if v.IsPointer() { + return "nil" + } + return t + "{}" +} diff --git a/internal/codegen/golang/iterator_test.go b/internal/codegen/golang/iterator_test.go new file mode 100644 index 0000000000..db4ec39cdf --- /dev/null +++ b/internal/codegen/golang/iterator_test.go @@ -0,0 +1,82 @@ +package golang + +import ( + "testing" + + "github.com/sqlc-dev/sqlc/internal/codegen/golang/opts" + "github.com/sqlc-dev/sqlc/internal/metadata" +) + +func TestIteratorMethodName(t *testing.T) { + t.Parallel() + tests := []struct { + method string + prefix string + want string + }{ + {"ListAuthors", "Iter", "IterAuthors"}, + {"GetAuthors", "Iter", "IterAuthors"}, + {"FindAuthors", "Iter", "IterAuthors"}, + {"Authors", "Iter", "IterAuthors"}, + {"StreamAuthors", "Iter", "IterAuthors"}, + } + for _, tc := range tests { + got := iteratorMethodName(tc.method, tc.prefix) + if got != tc.want { + t.Errorf("iteratorMethodName(%q, %q) = %q, want %q", tc.method, tc.prefix, got, tc.want) + } + } +} + +func TestQueryStreamAnnotated(t *testing.T) { + t.Parallel() + if !queryStreamAnnotated([]string{metadata.StreamAnnotationComment}) { + t.Fatal("expected stream annotation comment") + } + if queryStreamAnnotated(nil) { + t.Fatal("expected no annotation") + } +} + +func TestShouldEmitIterator(t *testing.T) { + t.Parallel() + global := &opts.Options{ + EmitIterators: true, + IteratorScope: IteratorScopeGlobal, + } + if !shouldEmitIterator(global, metadata.CmdMany, nil) { + t.Fatal("global scope should emit for :many") + } + if shouldEmitIterator(global, metadata.CmdOne, nil) { + t.Fatal(":one should not emit iterator") + } + + explicit := &opts.Options{ + EmitIterators: true, + IteratorScope: IteratorScopeExplicitOnly, + } + if shouldEmitIterator(explicit, metadata.CmdMany, nil) { + t.Fatal("explicit_only should skip unannotated :many") + } + if !shouldEmitIterator(explicit, metadata.CmdMany, []string{metadata.StreamAnnotationComment}) { + t.Fatal("explicit_only should emit annotated stream query") + } + + disabled := &opts.Options{EmitIterators: false, IteratorScope: IteratorScopeGlobal} + if shouldEmitIterator(disabled, metadata.CmdMany, nil) { + t.Fatal("emit_iterators false should not emit") + } +} + +func TestQueryValueZeroValue(t *testing.T) { + t.Parallel() + v := QueryValue{Struct: &Struct{Name: "Author"}} + if v.zeroValue() != "Author{}" { + t.Fatalf("got %q", v.zeroValue()) + } + v.EmitPointer = true + v.Struct = &Struct{Name: "Author"} + if v.zeroValue() != "nil" { + t.Fatalf("got %q", v.zeroValue()) + } +} diff --git a/internal/codegen/golang/opts/options.go b/internal/codegen/golang/opts/options.go index 646bf1e066..ed4255f233 100644 --- a/internal/codegen/golang/opts/options.go +++ b/internal/codegen/golang/opts/options.go @@ -16,7 +16,12 @@ type Options struct { EmitDbTags bool `json:"emit_db_tags" yaml:"emit_db_tags"` EmitPreparedQueries bool `json:"emit_prepared_queries" yaml:"emit_prepared_queries"` EmitExactTableNames bool `json:"emit_exact_table_names,omitempty" yaml:"emit_exact_table_names"` - EmitEmptySlices bool `json:"emit_empty_slices,omitempty" yaml:"emit_empty_slices"` + EmitEmptySlices bool `json:"emit_empty_slices,omitempty" yaml:"emit_empty_slices"` + EmitIterators bool `json:"emit_iterators,omitempty" yaml:"emit_iterators"` + IteratorScope string `json:"iterator_scope,omitempty" yaml:"iterator_scope"` + IteratorMethodPrefix string `json:"iterator_method_prefix,omitempty" yaml:"iterator_method_prefix"` + IteratorStyle string `json:"iterator_style,omitempty" yaml:"iterator_style"` + IteratorStart string `json:"iterator_start,omitempty" yaml:"iterator_start"` EmitExportedQueries bool `json:"emit_exported_queries" yaml:"emit_exported_queries"` EmitResultStructPointers bool `json:"emit_result_struct_pointers" yaml:"emit_result_struct_pointers"` EmitParamsStructPointers bool `json:"emit_params_struct_pointers" yaml:"emit_params_struct_pointers"` @@ -137,6 +142,19 @@ func parseOpts(req *plugin.GenerateRequest) (*Options, error) { *options.Initialisms = []string{"id"} } + if options.IteratorScope == "" { + options.IteratorScope = "global" + } + if options.IteratorMethodPrefix == "" { + options.IteratorMethodPrefix = "Iter" + } + if options.IteratorStyle == "" { + options.IteratorStyle = "seq2" + } + if options.IteratorStart == "" { + options.IteratorStart = "lazy" + } + options.InitialismsMap = map[string]struct{}{} for _, initial := range *options.Initialisms { options.InitialismsMap[initial] = struct{}{} @@ -169,6 +187,24 @@ func ValidateOpts(opts *Options) error { return fmt.Errorf("invalid options: query parameter limit must not be negative") } + if opts.EmitIterators { + switch opts.IteratorScope { + case "global", "explicit_only": + default: + return fmt.Errorf("invalid options: iterator_scope must be global or explicit_only") + } + switch opts.IteratorStyle { + case "seq2", "callback", "rows": + default: + return fmt.Errorf("invalid options: iterator_style must be seq2, callback, or rows") + } + switch opts.IteratorStart { + case "lazy", "eager": + default: + return fmt.Errorf("invalid options: iterator_start must be lazy or eager") + } + } + if err := validateModelsOptions(opts); err != nil { return err } diff --git a/internal/codegen/golang/query.go b/internal/codegen/golang/query.go index 27c596c24e..5543d52814 100644 --- a/internal/codegen/golang/query.go +++ b/internal/codegen/golang/query.go @@ -268,15 +268,17 @@ func (v QueryValue) VariableForField(f Field) string { // A struct used to generate methods and fields on the Queries struct type Query struct { - Cmd string - Comments []string - MethodName string - FieldName string - ConstantName string - SQL string - SourceName string - Ret QueryValue - Arg QueryValue + Cmd string + Comments []string + MethodName string + IterMethodName string + EmitIterator bool + FieldName string + ConstantName string + SQL string + SourceName string + Ret QueryValue + Arg QueryValue // Used for :copyfrom Table *plugin.Identifier } diff --git a/internal/codegen/golang/result.go b/internal/codegen/golang/result.go index c5126602da..9abfb8f5b2 100644 --- a/internal/codegen/golang/result.go +++ b/internal/codegen/golang/result.go @@ -201,7 +201,7 @@ func buildQueries(req *plugin.GenerateRequest, options *opts.Options, enums []En constantName = sdk.LowerTitle(query.Name) } - comments := query.Comments + comments := filterStreamAnnotationComments(query.Comments) if options.EmitSqlAsComment { if len(comments) == 0 { comments = append(comments, query.Name) @@ -218,14 +218,16 @@ func buildQueries(req *plugin.GenerateRequest, options *opts.Options, enums []En } gq := Query{ - Cmd: query.Cmd, - ConstantName: constantName, - FieldName: sdk.LowerTitle(query.Name) + "Stmt", - MethodName: query.Name, - SourceName: query.Filename, - SQL: query.Text, - Comments: comments, - Table: query.InsertIntoTable, + Cmd: query.Cmd, + ConstantName: constantName, + FieldName: sdk.LowerTitle(query.Name) + "Stmt", + MethodName: query.Name, + EmitIterator: shouldEmitIterator(options, query.Cmd, query.Comments), + IterMethodName: iteratorMethodName(query.Name, options.IteratorMethodPrefix), + SourceName: query.Filename, + SQL: query.Text, + Comments: comments, + Table: query.InsertIntoTable, } sqlpkg := parseDriver(options.SqlPackage) diff --git a/internal/codegen/golang/templates/pgx/queryCode.tmpl b/internal/codegen/golang/templates/pgx/queryCode.tmpl index 59a88c880a..c436fa8b5a 100644 --- a/internal/codegen/golang/templates/pgx/queryCode.tmpl +++ b/internal/codegen/golang/templates/pgx/queryCode.tmpl @@ -77,6 +77,37 @@ func (q *Queries) {{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) ([]{{.Ret. } return items, nil } +{{if and $.EmitIterators .EmitIterator}} +{{range .Comments}}//{{.}} +{{end -}} +func (q *Queries) {{.IterMethodName}}(ctx context.Context, {{ dbarg }} {{.Arg.Pair}}) iter.Seq2[{{.Ret.DefineType}}, error] { + return func(yield func({{.Ret.DefineType}}, error) bool) { + {{- if $.EmitMethodsWithDBArgument -}} + rows, err := db.Query(ctx, {{.ConstantName}}, {{.Arg.Params}}) + {{- else -}} + rows, err := q.db.Query(ctx, {{.ConstantName}}, {{.Arg.Params}}) + {{- end}} + if err != nil { + yield({{zeroValue .Ret}}, err) + return + } + defer rows.Close() + for rows.Next() { + var {{.Ret.Name}} {{.Ret.Type}} + if err := rows.Scan({{.Ret.Scan}}); err != nil { + yield({{zeroValue .Ret}}, err) + return + } + if !yield({{.Ret.ReturnName}}, nil) { + return + } + } + if err := rows.Err(); err != nil { + yield({{zeroValue .Ret}}, err) + } + } +} +{{end}} {{end}} {{if eq .Cmd ":exec"}} diff --git a/internal/codegen/golang/templates/stdlib/queryCode.tmpl b/internal/codegen/golang/templates/stdlib/queryCode.tmpl index 1e7f4e22a4..adc947f846 100644 --- a/internal/codegen/golang/templates/stdlib/queryCode.tmpl +++ b/internal/codegen/golang/templates/stdlib/queryCode.tmpl @@ -66,6 +66,33 @@ func (q *Queries) {{.MethodName}}(ctx context.Context, {{ dbarg }} {{.Arg.Pair}} } return items, nil } +{{if and $.EmitIterators .EmitIterator}} +{{range .Comments}}//{{.}} +{{end -}} +func (q *Queries) {{.IterMethodName}}(ctx context.Context, {{ dbarg }} {{.Arg.Pair}}) iter.Seq2[{{.Ret.DefineType}}, error] { + return func(yield func({{.Ret.DefineType}}, error) bool) { + {{- template "queryCodeStdExec" . }} + if err != nil { + yield({{zeroValue .Ret}}, err) + return + } + defer rows.Close() + for rows.Next() { + var {{.Ret.Name}} {{.Ret.Type}} + if err := rows.Scan({{.Ret.Scan}}); err != nil { + yield({{zeroValue .Ret}}, err) + return + } + if !yield({{.Ret.ReturnName}}, nil) { + return + } + } + if err := rows.Err(); err != nil { + yield({{zeroValue .Ret}}, err) + } + } +} +{{end}} {{end}} {{if eq .Cmd ":exec"}} diff --git a/internal/compiler/parse.go b/internal/compiler/parse.go index 2f9afb72c1..061e151429 100644 --- a/internal/compiler/parse.go +++ b/internal/compiler/parse.go @@ -44,7 +44,7 @@ func (c *Compiler) parseQuery(stmt ast.Node, src string, o opts.Parser) (*Query, return nil, errors.New("missing semicolon at end of file") } - name, cmd, err := metadata.ParseQueryNameAndType(rawSQL, metadata.CommentSyntax(c.parser.CommentSyntax())) + name, cmd, stream, err := metadata.ParseQueryNameAndType(rawSQL, metadata.CommentSyntax(c.parser.CommentSyntax())) if err != nil { return nil, err } @@ -58,8 +58,9 @@ func (c *Compiler) parseQuery(stmt ast.Node, src string, o opts.Parser) (*Query, } md := metadata.Metadata{ - Name: name, - Cmd: cmd, + Name: name, + Cmd: cmd, + Stream: stream, } // TODO eventually can use this for name and type/cmd parsing too diff --git a/internal/config/v_one.go b/internal/config/v_one.go index fa1a4d8d28..3491530ff8 100644 --- a/internal/config/v_one.go +++ b/internal/config/v_one.go @@ -34,6 +34,11 @@ type v1PackageSettings struct { EmitPreparedQueries bool `json:"emit_prepared_queries" yaml:"emit_prepared_queries"` EmitExactTableNames bool `json:"emit_exact_table_names,omitempty" yaml:"emit_exact_table_names"` EmitEmptySlices bool `json:"emit_empty_slices,omitempty" yaml:"emit_empty_slices"` + EmitIterators bool `json:"emit_iterators,omitempty" yaml:"emit_iterators"` + IteratorScope string `json:"iterator_scope,omitempty" yaml:"iterator_scope"` + IteratorMethodPrefix string `json:"iterator_method_prefix,omitempty" yaml:"iterator_method_prefix"` + IteratorStyle string `json:"iterator_style,omitempty" yaml:"iterator_style"` + IteratorStart string `json:"iterator_start,omitempty" yaml:"iterator_start"` EmitExportedQueries bool `json:"emit_exported_queries,omitempty" yaml:"emit_exported_queries"` EmitResultStructPointers bool `json:"emit_result_struct_pointers" yaml:"emit_result_struct_pointers"` EmitParamsStructPointers bool `json:"emit_params_struct_pointers" yaml:"emit_params_struct_pointers"` @@ -149,6 +154,11 @@ func (c *V1GenerateSettings) Translate() Config { EmitPreparedQueries: pkg.EmitPreparedQueries, EmitExactTableNames: pkg.EmitExactTableNames, EmitEmptySlices: pkg.EmitEmptySlices, + EmitIterators: pkg.EmitIterators, + IteratorScope: pkg.IteratorScope, + IteratorMethodPrefix: pkg.IteratorMethodPrefix, + IteratorStyle: pkg.IteratorStyle, + IteratorStart: pkg.IteratorStart, EmitExportedQueries: pkg.EmitExportedQueries, EmitResultStructPointers: pkg.EmitResultStructPointers, EmitParamsStructPointers: pkg.EmitParamsStructPointers, diff --git a/internal/endtoend/emit_iterators_codegen_test.go b/internal/endtoend/emit_iterators_codegen_test.go new file mode 100644 index 0000000000..b170758658 --- /dev/null +++ b/internal/endtoend/emit_iterators_codegen_test.go @@ -0,0 +1,91 @@ +package main + +import ( + "context" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/sqlc-dev/sqlc/internal/cmd" +) + +func TestEmitIteratorsCodegenGlobal(t *testing.T) { + t.Parallel() + ctx := context.Background() + + root := filepath.Join("testdata", "emit_iterators", "stdlib") + abs, err := filepath.Abs(root) + if err != nil { + t.Fatal(err) + } + + var stderr strings.Builder + _, err = cmd.Generate(ctx, abs, "", &cmd.Options{Stderr: &stderr}) + if err != nil { + t.Fatalf("generate failed: %v\n%s", err, stderr.String()) + } + + queryFile := filepath.Join(abs, "go", "query.sql.go") + body, err := os.ReadFile(queryFile) + if err != nil { + t.Fatal(err) + } + src := string(body) + for _, want := range []string{ + "func (q *Queries) ListAuthors", + "func (q *Queries) IterAuthors", + "iter.Seq2[Author, error]", + "defer rows.Close()", + } { + if !strings.Contains(src, want) { + t.Fatalf("missing %q in generated code:\n%s", want, src) + } + } + if strings.Contains(src, "sqlc:iterator-stream") { + t.Fatal("internal stream marker leaked into generated code") + } +} + +func TestEmitIteratorsCodegenExplicitOnly(t *testing.T) { + t.Parallel() + ctx := context.Background() + + root := filepath.Join("testdata", "emit_iterators_explicit", "stdlib") + abs, err := filepath.Abs(root) + if err != nil { + t.Fatal(err) + } + + var stderr strings.Builder + _, err = cmd.Generate(ctx, abs, "", &cmd.Options{Stderr: &stderr}) + if err != nil { + t.Fatalf("generate failed: %v\n%s", err, stderr.String()) + } + + queryFile := filepath.Join(abs, "go", "query.sql.go") + body, err := os.ReadFile(queryFile) + if err != nil { + t.Fatal(err) + } + src := string(body) + if strings.Contains(src, "sqlc:iterator-stream") { + t.Fatal("internal stream marker leaked into generated code") + } + if strings.Count(src, "func (q *Queries) IterAuthors") != 1 { + t.Fatalf("expected exactly one IterAuthors, got:\n%s", src) + } + for _, want := range []string{ + "func (q *Queries) StreamAuthors", + "func (q *Queries) IterAuthors", + "q.db.QueryContext(ctx, streamAuthors)", + } { + if !strings.Contains(src, want) { + t.Fatalf("missing %q in generated code:\n%s", want, src) + } + } + listAuthorsBlock := src[strings.Index(src, "func (q *Queries) ListAuthors"):strings.Index(src, "const streamAuthors")] + if strings.Contains(listAuthorsBlock, "IterAuthors") { + t.Fatal("ListAuthors block must not contain iterator method") + } +} diff --git a/internal/endtoend/testdata/emit_iterators/stdlib/go/db.go b/internal/endtoend/testdata/emit_iterators/stdlib/go/db.go new file mode 100644 index 0000000000..80dd6ab1f6 --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators/stdlib/go/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/endtoend/testdata/emit_iterators/stdlib/go/models.go b/internal/endtoend/testdata/emit_iterators/stdlib/go/models.go new file mode 100644 index 0000000000..2208139112 --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators/stdlib/go/models.go @@ -0,0 +1,15 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "database/sql" +) + +type Author struct { + ID int64 + Name string + Bio sql.NullString +} diff --git a/internal/endtoend/testdata/emit_iterators/stdlib/go/query.sql.go b/internal/endtoend/testdata/emit_iterators/stdlib/go/query.sql.go new file mode 100644 index 0000000000..a5e4f5c04a --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators/stdlib/go/query.sql.go @@ -0,0 +1,73 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: query.sql + +package querytest + +import ( + "context" + "iter" +) + +const getAuthorByID = `-- name: GetAuthorByID :one +SELECT id, name, bio FROM author WHERE id = ? +` + +func (q *Queries) GetAuthorByID(ctx context.Context, id int64) (Author, error) { + row := q.db.QueryRowContext(ctx, getAuthorByID, id) + var i Author + err := row.Scan(&i.ID, &i.Name, &i.Bio) + return i, err +} + +const listAuthors = `-- name: ListAuthors :many +SELECT id, name, bio FROM author ORDER BY name +` + +func (q *Queries) ListAuthors(ctx context.Context) ([]Author, error) { + rows, err := q.db.QueryContext(ctx, listAuthors) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Author + for rows.Next() { + var i Author + if err := rows.Scan(&i.ID, &i.Name, &i.Bio); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +func (q *Queries) IterAuthors(ctx context.Context) iter.Seq2[Author, error] { + return func(yield func(Author, error) bool) { + rows, err := q.db.QueryContext(ctx, listAuthors) + if err != nil { + yield(Author{}, err) + return + } + defer rows.Close() + for rows.Next() { + var i Author + if err := rows.Scan(&i.ID, &i.Name, &i.Bio); err != nil { + yield(Author{}, err) + return + } + if !yield(i, nil) { + return + } + } + if err := rows.Err(); err != nil { + yield(Author{}, err) + } + } +} diff --git a/internal/endtoend/testdata/emit_iterators/stdlib/query.sql b/internal/endtoend/testdata/emit_iterators/stdlib/query.sql new file mode 100644 index 0000000000..e010c48c0d --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators/stdlib/query.sql @@ -0,0 +1,5 @@ +-- name: ListAuthors :many +SELECT id, name, bio FROM author ORDER BY name; + +-- name: GetAuthorByID :one +SELECT id, name, bio FROM author WHERE id = ?; diff --git a/internal/endtoend/testdata/emit_iterators/stdlib/schema.sql b/internal/endtoend/testdata/emit_iterators/stdlib/schema.sql new file mode 100644 index 0000000000..3fe2df230c --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators/stdlib/schema.sql @@ -0,0 +1,5 @@ +CREATE TABLE author ( + id INTEGER NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + bio TEXT +); diff --git a/internal/endtoend/testdata/emit_iterators/stdlib/sqlc.json b/internal/endtoend/testdata/emit_iterators/stdlib/sqlc.json new file mode 100644 index 0000000000..918675b6dd --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators/stdlib/sqlc.json @@ -0,0 +1,14 @@ +{ + "version": "1", + "packages": [ + { + "path": "go", + "name": "querytest", + "engine": "sqlite", + "schema": "schema.sql", + "queries": "query.sql", + "emit_iterators": true, + "iterator_scope": "global" + } + ] +} diff --git a/internal/endtoend/testdata/emit_iterators_explicit/stdlib/go/db.go b/internal/endtoend/testdata/emit_iterators_explicit/stdlib/go/db.go new file mode 100644 index 0000000000..80dd6ab1f6 --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators_explicit/stdlib/go/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/endtoend/testdata/emit_iterators_explicit/stdlib/go/models.go b/internal/endtoend/testdata/emit_iterators_explicit/stdlib/go/models.go new file mode 100644 index 0000000000..b334d0651a --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators_explicit/stdlib/go/models.go @@ -0,0 +1,10 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +type Author struct { + ID int64 + Name string +} diff --git a/internal/endtoend/testdata/emit_iterators_explicit/stdlib/go/query.sql.go b/internal/endtoend/testdata/emit_iterators_explicit/stdlib/go/query.sql.go new file mode 100644 index 0000000000..20e14f8200 --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators_explicit/stdlib/go/query.sql.go @@ -0,0 +1,89 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: query.sql + +package querytest + +import ( + "context" + "iter" +) + +const listAuthors = `-- name: ListAuthors :many +SELECT id, name FROM author ORDER BY name +` + +func (q *Queries) ListAuthors(ctx context.Context) ([]Author, error) { + rows, err := q.db.QueryContext(ctx, listAuthors) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Author + for rows.Next() { + var i Author + if err := rows.Scan(&i.ID, &i.Name); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const streamAuthors = `-- name: StreamAuthors :many +SELECT id, name FROM author ORDER BY name DESC +` + +func (q *Queries) StreamAuthors(ctx context.Context) ([]Author, error) { + rows, err := q.db.QueryContext(ctx, streamAuthors) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Author + for rows.Next() { + var i Author + if err := rows.Scan(&i.ID, &i.Name); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +func (q *Queries) IterAuthors(ctx context.Context) iter.Seq2[Author, error] { + return func(yield func(Author, error) bool) { + rows, err := q.db.QueryContext(ctx, streamAuthors) + if err != nil { + yield(Author{}, err) + return + } + defer rows.Close() + for rows.Next() { + var i Author + if err := rows.Scan(&i.ID, &i.Name); err != nil { + yield(Author{}, err) + return + } + if !yield(i, nil) { + return + } + } + if err := rows.Err(); err != nil { + yield(Author{}, err) + } + } +} diff --git a/internal/endtoend/testdata/emit_iterators_explicit/stdlib/query.sql b/internal/endtoend/testdata/emit_iterators_explicit/stdlib/query.sql new file mode 100644 index 0000000000..53af8fbe41 --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators_explicit/stdlib/query.sql @@ -0,0 +1,5 @@ +-- name: ListAuthors :many +SELECT id, name FROM author ORDER BY name; + +-- name: StreamAuthors :stream +SELECT id, name FROM author ORDER BY name DESC; diff --git a/internal/endtoend/testdata/emit_iterators_explicit/stdlib/schema.sql b/internal/endtoend/testdata/emit_iterators_explicit/stdlib/schema.sql new file mode 100644 index 0000000000..b9e5ddaaf7 --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators_explicit/stdlib/schema.sql @@ -0,0 +1,4 @@ +CREATE TABLE author ( + id INTEGER NOT NULL PRIMARY KEY, + name TEXT NOT NULL +); diff --git a/internal/endtoend/testdata/emit_iterators_explicit/stdlib/sqlc.json b/internal/endtoend/testdata/emit_iterators_explicit/stdlib/sqlc.json new file mode 100644 index 0000000000..c7b431b7eb --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators_explicit/stdlib/sqlc.json @@ -0,0 +1,14 @@ +{ + "version": "1", + "packages": [ + { + "path": "go", + "name": "querytest", + "engine": "sqlite", + "schema": "schema.sql", + "queries": "query.sql", + "emit_iterators": true, + "iterator_scope": "explicit_only" + } + ] +} diff --git a/internal/metadata/meta.go b/internal/metadata/meta.go index 76ee992a7a..4f2c8312be 100644 --- a/internal/metadata/meta.go +++ b/internal/metadata/meta.go @@ -15,6 +15,7 @@ type CommentSyntax source.CommentSyntax type Metadata struct { Name string Cmd string + Stream bool Comments []string Params map[string]string Flags map[string]bool @@ -32,11 +33,16 @@ const ( CmdExecRows = ":execrows" CmdExecLastId = ":execlastid" CmdMany = ":many" + CmdStream = ":stream" + CmdManyStream = ":many:stream" CmdOne = ":one" CmdCopyFrom = ":copyfrom" CmdBatchExec = ":batchexec" CmdBatchMany = ":batchmany" CmdBatchOne = ":batchone" + + // StreamAnnotationComment marks queries annotated with :stream or :many:stream. + StreamAnnotationComment = " sqlc:iterator-stream" ) // A query name must be a valid Go identifier @@ -58,7 +64,7 @@ func validateQueryName(name string) error { return nil } -func ParseQueryNameAndType(t string, commentStyle CommentSyntax) (string, string, error) { +func ParseQueryNameAndType(t string, commentStyle CommentSyntax) (string, string, bool, error) { for line := range strings.SplitSeq(t, "\n") { var prefix string if strings.HasPrefix(line, "--") { @@ -90,7 +96,7 @@ func ParseQueryNameAndType(t string, commentStyle CommentSyntax) (string, string continue } if !strings.HasPrefix(rest, " name: ") { - return "", "", fmt.Errorf("invalid metadata: %s", line) + return "", "", false, fmt.Errorf("invalid metadata: %s", line) } part := strings.Split(strings.TrimSpace(line), " ") @@ -98,24 +104,28 @@ func ParseQueryNameAndType(t string, commentStyle CommentSyntax) (string, string part = part[:len(part)-1] // removes the trailing "*/" element } if len(part) == 3 { - return "", "", fmt.Errorf("missing query type [':one', ':many', ':exec', ':execrows', ':execlastid', ':execresult', ':copyfrom', 'batchexec', 'batchmany', 'batchone']: %s", line) + return "", "", false, fmt.Errorf("missing query type [':one', ':many', ':stream', ':exec', ':execrows', ':execlastid', ':execresult', ':copyfrom', 'batchexec', 'batchmany', 'batchone']: %s", line) } if len(part) != 4 { - return "", "", fmt.Errorf("invalid query comment: %s", line) + return "", "", false, fmt.Errorf("invalid query comment: %s", line) } queryName := part[2] queryType := strings.TrimSpace(part[3]) + stream := false switch queryType { + case CmdStream, CmdManyStream: + queryType = CmdMany + stream = true case CmdOne, CmdMany, CmdExec, CmdExecResult, CmdExecRows, CmdExecLastId, CmdCopyFrom, CmdBatchExec, CmdBatchMany, CmdBatchOne: default: - return "", "", fmt.Errorf("invalid query type: %s", queryType) + return "", "", false, fmt.Errorf("invalid query type: %s", queryType) } if err := validateQueryName(queryName); err != nil { - return "", "", err + return "", "", false, err } - return queryName, queryType, nil + return queryName, queryType, stream, nil } - return "", "", nil + return "", "", false, nil } // ParseCommentFlags processes the comments provided with queries to determine the metadata params, flags and rules to skip. diff --git a/internal/metadata/meta_test.go b/internal/metadata/meta_test.go index e9ef54586e..a614ff96b5 100644 --- a/internal/metadata/meta_test.go +++ b/internal/metadata/meta_test.go @@ -19,7 +19,7 @@ func TestParseQueryNameAndType(t *testing.T) { "-- name:CreateFoo", `--name:CreateFoo :two`, } { - if _, _, err := ParseQueryNameAndType(query, CommentSyntax{Dash: true}); err == nil { + if _, _, _, err := ParseQueryNameAndType(query, CommentSyntax{Dash: true}); err == nil { t.Errorf("expected invalid metadata: %q", query) } } @@ -29,7 +29,7 @@ func TestParseQueryNameAndType(t *testing.T) { `-- name comment`, `--name comment`, } { - if _, _, err := ParseQueryNameAndType(query, CommentSyntax{Dash: true}); err != nil { + if _, _, _, err := ParseQueryNameAndType(query, CommentSyntax{Dash: true}); err != nil { t.Errorf("expected valid comment: %q", query) } } @@ -39,7 +39,7 @@ func TestParseQueryNameAndType(t *testing.T) { `# name: CreateFoo :one`: {Hash: true}, `/* name: CreateFoo :one */`: {SlashStar: true}, } { - queryName, queryCmd, err := ParseQueryNameAndType(query, cs) + queryName, queryCmd, stream, err := ParseQueryNameAndType(query, cs) if err != nil { t.Errorf("expected valid metadata: %q", query) } @@ -49,6 +49,29 @@ func TestParseQueryNameAndType(t *testing.T) { if queryCmd != CmdOne { t.Errorf("incorrect queryCmd parsed: (%q) %q", queryCmd, query) } + if stream { + t.Errorf("unexpected stream flag for :one query") + } + } + + for query, wantStream := range map[string]bool{ + `-- name: StreamAuthors :stream`: true, + `-- name: ListAuthors :many:stream`: true, + `-- name: ListAuthors :many`: false, + } { + queryName, queryCmd, stream, err := ParseQueryNameAndType(query, CommentSyntax{Dash: true}) + if err != nil { + t.Errorf("expected valid metadata: %q: %v", query, err) + } + if queryName == "" { + t.Errorf("missing query name: %q", query) + } + if queryCmd != CmdMany { + t.Errorf("incorrect queryCmd for %q: got %q want %q", query, queryCmd, CmdMany) + } + if stream != wantStream { + t.Errorf("stream flag for %q: got %v want %v", query, stream, wantStream) + } } } From 6dfcc180a5998393075ade2b110fc3453f9efb4a Mon Sep 17 00:00:00 2001 From: Tilzen Date: Sun, 14 Jun 2026 16:24:22 -0300 Subject: [PATCH 2/2] test(codegen/go): expand emit_iterators coverage with runtime and matrix tests Add SQLite runtime tests (parity, lazy start, early break, parameterized queries), codegen matrix for global/explicit/pgx/disabled scenarios, opts validation tests, and golden replay fixtures for all cases. --- internal/codegen/golang/iterator_test.go | 14 ++ .../opts/options_emit_iterators_test.go | 85 +++++++++ .../endtoend/emit_iterators_codegen_test.go | 179 ++++++++++++------ .../testdata/emit_iterators/pgx/v5/go/db.go | 32 ++++ .../emit_iterators/pgx/v5/go/models.go | 15 ++ .../emit_iterators/pgx/v5/go/query.sql.go | 59 ++++++ .../testdata/emit_iterators/pgx/v5/query.sql | 2 + .../testdata/emit_iterators/pgx/v5/schema.sql | 5 + .../testdata/emit_iterators/pgx/v5/sqlc.json | 15 ++ .../emit_iterators/stdlib/go/iterator_test.go | 122 ++++++++++++ .../stdlib/go/db.go | 31 +++ .../stdlib/go/models.go | 10 + .../stdlib/go/query.sql.go | 89 +++++++++ .../stdlib/query.sql | 5 + .../stdlib/schema.sql | 4 + .../stdlib/sqlc.json | 14 ++ .../emit_iterators_off/stdlib/go/db.go | 31 +++ .../emit_iterators_off/stdlib/go/models.go | 15 ++ .../emit_iterators_off/stdlib/go/query.sql.go | 37 ++++ .../emit_iterators_off/stdlib/query.sql | 2 + .../emit_iterators_off/stdlib/schema.sql | 5 + .../emit_iterators_off/stdlib/sqlc.json | 13 ++ .../emit_iterators_params/stdlib/go/db.go | 31 +++ .../stdlib/go/iterator_test.go | 62 ++++++ .../emit_iterators_params/stdlib/go/models.go | 15 ++ .../stdlib/go/query.sql.go | 73 +++++++ .../emit_iterators_params/stdlib/query.sql | 5 + .../emit_iterators_params/stdlib/schema.sql | 5 + .../emit_iterators_params/stdlib/sqlc.json | 14 ++ internal/endtoend/testdata/go.mod | 16 +- internal/endtoend/testdata/go.sum | 83 ++++---- 31 files changed, 984 insertions(+), 104 deletions(-) create mode 100644 internal/codegen/golang/opts/options_emit_iterators_test.go create mode 100644 internal/endtoend/testdata/emit_iterators/pgx/v5/go/db.go create mode 100644 internal/endtoend/testdata/emit_iterators/pgx/v5/go/models.go create mode 100644 internal/endtoend/testdata/emit_iterators/pgx/v5/go/query.sql.go create mode 100644 internal/endtoend/testdata/emit_iterators/pgx/v5/query.sql create mode 100644 internal/endtoend/testdata/emit_iterators/pgx/v5/schema.sql create mode 100644 internal/endtoend/testdata/emit_iterators/pgx/v5/sqlc.json create mode 100644 internal/endtoend/testdata/emit_iterators/stdlib/go/iterator_test.go create mode 100644 internal/endtoend/testdata/emit_iterators_many_stream/stdlib/go/db.go create mode 100644 internal/endtoend/testdata/emit_iterators_many_stream/stdlib/go/models.go create mode 100644 internal/endtoend/testdata/emit_iterators_many_stream/stdlib/go/query.sql.go create mode 100644 internal/endtoend/testdata/emit_iterators_many_stream/stdlib/query.sql create mode 100644 internal/endtoend/testdata/emit_iterators_many_stream/stdlib/schema.sql create mode 100644 internal/endtoend/testdata/emit_iterators_many_stream/stdlib/sqlc.json create mode 100644 internal/endtoend/testdata/emit_iterators_off/stdlib/go/db.go create mode 100644 internal/endtoend/testdata/emit_iterators_off/stdlib/go/models.go create mode 100644 internal/endtoend/testdata/emit_iterators_off/stdlib/go/query.sql.go create mode 100644 internal/endtoend/testdata/emit_iterators_off/stdlib/query.sql create mode 100644 internal/endtoend/testdata/emit_iterators_off/stdlib/schema.sql create mode 100644 internal/endtoend/testdata/emit_iterators_off/stdlib/sqlc.json create mode 100644 internal/endtoend/testdata/emit_iterators_params/stdlib/go/db.go create mode 100644 internal/endtoend/testdata/emit_iterators_params/stdlib/go/iterator_test.go create mode 100644 internal/endtoend/testdata/emit_iterators_params/stdlib/go/models.go create mode 100644 internal/endtoend/testdata/emit_iterators_params/stdlib/go/query.sql.go create mode 100644 internal/endtoend/testdata/emit_iterators_params/stdlib/query.sql create mode 100644 internal/endtoend/testdata/emit_iterators_params/stdlib/schema.sql create mode 100644 internal/endtoend/testdata/emit_iterators_params/stdlib/sqlc.json diff --git a/internal/codegen/golang/iterator_test.go b/internal/codegen/golang/iterator_test.go index db4ec39cdf..8d086d4d3d 100644 --- a/internal/codegen/golang/iterator_test.go +++ b/internal/codegen/golang/iterator_test.go @@ -7,6 +7,20 @@ import ( "github.com/sqlc-dev/sqlc/internal/metadata" ) +func TestFilterStreamAnnotationComments(t *testing.T) { + t.Parallel() + in := []string{" doc", metadata.StreamAnnotationComment, " more"} + got := filterStreamAnnotationComments(in) + if len(got) != 2 { + t.Fatalf("len=%d", len(got)) + } + for _, c := range got { + if c == metadata.StreamAnnotationComment { + t.Fatal("marker not filtered") + } + } +} + func TestIteratorMethodName(t *testing.T) { t.Parallel() tests := []struct { diff --git a/internal/codegen/golang/opts/options_emit_iterators_test.go b/internal/codegen/golang/opts/options_emit_iterators_test.go new file mode 100644 index 0000000000..602884943b --- /dev/null +++ b/internal/codegen/golang/opts/options_emit_iterators_test.go @@ -0,0 +1,85 @@ +package opts_test + +import ( + "strings" + "testing" + + "github.com/sqlc-dev/sqlc/internal/codegen/golang/opts" +) + +func TestValidateOptsEmitIterators(t *testing.T) { + limit := int32(1) + base := func() *opts.Options { + return &opts.Options{ + Package: "db", + QueryParameterLimit: &limit, + } + } + + if err := opts.ValidateOpts(base()); err != nil { + t.Fatalf("valid defaults: %v", err) + } + + cases := []struct { + name string + mutate func(*opts.Options) + wantErr string + }{ + { + name: "invalid scope", + mutate: func(o *opts.Options) { + o.EmitIterators = true + o.IteratorScope = "nope" + o.IteratorStyle = "seq2" + o.IteratorStart = "lazy" + }, + wantErr: "iterator_scope", + }, + { + name: "invalid style", + mutate: func(o *opts.Options) { + o.EmitIterators = true + o.IteratorScope = "global" + o.IteratorStyle = "chan" + o.IteratorStart = "lazy" + }, + wantErr: "iterator_style", + }, + { + name: "invalid start", + mutate: func(o *opts.Options) { + o.EmitIterators = true + o.IteratorScope = "global" + o.IteratorStyle = "seq2" + o.IteratorStart = "now" + }, + wantErr: "iterator_start", + }, + { + name: "valid explicit_only", + mutate: func(o *opts.Options) { + o.EmitIterators = true + o.IteratorScope = "explicit_only" + o.IteratorStyle = "seq2" + o.IteratorStart = "lazy" + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + o := base() + tc.mutate(o) + err := opts.ValidateOpts(o) + if tc.wantErr == "" { + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + return + } + if err == nil || !strings.Contains(err.Error(), tc.wantErr) { + t.Fatalf("error = %v, want substring %q", err, tc.wantErr) + } + }) + } +} diff --git a/internal/endtoend/emit_iterators_codegen_test.go b/internal/endtoend/emit_iterators_codegen_test.go index b170758658..b331a57e24 100644 --- a/internal/endtoend/emit_iterators_codegen_test.go +++ b/internal/endtoend/emit_iterators_codegen_test.go @@ -10,82 +10,141 @@ import ( "github.com/sqlc-dev/sqlc/internal/cmd" ) -func TestEmitIteratorsCodegenGlobal(t *testing.T) { - t.Parallel() - ctx := context.Background() +type emitIterCase struct { + name string + root string + mustHave []string + mustNot []string + exactIter int // -1 = don't check count +} - root := filepath.Join("testdata", "emit_iterators", "stdlib") - abs, err := filepath.Abs(root) - if err != nil { - t.Fatal(err) +func TestEmitIteratorsCodegenMatrix(t *testing.T) { + cases := []emitIterCase{ + { + name: "global stdlib", + root: filepath.Join("testdata", "emit_iterators", "stdlib"), + mustHave: []string{ + "func (q *Queries) ListAuthors", + "func (q *Queries) IterAuthors", + "iter.Seq2[Author, error]", + "defer rows.Close()", + }, + mustNot: []string{"sqlc:iterator-stream"}, + exactIter: 1, + }, + { + name: "explicit_only stream query", + root: filepath.Join("testdata", "emit_iterators_explicit", "stdlib"), + mustHave: []string{ + "func (q *Queries) StreamAuthors", + "func (q *Queries) IterAuthors", + "q.db.QueryContext(ctx, streamAuthors)", + }, + mustNot: []string{"sqlc:iterator-stream"}, + exactIter: 1, + }, + { + name: "explicit_only many:stream annotation", + root: filepath.Join("testdata", "emit_iterators_many_stream", "stdlib"), + mustHave: []string{ + "func (q *Queries) IterAuthors", + "func (q *Queries) ListAllAuthors", + }, + mustNot: []string{ + "func (q *Queries) IterAllAuthors", + "sqlc:iterator-stream", + }, + exactIter: 1, + }, + { + name: "parameterized many", + root: filepath.Join("testdata", "emit_iterators_params", "stdlib"), + mustHave: []string{ + "func (q *Queries) ListAuthorsByMinID(ctx context.Context, id int64)", + "func (q *Queries) IterAuthorsByMinID(ctx context.Context, id int64) iter.Seq2[Author, error]", + }, + exactIter: 1, + }, + { + name: "emit_iterators disabled", + root: filepath.Join("testdata", "emit_iterators_off", "stdlib"), + mustHave: []string{ + "func (q *Queries) ListAuthors", + }, + mustNot: []string{ + "iter.Seq2", + "func (q *Queries) Iter", + }, + exactIter: 0, + }, + { + name: "pgx v5", + root: filepath.Join("testdata", "emit_iterators", "pgx", "v5"), + mustHave: []string{ + "func (q *Queries) IterAuthors(ctx context.Context) iter.Seq2[Author, error]", + "rows, err := q.db.Query(ctx, listAuthors", + "defer rows.Close()", + }, + exactIter: 1, + }, } - var stderr strings.Builder - _, err = cmd.Generate(ctx, abs, "", &cmd.Options{Stderr: &stderr}) - if err != nil { - t.Fatalf("generate failed: %v\n%s", err, stderr.String()) - } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + abs, err := filepath.Abs(tc.root) + if err != nil { + t.Fatal(err) + } - queryFile := filepath.Join(abs, "go", "query.sql.go") - body, err := os.ReadFile(queryFile) - if err != nil { - t.Fatal(err) - } - src := string(body) - for _, want := range []string{ - "func (q *Queries) ListAuthors", - "func (q *Queries) IterAuthors", - "iter.Seq2[Author, error]", - "defer rows.Close()", - } { - if !strings.Contains(src, want) { - t.Fatalf("missing %q in generated code:\n%s", want, src) - } - } - if strings.Contains(src, "sqlc:iterator-stream") { - t.Fatal("internal stream marker leaked into generated code") + var stderr strings.Builder + if _, err := cmd.Generate(ctx, abs, "", &cmd.Options{Stderr: &stderr}); err != nil { + t.Fatalf("generate failed: %v\n%s", err, stderr.String()) + } + + queryFile := filepath.Join(abs, "go", "query.sql.go") + body, err := os.ReadFile(queryFile) + if err != nil { + t.Fatal(err) + } + src := string(body) + + for _, want := range tc.mustHave { + if !strings.Contains(src, want) { + t.Fatalf("missing %q in:\n%s", want, src) + } + } + for _, bad := range tc.mustNot { + if strings.Contains(src, bad) { + t.Fatalf("unexpected %q in:\n%s", bad, src) + } + } + if tc.exactIter >= 0 { + count := strings.Count(src, "func (q *Queries) Iter") + if count != tc.exactIter { + t.Fatalf("Iter method count = %d, want %d\n%s", count, tc.exactIter, src) + } + } + }) } } -func TestEmitIteratorsCodegenExplicitOnly(t *testing.T) { - t.Parallel() - ctx := context.Background() - - root := filepath.Join("testdata", "emit_iterators_explicit", "stdlib") +func TestEmitIteratorsExplicitOnlySkipsPlainMany(t *testing.T) { + root := filepath.Join("testdata", "emit_iterators_many_stream", "stdlib") abs, err := filepath.Abs(root) if err != nil { t.Fatal(err) } - var stderr strings.Builder - _, err = cmd.Generate(ctx, abs, "", &cmd.Options{Stderr: &stderr}) - if err != nil { - t.Fatalf("generate failed: %v\n%s", err, stderr.String()) + if _, err := cmd.Generate(context.Background(), abs, "", &cmd.Options{Stderr: &stderr}); err != nil { + t.Fatal(err) } - - queryFile := filepath.Join(abs, "go", "query.sql.go") - body, err := os.ReadFile(queryFile) + body, err := os.ReadFile(filepath.Join(abs, "go", "query.sql.go")) if err != nil { t.Fatal(err) } src := string(body) - if strings.Contains(src, "sqlc:iterator-stream") { - t.Fatal("internal stream marker leaked into generated code") - } - if strings.Count(src, "func (q *Queries) IterAuthors") != 1 { - t.Fatalf("expected exactly one IterAuthors, got:\n%s", src) - } - for _, want := range []string{ - "func (q *Queries) StreamAuthors", - "func (q *Queries) IterAuthors", - "q.db.QueryContext(ctx, streamAuthors)", - } { - if !strings.Contains(src, want) { - t.Fatalf("missing %q in generated code:\n%s", want, src) - } - } - listAuthorsBlock := src[strings.Index(src, "func (q *Queries) ListAuthors"):strings.Index(src, "const streamAuthors")] - if strings.Contains(listAuthorsBlock, "IterAuthors") { - t.Fatal("ListAuthors block must not contain iterator method") + if strings.Contains(src, "IterAllAuthors") { + t.Fatal("plain :many must not get iterator in explicit_only mode") } } diff --git a/internal/endtoend/testdata/emit_iterators/pgx/v5/go/db.go b/internal/endtoend/testdata/emit_iterators/pgx/v5/go/db.go new file mode 100644 index 0000000000..0057c62319 --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators/pgx/v5/go/db.go @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "context" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +type DBTX interface { + Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) + Query(context.Context, string, ...interface{}) (pgx.Rows, error) + QueryRow(context.Context, string, ...interface{}) pgx.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx pgx.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/endtoend/testdata/emit_iterators/pgx/v5/go/models.go b/internal/endtoend/testdata/emit_iterators/pgx/v5/go/models.go new file mode 100644 index 0000000000..cdb82d094d --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators/pgx/v5/go/models.go @@ -0,0 +1,15 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "github.com/jackc/pgx/v5/pgtype" +) + +type Author struct { + ID int32 + Name string + Bio pgtype.Text +} diff --git a/internal/endtoend/testdata/emit_iterators/pgx/v5/go/query.sql.go b/internal/endtoend/testdata/emit_iterators/pgx/v5/go/query.sql.go new file mode 100644 index 0000000000..48c1586b7d --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators/pgx/v5/go/query.sql.go @@ -0,0 +1,59 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: query.sql + +package querytest + +import ( + "context" + "iter" +) + +const listAuthors = `-- name: ListAuthors :many +SELECT id, name, bio FROM author ORDER BY name +` + +func (q *Queries) ListAuthors(ctx context.Context) ([]Author, error) { + rows, err := q.db.Query(ctx, listAuthors) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Author + for rows.Next() { + var i Author + if err := rows.Scan(&i.ID, &i.Name, &i.Bio); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +func (q *Queries) IterAuthors(ctx context.Context) iter.Seq2[Author, error] { + return func(yield func(Author, error) bool) { + rows, err := q.db.Query(ctx, listAuthors) + if err != nil { + yield(Author{}, err) + return + } + defer rows.Close() + for rows.Next() { + var i Author + if err := rows.Scan(&i.ID, &i.Name, &i.Bio); err != nil { + yield(Author{}, err) + return + } + if !yield(i, nil) { + return + } + } + if err := rows.Err(); err != nil { + yield(Author{}, err) + } + } +} diff --git a/internal/endtoend/testdata/emit_iterators/pgx/v5/query.sql b/internal/endtoend/testdata/emit_iterators/pgx/v5/query.sql new file mode 100644 index 0000000000..0c4c489387 --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators/pgx/v5/query.sql @@ -0,0 +1,2 @@ +-- name: ListAuthors :many +SELECT id, name, bio FROM author ORDER BY name; diff --git a/internal/endtoend/testdata/emit_iterators/pgx/v5/schema.sql b/internal/endtoend/testdata/emit_iterators/pgx/v5/schema.sql new file mode 100644 index 0000000000..7d9f57e8c9 --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators/pgx/v5/schema.sql @@ -0,0 +1,5 @@ +CREATE TABLE author ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + bio TEXT +); diff --git a/internal/endtoend/testdata/emit_iterators/pgx/v5/sqlc.json b/internal/endtoend/testdata/emit_iterators/pgx/v5/sqlc.json new file mode 100644 index 0000000000..43caeb91fb --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators/pgx/v5/sqlc.json @@ -0,0 +1,15 @@ +{ + "version": "1", + "packages": [ + { + "path": "go", + "name": "querytest", + "engine": "postgresql", + "schema": "schema.sql", + "queries": "query.sql", + "sql_package": "pgx/v5", + "emit_iterators": true, + "iterator_scope": "global" + } + ] +} diff --git a/internal/endtoend/testdata/emit_iterators/stdlib/go/iterator_test.go b/internal/endtoend/testdata/emit_iterators/stdlib/go/iterator_test.go new file mode 100644 index 0000000000..162466f3b6 --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators/stdlib/go/iterator_test.go @@ -0,0 +1,122 @@ +package querytest_test + +import ( + "context" + "database/sql" + "testing" + + _ "github.com/ncruces/go-sqlite3/driver" + _ "github.com/ncruces/go-sqlite3/embed" + + "github.com/sqlc-dev/sqlc/endtoend/emit_iterators/stdlib/go" +) + +func TestIterAuthorsMatchesListAuthors(t *testing.T) { + t.Parallel() + ctx := context.Background() + + db := openTestDB(t, ctx) + q := querytest.New(db) + + slice, err := q.ListAuthors(ctx) + if err != nil { + t.Fatal(err) + } + + var streamed []querytest.Author + for author, err := range q.IterAuthors(ctx) { + if err != nil { + t.Fatal(err) + } + streamed = append(streamed, author) + } + if len(streamed) != len(slice) { + t.Fatalf("len(iter)=%d len(slice)=%d", len(streamed), len(slice)) + } + for i := range slice { + if streamed[i] != slice[i] { + t.Fatalf("row %d: iter=%+v slice=%+v", i, streamed[i], slice[i]) + } + } +} + +func TestIterAuthorsLazyStart(t *testing.T) { + t.Parallel() + ctx := context.Background() + + db := openTestDB(t, ctx) + q := querytest.New(db) + + seq := q.IterAuthors(ctx) + if seq == nil { + t.Fatal("expected non-nil iterator") + } + + closed, err := sql.Open("sqlite3", ":memory:") + if err != nil { + t.Fatal(err) + } + _ = closed.Close() + lazyQ := querytest.New(closed) + lazySeq := lazyQ.IterAuthors(ctx) + gotErr := false + for _, err := range lazySeq { + if err != nil { + gotErr = true + break + } + } + if !gotErr { + t.Fatal("expected error when iterating with closed db") + } +} + +func TestIterAuthorsEarlyBreak(t *testing.T) { + t.Parallel() + ctx := context.Background() + + db := openTestDB(t, ctx) + q := querytest.New(db) + + count := 0 + for author, err := range q.IterAuthors(ctx) { + if err != nil { + t.Fatal(err) + } + if author.Name == "" { + t.Fatal("empty name") + } + count++ + break + } + if count != 1 { + t.Fatalf("count=%d", count) + } + + // Connection must remain usable after early break. + if _, err := q.GetAuthorByID(ctx, 1); err != nil { + t.Fatalf("query after early break failed: %v", err) + } +} + +func openTestDB(t *testing.T, ctx context.Context) *sql.DB { + t.Helper() + db, err := sql.Open("sqlite3", ":memory:") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = db.Close() }) + if _, err := db.ExecContext(ctx, ` + CREATE TABLE author ( + id INTEGER NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + bio TEXT + ); + INSERT INTO author (id, name, bio) VALUES + (1, 'ada', 'first'), + (2, 'grace', 'second'); + `); err != nil { + t.Fatal(err) + } + return db +} diff --git a/internal/endtoend/testdata/emit_iterators_many_stream/stdlib/go/db.go b/internal/endtoend/testdata/emit_iterators_many_stream/stdlib/go/db.go new file mode 100644 index 0000000000..80dd6ab1f6 --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators_many_stream/stdlib/go/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/endtoend/testdata/emit_iterators_many_stream/stdlib/go/models.go b/internal/endtoend/testdata/emit_iterators_many_stream/stdlib/go/models.go new file mode 100644 index 0000000000..b334d0651a --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators_many_stream/stdlib/go/models.go @@ -0,0 +1,10 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +type Author struct { + ID int64 + Name string +} diff --git a/internal/endtoend/testdata/emit_iterators_many_stream/stdlib/go/query.sql.go b/internal/endtoend/testdata/emit_iterators_many_stream/stdlib/go/query.sql.go new file mode 100644 index 0000000000..800fce1417 --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators_many_stream/stdlib/go/query.sql.go @@ -0,0 +1,89 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: query.sql + +package querytest + +import ( + "context" + "iter" +) + +const listAllAuthors = `-- name: ListAllAuthors :many +SELECT id, name FROM author ORDER BY name +` + +func (q *Queries) ListAllAuthors(ctx context.Context) ([]Author, error) { + rows, err := q.db.QueryContext(ctx, listAllAuthors) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Author + for rows.Next() { + var i Author + if err := rows.Scan(&i.ID, &i.Name); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listAuthors = `-- name: ListAuthors :many +SELECT id, name FROM author ORDER BY name +` + +func (q *Queries) ListAuthors(ctx context.Context) ([]Author, error) { + rows, err := q.db.QueryContext(ctx, listAuthors) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Author + for rows.Next() { + var i Author + if err := rows.Scan(&i.ID, &i.Name); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +func (q *Queries) IterAuthors(ctx context.Context) iter.Seq2[Author, error] { + return func(yield func(Author, error) bool) { + rows, err := q.db.QueryContext(ctx, listAuthors) + if err != nil { + yield(Author{}, err) + return + } + defer rows.Close() + for rows.Next() { + var i Author + if err := rows.Scan(&i.ID, &i.Name); err != nil { + yield(Author{}, err) + return + } + if !yield(i, nil) { + return + } + } + if err := rows.Err(); err != nil { + yield(Author{}, err) + } + } +} diff --git a/internal/endtoend/testdata/emit_iterators_many_stream/stdlib/query.sql b/internal/endtoend/testdata/emit_iterators_many_stream/stdlib/query.sql new file mode 100644 index 0000000000..15edfa032a --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators_many_stream/stdlib/query.sql @@ -0,0 +1,5 @@ +-- name: ListAuthors :many:stream +SELECT id, name FROM author ORDER BY name; + +-- name: ListAllAuthors :many +SELECT id, name FROM author ORDER BY name; diff --git a/internal/endtoend/testdata/emit_iterators_many_stream/stdlib/schema.sql b/internal/endtoend/testdata/emit_iterators_many_stream/stdlib/schema.sql new file mode 100644 index 0000000000..b9e5ddaaf7 --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators_many_stream/stdlib/schema.sql @@ -0,0 +1,4 @@ +CREATE TABLE author ( + id INTEGER NOT NULL PRIMARY KEY, + name TEXT NOT NULL +); diff --git a/internal/endtoend/testdata/emit_iterators_many_stream/stdlib/sqlc.json b/internal/endtoend/testdata/emit_iterators_many_stream/stdlib/sqlc.json new file mode 100644 index 0000000000..c7b431b7eb --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators_many_stream/stdlib/sqlc.json @@ -0,0 +1,14 @@ +{ + "version": "1", + "packages": [ + { + "path": "go", + "name": "querytest", + "engine": "sqlite", + "schema": "schema.sql", + "queries": "query.sql", + "emit_iterators": true, + "iterator_scope": "explicit_only" + } + ] +} diff --git a/internal/endtoend/testdata/emit_iterators_off/stdlib/go/db.go b/internal/endtoend/testdata/emit_iterators_off/stdlib/go/db.go new file mode 100644 index 0000000000..80dd6ab1f6 --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators_off/stdlib/go/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/endtoend/testdata/emit_iterators_off/stdlib/go/models.go b/internal/endtoend/testdata/emit_iterators_off/stdlib/go/models.go new file mode 100644 index 0000000000..2208139112 --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators_off/stdlib/go/models.go @@ -0,0 +1,15 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "database/sql" +) + +type Author struct { + ID int64 + Name string + Bio sql.NullString +} diff --git a/internal/endtoend/testdata/emit_iterators_off/stdlib/go/query.sql.go b/internal/endtoend/testdata/emit_iterators_off/stdlib/go/query.sql.go new file mode 100644 index 0000000000..5bcdc59dde --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators_off/stdlib/go/query.sql.go @@ -0,0 +1,37 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: query.sql + +package querytest + +import ( + "context" +) + +const listAuthors = `-- name: ListAuthors :many +SELECT id, name, bio FROM author ORDER BY name +` + +func (q *Queries) ListAuthors(ctx context.Context) ([]Author, error) { + rows, err := q.db.QueryContext(ctx, listAuthors) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Author + for rows.Next() { + var i Author + if err := rows.Scan(&i.ID, &i.Name, &i.Bio); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/internal/endtoend/testdata/emit_iterators_off/stdlib/query.sql b/internal/endtoend/testdata/emit_iterators_off/stdlib/query.sql new file mode 100644 index 0000000000..0c4c489387 --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators_off/stdlib/query.sql @@ -0,0 +1,2 @@ +-- name: ListAuthors :many +SELECT id, name, bio FROM author ORDER BY name; diff --git a/internal/endtoend/testdata/emit_iterators_off/stdlib/schema.sql b/internal/endtoend/testdata/emit_iterators_off/stdlib/schema.sql new file mode 100644 index 0000000000..3fe2df230c --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators_off/stdlib/schema.sql @@ -0,0 +1,5 @@ +CREATE TABLE author ( + id INTEGER NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + bio TEXT +); diff --git a/internal/endtoend/testdata/emit_iterators_off/stdlib/sqlc.json b/internal/endtoend/testdata/emit_iterators_off/stdlib/sqlc.json new file mode 100644 index 0000000000..3b6e0be771 --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators_off/stdlib/sqlc.json @@ -0,0 +1,13 @@ +{ + "version": "1", + "packages": [ + { + "path": "go", + "name": "querytest", + "engine": "sqlite", + "schema": "schema.sql", + "queries": "query.sql", + "emit_iterators": false + } + ] +} diff --git a/internal/endtoend/testdata/emit_iterators_params/stdlib/go/db.go b/internal/endtoend/testdata/emit_iterators_params/stdlib/go/db.go new file mode 100644 index 0000000000..80dd6ab1f6 --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators_params/stdlib/go/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/endtoend/testdata/emit_iterators_params/stdlib/go/iterator_test.go b/internal/endtoend/testdata/emit_iterators_params/stdlib/go/iterator_test.go new file mode 100644 index 0000000000..e0579f8900 --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators_params/stdlib/go/iterator_test.go @@ -0,0 +1,62 @@ +package querytest_test + +import ( + "context" + "database/sql" + "testing" + + _ "github.com/ncruces/go-sqlite3/driver" + _ "github.com/ncruces/go-sqlite3/embed" + + "github.com/sqlc-dev/sqlc/endtoend/emit_iterators_params/stdlib/go" +) + +func TestIterAuthorsByMinIDFiltersRows(t *testing.T) { + t.Parallel() + ctx := context.Background() + + db, err := sql.Open("sqlite3", ":memory:") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = db.Close() }) + if _, err := db.ExecContext(ctx, ` + CREATE TABLE author ( + id INTEGER NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + bio TEXT + ); + INSERT INTO author (id, name, bio) VALUES + (1, 'ada', 'a'), + (2, 'grace', 'b'), + (3, 'linus', 'c'); + `); err != nil { + t.Fatal(err) + } + + q := querytest.New(db) + + slice, err := q.ListAuthorsByMinID(ctx, 2) + if err != nil { + t.Fatal(err) + } + if len(slice) != 2 { + t.Fatalf("slice len=%d", len(slice)) + } + + var streamed []querytest.Author + for author, err := range q.IterAuthorsByMinID(ctx, 2) { + if err != nil { + t.Fatal(err) + } + streamed = append(streamed, author) + } + if len(streamed) != len(slice) { + t.Fatalf("iter len=%d slice len=%d", len(streamed), len(slice)) + } + for i := range slice { + if streamed[i] != slice[i] { + t.Fatalf("row %d mismatch", i) + } + } +} diff --git a/internal/endtoend/testdata/emit_iterators_params/stdlib/go/models.go b/internal/endtoend/testdata/emit_iterators_params/stdlib/go/models.go new file mode 100644 index 0000000000..2208139112 --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators_params/stdlib/go/models.go @@ -0,0 +1,15 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "database/sql" +) + +type Author struct { + ID int64 + Name string + Bio sql.NullString +} diff --git a/internal/endtoend/testdata/emit_iterators_params/stdlib/go/query.sql.go b/internal/endtoend/testdata/emit_iterators_params/stdlib/go/query.sql.go new file mode 100644 index 0000000000..f1461da19a --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators_params/stdlib/go/query.sql.go @@ -0,0 +1,73 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: query.sql + +package querytest + +import ( + "context" + "iter" +) + +const getAuthorByID = `-- name: GetAuthorByID :one +SELECT id, name, bio FROM author WHERE id = ? +` + +func (q *Queries) GetAuthorByID(ctx context.Context, id int64) (Author, error) { + row := q.db.QueryRowContext(ctx, getAuthorByID, id) + var i Author + err := row.Scan(&i.ID, &i.Name, &i.Bio) + return i, err +} + +const listAuthorsByMinID = `-- name: ListAuthorsByMinID :many +SELECT id, name, bio FROM author WHERE id >= ? ORDER BY name +` + +func (q *Queries) ListAuthorsByMinID(ctx context.Context, id int64) ([]Author, error) { + rows, err := q.db.QueryContext(ctx, listAuthorsByMinID, id) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Author + for rows.Next() { + var i Author + if err := rows.Scan(&i.ID, &i.Name, &i.Bio); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +func (q *Queries) IterAuthorsByMinID(ctx context.Context, id int64) iter.Seq2[Author, error] { + return func(yield func(Author, error) bool) { + rows, err := q.db.QueryContext(ctx, listAuthorsByMinID, id) + if err != nil { + yield(Author{}, err) + return + } + defer rows.Close() + for rows.Next() { + var i Author + if err := rows.Scan(&i.ID, &i.Name, &i.Bio); err != nil { + yield(Author{}, err) + return + } + if !yield(i, nil) { + return + } + } + if err := rows.Err(); err != nil { + yield(Author{}, err) + } + } +} diff --git a/internal/endtoend/testdata/emit_iterators_params/stdlib/query.sql b/internal/endtoend/testdata/emit_iterators_params/stdlib/query.sql new file mode 100644 index 0000000000..3dec87c944 --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators_params/stdlib/query.sql @@ -0,0 +1,5 @@ +-- name: ListAuthorsByMinID :many +SELECT id, name, bio FROM author WHERE id >= ? ORDER BY name; + +-- name: GetAuthorByID :one +SELECT id, name, bio FROM author WHERE id = ?; diff --git a/internal/endtoend/testdata/emit_iterators_params/stdlib/schema.sql b/internal/endtoend/testdata/emit_iterators_params/stdlib/schema.sql new file mode 100644 index 0000000000..3fe2df230c --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators_params/stdlib/schema.sql @@ -0,0 +1,5 @@ +CREATE TABLE author ( + id INTEGER NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + bio TEXT +); diff --git a/internal/endtoend/testdata/emit_iterators_params/stdlib/sqlc.json b/internal/endtoend/testdata/emit_iterators_params/stdlib/sqlc.json new file mode 100644 index 0000000000..918675b6dd --- /dev/null +++ b/internal/endtoend/testdata/emit_iterators_params/stdlib/sqlc.json @@ -0,0 +1,14 @@ +{ + "version": "1", + "packages": [ + { + "path": "go", + "name": "querytest", + "engine": "sqlite", + "schema": "schema.sql", + "queries": "query.sql", + "emit_iterators": true, + "iterator_scope": "global" + } + ] +} diff --git a/internal/endtoend/testdata/go.mod b/internal/endtoend/testdata/go.mod index 502eef7017..d76b9828b0 100644 --- a/internal/endtoend/testdata/go.mod +++ b/internal/endtoend/testdata/go.mod @@ -1,19 +1,19 @@ module github.com/sqlc-dev/sqlc/endtoend -go 1.24.0 - -toolchain go1.24.7 +go 1.25.0 require ( github.com/go-sql-driver/mysql v1.7.0 github.com/gofrs/uuid v4.0.0+incompatible - github.com/google/uuid v1.3.0 + github.com/google/uuid v1.6.0 github.com/hexon/mysqltsv v0.1.0 github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853 github.com/jackc/pgtype v1.6.2 github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904 github.com/jackc/pgx/v5 v5.8.0 github.com/lib/pq v1.9.0 + github.com/ncruces/go-sqlite3 v0.34.4 + github.com/pgvector/pgvector-go v0.1.1 github.com/sqlc-dev/pqtype v0.2.0 github.com/sqlc-dev/sqlc-testdata v1.0.0 github.com/volatiletech/null/v8 v8.1.2 @@ -27,11 +27,13 @@ require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgproto3/v2 v2.0.1 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/pgvector/pgvector-go v0.1.1 // indirect + github.com/ncruces/go-sqlite3-wasm/v2 v2.6.35302 // indirect + github.com/ncruces/julianday v1.0.0 // indirect github.com/volatiletech/inflect v0.0.1 // indirect github.com/volatiletech/randomize v0.0.1 // indirect github.com/volatiletech/strmangle v0.0.1 // indirect - golang.org/x/crypto v0.17.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/crypto v0.52.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/text v0.37.0 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect ) diff --git a/internal/endtoend/testdata/go.sum b/internal/endtoend/testdata/go.sum index 0711cb8eaf..8301aeb225 100644 --- a/internal/endtoend/testdata/go.sum +++ b/internal/endtoend/testdata/go.sum @@ -9,6 +9,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/friendsofgo/errors v0.9.2 h1:X6NYxef4efCBdwI7BgS820zFaN7Cphrmb+Pljdzjtgk= github.com/friendsofgo/errors v0.9.2/go.mod h1:yCvFW5AkDIL9qn7suHVLiI/gH228n7PC4Pn44IGoTOI= +github.com/go-pg/pg/v10 v10.11.0 h1:CMKJqLgTrfpE/aOVeLdybezR2om071Vh38OLZjsyMI0= +github.com/go-pg/pg/v10 v10.11.0/go.mod h1:4BpHRoxE61y4Onpof3x1a2SQvi9c+q1dJnrNdMjsroA= +github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU= +github.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo= github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -16,8 +20,8 @@ github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hexon/mysqltsv v0.1.0 h1:48wYQlsPH8ZEkKAVCdsOYzMYAlEoevw8ZWD8rqYPdlg= github.com/hexon/mysqltsv v0.1.0/go.mod h1:p3vPBkpxebjHWF1bWKYNcXx5pFu+yAG89QZQEKSvVrY= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= @@ -45,10 +49,6 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1: github.com/jackc/pgproto3/v2 v2.0.1 h1:Rdjp4NFjwHnEslx2b66FfCI2S0LhO4itac3hXz6WX9M= github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= @@ -66,24 +66,17 @@ github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXg github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o= github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904 h1:SdGWuGg+Cpxq6Z+ArXt0nafaKeTvtKGEoW+yvycspUU= github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg= -github.com/jackc/pgx/v5 v5.0.1 h1:JZu9othr7l8so2JMDAGeDUMXqERAuZpovyfl4H50tdg= -github.com/jackc/pgx/v5 v5.0.1/go.mod h1:JBbvW3Hdw77jKl9uJrEDATUZIFM2VFPzRq4RWIhkF4o= -github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= -github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= -github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= -github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= -github.com/jackc/pgx/v5 v5.5.0 h1:NxstgwndsTRy7eq9/kqYc/BZh5w2hHJV86wjvO+1xPw= -github.com/jackc/pgx/v5 v5.5.0/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= -github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= -github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= -github.com/jackc/pgx/v5 v5.7.0 h1:FG6VLIdzvAPhnYqP14sQ2xhFLkiUQHCs6ySqO91kF4g= -github.com/jackc/pgx/v5 v5.7.0/go.mod h1:awP1KNnjylvpxHuHP63gzjhnGkI1iw+PMoIwvoleN/8= github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo= github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.1 h1:PJAw7H/9hoWC4Kf3J8iNmL1SwA6E8vfsLqBiL+F6CtI= github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -105,6 +98,12 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/ncruces/go-sqlite3 v0.34.4 h1:bp8jd1o2CMvjkrrp1lOtHDu1TdzExNWACt+piQeMwzg= +github.com/ncruces/go-sqlite3 v0.34.4/go.mod h1:tOyhDWnzlrzflKIGw177imjuO4ZEbfOS1NJ67B1BanQ= +github.com/ncruces/go-sqlite3-wasm/v2 v2.6.35302 h1:IZCiInPIp6OhOc1skMDGOBMwMQuE1TK+QqVe35vd/ro= +github.com/ncruces/go-sqlite3-wasm/v2 v2.6.35302/go.mod h1:ELHF6yqC51E0DiitfabHXl/aKKouCihugbhNT5a+yEY= +github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M= +github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g= github.com/pgvector/pgvector-go v0.1.1 h1:kqJigGctFnlWvskUiYIvJRNwUtQl/aMSUZVs0YWQe+g= github.com/pgvector/pgvector-go v0.1.1/go.mod h1:wLJgD/ODkdtd2LJK4l6evHXTuG+8PxymYAVomKHOWac= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= @@ -133,7 +132,24 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/uptrace/bun v1.1.12 h1:sOjDVHxNTuM6dNGaba0wUuz7KvDE1BmNu9Gqs2gJSXQ= +github.com/uptrace/bun v1.1.12/go.mod h1:NPG6JGULBeQ9IU6yHp7YGELRa5Agmd7ATZdz4tGZ6z0= +github.com/uptrace/bun/dialect/pgdialect v1.1.12 h1:m/CM1UfOkoBTglGO5CUTKnIKKOApOYxkcP2qn0F9tJk= +github.com/uptrace/bun/dialect/pgdialect v1.1.12/go.mod h1:Ij6WIxQILxLlL2frUBxUBOZJtLElD2QQNDcu/PWDHTc= +github.com/uptrace/bun/driver/pgdriver v1.1.12 h1:3rRWB1GK0psTJrHwxzNfEij2MLibggiLdTqjTtfHc1w= +github.com/uptrace/bun/driver/pgdriver v1.1.12/go.mod h1:ssYUP+qwSEgeDDS1xm2XBip9el1y9Mi5mTAvLoiADLM= +github.com/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94= +github.com/vmihailenco/bufpool v0.1.11/go.mod h1:AFf/MOy3l2CFTKbxwt0mp2MwnqjNEs5H/UxrkA5jxTQ= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc= +github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/volatiletech/inflect v0.0.1 h1:2a6FcMQyhmPZcLa+uet3VJ8gLn/9svWhJxJYwvE8KsU= github.com/volatiletech/inflect v0.0.1/go.mod h1:IBti31tG6phkHitLlr5j7shC5SOo//x0AjDzaJU1PLA= github.com/volatiletech/null/v8 v8.1.2 h1:kiTiX1PpwvuugKwfvUNX/SU/5A2KGZMXfGD0DUHdKEI= @@ -157,13 +173,8 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= -golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= +golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -171,6 +182,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -181,18 +194,12 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -203,7 +210,6 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -216,4 +222,7 @@ gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:a gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo= +mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw=