channeldb/invoices: extend invoice queries to allow backwards pagination

This commit is contained in:
Wilmer Paulino committed 2018-09-12 22:11:56 -07:00
1 parent e6925cc5be
commit 0fe35e0014
1 file changed
+70 -46
+70 -46
View File
@@ -402,15 +402,19 @@ func (d *DB) FetchAllInvoices(pendingOnly bool) ([]Invoice, error) {
type InvoiceQuery struct { type InvoiceQuery struct {
// IndexOffset is the offset within the add indices to start at. This // IndexOffset is the offset within the add indices to start at. This
// can be used to start the response at a particular invoice. // can be used to start the response at a particular invoice.
IndexOffset uint32 IndexOffset uint64
// NumMaxInvoices is the maximum number of invoices that should be // NumMaxInvoices is the maximum number of invoices that should be
// starting from the add index. // starting from the add index.
NumMaxInvoices uint32 NumMaxInvoices uint64
// PendingOnly, if set, returns unsettled invoices starting from the // PendingOnly, if set, returns unsettled invoices starting from the
// add index. // add index.
PendingOnly bool PendingOnly bool
// Reversed, if set, indicates that the invoices returned should start
// from the IndexOffset and go backwards.
Reversed bool
} }
// InvoiceSlice is the response to a invoice query. It includes the original // InvoiceSlice is the response to a invoice query. It includes the original
@@ -423,13 +427,19 @@ type InvoiceSlice struct {
InvoiceQuery InvoiceQuery
// Invoices is the set of invoices that matched the query above. // Invoices is the set of invoices that matched the query above.
Invoices []*Invoice Invoices []Invoice
// FirstIndexOffset is the index of the first element in the set of
// returned Invoices above. Callers can use this to resume their query
// in the event that the slice has too many events to fit into a single
// response.
FirstIndexOffset uint64
// LastIndexOffset is the index of the last element in the set of // LastIndexOffset is the index of the last element in the set of
// returned Invoices above. Callers can use this to resume their query // returned Invoices above. Callers can use this to resume their query
// in the event that the time slice has too many events to fit into a // in the event that the slice has too many events to fit into a single
// single response. // response.
LastIndexOffset uint32 LastIndexOffset uint64
} }
// QueryInvoices allows a caller to query the invoice database for invoices // QueryInvoices allows a caller to query the invoice database for invoices
@@ -439,12 +449,6 @@ func (d *DB) QueryInvoices(q InvoiceQuery) (InvoiceSlice, error) {
InvoiceQuery: q, InvoiceQuery: q,
} }
// If the caller provided an index offset, then we'll not know how many
// records we need to skip. We'll also keep track of the record offset
// as that's part of the final return value.
invoicesToSkip := q.IndexOffset
invoiceOffset := q.IndexOffset
err := d.View(func(tx *bolt.Tx) error { err := d.View(func(tx *bolt.Tx) error {
// If the bucket wasn't found, then there aren't any invoices // If the bucket wasn't found, then there aren't any invoices
// within the database yet, so we can simply exit. // within the database yet, so we can simply exit.
@@ -452,38 +456,50 @@ func (d *DB) QueryInvoices(q InvoiceQuery) (InvoiceSlice, error) {
if invoices == nil { if invoices == nil {
return ErrNoInvoicesCreated return ErrNoInvoicesCreated
} }
invoiceAddedIndex := invoices.Bucket(addIndexBucket) invoiceAddIndex := invoices.Bucket(addIndexBucket)
if invoiceAddedIndex == nil { if invoiceAddIndex == nil {
return ErrNoInvoicesCreated return ErrNoInvoicesCreated
} }
// We'll be using a cursor to seek into the database, so we'll // keyForIndex is a helper closure that retrieves the invoice
// populate byte slices that represent the start of the key // key for the given add index of an invoice.
// space we're interested in. keyForIndex := func(c *bolt.Cursor, index uint64) []byte {
var startIndex [8]byte var keyIndex [8]byte
switch q.PendingOnly { byteOrder.PutUint64(keyIndex[:], index)
case true: _, invoiceKey := c.Seek(keyIndex[:])
// We have to start from the beginning so we know return invoiceKey
// how many pending invoices we're skipping. }
byteOrder.PutUint64(startIndex[:], uint64(1))
default: // nextKey is a helper closure to determine what the next
// We can seek right to the invoice offset we want // invoice key is when iterating over the invoice add index.
// to start with. nextKey := func(c *bolt.Cursor) ([]byte, []byte) {
invoicesToSkip = 0 if q.Reversed {
byteOrder.PutUint64(startIndex[:], uint64(invoiceOffset+1)) return c.Prev()
}
return c.Next()
}
// We'll be using a cursor to seek into the database and return
// a slice of invoices. We'll need to determine where to start
// our cursor depending on the parameters set within the query.
c := invoiceAddIndex.Cursor()
invoiceKey := keyForIndex(c, q.IndexOffset+1)
if q.Reversed {
_, invoiceKey = c.Last()
if q.IndexOffset != 0 {
invoiceKey = keyForIndex(c, q.IndexOffset-1)
}
} }
// If we know that a set of invoices exists, then we'll begin // If we know that a set of invoices exists, then we'll begin
// our seek through the bucket in order to satisfy the query. // our seek through the bucket in order to satisfy the query.
// We'll continue until either we reach the end of the range, // We'll continue until either we reach the end of the range, or
// or reach our max number of events. // reach our max number of invoices.
cursor := invoiceAddedIndex.Cursor() for ; invoiceKey != nil; _, invoiceKey = nextKey(c) {
_, invoiceKey := cursor.Seek(startIndex[:])
for ; invoiceKey != nil; _, invoiceKey = cursor.Next() {
// If our current return payload exceeds the max number // If our current return payload exceeds the max number
// of invoices, then we'll exit now. // of invoices, then we'll exit now.
if uint32(len(resp.Invoices)) >= q.NumMaxInvoices { if uint64(len(resp.Invoices)) >= q.NumMaxInvoices {
return nil break
} }
invoice, err := fetchInvoice(invoiceKey, invoices) invoice, err := fetchInvoice(invoiceKey, invoices)
@@ -496,17 +512,22 @@ func (d *DB) QueryInvoices(q InvoiceQuery) (InvoiceSlice, error) {
if q.PendingOnly && invoice.Terms.Settled { if q.PendingOnly && invoice.Terms.Settled {
continue continue
} }
// If we're not yet past the user defined offset, then
// we'll continue to seek forward.
if invoicesToSkip > 0 {
invoicesToSkip--
continue
}
// At this point, we've exhausted the offset, so we'll // At this point, we've exhausted the offset, so we'll
// begin collecting invoices found within the range. // begin collecting invoices found within the range.
resp.Invoices = append(resp.Invoices, &invoice) resp.Invoices = append(resp.Invoices, invoice)
invoiceOffset++ }
// If we iterated through the add index in reverse order, then
// we'll need to reverse the slice of invoices to return them in
// forward order.
if q.Reversed {
numInvoices := len(resp.Invoices)
for i := 0; i < numInvoices/2; i++ {
opposite := numInvoices - i - 1
resp.Invoices[i], resp.Invoices[opposite] =
resp.Invoices[opposite], resp.Invoices[i]
}
} }
return nil return nil
@@ -515,9 +536,12 @@ func (d *DB) QueryInvoices(q InvoiceQuery) (InvoiceSlice, error) {
return resp, err return resp, err
} }
// Finally, record the index of the last invoice added so that the // Finally, record the indexes of the first and last invoices returned
// caller can resume from this point later on. // so that the caller can resume from this point later on.
resp.LastIndexOffset = invoiceOffset if len(resp.Invoices) > 0 {
resp.FirstIndexOffset = resp.Invoices[0].AddIndex
resp.LastIndexOffset = resp.Invoices[len(resp.Invoices)-1].AddIndex
}
return resp, nil return resp, nil
} }