github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/pkg/couchdb/cursor.go (about)

     1  package couchdb
     2  
     3  // A Cursor holds a reference to a page in a couchdb View
     4  type Cursor interface {
     5  	HasMore() bool
     6  	ApplyTo(req *ViewRequest)
     7  	UpdateFrom(res *ViewResponse)
     8  }
     9  
    10  // NewKeyCursor returns a new key based Cursor pointing to
    11  // the given start_key & startkey_docid
    12  func NewKeyCursor(limit int, key interface{}, id string) Cursor {
    13  	return &StartKeyCursor{
    14  		baseCursor: &baseCursor{Limit: limit},
    15  		NextKey:    key,
    16  		NextDocID:  id,
    17  	}
    18  }
    19  
    20  // NewSkipCursor returns a new skip based Cursor pointing to
    21  // the page after skip items
    22  func NewSkipCursor(limit, skip int) Cursor {
    23  	return &SkipCursor{
    24  		baseCursor: &baseCursor{Limit: limit},
    25  		Skip:       skip,
    26  	}
    27  }
    28  
    29  type baseCursor struct {
    30  	// Done will be true if there is no more result after last fetch
    31  	Done bool
    32  
    33  	// Limit is maximum number of items retrieved from a request
    34  	Limit int
    35  }
    36  
    37  // HasMore returns true if there is more document after the current batch.
    38  // This value is meaning full only after UpdateFrom
    39  func (c *baseCursor) HasMore() bool { return !c.Done }
    40  
    41  func (c *baseCursor) updateFrom(res *ViewResponse) {
    42  	lrows := len(res.Rows)
    43  	if lrows <= c.Limit {
    44  		c.Done = true
    45  	} else {
    46  		res.Rows = res.Rows[:lrows-1]
    47  		c.Done = false
    48  	}
    49  }
    50  
    51  // SkipCursor is a Cursor using Skip to know how deep in the request it is.
    52  type SkipCursor struct {
    53  	*baseCursor
    54  	// Skip is the number of elements to start from
    55  	Skip int
    56  }
    57  
    58  // ApplyTo applies the cursor to a ViewRequest
    59  // the transformed ViewRequest will retrieve elements from Cursor to
    60  // Limit or EndKey whichever comes first
    61  // /!\ Mutates req
    62  func (c *SkipCursor) ApplyTo(req *ViewRequest) {
    63  	if c.Skip != 0 {
    64  		req.Skip = c.Skip
    65  	}
    66  	if c.Limit != 0 {
    67  		req.Limit = c.Limit + 1
    68  	}
    69  }
    70  
    71  // UpdateFrom change the cursor status depending on information from
    72  // the view's response
    73  func (c *SkipCursor) UpdateFrom(res *ViewResponse) {
    74  	c.baseCursor.updateFrom(res)
    75  	c.Skip += c.Limit
    76  }
    77  
    78  // StartKeyCursor is a Cursor using start_key, ie a reference to the
    79  // last fetched item to keep pagination
    80  type StartKeyCursor struct {
    81  	*baseCursor
    82  	// NextKey & NextDocID contains a reference to the document
    83  	// right after the last fetched one
    84  	NextKey   interface{}
    85  	NextDocID string
    86  }
    87  
    88  // ApplyTo applies the cursor to a ViewRequest
    89  // the transformed ViewRequest will retrieve elements from Cursor to
    90  // Limit or EndKey whichever comes first
    91  // /!\ Mutates req
    92  func (c *StartKeyCursor) ApplyTo(req *ViewRequest) {
    93  	if c.NextKey != "" && c.NextKey != nil {
    94  		if req.Key != nil && req.StartKey == nil {
    95  			req.StartKey = req.Key
    96  			req.EndKey = req.Key
    97  			req.InclusiveEnd = true
    98  			req.Key = nil
    99  		}
   100  
   101  		req.StartKey = c.NextKey
   102  		if c.NextDocID != "" {
   103  			req.StartKeyDocID = c.NextDocID
   104  		}
   105  	}
   106  
   107  	if c.Limit != 0 {
   108  		req.Limit = c.Limit + 1
   109  	}
   110  }
   111  
   112  // UpdateFrom change the cursor status depending on information from
   113  // the view's response
   114  func (c *StartKeyCursor) UpdateFrom(res *ViewResponse) {
   115  	var next *ViewResponseRow
   116  	lrows := len(res.Rows)
   117  	if lrows > 0 {
   118  		next = res.Rows[lrows-1]
   119  	}
   120  	c.baseCursor.updateFrom(res)
   121  	if !c.Done {
   122  		c.NextKey = next.Key
   123  		c.NextDocID = next.ID
   124  	} else {
   125  		c.NextKey = nil
   126  		c.NextDocID = ""
   127  	}
   128  }