Skip to content

Commit 6dfcc18

Browse files
committed
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.
1 parent dd2f77c commit 6dfcc18

31 files changed

Lines changed: 984 additions & 104 deletions

File tree

internal/codegen/golang/iterator_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,20 @@ import (
77
"github.com/sqlc-dev/sqlc/internal/metadata"
88
)
99

10+
func TestFilterStreamAnnotationComments(t *testing.T) {
11+
t.Parallel()
12+
in := []string{" doc", metadata.StreamAnnotationComment, " more"}
13+
got := filterStreamAnnotationComments(in)
14+
if len(got) != 2 {
15+
t.Fatalf("len=%d", len(got))
16+
}
17+
for _, c := range got {
18+
if c == metadata.StreamAnnotationComment {
19+
t.Fatal("marker not filtered")
20+
}
21+
}
22+
}
23+
1024
func TestIteratorMethodName(t *testing.T) {
1125
t.Parallel()
1226
tests := []struct {
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package opts_test
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/sqlc-dev/sqlc/internal/codegen/golang/opts"
8+
)
9+
10+
func TestValidateOptsEmitIterators(t *testing.T) {
11+
limit := int32(1)
12+
base := func() *opts.Options {
13+
return &opts.Options{
14+
Package: "db",
15+
QueryParameterLimit: &limit,
16+
}
17+
}
18+
19+
if err := opts.ValidateOpts(base()); err != nil {
20+
t.Fatalf("valid defaults: %v", err)
21+
}
22+
23+
cases := []struct {
24+
name string
25+
mutate func(*opts.Options)
26+
wantErr string
27+
}{
28+
{
29+
name: "invalid scope",
30+
mutate: func(o *opts.Options) {
31+
o.EmitIterators = true
32+
o.IteratorScope = "nope"
33+
o.IteratorStyle = "seq2"
34+
o.IteratorStart = "lazy"
35+
},
36+
wantErr: "iterator_scope",
37+
},
38+
{
39+
name: "invalid style",
40+
mutate: func(o *opts.Options) {
41+
o.EmitIterators = true
42+
o.IteratorScope = "global"
43+
o.IteratorStyle = "chan"
44+
o.IteratorStart = "lazy"
45+
},
46+
wantErr: "iterator_style",
47+
},
48+
{
49+
name: "invalid start",
50+
mutate: func(o *opts.Options) {
51+
o.EmitIterators = true
52+
o.IteratorScope = "global"
53+
o.IteratorStyle = "seq2"
54+
o.IteratorStart = "now"
55+
},
56+
wantErr: "iterator_start",
57+
},
58+
{
59+
name: "valid explicit_only",
60+
mutate: func(o *opts.Options) {
61+
o.EmitIterators = true
62+
o.IteratorScope = "explicit_only"
63+
o.IteratorStyle = "seq2"
64+
o.IteratorStart = "lazy"
65+
},
66+
},
67+
}
68+
69+
for _, tc := range cases {
70+
t.Run(tc.name, func(t *testing.T) {
71+
o := base()
72+
tc.mutate(o)
73+
err := opts.ValidateOpts(o)
74+
if tc.wantErr == "" {
75+
if err != nil {
76+
t.Fatalf("unexpected error: %v", err)
77+
}
78+
return
79+
}
80+
if err == nil || !strings.Contains(err.Error(), tc.wantErr) {
81+
t.Fatalf("error = %v, want substring %q", err, tc.wantErr)
82+
}
83+
})
84+
}
85+
}

internal/endtoend/emit_iterators_codegen_test.go

Lines changed: 119 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -10,82 +10,141 @@ import (
1010
"github.com/sqlc-dev/sqlc/internal/cmd"
1111
)
1212

13-
func TestEmitIteratorsCodegenGlobal(t *testing.T) {
14-
t.Parallel()
15-
ctx := context.Background()
13+
type emitIterCase struct {
14+
name string
15+
root string
16+
mustHave []string
17+
mustNot []string
18+
exactIter int // -1 = don't check count
19+
}
1620

17-
root := filepath.Join("testdata", "emit_iterators", "stdlib")
18-
abs, err := filepath.Abs(root)
19-
if err != nil {
20-
t.Fatal(err)
21+
func TestEmitIteratorsCodegenMatrix(t *testing.T) {
22+
cases := []emitIterCase{
23+
{
24+
name: "global stdlib",
25+
root: filepath.Join("testdata", "emit_iterators", "stdlib"),
26+
mustHave: []string{
27+
"func (q *Queries) ListAuthors",
28+
"func (q *Queries) IterAuthors",
29+
"iter.Seq2[Author, error]",
30+
"defer rows.Close()",
31+
},
32+
mustNot: []string{"sqlc:iterator-stream"},
33+
exactIter: 1,
34+
},
35+
{
36+
name: "explicit_only stream query",
37+
root: filepath.Join("testdata", "emit_iterators_explicit", "stdlib"),
38+
mustHave: []string{
39+
"func (q *Queries) StreamAuthors",
40+
"func (q *Queries) IterAuthors",
41+
"q.db.QueryContext(ctx, streamAuthors)",
42+
},
43+
mustNot: []string{"sqlc:iterator-stream"},
44+
exactIter: 1,
45+
},
46+
{
47+
name: "explicit_only many:stream annotation",
48+
root: filepath.Join("testdata", "emit_iterators_many_stream", "stdlib"),
49+
mustHave: []string{
50+
"func (q *Queries) IterAuthors",
51+
"func (q *Queries) ListAllAuthors",
52+
},
53+
mustNot: []string{
54+
"func (q *Queries) IterAllAuthors",
55+
"sqlc:iterator-stream",
56+
},
57+
exactIter: 1,
58+
},
59+
{
60+
name: "parameterized many",
61+
root: filepath.Join("testdata", "emit_iterators_params", "stdlib"),
62+
mustHave: []string{
63+
"func (q *Queries) ListAuthorsByMinID(ctx context.Context, id int64)",
64+
"func (q *Queries) IterAuthorsByMinID(ctx context.Context, id int64) iter.Seq2[Author, error]",
65+
},
66+
exactIter: 1,
67+
},
68+
{
69+
name: "emit_iterators disabled",
70+
root: filepath.Join("testdata", "emit_iterators_off", "stdlib"),
71+
mustHave: []string{
72+
"func (q *Queries) ListAuthors",
73+
},
74+
mustNot: []string{
75+
"iter.Seq2",
76+
"func (q *Queries) Iter",
77+
},
78+
exactIter: 0,
79+
},
80+
{
81+
name: "pgx v5",
82+
root: filepath.Join("testdata", "emit_iterators", "pgx", "v5"),
83+
mustHave: []string{
84+
"func (q *Queries) IterAuthors(ctx context.Context) iter.Seq2[Author, error]",
85+
"rows, err := q.db.Query(ctx, listAuthors",
86+
"defer rows.Close()",
87+
},
88+
exactIter: 1,
89+
},
2190
}
2291

23-
var stderr strings.Builder
24-
_, err = cmd.Generate(ctx, abs, "", &cmd.Options{Stderr: &stderr})
25-
if err != nil {
26-
t.Fatalf("generate failed: %v\n%s", err, stderr.String())
27-
}
92+
for _, tc := range cases {
93+
t.Run(tc.name, func(t *testing.T) {
94+
ctx := context.Background()
95+
abs, err := filepath.Abs(tc.root)
96+
if err != nil {
97+
t.Fatal(err)
98+
}
2899

29-
queryFile := filepath.Join(abs, "go", "query.sql.go")
30-
body, err := os.ReadFile(queryFile)
31-
if err != nil {
32-
t.Fatal(err)
33-
}
34-
src := string(body)
35-
for _, want := range []string{
36-
"func (q *Queries) ListAuthors",
37-
"func (q *Queries) IterAuthors",
38-
"iter.Seq2[Author, error]",
39-
"defer rows.Close()",
40-
} {
41-
if !strings.Contains(src, want) {
42-
t.Fatalf("missing %q in generated code:\n%s", want, src)
43-
}
44-
}
45-
if strings.Contains(src, "sqlc:iterator-stream") {
46-
t.Fatal("internal stream marker leaked into generated code")
100+
var stderr strings.Builder
101+
if _, err := cmd.Generate(ctx, abs, "", &cmd.Options{Stderr: &stderr}); err != nil {
102+
t.Fatalf("generate failed: %v\n%s", err, stderr.String())
103+
}
104+
105+
queryFile := filepath.Join(abs, "go", "query.sql.go")
106+
body, err := os.ReadFile(queryFile)
107+
if err != nil {
108+
t.Fatal(err)
109+
}
110+
src := string(body)
111+
112+
for _, want := range tc.mustHave {
113+
if !strings.Contains(src, want) {
114+
t.Fatalf("missing %q in:\n%s", want, src)
115+
}
116+
}
117+
for _, bad := range tc.mustNot {
118+
if strings.Contains(src, bad) {
119+
t.Fatalf("unexpected %q in:\n%s", bad, src)
120+
}
121+
}
122+
if tc.exactIter >= 0 {
123+
count := strings.Count(src, "func (q *Queries) Iter")
124+
if count != tc.exactIter {
125+
t.Fatalf("Iter method count = %d, want %d\n%s", count, tc.exactIter, src)
126+
}
127+
}
128+
})
47129
}
48130
}
49131

50-
func TestEmitIteratorsCodegenExplicitOnly(t *testing.T) {
51-
t.Parallel()
52-
ctx := context.Background()
53-
54-
root := filepath.Join("testdata", "emit_iterators_explicit", "stdlib")
132+
func TestEmitIteratorsExplicitOnlySkipsPlainMany(t *testing.T) {
133+
root := filepath.Join("testdata", "emit_iterators_many_stream", "stdlib")
55134
abs, err := filepath.Abs(root)
56135
if err != nil {
57136
t.Fatal(err)
58137
}
59-
60138
var stderr strings.Builder
61-
_, err = cmd.Generate(ctx, abs, "", &cmd.Options{Stderr: &stderr})
62-
if err != nil {
63-
t.Fatalf("generate failed: %v\n%s", err, stderr.String())
139+
if _, err := cmd.Generate(context.Background(), abs, "", &cmd.Options{Stderr: &stderr}); err != nil {
140+
t.Fatal(err)
64141
}
65-
66-
queryFile := filepath.Join(abs, "go", "query.sql.go")
67-
body, err := os.ReadFile(queryFile)
142+
body, err := os.ReadFile(filepath.Join(abs, "go", "query.sql.go"))
68143
if err != nil {
69144
t.Fatal(err)
70145
}
71146
src := string(body)
72-
if strings.Contains(src, "sqlc:iterator-stream") {
73-
t.Fatal("internal stream marker leaked into generated code")
74-
}
75-
if strings.Count(src, "func (q *Queries) IterAuthors") != 1 {
76-
t.Fatalf("expected exactly one IterAuthors, got:\n%s", src)
77-
}
78-
for _, want := range []string{
79-
"func (q *Queries) StreamAuthors",
80-
"func (q *Queries) IterAuthors",
81-
"q.db.QueryContext(ctx, streamAuthors)",
82-
} {
83-
if !strings.Contains(src, want) {
84-
t.Fatalf("missing %q in generated code:\n%s", want, src)
85-
}
86-
}
87-
listAuthorsBlock := src[strings.Index(src, "func (q *Queries) ListAuthors"):strings.Index(src, "const streamAuthors")]
88-
if strings.Contains(listAuthorsBlock, "IterAuthors") {
89-
t.Fatal("ListAuthors block must not contain iterator method")
147+
if strings.Contains(src, "IterAllAuthors") {
148+
t.Fatal("plain :many must not get iterator in explicit_only mode")
90149
}
91150
}

internal/endtoend/testdata/emit_iterators/pgx/v5/go/db.go

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/endtoend/testdata/emit_iterators/pgx/v5/go/models.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)