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 }