Skip to content

Commit 8d5d5cf

Browse files
authored
refactor: introduce Iterator type alias for juicecli code generation (#537)
- Add sql.Iterator[T] as a type alias for iter.Seq2[T, error] - Move Iter function implementation from binder.go to new iter.go file - Update Iter and QueryIterContext to use sql.Iterator[T] type - Remove direct iter package imports from bind.go and shortcuts.go This change improves code organization and provides a dedicated type for juicecli to recognize during code generation, enabling proper iterator-based query method generation. Files changed: - sql/iter.go: New file with Iterator type and Iter implementation - sql/binder.go: Remove Iter function (moved to iter.go) - bind.go: Update Iter return type to sql.Iterator[T] - shortcuts.go: Update QueryIterContext return type to sql.Iterator[T]
1 parent 4196d19 commit 8d5d5cf

File tree

4 files changed

+96
-68
lines changed

4 files changed

+96
-68
lines changed

bind.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ limitations under the License.
1717
package juice
1818

1919
import (
20-
"iter"
21-
2220
"github.com/go-juicedev/juice/sql"
2321
)
2422

@@ -139,6 +137,6 @@ func List2[T any](rows sql.Rows) ([]*T, error) {
139137
// Note: The caller is responsible for closing the rows when iteration is complete.
140138
//
141139
// Returns an iterator that yields (value, error) pairs of type T, and any initialization error.
142-
func Iter[T any](rows sql.Rows) (iter.Seq2[T, error], error) {
140+
func Iter[T any](rows sql.Rows) (sql.Iterator[T], error) {
143141
return sql.Iter[T](rows)
144142
}

shortcuts.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ package juice
1919
import (
2020
"context"
2121
"database/sql"
22-
"iter"
23-
2422
sqllib "github.com/go-juicedev/juice/sql"
2523
)
2624

@@ -69,7 +67,7 @@ func QueryList2Context[T any](ctx context.Context, statement, param any) (result
6967
// QueryIterContext executes a query and returns an iterator over T.
7068
// Rows are automatically closed when iteration completes or stops.
7169
// (ctx must contain a Manager via ManagerFromContext)
72-
func QueryIterContext[T any](ctx context.Context, statement, param any) (iter.Seq2[T, error], error) {
70+
func QueryIterContext[T any](ctx context.Context, statement, param any) (sqllib.Iterator[T], error) {
7371
manager := ManagerFromContext(ctx)
7472
rows, err := manager.Object(statement).QueryContext(ctx, param)
7573
if err != nil {

sql/binder.go

Lines changed: 0 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package sql
1818

1919
import (
2020
"database/sql"
21-
"iter"
2221
"reflect"
2322
"time"
2423
)
@@ -178,64 +177,3 @@ func List2[T any](rows Rows) ([]*T, error) {
178177
}
179178
return result, nil
180179
}
181-
182-
func Iter[T any](rows Rows) (iter.Seq2[T, error], error) {
183-
columns, err := rows.Columns()
184-
if err != nil {
185-
return nil, err
186-
}
187-
188-
columnDest := &rowDestination{}
189-
t := reflect.TypeFor[T]()
190-
191-
var objectFactory func() T
192-
193-
isPtr := t.Kind() == reflect.Ptr
194-
195-
// Override object factory for pointer types to properly allocate memory
196-
if isPtr {
197-
objectFactory = func() T {
198-
result, _ := reflect.TypeAssert[T](reflect.New(t.Elem()))
199-
return result
200-
}
201-
} else {
202-
objectFactory = func() T { return *new(T) }
203-
}
204-
205-
// handler encapsulates the row scanning logic and object creation
206-
handler := func() (T, error) {
207-
var t = objectFactory()
208-
209-
var v reflect.Value
210-
211-
if isPtr {
212-
v = reflect.ValueOf(t)
213-
} else {
214-
v = reflect.ValueOf(&t)
215-
}
216-
217-
// Create destination slice for scanning row values
218-
dest, err := columnDest.Destination(v, columns)
219-
if err != nil {
220-
return t, err
221-
}
222-
if err = rows.Scan(dest...); err != nil {
223-
return t, err
224-
}
225-
return t, nil
226-
}
227-
228-
return func(yield func(T, error) bool) {
229-
for rows.Next() {
230-
value, err := handler()
231-
if !yield(value, err) {
232-
return
233-
}
234-
}
235-
// Check for any errors that occurred during iteration
236-
if err := rows.Err(); err != nil {
237-
var zero T
238-
yield(zero, err)
239-
}
240-
}, nil
241-
}

sql/iter.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
Copyright 2025 eatmoreapple
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package sql
18+
19+
import (
20+
"iter"
21+
"reflect"
22+
)
23+
24+
// Iterator is a type alias for iter.Seq2[T, error].
25+
// It represents an iterator that yields values of type T and may return an error.
26+
//
27+
// This type is specifically designed for juicecli to use as a type annotation
28+
// during code generation, allowing the tool to recognize and generate proper
29+
// iterator-based query methods.
30+
type Iterator[T any] iter.Seq2[T, error]
31+
32+
// Iter creates an iterator from the given Rows.
33+
// It scans each row into a new instance of type T and yields it.
34+
// If an error occurs during scanning, it yields the error.
35+
func Iter[T any](rows Rows) (Iterator[T], error) {
36+
columns, err := rows.Columns()
37+
if err != nil {
38+
return nil, err
39+
}
40+
41+
columnDest := &rowDestination{}
42+
t := reflect.TypeFor[T]()
43+
44+
var objectFactory func() T
45+
46+
isPtr := t.Kind() == reflect.Ptr
47+
48+
// Override object factory for pointer types to properly allocate memory
49+
if isPtr {
50+
objectFactory = func() T {
51+
result, _ := reflect.TypeAssert[T](reflect.New(t.Elem()))
52+
return result
53+
}
54+
} else {
55+
objectFactory = func() T { return *new(T) }
56+
}
57+
58+
// handler encapsulates the row scanning logic and object creation
59+
handler := func() (T, error) {
60+
var t = objectFactory()
61+
62+
var v reflect.Value
63+
64+
if isPtr {
65+
v = reflect.ValueOf(t)
66+
} else {
67+
v = reflect.ValueOf(&t)
68+
}
69+
70+
// Create destination slice for scanning row values
71+
dest, err := columnDest.Destination(v, columns)
72+
if err != nil {
73+
return t, err
74+
}
75+
if err = rows.Scan(dest...); err != nil {
76+
return t, err
77+
}
78+
return t, nil
79+
}
80+
81+
return func(yield func(T, error) bool) {
82+
for rows.Next() {
83+
value, err := handler()
84+
if !yield(value, err) {
85+
return
86+
}
87+
}
88+
// Check for any errors that occurred during iteration
89+
if err := rows.Err(); err != nil {
90+
var zero T
91+
yield(zero, err)
92+
}
93+
}, nil
94+
}

0 commit comments

Comments
 (0)