paymentsdb+sqldb: add migration related query
What changed, and why it matters
This commit adds two database helper functions used only during a one-time upgrade from the old key-value storage to the new SQL database in LND. One helper lets the migration insert old payments that already have a failure reason, and the other fetches batches of payments with their attempt counts for validation. There is no user-facing change, no bug fix, and no security patch in the diff itself.
No security action required. Review the broader KV-to-SQL migration series for correctness if auditing the migration, but this commit alone is a benign schema/data-movement helper.
Security signals we found
No security relevance claimed by the vendor in commit message or comments
Migration-only code path, not used in normal runtime
No changes to authentication, authorization, cryptography, or network handling
No input sanitization or SQL injection pattern changes beyond existing sqlc slice handling
Evidence from the diff
The change introduces migration-only SQL queries: InsertPaymentMig (insert into payments with an explicit fail_reason column) and FetchPaymentsByIDsMig (batch select with HTLC attempt count). These are wired into the generated sqlc code and the SQLQueries interface. The commit message and comments explicitly state the queries are for the KV-to-SQL migration. No existing behavior is modified; no input validation, access control, or cryptographic logic is changed.
Changed components
payments/db/sql_store.gosqldb/sqlc/payments.sql.gosqldb/sqlc/querier.gosqldb/sqlc/queries/payments.sqlInspect captured patch +179 / −0
diff --git a/payments/db/sql_store.go b/payments/db/sql_store.go
index 3637a98..7f8f9e6 100644
--- a/payments/db/sql_store.go
+++ b/payments/db/sql_store.go
@@ -87,6 +87,25 @@ type SQLQueries interface {
// DeleteFailedAttempts removes all failed HTLCs from the db for a
// given payment.
DeleteFailedAttempts(ctx context.Context, paymentID int64) error
+
+ /*
+ Migration specific queries.
+
+ These queries are used ONLY for the one-time migration from KV
+ to SQL.
+ */
+
+ // FetchPaymentsByIDsMig is a migration-only batch fetch that returns
+ // payment data along with HTLC attempt counts for structural
+ // validation.
+ FetchPaymentsByIDsMig(ctx context.Context, paymentIDs []int64) ([]sqlc.FetchPaymentsByIDsMigRow, error)
+
+ // InsertPaymentMig is a migration-only variant of InsertPayment that
+ // allows setting fail_reason when inserting historical payments, since
+ // for real payments they have not failed at creation time and so no
+ // failure reason would exist yet.
+ InsertPaymentMig(ctx context.Context, arg sqlc.InsertPaymentMigParams) (int64, error)
+
}
// BatchedSQLQueries is a version of the SQLQueries that's capable
diff --git a/sqldb/sqlc/payments.sql.go b/sqldb/sqlc/payments.sql.go
index b9ec314..fe02be4 100644
--- a/sqldb/sqlc/payments.sql.go
+++ b/sqldb/sqlc/payments.sql.go
@@ -597,6 +597,72 @@ func (q *Queries) FetchPaymentsByIDs(ctx context.Context, paymentIds []int64) ([
return items, nil
}
+const fetchPaymentsByIDsMig = `-- name: FetchPaymentsByIDsMig :many
+SELECT
+ p.id,
+ p.amount_msat,
+ p.created_at,
+ p.payment_identifier,
+ p.fail_reason,
+ COUNT(ha.id) AS htlc_attempt_count
+FROM payments p
+LEFT JOIN payment_htlc_attempts ha ON ha.payment_id = p.id
+WHERE p.id IN (/*SLICE:payment_ids*/?)
+GROUP BY p.id, p.amount_msat, p.created_at, p.payment_identifier, p.fail_reason
+ORDER BY p.id ASC
+`
+
+type FetchPaymentsByIDsMigRow struct {
+ ID int64
+ AmountMsat int64
+ CreatedAt time.Time
+ PaymentIdentifier []byte
+ FailReason sql.NullInt32
+ HtlcAttemptCount int64
+}
+
+// Migration-specific batch fetch that returns payment data along with HTLC
+// attempt counts for structural validation during KV to SQL migration.
+func (q *Queries) FetchPaymentsByIDsMig(ctx context.Context, paymentIds []int64) ([]FetchPaymentsByIDsMigRow, error) {
+ query := fetchPaymentsByIDsMig
+ var queryParams []interface{}
+ if len(paymentIds) > 0 {
+ for _, v := range paymentIds {
+ queryParams = append(queryParams, v)
+ }
+ query = strings.Replace(query, "/*SLICE:payment_ids*/?", makeQueryParams(len(queryParams), len(paymentIds)), 1)
+ } else {
+ query = strings.Replace(query, "/*SLICE:payment_ids*/?", "NULL", 1)
+ }
+ rows, err := q.db.QueryContext(ctx, query, queryParams...)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ var items []FetchPaymentsByIDsMigRow
+ for rows.Next() {
+ var i FetchPaymentsByIDsMigRow
+ if err := rows.Scan(
+ &i.ID,
+ &i.AmountMsat,
+ &i.CreatedAt,
+ &i.PaymentIdentifier,
+ &i.FailReason,
+ &i.HtlcAttemptCount,
+ ); err != nil {
+ return nil, err
+ }
+ items = append(items, i)
+ }
+ if err := rows.Close(); err != nil {
+ return nil, err
+ }
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ return items, nil
+}
+
const fetchRouteLevelFirstHopCustomRecords = `-- name: FetchRouteLevelFirstHopCustomRecords :many
SELECT
l.id,
@@ -918,6 +984,51 @@ func (q *Queries) InsertPaymentIntent(ctx context.Context, arg InsertPaymentInte
return id, err
}
+const insertPaymentMig = `-- name: InsertPaymentMig :one
+/* ─────────────────────────────────────────────
+ Migration-specific queries
+
+ These queries are used ONLY for the one-time migration from KV to SQL.
+ ─────────────────────────────────────────────
+*/
+
+INSERT INTO payments (
+ amount_msat,
+ created_at,
+ payment_identifier,
+ fail_reason)
+VALUES (
+ $1,
+ $2,
+ $3,
+ $4
+)
+RETURNING id
+`
+
+type InsertPaymentMigParams struct {
+ AmountMsat int64
+ CreatedAt time.Time
+ PaymentIdentifier []byte
+ FailReason sql.NullInt32
+}
+
+// Migration-specific payment insert that allows setting fail_reason.
+// Normal InsertPayment forces fail_reason to NULL since new payments
+// aren't failed yet. During migration, we're inserting historical data
+// that may already be failed.
+func (q *Queries) InsertPaymentMig(ctx context.Context, arg InsertPaymentMigParams) (int64, error) {
+ row := q.db.QueryRowContext(ctx, insertPaymentMig,
+ arg.AmountMsat,
+ arg.CreatedAt,
+ arg.PaymentIdentifier,
+ arg.FailReason,
+ )
+ var id int64
+ err := row.Scan(&id)
+ return id, err
+}
+
const insertRouteHop = `-- name: InsertRouteHop :one
INSERT INTO payment_route_hops (
htlc_attempt_index,
diff --git a/sqldb/sqlc/querier.go b/sqldb/sqlc/querier.go
index 3be738b..02f2297 100644
--- a/sqldb/sqlc/querier.go
+++ b/sqldb/sqlc/querier.go
@@ -54,6 +54,9 @@ type Querier interface {
// Used to avoid fetching redundant payment data when processing multiple
// attempts for the same payment.
FetchPaymentsByIDs(ctx context.Context, paymentIds []int64) ([]FetchPaymentsByIDsRow, error)
+ // Migration-specific batch fetch that returns payment data along with HTLC
+ // attempt counts for structural validation during KV to SQL migration.
+ FetchPaymentsByIDsMig(ctx context.Context, paymentIds []int64) ([]FetchPaymentsByIDsMigRow, error)
// FetchPendingInvoices returns all invoices in a pending state (open or
// accepted). The invoices_state_idx index on the state column makes this a
// fast index scan rather than a full table scan.
@@ -178,6 +181,11 @@ type Querier interface {
InsertPaymentHopCustomRecord(ctx context.Context, arg InsertPaymentHopCustomRecordParams) error
// Insert a payment intent for a given payment and return its ID.
InsertPaymentIntent(ctx context.Context, arg InsertPaymentIntentParams) (int64, error)
+ // Migration-specific payment insert that allows setting fail_reason.
+ // Normal InsertPayment forces fail_reason to NULL since new payments
+ // aren't failed yet. During migration, we're inserting historical data
+ // that may already be failed.
+ InsertPaymentMig(ctx context.Context, arg InsertPaymentMigParams) (int64, error)
InsertRouteHop(ctx context.Context, arg InsertRouteHopParams) (int64, error)
InsertRouteHopAmp(ctx context.Context, arg InsertRouteHopAmpParams) error
InsertRouteHopBlinded(ctx context.Context, arg InsertRouteHopBlindedParams) error
diff --git a/sqldb/sqlc/queries/payments.sql b/sqldb/sqlc/queries/payments.sql
index 419f7bf..46f84e4 100644
--- a/sqldb/sqlc/queries/payments.sql
+++ b/sqldb/sqlc/queries/payments.sql
@@ -368,3 +368,44 @@ VALUES (
-- name: FailPayment :execresult
UPDATE payments SET fail_reason = $1 WHERE payment_identifier = $2;
+
+/* ─────────────────────────────────────────────
+ Migration-specific queries
+
+ These queries are used ONLY for the one-time migration from KV to SQL.
+ ─────────────────────────────────────────────
+*/
+
+-- name: InsertPaymentMig :one
+-- Migration-specific payment insert that allows setting fail_reason.
+-- Normal InsertPayment forces fail_reason to NULL since new payments
+-- aren't failed yet. During migration, we're inserting historical data
+-- that may already be failed.
+INSERT INTO payments (
+ amount_msat,
+ created_at,
+ payment_identifier,
+ fail_reason)
+VALUES (
+ @amount_msat,
+ @created_at,
+ @payment_identifier,
+ @fail_reason
+)
+RETURNING id;
+
+-- name: FetchPaymentsByIDsMig :many
+-- Migration-specific batch fetch that returns payment data along with HTLC
+-- attempt counts for structural validation during KV to SQL migration.
+SELECT
+ p.id,
+ p.amount_msat,
+ p.created_at,
+ p.payment_identifier,
+ p.fail_reason,
+ COUNT(ha.id) AS htlc_attempt_count
+FROM payments p
+LEFT JOIN payment_htlc_attempts ha ON ha.payment_id = p.id
+WHERE p.id IN (sqlc.slice('payment_ids')/*SLICE:payment_ids*/)
+GROUP BY p.id, p.amount_msat, p.created_at, p.payment_identifier, p.fail_reason
+ORDER BY p.id ASC;
\ No newline at end of file
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.