diff --git a/db.go b/db.go index 7c5926c9e..7cade6e20 100644 --- a/db.go +++ b/db.go @@ -198,7 +198,7 @@ func (r *Document) ScanDoc(i interface{}) error { } // Attachments returns an attachments iterator if the document includes -// attachments. +// attachments and they are not inline. func (r *Document) Attachments() (*AttachmentsIterator, error) { if r.err != nil { return nil, r.err diff --git a/resultset.go b/resultset.go index 0f1a223ed..5833721ba 100644 --- a/resultset.go +++ b/resultset.go @@ -152,8 +152,8 @@ func (r *ResultSet) Metadata() (*ResultMetadata, error) { // // Refer to the documentation for [encoding/json.Unmarshal] for unmarshaling // details. -func (r *ResultSet) ScanValue(dest interface{}) (err error) { - runlock, err := r.makeReady(&err) +func (r *ResultSet) ScanValue(dest interface{}) error { + runlock, err := r.makeReady() if err != nil { return err } @@ -174,8 +174,8 @@ func (r *ResultSet) ScanValue(dest interface{}) (err error) { // // If the row returned an error, it will be returned rather than // unmarshaling the doc, as error rows do not include docs. -func (r *ResultSet) ScanDoc(dest interface{}) (err error) { - runlock, err := r.makeReady(&err) +func (r *ResultSet) ScanDoc(dest interface{}) error { + runlock, err := r.makeReady() if err != nil { return err } @@ -196,8 +196,8 @@ func (r *ResultSet) ScanDoc(dest interface{}) (err error) { // // Unlike [ResultSet.ScanValue] and [ResultSet.ScanDoc], this may successfully // scan the key, and also return an error, if the row itself represents an error. -func (r *ResultSet) ScanKey(dest interface{}) (err error) { - runlock, err := r.makeReady(&err) +func (r *ResultSet) ScanKey(dest interface{}) error { + runlock, err := r.makeReady() if err != nil { return err } @@ -211,7 +211,7 @@ func (r *ResultSet) ScanKey(dest interface{}) (err error) { // ID returns the ID of the most recent result. func (r *ResultSet) ID() (string, error) { - runlock, err := r.makeReady(nil) + runlock, err := r.makeReady() if err != nil { return "", err } @@ -224,7 +224,7 @@ func (r *ResultSet) ID() (string, error) { // as those from views) include revision IDs, so this will return an empty // string in such cases. func (r *ResultSet) Rev() (string, error) { - runlock, err := r.makeReady(nil) + runlock, err := r.makeReady() if err != nil { return "", err } @@ -236,7 +236,7 @@ func (r *ResultSet) Rev() (string, error) { // Key returns the Key of the most recent result as a raw JSON string. For // compound keys, [ResultSet.ScanKey] may be more convenient. func (r *ResultSet) Key() (string, error) { - runlock, err := r.makeReady(nil) + runlock, err := r.makeReady() if err != nil { return "", err } @@ -248,7 +248,7 @@ func (r *ResultSet) Key() (string, error) { // Attachments returns an attachments iterator if the document includes // attachments. func (r *ResultSet) Attachments() (*AttachmentsIterator, error) { - runlock, err := r.makeReady(nil) + runlock, err := r.makeReady() if err != nil { return nil, err } @@ -268,31 +268,20 @@ func (r *ResultSet) Attachments() (*AttachmentsIterator, error) { } // makeReady ensures that the iterator is ready to be read from. If i.err is -// set, it is returned. In the case that [iter.Next] has not been called, the -// returned unlock function will also close the iterator, and set e if -// [iter.Close] errors and e != nil. -func (r *ResultSet) makeReady(e *error) (unlock func(), err error) { +// set, it is returned. +func (r *ResultSet) makeReady() (unlock func(), err error) { r.mu.Lock() if r.err != nil { r.mu.Unlock() return nil, r.err } + if r.state == stateClosed { + r.mu.Unlock() + return nil, &internal.Error{Status: http.StatusBadRequest, Message: "kivik: Iterator is closed"} + } if !stateIsReady(r.state) { - if !r.iter.next() { - r.mu.Unlock() - return nil, &internal.Error{Status: http.StatusNotFound, Message: "no results"} - } - var once sync.Once - r.wg.Add(1) - return func() { - once.Do(func() { - r.wg.Done() - r.mu.Unlock() - if err := r.Close(); err != nil && e != nil { - *e = err - } - }) - }, nil + r.mu.Unlock() + return nil, &internal.Error{Status: http.StatusBadRequest, Message: "kivik: Iterator access before calling Next"} } var once sync.Once r.wg.Add(1) diff --git a/resultset_test.go b/resultset_test.go index 87bc838c4..c1fd8961b 100644 --- a/resultset_test.go +++ b/resultset_test.go @@ -345,9 +345,9 @@ func TestResultSet_Getters(t *testing.T) { } r := newResultSet(context.Background(), nil, rowsi) - result, _ := r.ID() - if result != id { - t.Errorf("Unexpected result: %v", result) + _, err := r.ID() + if !testy.ErrorMatches("kivik: Iterator access before calling Next", err) { + t.Errorf("Unexpected error: %v", err) } }) @@ -360,57 +360,9 @@ func TestResultSet_Getters(t *testing.T) { } r := newResultSet(context.Background(), nil, rowsi) - result, _ := r.Key() - if result != string(key) { - t.Errorf("Unexpected result: %v", result) - } - }) - }) - - t.Run("after close", func(t *testing.T) { - rowsi := &mock.Rows{ - OffsetFunc: func() int64 { return offset }, - TotalRowsFunc: func() int64 { return totalrows }, - UpdateSeqFunc: func() string { return updateseq }, - } - r := &ResultSet{ - iter: &iter{ - state: stateRowReady, - curVal: &driver.Row{ - ID: id, - Key: key, - }, - feed: &rowsIterator{Rows: rowsi}, - }, - rowsi: rowsi, - } - - if err := r.Close(); err != nil { - t.Fatal(err) - } - - t.Run("ID", func(t *testing.T) { - result, _ := r.ID() - if id != result { - t.Errorf("Unexpected result: %v", result) - } - }) - - t.Run("Key", func(t *testing.T) { - result, _ := r.Key() - if string(key) != result { - t.Errorf("Unexpected result: %v", result) - } - }) - - t.Run("ScanKey", func(t *testing.T) { - var result json.RawMessage - err := r.ScanKey(&result) - if err != nil { - t.Fatal(err) - } - if string(result) != string(key) { - t.Errorf("Unexpected result: %v", result) + _, err := r.Key() + if !testy.ErrorMatches("kivik: Iterator access before calling Next", err) { + t.Errorf("Unexpected error: %v", err) } }) }) @@ -568,8 +520,8 @@ func Test_bug576(t *testing.T) { var result interface{} err := rows.ScanDoc(&result) - const wantErr = "no results" - wantStatus := http.StatusNotFound + const wantErr = "kivik: Iterator access before calling Next" + wantStatus := http.StatusBadRequest if !testy.ErrorMatches(wantErr, err) { t.Errorf("unexpected error: %s", err) } @@ -578,41 +530,6 @@ func Test_bug576(t *testing.T) { } } -func TestResultSet_single(t *testing.T) { - const wantRev = "1-abc" - const docContent = `{"_id":"foo"}` - wantDoc := map[string]interface{}{"_id": "foo"} - rows := newResultSet(context.Background(), nil, &mock.Rows{ - NextFunc: func(row *driver.Row) error { - row.Rev = wantRev - row.Doc = strings.NewReader(docContent) - return nil - }, - }) - - rev, err := rows.Rev() - if err != nil { - t.Fatal(err) - } - if rev != wantRev { - t.Errorf("Unexpected rev: %s", rev) - } - var doc map[string]interface{} - if err := rows.ScanDoc(&doc); err != nil { - t.Fatal(err) - } - if d := cmp.Diff(wantDoc, doc); d != "" { - t.Error(d) - } - rev2, err := rows.Rev() - if err != nil { - t.Fatal(err) - } - if rev2 != wantRev { - t.Errorf("Unexpected rev on second read: %s", rev2) - } -} - func TestResultSet_Close_blocks(t *testing.T) { t.Parallel() @@ -636,6 +553,7 @@ func TestResultSet_Close_blocks(t *testing.T) { }, work: func(rs *ResultSet) { var i interface{} + rs.Next() _ = rs.ScanDoc(&i) }, }) @@ -651,6 +569,7 @@ func TestResultSet_Close_blocks(t *testing.T) { }, work: func(rs *ResultSet) { var i interface{} + rs.Next() _ = rs.ScanValue(&i) }, }) @@ -667,6 +586,7 @@ func TestResultSet_Close_blocks(t *testing.T) { }, }, work: func(rs *ResultSet) { + rs.Next() atts, err := rs.Attachments() if err != nil { t.Fatal(err) @@ -687,7 +607,7 @@ func TestResultSet_Close_blocks(t *testing.T) { time.Sleep(delay / 2) _ = rs.Close() if elapsed := time.Since(start); elapsed < delay { - t.Errorf("rs.Close() didn't block long enouggh (%v < %v)", elapsed, delay) + t.Errorf("rs.Close() didn't block long enough (%v < %v)", elapsed, delay) } }) }