1package kivik
2
3import (
4	"context"
5	"strings"
6
7	"github.com/go-kivik/kivik/driver"
8	"github.com/go-kivik/kivik/errors"
9)
10
11// Rows is an iterator over a a multi-value query.
12type Rows struct {
13	*iter
14	rowsi driver.Rows
15}
16
17// Next prepares the next result value for reading. It returns true on success
18// or false if there are no more results or an error  occurs while preparing it.
19// Err should be consulted to distinguish between the two.
20func (r *Rows) Next() bool {
21	return r.iter.Next()
22}
23
24// Err returns the error, if any, that was encountered during iteration. Err may
25// be called after an explicit or implicit Close.
26func (r *Rows) Err() error {
27	return r.iter.Err()
28}
29
30// Close closes the Rows, preventing further enumeration, and freeing any
31// resources (such as the http request body) of the underlying query. If Next is
32// called and there are no further results, Rows is closed automatically and it
33// will suffice to check the result of Err. Close is idempotent and does not
34// affect the result of Err.
35func (r *Rows) Close() error {
36	return r.iter.Close()
37}
38
39type rowsIterator struct{ driver.Rows }
40
41var _ iterator = &rowsIterator{}
42
43func (r *rowsIterator) Next(i interface{}) error { return r.Rows.Next(i.(*driver.Row)) }
44
45func newRows(ctx context.Context, rowsi driver.Rows) *Rows {
46	return &Rows{
47		iter:  newIterator(ctx, &rowsIterator{rowsi}, &driver.Row{}),
48		rowsi: rowsi,
49	}
50}
51
52var (
53	errNilPtr = errors.Status(StatusBadRequest, "kivik: destination pointer is nil")
54	errNonPtr = errors.Status(StatusBadRequest, "kivik: destination is not a pointer")
55)
56
57// ScanValue copies the data from the result value into the value pointed at by
58// dest. Think of this as a json.Unmarshal into dest.
59//
60// If the dest argument has type *[]byte, Scan stores a copy of the input data.
61// The copy is owned by the caller and can be modified and held indefinitely.
62//
63// The copy can be avoided by using an argument of type *json.RawMessage
64// instead. After a Scaninto a json.RawMessage, the slice is only valid until
65// the next call to Next, Scan, or Close.
66//
67// For all other types, refer to the documentation for json.Unmarshal for type
68// conversion rules.
69func (r *Rows) ScanValue(dest interface{}) error {
70	runlock, err := r.rlock()
71	if err != nil {
72		return err
73	}
74	defer runlock()
75	return scan(dest, r.curVal.(*driver.Row).Value)
76}
77
78// ScanDoc works the same as ScanValue, but on the doc field of the result. It
79// is only valid for results that include documents.
80func (r *Rows) ScanDoc(dest interface{}) error {
81	runlock, err := r.rlock()
82	if err != nil {
83		return err
84	}
85	defer runlock()
86	doc := r.curVal.(*driver.Row).Doc
87	if doc == nil {
88		return errors.Status(StatusBadRequest, "kivik: doc is nil; does the query include docs?")
89	}
90	return scan(dest, doc)
91}
92
93// ScanKey works the same as ScanValue, but on the key field of the result. For
94// simple keys, which are just strings, the Key() method may be easier to use.
95func (r *Rows) ScanKey(dest interface{}) error {
96	runlock, err := r.rlock()
97	if err != nil {
98		return err
99	}
100	defer runlock()
101	return scan(dest, r.curVal.(*driver.Row).Key)
102}
103
104// ID returns the ID of the current result.
105func (r *Rows) ID() string {
106	runlock, err := r.rlock()
107	if err != nil {
108		return ""
109	}
110	defer runlock()
111	return r.curVal.(*driver.Row).ID
112}
113
114// Key returns the Key of the current result as a de-quoted JSON object. For
115// compound keys, the ScanKey() method may be more convenient.
116func (r *Rows) Key() string {
117	runlock, err := r.rlock()
118	if err != nil {
119		return ""
120	}
121	defer runlock()
122	return strings.Trim(string(r.curVal.(*driver.Row).Key), `"`)
123}
124
125// Offset returns the starting offset where the result set started. It is
126// only guaranteed to be set after all result rows have been enumerated through
127// by Next, and thus should only be read after processing all rows in a result
128// set. Calling Close before enumerating will render this value unreliable.
129func (r *Rows) Offset() int64 {
130	return r.rowsi.Offset()
131}
132
133// TotalRows returns the total number of rows in the view which would have been
134// returned if no limiting were used. This value is only guaranteed to be set
135// after all result rows have been enumerated through by Next, and thus should
136// only be read after processing all rows in a result set. Calling Close before
137// enumerating will render this value unreliable.
138func (r *Rows) TotalRows() int64 {
139	return r.rowsi.TotalRows()
140}
141
142// UpdateSeq returns the sequence id of the underlying database the view
143// reflects, if requested in the query.
144func (r *Rows) UpdateSeq() string {
145	return r.rowsi.UpdateSeq()
146}
147
148// Warning returns a warning generated by the query, if any. This value is only
149// guaranteed to be set after all result rows have been enumeratd through by
150// Next.
151func (r *Rows) Warning() string {
152	if w, ok := r.rowsi.(driver.RowsWarner); ok {
153		return w.Warning()
154	}
155	return ""
156}
157
158// Bookmark returns the paging bookmark, if one was provided with the result
159// set. This is intended for use with the Mango /_find interface, with CouchDB
160// 2.1.1 and later. Consult the official CouchDB documentation for detailed
161// usage instructions. http://docs.couchdb.org/en/2.1.1/api/database/find.html#pagination
162func (r *Rows) Bookmark() string {
163	if b, ok := r.rowsi.(driver.Bookmarker); ok {
164		return b.Bookmark()
165	}
166	return ""
167}
168