github.com/decred/dcrlnd@v0.7.6/channeldb/paginate.go (about) 1 package channeldb 2 3 import "github.com/decred/dcrlnd/kvdb" 4 5 type paginator struct { 6 // cursor is the cursor which we are using to iterate through a bucket. 7 cursor kvdb.RCursor 8 9 // reversed indicates whether we are paginating forwards or backwards. 10 reversed bool 11 12 // indexOffset is the index from which we will begin querying. 13 indexOffset uint64 14 15 // totalItems is the total number of items we allow in our response. 16 totalItems uint64 17 } 18 19 // newPaginator returns a struct which can be used to query an indexed bucket 20 // in pages. 21 func newPaginator(c kvdb.RCursor, reversed bool, 22 indexOffset, totalItems uint64) paginator { 23 24 return paginator{ 25 cursor: c, 26 reversed: reversed, 27 indexOffset: indexOffset, 28 totalItems: totalItems, 29 } 30 } 31 32 // keyValueForIndex seeks our cursor to a given index and returns the key and 33 // value at that position. 34 func (p paginator) keyValueForIndex(index uint64) ([]byte, []byte) { 35 var keyIndex [8]byte 36 byteOrder.PutUint64(keyIndex[:], index) 37 return p.cursor.Seek(keyIndex[:]) 38 } 39 40 // lastIndex returns the last value in our index, if our index is empty it 41 // returns 0. 42 func (p paginator) lastIndex() uint64 { 43 keyIndex, _ := p.cursor.Last() 44 if keyIndex == nil { 45 return 0 46 } 47 48 return byteOrder.Uint64(keyIndex) 49 } 50 51 // nextKey is a helper closure to determine what key we should use next when 52 // we are iterating, depending on whether we are iterating forwards or in 53 // reverse. 54 func (p paginator) nextKey() ([]byte, []byte) { 55 if p.reversed { 56 return p.cursor.Prev() 57 } 58 return p.cursor.Next() 59 } 60 61 // cursorStart gets the index key and value for the first item we are looking 62 // up, taking into account that we may be paginating in reverse. The index 63 // offset provided is *excusive* so we will start with the item after the offset 64 // for forwards queries, and the item before the index for backwards queries. 65 func (p paginator) cursorStart() ([]byte, []byte) { 66 indexKey, indexValue := p.keyValueForIndex(p.indexOffset + 1) 67 68 // If the query is specifying reverse iteration, then we must 69 // handle a few offset cases. 70 if p.reversed { 71 switch { 72 73 // This indicates the default case, where no offset was 74 // specified. In that case we just start from the last 75 // entry. 76 case p.indexOffset == 0: 77 indexKey, indexValue = p.cursor.Last() 78 79 // This indicates the offset being set to the very 80 // first entry. Since there are no entries before 81 // this offset, and the direction is reversed, we can 82 // return without adding any invoices to the response. 83 case p.indexOffset == 1: 84 return nil, nil 85 86 // If we have been given an index offset that is beyond our last 87 // index value, we just return the last indexed value in our set 88 // since we are querying in reverse. We do not cover the case 89 // where our index offset equals our last index value, because 90 // index offset is exclusive, so we would want to start at the 91 // value before our last index. 92 case p.indexOffset > p.lastIndex(): 93 return p.cursor.Last() 94 95 // Otherwise we have an index offset which is within our set of 96 // indexed keys, and we want to start at the item before our 97 // offset. We seek to our index offset, then return the element 98 // before it. We do this rather than p.indexOffset-1 to account 99 // for indexes that have gaps. 100 default: 101 p.keyValueForIndex(p.indexOffset) 102 indexKey, indexValue = p.cursor.Prev() 103 } 104 } 105 106 return indexKey, indexValue 107 } 108 109 // query gets the start point for our index offset and iterates through keys 110 // in our index until we reach the total number of items required for the query 111 // or we run out of cursor values. This function takes a fetchAndAppend function 112 // which is responsible for looking up the entry at that index, adding the entry 113 // to its set of return items (if desired) and return a boolean which indicates 114 // whether the item was added. This is required to allow the paginator to 115 // determine when the response has the maximum number of required items. 116 func (p paginator) query(fetchAndAppend func(k, v []byte) (bool, error)) error { 117 indexKey, indexValue := p.cursorStart() 118 119 var totalItems int 120 for ; indexKey != nil; indexKey, indexValue = p.nextKey() { 121 // If our current return payload exceeds the max number 122 // of invoices, then we'll exit now. 123 if uint64(totalItems) >= p.totalItems { 124 break 125 } 126 127 added, err := fetchAndAppend(indexKey, indexValue) 128 if err != nil { 129 return err 130 } 131 132 // If we added an item to our set in the latest fetch and append 133 // we increment our total count. 134 if added { 135 totalItems++ 136 } 137 } 138 139 return nil 140 }