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  }