diff --git a/docs/release-notes/release-notes-0.19.0.md b/docs/release-notes/release-notes-0.19.0.md index bdbf127351..84c65c786a 100644 --- a/docs/release-notes/release-notes-0.19.0.md +++ b/docs/release-notes/release-notes-0.19.0.md @@ -369,6 +369,11 @@ config option](https://github.com/lightningnetwork/lnd/pull/9182) and introduce a new option `channel-max-fee-exposure` which is unambiguous in its description. The underlying functionality between those two options remain the same. +* [Replace offset with keyset pagination +for sql invoice queries](https://github.com/lightningnetwork/lnd/pull/9756) +because the offset query becomes inefficient for large databases. + + * Graph abstraction work: - [Abstract autopilot access](https://github.com/lightningnetwork/lnd/pull/9480) - [Abstract invoicerpc server access](https://github.com/lightningnetwork/lnd/pull/9516) diff --git a/go.mod b/go.mod index e8c0dbe7ab..e0f8bf5a2a 100644 --- a/go.mod +++ b/go.mod @@ -218,3 +218,5 @@ go 1.23.6 retract v0.0.2 replace github.com/lightningnetwork/lnd/kvdb => ./kvdb + +replace github.com/lightningnetwork/lnd/sqldb => ./sqldb diff --git a/go.sum b/go.sum index 2f385e6d22..0f1de160e5 100644 --- a/go.sum +++ b/go.sum @@ -373,8 +373,6 @@ github.com/lightningnetwork/lnd/healthcheck v1.2.6 h1:1sWhqr93GdkWy4+6U7JxBfcyZI github.com/lightningnetwork/lnd/healthcheck v1.2.6/go.mod h1:Mu02um4CWY/zdTOvFje7WJgJcHyX2zq/FG3MhOAiGaQ= github.com/lightningnetwork/lnd/queue v1.1.1 h1:99ovBlpM9B0FRCGYJo6RSFDlt8/vOkQQZznVb18iNMI= github.com/lightningnetwork/lnd/queue v1.1.1/go.mod h1:7A6nC1Qrm32FHuhx/mi1cieAiBZo5O6l8IBIoQxvkz4= -github.com/lightningnetwork/lnd/sqldb v1.0.9 h1:7OHi+Hui823mB/U9NzCdlZTAGSVdDCbjp33+6d/Q+G0= -github.com/lightningnetwork/lnd/sqldb v1.0.9/go.mod h1:OG09zL/PHPaBJefp4HsPz2YLUJ+zIQHbpgCtLnOx8I4= github.com/lightningnetwork/lnd/ticker v1.1.1 h1:J/b6N2hibFtC7JLV77ULQp++QLtCwT6ijJlbdiZFbSM= github.com/lightningnetwork/lnd/ticker v1.1.1/go.mod h1:waPTRAAcwtu7Ji3+3k+u/xH5GHovTsCoSVpho0KDvdA= github.com/lightningnetwork/lnd/tlv v1.3.0 h1:exS/KCPEgpOgviIttfiXAPaUqw2rHQrnUOpP7HPBPiY= diff --git a/invoices/sql_store.go b/invoices/sql_store.go index 1372a31317..f255993422 100644 --- a/invoices/sql_store.go +++ b/invoices/sql_store.go @@ -741,34 +741,48 @@ func (i *SQLStore) FetchPendingInvoices(ctx context.Context) ( readTxOpt := NewSQLInvoiceQueryReadTx() err := i.db.ExecTx(ctx, &readTxOpt, func(db SQLInvoiceQueries) error { - return queryWithLimit(func(offset int) (int, error) { - params := sqlc.FilterInvoicesParams{ - PendingOnly: true, - NumOffset: int32(offset), - NumLimit: int32(i.opts.paginationLimit), - Reverse: false, - } + return queryWithLimit(0, false, i.opts.paginationLimit, + func(addIndexGet int64) (int, int64, error) { + params := sqlc.FilterInvoicesParams{ + PendingOnly: true, + NumLimit: int32( + i.opts.paginationLimit, + ), + Reverse: false, + AddIndexGet: sqldb.SQLInt64( + addIndexGet, + ), + } - rows, err := db.FilterInvoices(ctx, params) - if err != nil && !errors.Is(err, sql.ErrNoRows) { - return 0, fmt.Errorf("unable to get invoices "+ - "from db: %w", err) - } + rows, err := db.FilterInvoices(ctx, params) + if err != nil && + !errors.Is(err, sql.ErrNoRows) { - // Load all the information for the invoices. - for _, row := range rows { - hash, invoice, err := fetchInvoiceData( - ctx, db, row, nil, true, - ) - if err != nil { - return 0, err + return 0, 0, fmt.Errorf("unable to "+ + "get pending invoices from "+ + "db: %w", err) } - invoices[*hash] = *invoice - } + // In case we do not have any rows we exit + // early. + if len(rows) == 0 { + return 0, 0, nil + } + + // Load all the information for the invoices. + for _, row := range rows { + hash, invoice, err := fetchInvoiceData( + ctx, db, row, nil, true, + ) + if err != nil { + return 0, 0, err + } + + invoices[*hash] = *invoice + } - return len(rows), nil - }, i.opts.paginationLimit) + return len(rows), rows[len(rows)-1].ID, nil + }) }, func() { invoices = make(map[lntypes.Hash]Invoice) }) @@ -802,113 +816,143 @@ func (i *SQLStore) InvoicesSettledSince(ctx context.Context, idx uint64) ( readTxOpt := NewSQLInvoiceQueryReadTx() err := i.db.ExecTx(ctx, &readTxOpt, func(db SQLInvoiceQueries) error { - err := queryWithLimit(func(offset int) (int, error) { - params := sqlc.FilterInvoicesParams{ - SettleIndexGet: sqldb.SQLInt64(idx + 1), - NumOffset: int32(offset), - NumLimit: int32(i.opts.paginationLimit), - Reverse: false, - } + err := queryWithLimit(int64(idx), false, i.opts.paginationLimit, + func(offset int64) (int, int64, error) { + params := sqlc.FilterInvoicesParams{ + SettleIndexGet: sqldb.SQLInt64(offset), + NumLimit: int32( + i.opts.paginationLimit, + ), + Reverse: false, + } - rows, err := db.FilterInvoices(ctx, params) - if err != nil && !errors.Is(err, sql.ErrNoRows) { - return 0, fmt.Errorf("unable to get invoices "+ - "from db: %w", err) - } + rows, err := db.FilterInvoices(ctx, params) + if err != nil && + !errors.Is(err, sql.ErrNoRows) { - // Load all the information for the invoices. - for _, row := range rows { - _, invoice, err := fetchInvoiceData( - ctx, db, row, nil, true, - ) - if err != nil { - return 0, fmt.Errorf("unable to fetch "+ - "invoice(id=%d) from db: %w", - row.ID, err) + return 0, 0, fmt.Errorf("unable to "+ + "get invoices from db: %w", err) } - invoices = append(invoices, *invoice) + // If we do not have any rows, we exit early. + if len(rows) == 0 { + return 0, 0, nil + } + + // Load all the information for the invoices. + for _, row := range rows { + _, invoice, err := fetchInvoiceData( + ctx, db, row, nil, true, + ) + if err != nil { + err = fmt.Errorf("unable to "+ + "fetch invoice(id=%d) "+ + "from db: %w", row.ID, + err) + + return 0, 0, err + } + + invoices = append(invoices, *invoice) - processedCount++ - if time.Since(lastLogTime) >= - invoiceProgressLogInterval { + processedCount++ + if time.Since(lastLogTime) >= + invoiceProgressLogInterval { - log.Debugf("Processed %d settled "+ - "invoices which have a settle "+ - "index greater than %v", - processedCount, idx) + log.Debugf("Processed %d "+ + "settled invoices "+ + "which have a settle "+ + "index greater than %v", + processedCount, idx) - lastLogTime = time.Now() + lastLogTime = time.Now() + } } - } - return len(rows), nil - }, i.opts.paginationLimit) + return len(rows), + rows[len(rows)-1].SettleIndex.Int64, nil + }) if err != nil { return err } // Now fetch all the AMP sub invoices that were settled since // the provided index. - ampInvoices, err := i.db.FetchSettledAMPSubInvoices( - ctx, sqlc.FetchSettledAMPSubInvoicesParams{ - SettleIndexGet: sqldb.SQLInt64(idx + 1), - }, - ) - if err != nil { - return err - } + err = queryWithLimit(int64(idx), false, i.opts.paginationLimit, + //nolint:ll + func(offset int64) (int, int64, error) { + ampInvoices, err := i.db.FetchSettledAMPSubInvoices( + ctx, sqlc.FetchSettledAMPSubInvoicesParams{ + SettleIndexGet: sqldb.SQLInt64( + offset, + ), + NumLimit: int32(i.opts.paginationLimit), + }, + ) + if err != nil { + return 0, 0, err + } - for _, ampInvoice := range ampInvoices { - // Convert the row to a sqlc.Invoice so we can use the - // existing fetchInvoiceData function. - sqlInvoice := sqlc.Invoice{ - ID: ampInvoice.ID, - Hash: ampInvoice.Hash, - Preimage: ampInvoice.Preimage, - SettleIndex: ampInvoice.AmpSettleIndex, - SettledAt: ampInvoice.AmpSettledAt, - Memo: ampInvoice.Memo, - AmountMsat: ampInvoice.AmountMsat, - CltvDelta: ampInvoice.CltvDelta, - Expiry: ampInvoice.Expiry, - PaymentAddr: ampInvoice.PaymentAddr, - PaymentRequest: ampInvoice.PaymentRequest, - State: ampInvoice.State, - AmountPaidMsat: ampInvoice.AmountPaidMsat, - IsAmp: ampInvoice.IsAmp, - IsHodl: ampInvoice.IsHodl, - IsKeysend: ampInvoice.IsKeysend, - CreatedAt: ampInvoice.CreatedAt.UTC(), - } + if len(ampInvoices) == 0 { + return 0, 0, nil + } - // Fetch the state and HTLCs for this AMP sub invoice. - _, invoice, err := fetchInvoiceData( - ctx, db, sqlInvoice, - (*[32]byte)(ampInvoice.SetID), true, - ) - if err != nil { - return fmt.Errorf("unable to fetch "+ - "AMP invoice(id=%d) from db: %w", - ampInvoice.ID, err) - } + for _, ampInvoice := range ampInvoices { + // Convert the row to a sqlc.Invoice so we can use the + // existing fetchInvoiceData function. + sqlInvoice := sqlc.Invoice{ + ID: ampInvoice.ID, + Hash: ampInvoice.Hash, + Preimage: ampInvoice.Preimage, + SettleIndex: ampInvoice.AmpSettleIndex, + SettledAt: ampInvoice.AmpSettledAt, + Memo: ampInvoice.Memo, + AmountMsat: ampInvoice.AmountMsat, + CltvDelta: ampInvoice.CltvDelta, + Expiry: ampInvoice.Expiry, + PaymentAddr: ampInvoice.PaymentAddr, + PaymentRequest: ampInvoice.PaymentRequest, + State: ampInvoice.State, + AmountPaidMsat: ampInvoice.AmountPaidMsat, + IsAmp: ampInvoice.IsAmp, + IsHodl: ampInvoice.IsHodl, + IsKeysend: ampInvoice.IsKeysend, + CreatedAt: ampInvoice.CreatedAt.UTC(), + } + + // Fetch the state and HTLCs for this AMP sub invoice. + _, invoice, err := fetchInvoiceData( + ctx, db, sqlInvoice, + (*[32]byte)(ampInvoice.SetID), true, + ) + if err != nil { + return 0, 0, fmt.Errorf("unable to fetch "+ + "AMP invoice(id=%d) from db: %w", + ampInvoice.ID, err) + } - invoices = append(invoices, *invoice) + invoices = append(invoices, *invoice) - processedCount++ - if time.Since(lastLogTime) >= - invoiceProgressLogInterval { + processedCount++ + if time.Since(lastLogTime) >= + invoiceProgressLogInterval { - log.Debugf("Processed %d settled invoices "+ - "including AMP sub invoices which "+ - "have a settle index greater than %v", - processedCount, idx) + log.Debugf("Processed %d settled invoices "+ + "including AMP sub invoices which "+ + "have a settle index greater than %v", + processedCount, idx) - lastLogTime = time.Now() - } - } + lastLogTime = time.Now() + } + } - return nil + lastInv := ampInvoices[len(ampInvoices)-1] + LastIndexOffset := lastInv.SettleIndex.Int64 + + return len(ampInvoices), LastIndexOffset, nil + }) + + return err }, func() { invoices = nil }) @@ -948,45 +992,61 @@ func (i *SQLStore) InvoicesAddedSince(ctx context.Context, idx uint64) ( readTxOpt := NewSQLInvoiceQueryReadTx() err := i.db.ExecTx(ctx, &readTxOpt, func(db SQLInvoiceQueries) error { - return queryWithLimit(func(offset int) (int, error) { - params := sqlc.FilterInvoicesParams{ - AddIndexGet: sqldb.SQLInt64(idx + 1), - NumOffset: int32(offset), - NumLimit: int32(i.opts.paginationLimit), - Reverse: false, - } + return queryWithLimit(int64(idx), false, i.opts.paginationLimit, + func(offset int64) (int, int64, error) { + params := sqlc.FilterInvoicesParams{ + AddIndexGet: sqldb.SQLInt64(offset), + NumLimit: int32( + i.opts.paginationLimit, + ), + Reverse: false, + } - rows, err := db.FilterInvoices(ctx, params) - if err != nil && !errors.Is(err, sql.ErrNoRows) { - return 0, fmt.Errorf("unable to get invoices "+ - "from db: %w", err) - } + rows, err := db.FilterInvoices(ctx, params) + if err != nil && + !errors.Is(err, sql.ErrNoRows) { - // Load all the information for the invoices. - for _, row := range rows { - _, invoice, err := fetchInvoiceData( - ctx, db, row, nil, true, - ) - if err != nil { - return 0, err + return 0, 0, fmt.Errorf("unable to "+ + "get invoices from db: %w", err) } - result = append(result, *invoice) + // If we do not have any rows, we exit early. + if len(rows) == 0 { + return 0, 0, nil + } + + // Load all the information for the invoices. + for _, row := range rows { + _, invoice, err := fetchInvoiceData( + ctx, db, row, nil, true, + ) + if err != nil { + err = fmt.Errorf("unable to "+ + "fetch invoice(id=%d) "+ + "from db: %w", row.ID, + err) + + return 0, 0, err + } + + result = append(result, *invoice) - processedCount++ - if time.Since(lastLogTime) >= - invoiceProgressLogInterval { + processedCount++ + if time.Since(lastLogTime) >= + invoiceProgressLogInterval { - log.Debugf("Processed %d invoices "+ - "which were added since add "+ - "index %v", processedCount, idx) + log.Debugf("Processed %d "+ + "invoices which were "+ + "added since add "+ + "index %v", + processedCount, idx) - lastLogTime = time.Now() + lastLogTime = time.Now() + } } - } - return len(rows), nil - }, i.opts.paginationLimit) + return len(rows), rows[len(rows)-1].ID, nil + }) }, func() { result = nil }) @@ -1019,74 +1079,96 @@ func (i *SQLStore) QueryInvoices(ctx context.Context, readTxOpt := NewSQLInvoiceQueryReadTx() err := i.db.ExecTx(ctx, &readTxOpt, func(db SQLInvoiceQueries) error { - return queryWithLimit(func(offset int) (int, error) { - params := sqlc.FilterInvoicesParams{ - NumOffset: int32(offset), - NumLimit: int32(i.opts.paginationLimit), - PendingOnly: q.PendingOnly, - Reverse: q.Reversed, - } + return queryWithLimit(int64(q.IndexOffset), q.Reversed, + i.opts.paginationLimit, + func(offset int64) (int, int64, error) { + params := sqlc.FilterInvoicesParams{ + NumLimit: int32( + i.opts.paginationLimit, + ), + PendingOnly: q.PendingOnly, + Reverse: q.Reversed, + AddIndexGet: sqldb.SQLInt64(offset), + } - if q.Reversed { - // If the index offset was not set, we want to - // fetch from the lastest invoice. - if q.IndexOffset == 0 { - params.AddIndexLet = sqldb.SQLInt64( - int64(math.MaxInt64), - ) - } else { - // The invoice with index offset id must - // not be included in the results. - params.AddIndexLet = sqldb.SQLInt64( - q.IndexOffset - 1, + // We need to set the other index key if we are + // iterating in reverse order. + if q.Reversed { + params = sqlc.FilterInvoicesParams{ + NumLimit: int32( + i.opts.paginationLimit, + ), + PendingOnly: q.PendingOnly, + Reverse: q.Reversed, + AddIndexLet: sqldb.SQLInt64( + offset, + ), + } + } + + if q.CreationDateStart != 0 { + params.CreatedAfter = sqldb.SQLTime( + time.Unix( + q.CreationDateStart, 0, + ).UTC(), ) } - } else { - // The invoice with index offset id must not be - // included in the results. - 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 { + // We need to add 1 to the end date + // as we're checking less than the end + // date in SQL. + params.CreatedBefore = sqldb.SQLTime( + time.Unix( + q.CreationDateEnd+1, 0, + ).UTC(), + ) + } - if q.CreationDateEnd != 0 { - // We need to add 1 to the end date as we're - // checking less than the end date in SQL. - params.CreatedBefore = sqldb.SQLTime( - time.Unix(q.CreationDateEnd+1, 0).UTC(), - ) - } + rows, err := db.FilterInvoices(ctx, params) + if err != nil && + !errors.Is(err, sql.ErrNoRows) { - rows, err := db.FilterInvoices(ctx, params) - if err != nil && !errors.Is(err, sql.ErrNoRows) { - return 0, fmt.Errorf("unable to get invoices "+ - "from db: %w", err) - } + err = fmt.Errorf("unable to get "+ + "invoices from db: %w", err) - // Load all the information for the invoices. - for _, row := range rows { - _, invoice, err := fetchInvoiceData( - ctx, db, row, nil, true, - ) - if err != nil { - return 0, err + return 0, 0, err } - invoices = append(invoices, *invoice) + // Load all the information for the invoices. + for _, row := range rows { + _, invoice, err := fetchInvoiceData( + ctx, db, row, nil, true, + ) + if err != nil { + err = fmt.Errorf("unable to "+ + "fetch invoice(id=%d) "+ + "from db: %w", row.ID, + err) + + return 0, 0, err + } + + invoices = append(invoices, *invoice) - if len(invoices) == int(q.NumMaxInvoices) { - return 0, nil + // TODO(ziggie): Should we instead use + // `NumMaxInvoices` as pagination limit? + if len(invoices) == + int(q.NumMaxInvoices) { + + return 0, 0, nil + } } - } - return len(rows), nil - }, i.opts.paginationLimit) + // In case we have iterated through the index + // and have no invoices we return a last index + // of 0. + if len(rows) == 0 { + return 0, 0, nil + } + + return len(rows), rows[len(rows)-1].ID, nil + }) }, func() { invoices = nil }) @@ -1847,10 +1929,21 @@ func unmarshalInvoiceHTLC(row sqlc.InvoiceHtlc) (CircuitKey, // queryWithLimit is a helper method that can be used to query the database // using a limit and offset. The passed query function should return the number // of rows returned and an error if any. -func queryWithLimit(query func(int) (int, error), limit int) error { - offset := 0 +func queryWithLimit(indexOffset int64, reversed bool, limit int, + query func(indexOffset int64) (int, int64, error)) error { + + // We make sure we do not include the last row in the next query. + offset := indexOffset + 1 + if reversed { + if indexOffset == 0 { + offset = int64(math.MaxInt64) + } else { + offset = indexOffset - 1 + } + } + for { - rows, err := query(offset) + rows, lastID, err := query(offset) if err != nil { return err } @@ -1859,6 +1952,10 @@ func queryWithLimit(query func(int) (int, error), limit int) error { return nil } - offset += limit + if reversed { + offset = lastID - 1 + } else { + offset = lastID + 1 + } } } diff --git a/sqldb/migrations.go b/sqldb/migrations.go index 1eed29828d..d7e89269d8 100644 --- a/sqldb/migrations.go +++ b/sqldb/migrations.go @@ -70,6 +70,11 @@ var ( // schema. This is optional and can be disabled by the // user if necessary. }, + { + Name: "000007_invoice_modification", + Version: 8, + SchemaVersion: 7, + }, } ) diff --git a/sqldb/sqlc/amp_invoices.sql.go b/sqldb/sqlc/amp_invoices.sql.go index 182848e146..61ffb05814 100644 --- a/sqldb/sqlc/amp_invoices.sql.go +++ b/sqldb/sqlc/amp_invoices.sql.go @@ -148,11 +148,14 @@ WHERE ( a.settle_index <= $2 OR $2 IS NULL ) +ORDER BY a.settle_index ASC +LIMIT $3 ` type FetchSettledAMPSubInvoicesParams struct { SettleIndexGet sql.NullInt64 SettleIndexLet sql.NullInt64 + NumLimit int32 } type FetchSettledAMPSubInvoicesRow struct { @@ -180,7 +183,7 @@ type FetchSettledAMPSubInvoicesRow struct { } func (q *Queries) FetchSettledAMPSubInvoices(ctx context.Context, arg FetchSettledAMPSubInvoicesParams) ([]FetchSettledAMPSubInvoicesRow, error) { - rows, err := q.db.QueryContext(ctx, fetchSettledAMPSubInvoices, arg.SettleIndexGet, arg.SettleIndexLet) + rows, err := q.db.QueryContext(ctx, fetchSettledAMPSubInvoices, arg.SettleIndexGet, arg.SettleIndexLet, arg.NumLimit) if err != nil { return nil, err } diff --git a/sqldb/sqlc/invoices.sql.go b/sqldb/sqlc/invoices.sql.go index 1cd7dfff4e..53811c34c0 100644 --- a/sqldb/sqlc/invoices.sql.go +++ b/sqldb/sqlc/invoices.sql.go @@ -104,7 +104,7 @@ CASE WHEN $9 = TRUE THEN id ELSE NULL END DESC -LIMIT $11 OFFSET $10 +LIMIT $10 ` type FilterInvoicesParams struct { @@ -117,7 +117,6 @@ type FilterInvoicesParams struct { CreatedBefore sql.NullTime PendingOnly interface{} Reverse interface{} - NumOffset int32 NumLimit int32 } @@ -132,7 +131,6 @@ func (q *Queries) FilterInvoices(ctx context.Context, arg FilterInvoicesParams) arg.CreatedBefore, arg.PendingOnly, arg.Reverse, - arg.NumOffset, arg.NumLimit, ) if err != nil { diff --git a/sqldb/sqlc/migrations/000007_invoice_modification.down.sql b/sqldb/sqlc/migrations/000007_invoice_modification.down.sql new file mode 100644 index 0000000000..5f4c20bdaa --- /dev/null +++ b/sqldb/sqlc/migrations/000007_invoice_modification.down.sql @@ -0,0 +1,2 @@ +-- Drop the settle_index index. +DROP INDEX IF EXISTS idx_invoices_settle_index; \ No newline at end of file diff --git a/sqldb/sqlc/migrations/000007_invoice_modification.up.sql b/sqldb/sqlc/migrations/000007_invoice_modification.up.sql new file mode 100644 index 0000000000..35bc60ecb8 --- /dev/null +++ b/sqldb/sqlc/migrations/000007_invoice_modification.up.sql @@ -0,0 +1,3 @@ +-- Add index on settle_index for better query performance because we are +-- filtering by settle_index. +CREATE INDEX IF NOT EXISTS idx_invoices_settle_index ON invoices (settle_index); diff --git a/sqldb/sqlc/queries/amp_invoices.sql b/sqldb/sqlc/queries/amp_invoices.sql index 1184fd2a41..682d3b9f0c 100644 --- a/sqldb/sqlc/queries/amp_invoices.sql +++ b/sqldb/sqlc/queries/amp_invoices.sql @@ -57,7 +57,9 @@ WHERE ( ) AND ( a.settle_index <= sqlc.narg('settle_index_let') OR sqlc.narg('settle_index_let') IS NULL -); +) +ORDER BY a.settle_index ASC +LIMIT @num_limit; -- name: UpdateAMPSubInvoiceHTLCPreimage :execresult UPDATE amp_sub_invoice_htlcs AS a diff --git a/sqldb/sqlc/queries/invoices.sql b/sqldb/sqlc/queries/invoices.sql index db1f46e617..8da321966d 100644 --- a/sqldb/sqlc/queries/invoices.sql +++ b/sqldb/sqlc/queries/invoices.sql @@ -105,7 +105,7 @@ CASE WHEN sqlc.narg('reverse') = TRUE THEN id ELSE NULL END DESC -LIMIT @num_limit OFFSET @num_offset; +LIMIT @num_limit; -- name: UpdateInvoiceState :execresult UPDATE invoices