Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
module github.com/jmoiron/sqlx

go 1.10
go 1.27

require (
github.com/go-sql-driver/mysql v1.8.1
github.com/lib/pq v1.10.9
github.com/mattn/go-sqlite3 v1.14.22
github.com/go-sql-driver/mysql v1.10.0
github.com/lib/pq v1.12.3
github.com/mattn/go-sqlite3 v1.14.45
)

require filippo.io/edwards25519 v1.2.0 // indirect
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw=
github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.45 h1:6KA/spDguL3KV8rnybG7ezSaE4SeMR3KC9VbUoAQaIk=
github.com/mattn/go-sqlite3 v1.14.45/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
63 changes: 63 additions & 0 deletions sqlx.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"io/ioutil"
"iter"
"path/filepath"
"reflect"
"strings"
Expand Down Expand Up @@ -320,6 +321,10 @@ func (db *DB) Select(dest interface{}, query string, args ...interface{}) error
return Select(db, dest, query, args...)
}

func (db *DB) SelectIter[T any](query string, args ...interface{}) iter.Seq2[*T, error] {
return SelectIter[T](db, query, args...)
}

// Get using this DB.
// Any placeholder parameters are replaced with supplied args.
// An error is returned if the result set is empty.
Expand Down Expand Up @@ -434,6 +439,12 @@ func (tx *Tx) Select(dest interface{}, query string, args ...interface{}) error
return Select(tx, dest, query, args...)
}

// SelectIter within a transaction.
// Any placeholder parameters are replaced with supplied args.
func (tx *Tx) SelectIter[T any](query string, args ...interface{}) iter.Seq2[*T, error] {
return SelectIter[T](tx, query, args...)
}

// Queryx within a transaction.
// Any placeholder parameters are replaced with supplied args.
func (tx *Tx) Queryx(query string, args ...interface{}) (*Rows, error) {
Expand Down Expand Up @@ -519,6 +530,12 @@ func (s *Stmt) Select(dest interface{}, args ...interface{}) error {
return Select(&qStmt{s}, dest, "", args...)
}

// SelectIter using the prepared statement.
// Any placeholder parameters are replaced with supplied args.
func (s *Stmt) SelectIter[T any](query string, args ...interface{}) iter.Seq2[*T, error] {
return SelectIter[T](&qStmt{s}, query, args...)
}

// Get using the prepared statement.
// Any placeholder parameters are replaced with supplied args.
// An error is returned if the result set is empty.
Expand Down Expand Up @@ -683,6 +700,52 @@ func Select(q Queryer, dest interface{}, query string, args ...interface{}) erro
return scanAll(rows, dest, false)
}

// SelectIter returns an iterator that executes the query and then starts
// returning rows of the specific type one by one. When an error is returned
// iteration stops.
//
// Example:
//
// for u, err := range SelectIter[User](db, `SELECT * FROM users ORDER BY id`) {
// if err != nil { ... }
// ...
// }
func SelectIter[T any](q Queryer, query string, args ...interface{}) iter.Seq2[*T, error] {
return func(yield func(*T, error) bool) {
rows, err := q.Queryx(query, args...)
if err != nil {
yield(nil, err)
return
}
// We cannot use yield() inside defer, so we Close() the rows manually.

for rows.Next() {
var row T
if err := rows.StructScan(&row); err != nil {
yield(nil, err)

// We've already returned a single error, so ignoring Close()
// error here.
_ = rows.Close()
return
}

if !yield(&row, nil) {
// Cannot yield again after the iteration has stopped, so ignoring
// Close() error here.
_ = rows.Close()
return
}
}

// It's a common mistake to ignore rows.Close() error value, however
// it can still happen, so we need to handle it.
if err := rows.Close(); err != nil {
yield(nil, err)
}
}
}

// Get does a QueryRow using the provided Queryer, and scans the resulting row
// to dest. If dest is scannable, the result must only have one column. Otherwise,
// StructScan is used. Get will return sql.ErrNoRows like row.Scan would.
Expand Down
36 changes: 36 additions & 0 deletions sqlx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1923,3 +1923,39 @@ func TestSelectReset(t *testing.T) {
}
})
}

func TestSelectIter(t *testing.T) {
RunWithSchema(defaultSchema, t, func(db *DB, t *testing.T, now string) {
loadDefaultFixture(db, t)

var people []*Person
const query = "SELECT * FROM person ORDER BY first_name"

err := db.Select(&people, query)
if err != nil {
t.Fatal(err)
}
if want := 2; len(people) != want {
t.Errorf("Expected %d first names, got %d", want, len(people))
}

var peopleIter []*Person
for p, err := range db.SelectIter[Person](query) {
if err != nil {
t.Fatalf("Iteration failed for %s: %v", query, err)
}

peopleIter = append(peopleIter, p)
}

if len(people) != len(peopleIter) {
t.Fatalf("SelectIter() returned %d rows, want %d", len(peopleIter), len(people))
}

for i, p := range people {
if !reflect.DeepEqual(p, peopleIter[i]) {
t.Errorf("SelectIter() %d result is wrong, got %+v, want %+v", i, peopleIter[i], p)
}
}
})
}