package channeldb import "github.com/lightningnetwork/lnd/channeldb/kvdb" type paginator struct { // cursor is the cursor which we are using to iterate through a bucket. cursor kvdb.RCursor // reversed indicates whether we are paginating forwards or backwards. reversed bool // indexOffset is the index from which we will begin querying. indexOffset uint64 // totalItems is the total number of items we allow in our response. totalItems uint64 } // newPaginator returns a struct which can be used to query an indexed bucket // in pages. func newPaginator(c kvdb.RCursor, reversed bool, indexOffset, totalItems uint64) paginator { return paginator{ cursor: c, reversed: reversed, indexOffset: indexOffset, totalItems: totalItems, } } // keyValueForIndex seeks our cursor to a given index and returns the key and // value at that position. func (p paginator) keyValueForIndex(index uint64) ([]byte, []byte) { var keyIndex [8]byte byteOrder.PutUint64(keyIndex[:], index) return p.cursor.Seek(keyIndex[:]) } // lastIndex returns the last value in our index, if our index is empty it // returns 0. func (p paginator) lastIndex() uint64 { keyIndex, _ := p.cursor.Last() if keyIndex == nil { return 0 } return byteOrder.Uint64(keyIndex) } // nextKey is a helper closure to determine what key we should use next when // we are iterating, depending on whether we are iterating forwards or in // reverse. func (p paginator) nextKey() ([]byte, []byte) { if p.reversed { return p.cursor.Prev() } return p.cursor.Next() } // cursorStart gets the index key and value for the first item we are looking // up, taking into account that we may be paginating in reverse. The index // offset provided is *excusive* so we will start with the item after the offset // for forwards queries, and the item before the index for backwards queries. func (p paginator) cursorStart() ([]byte, []byte) { indexKey, indexValue := p.keyValueForIndex(p.indexOffset + 1) // If the query is specifying reverse iteration, then we must // handle a few offset cases. if p.reversed { switch { // This indicates the default case, where no offset was // specified. In that case we just start from the last // entry. case p.indexOffset == 0: indexKey, indexValue = p.cursor.Last() // This indicates the offset being set to the very // first entry. Since there are no entries before // this offset, and the direction is reversed, we can // return without adding any invoices to the response. case p.indexOffset == 1: return nil, nil // If we have been given an index offset that is beyond our last // index value, we just return the last indexed value in our set // since we are querying in reverse. We do not cover the case // where our index offset equals our last index value, because // index offset is exclusive, so we would want to start at the // value before our last index. case p.indexOffset > p.lastIndex(): return p.cursor.Last() // Otherwise we have an index offset which is within our set of // indexed keys, and we want to start at the item before our // offset. We seek to our index offset, then return the element // before it. We do this rather than p.indexOffset-1 to account // for indexes that have gaps. default: p.keyValueForIndex(p.indexOffset) indexKey, indexValue = p.cursor.Prev() } } return indexKey, indexValue } // query gets the start point for our index offset and iterates through keys // in our index until we reach the total number of items required for the query // or we run out of cursor values. This function takes a fetchAndAppend function // which is responsible for looking up the entry at that index, adding the entry // to its set of return items (if desired) and return a boolean which indicates // whether the item was added. This is required to allow the paginator to // determine when the response has the maximum number of required items. func (p paginator) query(fetchAndAppend func(k, v []byte) (bool, error)) error { indexKey, indexValue := p.cursorStart() var totalItems int for ; indexKey != nil; indexKey, indexValue = p.nextKey() { // If our current return payload exceeds the max number // of invoices, then we'll exit now. if uint64(totalItems) >= p.totalItems { break } added, err := fetchAndAppend(indexKey, indexValue) if err != nil { return err } // If we added an item to our set in the latest fetch and append // we increment our total count. if added { totalItems++ } } return nil }