Skip to content

Commit

Permalink
Add sql iteration package
Browse files Browse the repository at this point in the history
  • Loading branch information
Zamony committed Jan 8, 2025
1 parent 54b6324 commit f45c6f2
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ A collection of Go libraries:
* [errors](https://github.com/Zamony/go/tree/main/errors) - errors with stacktraces;
* [sync](https://github.com/Zamony/go/tree/main/sync) - additional synchronization primitives;
* [zap](https://github.com/Zamony/go/tree/main/zap) - structured logging;
* [sqliter](https://github.com/Zamony/go/tree/main/sqliter) - iterator for sql queries;
* [golimit](https://github.com/Zamony/go/tree/main/golimit) - simple rate limiter;
* [toast](https://github.com/Zamony/go/tree/main/toast) - test helpers;
* [squll](https://github.com/Zamony/go/tree/main/squll) - sql templating;
Expand Down
3 changes: 3 additions & 0 deletions sqliter/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/Zamony/go/sqliter

go 1.23
54 changes: 54 additions & 0 deletions sqliter/sqliter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
Package sqliter provides utilities for querying SQL databases using iterators.
It defines a Querier interface for executing SQL queries and a Query function
that returns an iterator over the results of a query. The iterator yields
values of a specified type and any errors encountered during the iteration.
*/

package sqliter

import (
"context"
"database/sql"
"iter"
)

// Row represents a single row returned by a query.
type Row interface {
// Scan copies the columns from the current row into the values pointed to by dest.
Scan(dest ...any) error
}

// Querier is an interface for executing SQL queries.
type Querier interface {
// QueryContext executes a query with the given context and arguments,
// returning the result set as *sql.Rows.
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
}

// Query executes a query with the provided context and returns an iterator over the results.
// The scanner function is used to convert each row into a value of type T.
// If an error occurs during the query execution, the iterator will yield an error as the second return value.
func Query[T any](ctx context.Context, querier Querier, scanner func(row Row) (T, error), query string, args ...any) iter.Seq2[T, error] {
rows, err := querier.QueryContext(ctx, query, args...)
if err != nil {
return func(yield func(T, error) bool) {
var t T
yield(t, err) // Yield an error if the query fails
}
}
return func(yield func(T, error) bool) {
defer rows.Close() // Ensure rows are closed after iteration
for rows.Next() {
v, err := scanner(rows) // Scan the row into a value of type T
if !yield(v, err) { // Yield the value and error
return
}
}
if err := rows.Err(); err != nil {
var t T
yield(t, err) // Yield an error if there was an issue during iteration
}
}
}

0 comments on commit f45c6f2

Please sign in to comment.