Skip to content

[feature]: add a flag --count_total_invoices to listinvoices #9740

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions cmd/commands/cmd_invoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,11 @@ var listInvoicesCommand = cli.Command{
"invoices with creation date less than or " +
"equal to it",
},
cli.BoolFlag{
Name: "count_total_invoices",
Usage: "if set, only the total number of invoices will be " +
"returned, no invoice data",
},
},
Action: actionDecorator(listInvoices),
}
Expand All @@ -358,12 +363,13 @@ func listInvoices(ctx *cli.Context) error {
defer cleanUp()

req := &lnrpc.ListInvoiceRequest{
PendingOnly: ctx.Bool("pending_only"),
IndexOffset: ctx.Uint64("index_offset"),
NumMaxInvoices: ctx.Uint64("max_invoices"),
Reversed: !ctx.Bool("paginate-forwards"),
CreationDateStart: ctx.Uint64("creation_date_start"),
CreationDateEnd: ctx.Uint64("creation_date_end"),
PendingOnly: ctx.Bool("pending_only"),
IndexOffset: ctx.Uint64("index_offset"),
NumMaxInvoices: ctx.Uint64("max_invoices"),
Reversed: !ctx.Bool("paginate-forwards"),
CreationDateStart: ctx.Uint64("creation_date_start"),
CreationDateEnd: ctx.Uint64("creation_date_end"),
CountTotalInvoices: ctx.Bool("count_total_invoices"),
}

invoices, err := client.ListInvoices(ctxc, req)
Expand Down
2 changes: 2 additions & 0 deletions invoices/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ type InvoiceQuery struct {
// CreationDateEnd, if set, filters out all invoices with a creation
// date less than or equal to it.
CreationDateEnd int64
CountOnly bool
}

// InvoiceSlice is the response to a invoice query. It includes the original
Expand All @@ -173,6 +174,7 @@ type InvoiceSlice struct {
// in the event that the slice has too many events to fit into a single
// response.
LastIndexOffset uint64
TotalInvoices uint64
}

// CircuitKey is a tuple of channel ID and HTLC ID, used to uniquely identify
Expand Down
86 changes: 85 additions & 1 deletion invoices/sql_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -990,20 +990,96 @@ func (i *SQLStore) InvoicesAddedSince(ctx context.Context, idx uint64) (
return result, nil
}

// countInvoices returns the total number of invoices matching the given query parameters.
func countInvoices(ctx context.Context, db SQLInvoiceQueries, q InvoiceQuery,
paginationLimit int) (uint64, error) {

var count uint64

// Use the queryWithLimit helper to count all matching invoices
err := queryWithLimit(func(offset int) (int, error) {
params := sqlc.FilterInvoicesParams{
NumOffset: int32(offset),
NumLimit: int32(paginationLimit),
PendingOnly: q.PendingOnly,
Reverse: q.Reversed,
}

// Set up the filter parameters based on the query
if q.Reversed {
if q.IndexOffset == 0 {
params.AddIndexLet = sqldb.SQLInt64(
int64(math.MaxInt64),
)
} else {
params.AddIndexLet = sqldb.SQLInt64(
q.IndexOffset - 1,
)
}
} else {
params.AddIndexGet = sqldb.SQLInt64(
q.IndexOffset + 1,
)
}

if q.CreationDateStart != 0 {
params.CreatedAfter = sqldb.SQLTime(
time.Unix(q.CreationDateStart, 0).UTC(),
)
}

if q.CreationDateEnd != 0 {
params.CreatedBefore = sqldb.SQLTime(
time.Unix(q.CreationDateEnd+1, 0).UTC(),
)
}

// Execute the query to get the matching rows, but don't process them
rows, err := db.FilterInvoices(ctx, params)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return 0, fmt.Errorf("unable to count invoices: %w", err)
}

// Update the count
count += uint64(len(rows))

return len(rows), nil
}, paginationLimit)

if err != nil {
return 0, err
}

return count, nil
}

// QueryInvoices allows a caller to query the invoice database for invoices
// within the specified add index range.
func (i *SQLStore) QueryInvoices(ctx context.Context,
q InvoiceQuery) (InvoiceSlice, error) {

var invoices []Invoice
var totalCount uint64

if q.NumMaxInvoices == 0 {
// If CountOnly is set, we only need to count invoices, so NumMaxInvoices can be 0
if q.NumMaxInvoices == 0 && !q.CountOnly {
return InvoiceSlice{}, fmt.Errorf("max invoices must " +
"be non-zero")
}

readTxOpt := NewSQLInvoiceQueryReadTx()
err := i.db.ExecTx(ctx, &readTxOpt, func(db SQLInvoiceQueries) error {
// If we just want to count the invoices without fetching them
if q.CountOnly {
count, err := countInvoices(ctx, db, q, i.opts.paginationLimit)
if err != nil {
return err
}
totalCount = count
return nil
}

// Otherwise, proceed with the regular query to fetch invoices
return queryWithLimit(func(offset int) (int, error) {
params := sqlc.FilterInvoicesParams{
NumOffset: int32(offset),
Expand Down Expand Up @@ -1080,6 +1156,14 @@ func (i *SQLStore) QueryInvoices(ctx context.Context,
"invoices: %w", err)
}

// For count-only queries, return just the count in the InvoiceSlice
if q.CountOnly {
return InvoiceSlice{
InvoiceQuery: q,
TotalInvoices: totalCount,
}, nil
}

if len(invoices) == 0 {
return InvoiceSlice{
InvoiceQuery: q,
Expand Down
9 changes: 9 additions & 0 deletions lnrpc/lightning.proto
Original file line number Diff line number Diff line change
Expand Up @@ -4143,6 +4143,10 @@ message ListInvoiceRequest {
// If set, returns all invoices with a creation date less than or equal to
// it. Measured in seconds since the unix epoch.
uint64 creation_date_end = 8;

// If set, only return the total number of invoices, and don't actually
// include the invoices themselves.
bool count_total_invoices = 9;
}

message ListInvoiceResponse {
Expand All @@ -4163,6 +4167,11 @@ message ListInvoiceResponse {
to seek backwards, pagination style.
*/
uint64 first_index_offset = 3;

// Will only be set if count_total_invoices in the request was set.
// Represents the total number of invoices currently present in the invoices
// database.
uint64 total_num_invoices = 4;
}

message InvoiceSubscription {
Expand Down
24 changes: 24 additions & 0 deletions rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -6413,6 +6413,29 @@ func (r *rpcServer) LookupInvoice(ctx context.Context,
func (r *rpcServer) ListInvoices(ctx context.Context,
req *lnrpc.ListInvoiceRequest) (*lnrpc.ListInvoiceResponse, error) {

// If count_total_invoices is set, we'll only return the total count
// without retrieving all invoice data.
if req.CountTotalInvoices {
// Create a query to count all invoices with the provided filters
q := invoices.InvoiceQuery{
PendingOnly: req.PendingOnly,
CreationDateStart: int64(req.CreationDateStart),
CreationDateEnd: int64(req.CreationDateEnd),
CountOnly: true, // This flag tells the DB to only count
}

// Query the database for the count
invoiceSlice, err := r.server.invoicesDB.QueryInvoices(ctx, q)
if err != nil {
return nil, fmt.Errorf("unable to count invoices: %w", err)
}

// Return just the count in the response
return &lnrpc.ListInvoiceResponse{
TotalNumInvoices: invoiceSlice.TotalInvoices,
}, nil
}

// If the number of invoices was not specified, then we'll default to
// returning the latest 100 invoices.
if req.NumMaxInvoices == 0 {
Expand Down Expand Up @@ -6451,6 +6474,7 @@ func (r *rpcServer) ListInvoices(ctx context.Context,
Invoices: make([]*lnrpc.Invoice, len(invoiceSlice.Invoices)),
FirstIndexOffset: invoiceSlice.FirstIndexOffset,
LastIndexOffset: invoiceSlice.LastIndexOffset,
TotalNumInvoices: invoiceSlice.TotalInvoices,
}
for i, invoice := range invoiceSlice.Invoices {
invoice := invoice
Expand Down