Skip to content

Move query-scoped data to a separate data type accessed by Rows.Finish() #554

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jan 6, 2022
Merged
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
13 changes: 11 additions & 2 deletions internal/mock/rows.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,32 +33,41 @@ var _ driver.Rows = &Rows{}

// Close calls r.CloseFunc
func (r *Rows) Close() error {
if r.CloseFunc == nil {
if r == nil || r.CloseFunc == nil {
return nil
}
return r.CloseFunc()
}

// Next calls r.NextFunc
func (r *Rows) Next(row *driver.Row) error {
if r.NextFunc == nil {
if r == nil || r.NextFunc == nil {
return io.EOF
}
return r.NextFunc(row)
}

// Offset calls r.OffsetFunc
func (r *Rows) Offset() int64 {
if r == nil || r.OffsetFunc == nil {
return 0
}
return r.OffsetFunc()
}

// TotalRows calls r.TotalRowsFunc
func (r *Rows) TotalRows() int64 {
if r == nil || r.TotalRowsFunc == nil {
return 0
}
return r.TotalRowsFunc()
}

// UpdateSeq calls r.UpdateSeqFunc
func (r *Rows) UpdateSeq() string {
if r == nil || r.UpdateSeqFunc == nil {
return ""
}
return r.UpdateSeqFunc()
}

Expand Down
5 changes: 5 additions & 0 deletions row.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ func (r *row) Close() error {
atomic.StoreInt32(&r.closed, 1)
return nil
}

func (r *row) Finish() (ResultMetadata, error) {
return ResultMetadata{}, r.Close()
}

func (r *row) Err() error { return r.Row.Err }
func (r *row) ID() string { return r.id }
func (r *row) Key() string { return "" }
Expand Down
113 changes: 50 additions & 63 deletions rows.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,30 @@ import (
"github.com/go-kivik/kivik/v4/driver"
)

// ResultMetadata contains metadata about certain queries.
type ResultMetadata struct {
// Offset is the starting offset where the result set started.
Offset int64

// TotalRows is the total number of rows in the view which would have been
// returned if no limiting were used.
TotalRows int64

// UpdateSeq is the sequence id of the underlying database the view
// reflects, if requested in the query.
UpdateSeq string

// Warning is a warning generated by the query, if any.
Warning string

// Bookmark is the paging bookmark, if one was provided with the result
// set. This is intended for use with the Mango /_find interface, with
// CouchDB 2.1.1 and later. Consult the official CouchDB documentation for
// detailed usage instructions:
// http://docs.couchdb.org/en/2.1.1/api/database/find.html#pagination
Bookmark string
}

// Rows is an iterator over a a multi-value query.
//
// Call Next() to advance the iterator to the next item in the result set.
Expand All @@ -46,6 +70,13 @@ type Rows interface {
// idempotent and does not affect the result of Err.
Close() error

// Finish will consume any remaining results in the result set, then close
// the iterator, and return any available query metadata. Use this in
// place of Close() if the result metadata is needed, otherwise Close()
// may be more efficient, as Close() does not read any more data from the
// network.
Finish() (ResultMetadata, error)

// ScanValue copies the data from the result value into the value pointed
// at by dest. Think of this as a json.Unmarshal into dest.
//
Expand Down Expand Up @@ -85,44 +116,11 @@ type Rows interface {
// compound keys, the ScanKey() method may be more convenient.
Key() string

// Offset returns the starting offset where the result set started. It is
// only guaranteed to be set after all result rows have been enumerated
// through by Next, and thus should only be read after processing all rows
// in a result set. Calling Close before enumerating will render this value
// unreliable.
Offset() int64

// TotalRows returns the total number of rows in the view which would have
// been returned if no limiting were used. This value is only guaranteed to
// be set after all result rows have been enumerated through by Next, and
// thus should only be read after processing all rows in a result set.
// Calling Close before enumerating will render this value unreliable.
TotalRows() int64

// UpdateSeq returns the sequence id of the underlying database the view
// reflects, if requested in the query. This value is only guaranteed to be
// set after all result rows have been enumerated through by Next, and thus
// should only be read after processing all rows in a result set. Calling
// Close before enumerating will render this value unreliable.
UpdateSeq() string

// Warning returns a warning generated by the query, if any. This value is
// only guaranteed to be set after all result rows have been enumeratd
// through by Next.
Warning() string

// QueryIndex returns the 0-based index of the query. For standard queries,
// this is always 0. When multiple queries are passed to the view, this will
// represent the query currently being iterated
QueryIndex() int

// Bookmark returns the paging bookmark, if one was provided with the result
// set. This is intended for use with the Mango /_find interface, with
// CouchDB 2.1.1 and later. Consult the official CouchDB documentation for
// detailed usage instructions:
// http://docs.couchdb.org/en/2.1.1/api/database/find.html#pagination
Bookmark() string

// EOQ returns true if the iterator has reached the end of a query in a
// multi-query query. When EOQ is true, the row data will not have been
// updated. It is common to simply `continue` in case of EOQ, unless you
Expand All @@ -135,13 +133,9 @@ type Rows interface {
// implementations
type baseRows struct{}

func (baseRows) Bookmark() string { return "" }
func (baseRows) EOQ() bool { return false }
func (baseRows) Offset() int64 { return 0 }
func (baseRows) TotalRows() int64 { return 0 }
func (baseRows) QueryIndex() int { return 0 }
func (baseRows) UpdateSeq() string { return "" }
func (baseRows) Warning() string { return "" }

type rows struct {
*iter
Expand All @@ -166,6 +160,25 @@ func (r *rows) Close() error {
return r.iter.Close()
}

func (r *rows) Finish() (ResultMetadata, error) {
for r.Next() {
}
var warning, bookmark string
if w, ok := r.rowsi.(driver.RowsWarner); ok {
warning = w.Warning()
}
if b, ok := r.rowsi.(driver.Bookmarker); ok {
bookmark = b.Bookmark()
}
return ResultMetadata{
Offset: r.rowsi.Offset(),
TotalRows: r.rowsi.TotalRows(),
UpdateSeq: r.rowsi.UpdateSeq(),
Warning: warning,
Bookmark: bookmark,
}, r.Close()
}

type rowsIterator struct{ driver.Rows }

var _ iterator = &rowsIterator{}
Expand Down Expand Up @@ -313,35 +326,9 @@ func (r *rows) Key() string {
return string(r.curVal.(*driver.Row).Key)
}

func (r *rows) Offset() int64 {
return r.rowsi.Offset()
}

func (r *rows) TotalRows() int64 {
return r.rowsi.TotalRows()
}

func (r *rows) UpdateSeq() string {
return r.rowsi.UpdateSeq()
}

func (r *rows) Warning() string {
if w, ok := r.rowsi.(driver.RowsWarner); ok {
return w.Warning()
}
return ""
}

func (r *rows) QueryIndex() int {
if qi, ok := r.rowsi.(driver.QueryIndexer); ok {
return qi.QueryIndex()
}
return 0
}

func (r *rows) Bookmark() string {
if b, ok := r.rowsi.(driver.Bookmarker); ok {
return b.Bookmark()
}
return ""
}
99 changes: 27 additions & 72 deletions rows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,27 +331,6 @@ func TestRowsGetters(t *testing.T) {
}
})

t.Run("Offset", func(t *testing.T) {
result := r.Offset()
if offset != result {
t.Errorf("Unexpected result: %v", result)
}
})

t.Run("TotalRows", func(t *testing.T) {
result := r.TotalRows()
if totalrows != result {
t.Errorf("Unexpected result: %v", result)
}
})

t.Run("UpdateSeq", func(t *testing.T) {
result := r.UpdateSeq()
if updateseq != result {
t.Errorf("Unexpected result: %v", result)
}
})

t.Run("Not Ready", func(t *testing.T) {
r := &rows{
iter: &iter{
Expand Down Expand Up @@ -429,47 +408,6 @@ func TestRowsGetters(t *testing.T) {
t.Errorf("Unexpected result: %v", result)
}
})

t.Run("Offset", func(t *testing.T) {
result := r.Offset()
if offset != result {
t.Errorf("Unexpected result: %v", result)
}
})

t.Run("TotalRows", func(t *testing.T) {
result := r.TotalRows()
if totalrows != result {
t.Errorf("Unexpected result: %v", result)
}
})

t.Run("UpdateSeq", func(t *testing.T) {
result := r.UpdateSeq()
if updateseq != result {
t.Errorf("Unexpected result: %v", result)
}
})
})
}

func TestWarning(t *testing.T) {
t.Run("Warner", func(t *testing.T) {
expected := "test warning"
r := newRows(context.Background(), &mock.RowsWarner{
WarningFunc: func() string { return expected },
})
if w := r.Warning(); w != expected {
t.Errorf("Warning\nExpected: %s\n Actual: %s", expected, w)
}
})

t.Run("NonWarner", func(t *testing.T) {
r := newRows(context.Background(), &mock.Rows{})
expected := ""
if w := r.Warning(); w != expected {
t.Errorf("Warning\nExpected: %s\n Actual: %s", expected, w)
}
})
}

Expand All @@ -493,22 +431,39 @@ func TestQueryIndex(t *testing.T) {
})
}

func TestBookmark(t *testing.T) {
func TestFinish(t *testing.T) {
check := func(t *testing.T, r Rows) {
t.Helper()
meta, err := r.Finish()
if err != nil {
t.Fatal(err)
}
if d := testy.DiffInterface(testy.Snapshot(t), meta); d != nil {
t.Error(d)
}
}

t.Run("Standard", func(t *testing.T) {
r := newRows(context.Background(), &mock.Rows{
OffsetFunc: func() int64 { return 123 },
TotalRowsFunc: func() int64 { return 234 },
UpdateSeqFunc: func() string { return "seq" },
})
check(t, r)
})
t.Run("Bookmarker", func(t *testing.T) {
expected := "test bookmark"
r := newRows(context.Background(), &mock.Bookmarker{
BookmarkFunc: func() string { return expected },
})
if w := r.Bookmark(); w != expected {
t.Errorf("Warning\nExpected: %s\n Actual: %s", expected, w)
}
check(t, r)
})
t.Run("Non Bookmarker", func(t *testing.T) {
r := newRows(context.Background(), &mock.Rows{})
expected := ""
if w := r.Bookmark(); w != expected {
t.Errorf("Warning\nExpected: %s\n Actual: %s", expected, w)
}
t.Run("Warner", func(t *testing.T) {
expected := "test warning"
r := newRows(context.Background(), &mock.RowsWarner{
WarningFunc: func() string { return expected },
})
check(t, r)
})
}

Expand Down
7 changes: 7 additions & 0 deletions testdata/TestFinish_Bookmarker
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
(kivik.ResultMetadata) {
Offset: (int64) 0,
TotalRows: (int64) 0,
UpdateSeq: (string) "",
Warning: (string) "",
Bookmark: (string) (len=13) "test bookmark"
}
7 changes: 7 additions & 0 deletions testdata/TestFinish_Standard
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
(kivik.ResultMetadata) {
Offset: (int64) 123,
TotalRows: (int64) 234,
UpdateSeq: (string) (len=3) "seq",
Warning: (string) "",
Bookmark: (string) ""
}
7 changes: 7 additions & 0 deletions testdata/TestFinish_Warner
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
(kivik.ResultMetadata) {
Offset: (int64) 0,
TotalRows: (int64) 0,
UpdateSeq: (string) "",
Warning: (string) (len=12) "test warning",
Bookmark: (string) ""
}