diff --git a/internal/sqladapter/testing/adapter.go.tpl b/internal/sqladapter/testing/adapter.go.tpl index f9d1af5a6a5c2b21c725db87d33e60eb89e993df..88d431a6e1366ab6887d51b2d0d92bf75a9c9aa2 100644 --- a/internal/sqladapter/testing/adapter.go.tpl +++ b/internal/sqladapter/testing/adapter.go.tpl @@ -1099,6 +1099,63 @@ func TestBuilder(t *testing.T) { assert.NoError(t, err) assert.NotZero(t, all) + // Using explicit iterator and NextScan. + iter = sess.SelectFrom("artist").Iterator() + var id int + var name string + + if Adapter == "ql" { + err = iter.NextScan(&name) + id = 1 + } else { + err = iter.NextScan(&id, &name) + } + + assert.NoError(t, err) + assert.NotZero(t, id) + assert.NotEmpty(t, name) + assert.NoError(t, iter.Close()) + + err = iter.NextScan(&id, &name) + assert.Error(t, err) + + // Using explicit iterator and ScanOne. + iter = sess.SelectFrom("artist").Iterator() + id, name = 0, "" + if Adapter == "ql" { + err = iter.ScanOne(&name) + id = 1 + } else { + err = iter.ScanOne(&id, &name) + } + + assert.NoError(t, err) + assert.NotZero(t, id) + assert.NotEmpty(t, name) + + err = iter.ScanOne(&id, &name) + assert.Error(t, err) + + // Using explicit iterator and Next. + iter = sess.SelectFrom("artist").Iterator() + + var artist map[string]interface{} + for iter.Next(&artist) { + if Adapter != "ql" { + assert.NotZero(t, artist["id"]) + } + assert.NotEmpty(t, artist["name"]) + } + // We should not have any error after finishing successfully exiting a Next() loop. + assert.Empty(t, iter.Err()) + + for i := 0; i < 5; i++ { + // But we'll get errors if we attempt to continue using Next(). + assert.False(t, iter.Next(&artist)) + assert.Error(t, iter.Err()) + } + + // Using implicit iterator. q := sess.SelectFrom("artist") err = q.All(&all) diff --git a/lib/sqlbuilder/builder.go b/lib/sqlbuilder/builder.go index edeaebc5469d64f60ad4adfc5174e06dae02fda5..1edfd366065c3e77188ff511f94198731d355280 100644 --- a/lib/sqlbuilder/builder.go +++ b/lib/sqlbuilder/builder.go @@ -312,6 +312,21 @@ func (s *stringer) compileAndReplacePlaceholders(stmt *exql.Statement) (query st return query } +func (iter *iterator) NextScan(dst ...interface{}) error { + if ok := iter.Next(); ok { + return iter.Scan(dst...) + } + if err := iter.Err(); err != nil { + return err + } + return db.ErrNoMoreRows +} + +func (iter *iterator) ScanOne(dst ...interface{}) error { + defer iter.Close() + return iter.NextScan(dst...) +} + func (iter *iterator) Scan(dst ...interface{}) error { if err := iter.Err(); err != nil { return err @@ -351,7 +366,6 @@ func (iter *iterator) Err() (err error) { } func (iter *iterator) Next(dst ...interface{}) bool { - if err := iter.Err(); err != nil { return false } @@ -368,6 +382,9 @@ func (iter *iterator) Next(dst ...interface{}) bool { } func (iter *iterator) next(dst ...interface{}) error { + if iter.cursor == nil { + return iter.setErr(db.ErrNoMoreRows) + } switch len(dst) { case 0: diff --git a/lib/sqlbuilder/interfaces.go b/lib/sqlbuilder/interfaces.go index ccdb7c57b515d715a543555cd70d04edb6c7d7f3..094e863991b8225c9e35a350f6da3bbc00efa2c4 100644 --- a/lib/sqlbuilder/interfaces.go +++ b/lib/sqlbuilder/interfaces.go @@ -416,6 +416,12 @@ type Iterator interface { // Scan dumps the current result into the given pointer variable pointers. Scan(dest ...interface{}) error + // NextScan advances the iterator and performs Scan. + NextScan(dest ...interface{}) error + + // ScanOne advances the iterator, performs Scan and closes the iterator. + ScanOne(dest ...interface{}) error + // Next dumps the current element into the given destination, which could be // a pointer to either a map or a struct. Next(dest ...interface{}) bool