From 0fe35e0014c8a89ab57681b1eb87e97d49a1a098 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Mon, 10 Sep 2018 18:19:55 -0700 Subject: [PATCH] channeldb/invoices: extend invoice queries to allow backwards pagination --- channeldb/invoices.go | 116 +++++++++++++++++++++++++----------------- 1 file changed, 70 insertions(+), 46 deletions(-) diff --git a/channeldb/invoices.go b/channeldb/invoices.go index 141f79067..e9149c4d6 100644 --- a/channeldb/invoices.go +++ b/channeldb/invoices.go @@ -402,15 +402,19 @@ func (d *DB) FetchAllInvoices(pendingOnly bool) ([]Invoice, error) { type InvoiceQuery struct { // IndexOffset is the offset within the add indices to start at. This // 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 // starting from the add index. - NumMaxInvoices uint32 + NumMaxInvoices uint64 // PendingOnly, if set, returns unsettled invoices starting from the // add index. 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 @@ -423,13 +427,19 @@ type InvoiceSlice struct { InvoiceQuery // 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 // 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 - // single response. - LastIndexOffset uint32 + // in the event that the slice has too many events to fit into a single + // response. + LastIndexOffset uint64 } // 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, } - // 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 { // If the bucket wasn't found, then there aren't any invoices // within the database yet, so we can simply exit. @@ -452,38 +456,50 @@ func (d *DB) QueryInvoices(q InvoiceQuery) (InvoiceSlice, error) { if invoices == nil { return ErrNoInvoicesCreated } - invoiceAddedIndex := invoices.Bucket(addIndexBucket) - if invoiceAddedIndex == nil { + invoiceAddIndex := invoices.Bucket(addIndexBucket) + if invoiceAddIndex == nil { return ErrNoInvoicesCreated } - // We'll be using a cursor to seek into the database, so we'll - // populate byte slices that represent the start of the key - // space we're interested in. - var startIndex [8]byte - switch q.PendingOnly { - case true: - // We have to start from the beginning so we know - // how many pending invoices we're skipping. - byteOrder.PutUint64(startIndex[:], uint64(1)) - default: - // We can seek right to the invoice offset we want - // to start with. - invoicesToSkip = 0 - byteOrder.PutUint64(startIndex[:], uint64(invoiceOffset+1)) + // keyForIndex is a helper closure that retrieves the invoice + // key for the given add index of an invoice. + keyForIndex := func(c *bolt.Cursor, index uint64) []byte { + var keyIndex [8]byte + byteOrder.PutUint64(keyIndex[:], index) + _, invoiceKey := c.Seek(keyIndex[:]) + return invoiceKey + } + + // nextKey is a helper closure to determine what the next + // invoice key is when iterating over the invoice add index. + nextKey := func(c *bolt.Cursor) ([]byte, []byte) { + if q.Reversed { + 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 // our seek through the bucket in order to satisfy the query. - // We'll continue until either we reach the end of the range, - // or reach our max number of events. - cursor := invoiceAddedIndex.Cursor() - _, invoiceKey := cursor.Seek(startIndex[:]) - for ; invoiceKey != nil; _, invoiceKey = cursor.Next() { + // We'll continue until either we reach the end of the range, or + // reach our max number of invoices. + for ; invoiceKey != nil; _, invoiceKey = nextKey(c) { // If our current return payload exceeds the max number // of invoices, then we'll exit now. - if uint32(len(resp.Invoices)) >= q.NumMaxInvoices { - return nil + if uint64(len(resp.Invoices)) >= q.NumMaxInvoices { + break } invoice, err := fetchInvoice(invoiceKey, invoices) @@ -496,17 +512,22 @@ func (d *DB) QueryInvoices(q InvoiceQuery) (InvoiceSlice, error) { if q.PendingOnly && invoice.Terms.Settled { 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 // begin collecting invoices found within the range. - resp.Invoices = append(resp.Invoices, &invoice) - invoiceOffset++ + resp.Invoices = append(resp.Invoices, invoice) + } + + // 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 @@ -515,9 +536,12 @@ func (d *DB) QueryInvoices(q InvoiceQuery) (InvoiceSlice, error) { return resp, err } - // Finally, record the index of the last invoice added so that the - // caller can resume from this point later on. - resp.LastIndexOffset = invoiceOffset + // Finally, record the indexes of the first and last invoices returned + // so that the caller can resume from this point later on. + if len(resp.Invoices) > 0 { + resp.FirstIndexOffset = resp.Invoices[0].AddIndex + resp.LastIndexOffset = resp.Invoices[len(resp.Invoices)-1].AddIndex + } return resp, nil }