You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hello sqlc folks, this package does really give a peace of mind, thanks!! 馃憤
I noticed that you are tagging query return cardinality: :exec (aka none), :one, :many. In the latter, :many, have you ever considered the case in which the whole result doesn't fit in memory? Apart from using DB.QueryContex is there any typed work-around?
I haven't tackled the case where query results don't fit into memory. If I was writing the queries, I'd fix it by doing paging myself with filtering and limits. However, I get that there are times when you'd rather just stream the results.
I could imagine a world where tag a query :iter and generate code like this:
Actually you're right, to close prematurely I would use the context: to me this context use feels right since the closure is a way to iteratively explore the context call results. How do you feel about it?
package db
const streamAuthors = `-- name: StreamAuthors :iter
SELECT id, name, bio FROM authors
ORDER BY name
`
func (q *Queries) StreamAuthors(ctx context.Context) func() (Author, error) {
rows, queryErr := q.db.QueryContext(ctx, streamAuthors)
if queryErr != nil {
return func() (Author, error) {
return Author{}, queryErr
}
}
ctx, cancel := context.WithCancel(ctx)
go func() {
<-ctx.Done()
rows.Close() //Close is safe for concurrent use.
}()
return func() (a Author, err error) {
if !rows.Next() {
//Cancel used to terminate child context, Close already called by Next.
cancel()
return Author{}, rows.Err()
}
return a, rows.Scan(&a.ID, &a.Name, &a.Bio)
}
}
The above code snippet still has concurrency issues. Since the cancel function isn't returned, a caller would need to continue to call the returned function until rows.Next returns false. The complexity of the returned closure leads me to believe sqlc shouldn't generate this type of code right now.
If more people need this functionality, we can revisit this in the future.
Someone more knowledgeable than me noticed that " QueryContext plumbs its ctx all the way down to rows.initContextClose, which arranges for rows.close to be called when ctx is canceled". So the code can be simplified back to the first revision:
package db
const streamAuthors = `-- name: StreamAuthors :iter
SELECT id, name, bio FROM authors
ORDER BY name
`
func (q *Queries) StreamAuthors(ctx context.Context) func() (Author, error) {
rows, queryErr := q.db.QueryContext(ctx, streamAuthors)
if queryErr != nil {
return func() (Author, error) {
return Author{}, queryErr
}
}
return func() (a Author, err error) {
if !rows.Next() {
return Author{}, rows.Err()
}
return a, rows.Scan(&a.ID, &a.Name, &a.Bio)
}
}
Sure a Close would be more apparent, still this code should now work like a charm:
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
next := q.StreamAuthors(ctx)
var a Author
var err error
for a, err = next(); err != nil; a, err = next() {
//Do something with a
if stopCondition {
break
}
}
Activity
[-]Query Iterator[/-][+]Query Result Iterator[/+]kyleconroy commentedon Dec 12, 2019
I haven't tackled the case where query results don't fit into memory. If I was writing the queries, I'd fix it by doing paging myself with filtering and limits. However, I get that there are times when you'd rather just stream the results.
I could imagine a world where tag a query
:iterand generate code like this:ebonetti commentedon Dec 13, 2019
Paging, I didn't think about it.. 馃 As for our parallel world, maybe even a closure iterator could work:
kyleconroy commentedon Dec 13, 2019
The closure iterator won't work as written since you need to expose a
Closemethod. Maybe you could return two funcs?ebonetti commentedon Dec 14, 2019
Actually you're right, to close prematurely I would use the context: to me this context use feels right since the closure is a way to iteratively explore the context call results. How do you feel about it?
kyleconroy commentedon Dec 17, 2019
The above code snippet still has concurrency issues. Since the cancel function isn't returned, a caller would need to continue to call the returned function until
rows.Nextreturnsfalse. The complexity of the returned closure leads me to believe sqlc shouldn't generate this type of code right now.If more people need this functionality, we can revisit this in the future.
ebonetti commentedon Dec 18, 2019
Someone more knowledgeable than me noticed that " QueryContext plumbs its ctx all the way down to rows.initContextClose, which arranges for rows.close to be called when ctx is canceled". So the code can be simplified back to the first revision:
Sure a Close would be more apparent, still this code should now work like a charm:
Keep up the great work,
Enrico Bonetti Vieno
eivindr commentedon Jul 4, 2020
Hi, sorry for bumping a closed issue. What if :iter generated something like
Compared to :many its basically adding a func type and calling the passed iter function instead of appending.
Pushed a quick and dirty poc - eivindr@64043b3