diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 43ef4b815..1796cad3f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -32,6 +32,29 @@ env: GO_VERSION: 1.20.3 jobs: + ######################## + # SQLC code gen check + ######################## + sqlc-check: + name: Sqlc check + runs-on: ubuntu-latest + steps: + - name: git checkout + uses: actions/checkout@v3 + + - name: setup go ${{ env.GO_VERSION }} + uses: ./.github/actions/setup-go + with: + go-version: '${{ env.GO_VERSION }}' + + - name: docker image cache + uses: satackey/action-docker-layer-caching@v0.0.11 + # Ignore the failure of a step and avoid terminating the job. + continue-on-error: true + + - name: Generate sql models + run: make sqlc-check + ######################## # RPC and mobile compilation check ######################## diff --git a/Makefile b/Makefile index f5bf7289a..ec1aa7128 100644 --- a/Makefile +++ b/Makefile @@ -249,6 +249,14 @@ list: grep -v Makefile | \ sort +sqlc: + @$(call print, "Generating sql models and queries in Go") + ./scripts/gen_sqlc_docker.sh + +sqlc-check: sqlc + @$(call print, "Verifying sql code generation.") + if test -n "$$(git status --porcelain '*.go')"; then echo "SQL models not properly generated!"; git status --porcelain '*.go'; exit 1; fi + rpc: @$(call print, "Compiling protos.") cd ./lnrpc; ./gen_protos_docker.sh diff --git a/docs/release-notes/release-notes-0.17.0.md b/docs/release-notes/release-notes-0.17.0.md index 0a7b64258..800f5509e 100644 --- a/docs/release-notes/release-notes-0.17.0.md +++ b/docs/release-notes/release-notes-0.17.0.md @@ -6,6 +6,9 @@ implementation](https://github.com/lightningnetwork/lnd/pull/7377) logic in different update types. +* Add [invoice SQL schema and + queries](https://github.com/lightningnetwork/lnd/pull/7354) + ## Watchtowers * Let the task pipeline [only carry diff --git a/scripts/gen_sqlc_docker.sh b/scripts/gen_sqlc_docker.sh new file mode 100755 index 000000000..9c8e88487 --- /dev/null +++ b/scripts/gen_sqlc_docker.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -e + +# Directory of the script file, independent of where it's called from. +DIR="$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)" +# Use the user's cache directories +GOCACHE=`go env GOCACHE` +GOMODCACHE=`go env GOMODCACHE` + +echo "Generating sql models and queries in go..." + +docker run \ + --rm \ + --user "$UID:$(id -g)" \ + -e UID=$UID \ + -v "$DIR/../:/build" \ + -w /build \ + kjconroy/sqlc:1.18.0 generate diff --git a/sqlc.yaml b/sqlc.yaml new file mode 100644 index 000000000..2df372db1 --- /dev/null +++ b/sqlc.yaml @@ -0,0 +1,10 @@ +version: "2" +sql: + - engine: "postgresql" + schema: "sqldb/sqlc/migrations" + queries: "sqldb/sqlc/queries" + gen: + go: + out: sqldb/sqlc + package: sqlc + emit_interface: true diff --git a/sqldb/sqlc/amp_invoices.sql.go b/sqldb/sqlc/amp_invoices.sql.go new file mode 100644 index 000000000..c803a8307 --- /dev/null +++ b/sqldb/sqlc/amp_invoices.sql.go @@ -0,0 +1,326 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.18.0 +// source: amp_invoices.sql + +package sqlc + +import ( + "context" + "database/sql" + "time" +) + +const deleteAMPHTLCCustomRecords = `-- name: DeleteAMPHTLCCustomRecords :exec +WITH htlc_ids AS ( + SELECT htlc_id + FROM amp_invoice_htlcs + WHERE invoice_id = $1 +) +DELETE +FROM invoice_htlc_custom_records +WHERE htlc_id IN (SELECT id FROM htlc_ids) +` + +func (q *Queries) DeleteAMPHTLCCustomRecords(ctx context.Context, invoiceID int32) error { + _, err := q.db.ExecContext(ctx, deleteAMPHTLCCustomRecords, invoiceID) + return err +} + +const deleteAMPHTLCs = `-- name: DeleteAMPHTLCs :exec +DELETE +FROM amp_invoice_htlcs +WHERE invoice_id = $1 +` + +func (q *Queries) DeleteAMPHTLCs(ctx context.Context, invoiceID int32) error { + _, err := q.db.ExecContext(ctx, deleteAMPHTLCs, invoiceID) + return err +} + +const deleteAMPInvoiceHTLC = `-- name: DeleteAMPInvoiceHTLC :exec +DELETE +FROM amp_invoice_htlcs +WHERE set_id = $1 +` + +func (q *Queries) DeleteAMPInvoiceHTLC(ctx context.Context, setID []byte) error { + _, err := q.db.ExecContext(ctx, deleteAMPInvoiceHTLC, setID) + return err +} + +const getAMPInvoiceHTLCsByInvoiceID = `-- name: GetAMPInvoiceHTLCsByInvoiceID :many +SELECT set_id, htlc_id, invoice_id, root_share, child_index, hash, preimage +FROM amp_invoice_htlcs +WHERE invoice_id = $1 +` + +func (q *Queries) GetAMPInvoiceHTLCsByInvoiceID(ctx context.Context, invoiceID int32) ([]AmpInvoiceHtlc, error) { + rows, err := q.db.QueryContext(ctx, getAMPInvoiceHTLCsByInvoiceID, invoiceID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AmpInvoiceHtlc + for rows.Next() { + var i AmpInvoiceHtlc + if err := rows.Scan( + &i.SetID, + &i.HtlcID, + &i.InvoiceID, + &i.RootShare, + &i.ChildIndex, + &i.Hash, + &i.Preimage, + ); 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 getAMPInvoiceHTLCsBySetID = `-- name: GetAMPInvoiceHTLCsBySetID :many +SELECT set_id, htlc_id, invoice_id, root_share, child_index, hash, preimage +FROM amp_invoice_htlcs +WHERE set_id = $1 +` + +func (q *Queries) GetAMPInvoiceHTLCsBySetID(ctx context.Context, setID []byte) ([]AmpInvoiceHtlc, error) { + rows, err := q.db.QueryContext(ctx, getAMPInvoiceHTLCsBySetID, setID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AmpInvoiceHtlc + for rows.Next() { + var i AmpInvoiceHtlc + if err := rows.Scan( + &i.SetID, + &i.HtlcID, + &i.InvoiceID, + &i.RootShare, + &i.ChildIndex, + &i.Hash, + &i.Preimage, + ); 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 getSetIDHTLCsCustomRecords = `-- name: GetSetIDHTLCsCustomRecords :many +SELECT ihcr.htlc_id, key, value +FROM amp_invoice_htlcs aih JOIN invoice_htlc_custom_records ihcr ON aih.id=ihcr.htlc_id +WHERE aih.set_id = $1 +` + +type GetSetIDHTLCsCustomRecordsRow struct { + HtlcID int64 + Key int64 + Value []byte +} + +func (q *Queries) GetSetIDHTLCsCustomRecords(ctx context.Context, setID []byte) ([]GetSetIDHTLCsCustomRecordsRow, error) { + rows, err := q.db.QueryContext(ctx, getSetIDHTLCsCustomRecords, setID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetSetIDHTLCsCustomRecordsRow + for rows.Next() { + var i GetSetIDHTLCsCustomRecordsRow + if err := rows.Scan(&i.HtlcID, &i.Key, &i.Value); 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 insertAMPInvoiceHTLC = `-- name: InsertAMPInvoiceHTLC :exec +INSERT INTO amp_invoice_htlcs ( + set_id, htlc_id, root_share, child_index, hash, preimage +) VALUES ( + $1, $2, $3, $4, $5, $6 +) +` + +type InsertAMPInvoiceHTLCParams struct { + SetID []byte + HtlcID int64 + RootShare []byte + ChildIndex int64 + Hash []byte + Preimage []byte +} + +func (q *Queries) InsertAMPInvoiceHTLC(ctx context.Context, arg InsertAMPInvoiceHTLCParams) error { + _, err := q.db.ExecContext(ctx, insertAMPInvoiceHTLC, + arg.SetID, + arg.HtlcID, + arg.RootShare, + arg.ChildIndex, + arg.Hash, + arg.Preimage, + ) + return err +} + +const insertAMPInvoicePayment = `-- name: InsertAMPInvoicePayment :exec +INSERT INTO amp_invoice_payments ( + set_id, state, created_at, settled_index, invoice_id +) VALUES ( + $1, $2, $3, $4, $5 +) +` + +type InsertAMPInvoicePaymentParams struct { + SetID []byte + State int16 + CreatedAt time.Time + SettledIndex sql.NullInt32 + InvoiceID int32 +} + +func (q *Queries) InsertAMPInvoicePayment(ctx context.Context, arg InsertAMPInvoicePaymentParams) error { + _, err := q.db.ExecContext(ctx, insertAMPInvoicePayment, + arg.SetID, + arg.State, + arg.CreatedAt, + arg.SettledIndex, + arg.InvoiceID, + ) + return err +} + +const selectAMPInvoicePayments = `-- name: SelectAMPInvoicePayments :many +SELECT aip.set_id, aip.state, aip.created_at, aip.settled_index, aip.invoice_id, ip.id, ip.settled_at, ip.amount_paid_msat, ip.invoice_id +FROM amp_invoice_payments aip LEFT JOIN invoice_payments ip ON aip.settled_index = ip.id +WHERE ( + set_id = $1 OR + $1 IS NULL +) AND ( + aip.settled_index = $2 OR + $2 IS NULL +) AND ( + aip.invoice_id = $3 OR + $3 IS NULL +) +` + +type SelectAMPInvoicePaymentsParams struct { + SetID []byte + SettledIndex sql.NullInt32 + InvoiceID sql.NullInt32 +} + +type SelectAMPInvoicePaymentsRow struct { + SetID []byte + State int16 + CreatedAt time.Time + SettledIndex sql.NullInt32 + InvoiceID int32 + ID sql.NullInt32 + SettledAt sql.NullTime + AmountPaidMsat sql.NullInt64 + InvoiceID_2 sql.NullInt32 +} + +func (q *Queries) SelectAMPInvoicePayments(ctx context.Context, arg SelectAMPInvoicePaymentsParams) ([]SelectAMPInvoicePaymentsRow, error) { + rows, err := q.db.QueryContext(ctx, selectAMPInvoicePayments, arg.SetID, arg.SettledIndex, arg.InvoiceID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []SelectAMPInvoicePaymentsRow + for rows.Next() { + var i SelectAMPInvoicePaymentsRow + if err := rows.Scan( + &i.SetID, + &i.State, + &i.CreatedAt, + &i.SettledIndex, + &i.InvoiceID, + &i.ID, + &i.SettledAt, + &i.AmountPaidMsat, + &i.InvoiceID_2, + ); 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 updateAMPInvoiceHTLC = `-- name: UpdateAMPInvoiceHTLC :exec +UPDATE amp_invoice_htlcs +SET preimage = $1 +WHERE htlc_id = $2 +` + +type UpdateAMPInvoiceHTLCParams struct { + Preimage []byte + HtlcID int64 +} + +func (q *Queries) UpdateAMPInvoiceHTLC(ctx context.Context, arg UpdateAMPInvoiceHTLCParams) error { + _, err := q.db.ExecContext(ctx, updateAMPInvoiceHTLC, arg.Preimage, arg.HtlcID) + return err +} + +const updateAMPPayment = `-- name: UpdateAMPPayment :exec +UPDATE amp_invoice_payments +SET state = $1, settled_index = $2 +WHERE state = 0 AND ( + set_id = $3 OR + $3 IS NULL +) AND ( + invoice_id = $4 OR + $4 IS NULL +) +` + +type UpdateAMPPaymentParams struct { + State int16 + SettledIndex sql.NullInt32 + SetID []byte + InvoiceID sql.NullInt32 +} + +func (q *Queries) UpdateAMPPayment(ctx context.Context, arg UpdateAMPPaymentParams) error { + _, err := q.db.ExecContext(ctx, updateAMPPayment, + arg.State, + arg.SettledIndex, + arg.SetID, + arg.InvoiceID, + ) + return err +} diff --git a/sqldb/sqlc/db.go b/sqldb/sqlc/db.go new file mode 100644 index 000000000..9848f3e6a --- /dev/null +++ b/sqldb/sqlc/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.18.0 + +package sqlc + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/sqldb/sqlc/invoice_events.sql.go b/sqldb/sqlc/invoice_events.sql.go new file mode 100644 index 000000000..054d9091c --- /dev/null +++ b/sqldb/sqlc/invoice_events.sql.go @@ -0,0 +1,128 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.18.0 +// source: invoice_events.sql + +package sqlc + +import ( + "context" + "database/sql" + "time" +) + +const deleteInvoiceEvents = `-- name: DeleteInvoiceEvents :exec +DELETE +FROM invoice_events +WHERE invoice_id = $1 +` + +func (q *Queries) DeleteInvoiceEvents(ctx context.Context, invoiceID int32) error { + _, err := q.db.ExecContext(ctx, deleteInvoiceEvents, invoiceID) + return err +} + +const insertInvoiceEvent = `-- name: InsertInvoiceEvent :exec +INSERT INTO invoice_events ( + created_at, invoice_id, htlc_id, set_id, event_type, event_metadata +) VALUES ( + $1, $2, $3, $4, $5, $6 +) +` + +type InsertInvoiceEventParams struct { + CreatedAt time.Time + InvoiceID int32 + HtlcID sql.NullInt64 + SetID []byte + EventType int32 + EventMetadata []byte +} + +func (q *Queries) InsertInvoiceEvent(ctx context.Context, arg InsertInvoiceEventParams) error { + _, err := q.db.ExecContext(ctx, insertInvoiceEvent, + arg.CreatedAt, + arg.InvoiceID, + arg.HtlcID, + arg.SetID, + arg.EventType, + arg.EventMetadata, + ) + return err +} + +const selectInvoiceEvents = `-- name: SelectInvoiceEvents :many +SELECT id, created_at, invoice_id, htlc_id, set_id, event_type, event_metadata +FROM invoice_events +WHERE ( + invoice_id = $1 OR + $1 IS NULL +) AND ( + htlc_id = $2 OR + $2 IS NULL +) AND ( + set_id = $3 OR + $3 IS NULL +) AND ( + event_type = $4 OR + $4 IS NULL +) AND ( + created_at >= $5 OR + $5 IS NULL +) AND ( + created_at <= $6 OR + $6 IS NULL +) +LIMIT $8 OFFSET $7 +` + +type SelectInvoiceEventsParams struct { + InvoiceID sql.NullInt32 + HtlcID sql.NullInt64 + SetID []byte + EventType sql.NullInt32 + CreatedAfter sql.NullTime + CreatedBefore sql.NullTime + NumOffset int32 + NumLimit int32 +} + +func (q *Queries) SelectInvoiceEvents(ctx context.Context, arg SelectInvoiceEventsParams) ([]InvoiceEvent, error) { + rows, err := q.db.QueryContext(ctx, selectInvoiceEvents, + arg.InvoiceID, + arg.HtlcID, + arg.SetID, + arg.EventType, + arg.CreatedAfter, + arg.CreatedBefore, + arg.NumOffset, + arg.NumLimit, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []InvoiceEvent + for rows.Next() { + var i InvoiceEvent + if err := rows.Scan( + &i.ID, + &i.CreatedAt, + &i.InvoiceID, + &i.HtlcID, + &i.SetID, + &i.EventType, + &i.EventMetadata, + ); 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 +} diff --git a/sqldb/sqlc/invoices.sql.go b/sqldb/sqlc/invoices.sql.go new file mode 100644 index 000000000..fa8d9c294 --- /dev/null +++ b/sqldb/sqlc/invoices.sql.go @@ -0,0 +1,709 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.18.0 +// source: invoices.sql + +package sqlc + +import ( + "context" + "database/sql" + "time" +) + +const deleteInvoice = `-- name: DeleteInvoice :exec +DELETE +FROM invoices +WHERE ( + id = $1 OR + $1 IS NULL +) AND ( + hash = $2 OR + $2 IS NULL +) AND ( + preimage = $3 OR + $3 IS NULL +) AND ( + payment_addr = $4 OR + $4 IS NULL +) +` + +type DeleteInvoiceParams struct { + AddIndex sql.NullInt32 + Hash []byte + Preimage []byte + PaymentAddr []byte +} + +func (q *Queries) DeleteInvoice(ctx context.Context, arg DeleteInvoiceParams) error { + _, err := q.db.ExecContext(ctx, deleteInvoice, + arg.AddIndex, + arg.Hash, + arg.Preimage, + arg.PaymentAddr, + ) + return err +} + +const deleteInvoiceFeatures = `-- name: DeleteInvoiceFeatures :exec +DELETE +FROM invoice_features +WHERE invoice_id = $1 +` + +func (q *Queries) DeleteInvoiceFeatures(ctx context.Context, invoiceID int32) error { + _, err := q.db.ExecContext(ctx, deleteInvoiceFeatures, invoiceID) + return err +} + +const deleteInvoiceHTLC = `-- name: DeleteInvoiceHTLC :exec +DELETE +FROM invoice_htlcs +WHERE htlc_id = $1 +` + +func (q *Queries) DeleteInvoiceHTLC(ctx context.Context, htlcID int64) error { + _, err := q.db.ExecContext(ctx, deleteInvoiceHTLC, htlcID) + return err +} + +const deleteInvoiceHTLCCustomRecords = `-- name: DeleteInvoiceHTLCCustomRecords :exec +WITH htlc_ids AS ( + SELECT ih.id + FROM invoice_htlcs ih JOIN invoice_htlc_custom_records ihcr ON ih.id=ihcr.htlc_id + WHERE ih.invoice_id = $1 +) +DELETE +FROM invoice_htlc_custom_records +WHERE htlc_id IN (SELECT id FROM htlc_ids) +` + +func (q *Queries) DeleteInvoiceHTLCCustomRecords(ctx context.Context, invoiceID int32) error { + _, err := q.db.ExecContext(ctx, deleteInvoiceHTLCCustomRecords, invoiceID) + return err +} + +const deleteInvoiceHTLCs = `-- name: DeleteInvoiceHTLCs :exec +DELETE +FROM invoice_htlcs +WHERE invoice_id = $1 +` + +func (q *Queries) DeleteInvoiceHTLCs(ctx context.Context, invoiceID int32) error { + _, err := q.db.ExecContext(ctx, deleteInvoiceHTLCs, invoiceID) + return err +} + +const filterInvoicePayments = `-- name: FilterInvoicePayments :many +SELECT + ip.id AS settle_index, ip.amount_paid_msat, ip.settled_at AS settle_date, + i.id, i.hash, i.preimage, i.memo, i.amount_msat, i.cltv_delta, i.expiry, i.payment_addr, i.payment_request, i.state, i.amount_paid_msat, i.is_amp, i.is_hodl, i.is_keysend, i.created_at +FROM invoice_payments ip JOIN invoices i ON ip.invoice_id = i.id +WHERE ( + ip.id >= $1 OR + $1 IS NULL +) AND ( + ip.settled_at >= $2 OR + $2 IS NULL +) +ORDER BY + CASE + WHEN $3 = FALSE THEN ip.id + ELSE NULL + END ASC, + CASE + WHEN $3 = TRUE THEN ip.id + ELSE NULL + END DESC +LIMIT $5 OFFSET $4 +` + +type FilterInvoicePaymentsParams struct { + SettleIndexGet sql.NullInt32 + SettledAfter sql.NullTime + Reverse interface{} + NumOffset int32 + NumLimit int32 +} + +type FilterInvoicePaymentsRow struct { + SettleIndex int32 + AmountPaidMsat int64 + SettleDate time.Time + ID int32 + Hash []byte + Preimage []byte + Memo sql.NullString + AmountMsat int64 + CltvDelta sql.NullInt32 + Expiry int32 + PaymentAddr []byte + PaymentRequest sql.NullString + State int16 + AmountPaidMsat_2 int64 + IsAmp bool + IsHodl bool + IsKeysend bool + CreatedAt time.Time +} + +func (q *Queries) FilterInvoicePayments(ctx context.Context, arg FilterInvoicePaymentsParams) ([]FilterInvoicePaymentsRow, error) { + rows, err := q.db.QueryContext(ctx, filterInvoicePayments, + arg.SettleIndexGet, + arg.SettledAfter, + arg.Reverse, + arg.NumOffset, + arg.NumLimit, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []FilterInvoicePaymentsRow + for rows.Next() { + var i FilterInvoicePaymentsRow + if err := rows.Scan( + &i.SettleIndex, + &i.AmountPaidMsat, + &i.SettleDate, + &i.ID, + &i.Hash, + &i.Preimage, + &i.Memo, + &i.AmountMsat, + &i.CltvDelta, + &i.Expiry, + &i.PaymentAddr, + &i.PaymentRequest, + &i.State, + &i.AmountPaidMsat_2, + &i.IsAmp, + &i.IsHodl, + &i.IsKeysend, + &i.CreatedAt, + ); 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 filterInvoices = `-- name: FilterInvoices :many +SELECT id, hash, preimage, memo, amount_msat, cltv_delta, expiry, payment_addr, payment_request, state, amount_paid_msat, is_amp, is_hodl, is_keysend, created_at +FROM invoices +WHERE ( + id >= $1 OR + $1 IS NULL +) AND ( + id <= $2 OR + $2 IS NULL +) AND ( + state = $3 OR + $3 IS NULL +) AND ( + created_at >= $4 OR + $4 IS NULL +) AND ( + created_at <= $5 OR + $5 IS NULL +) AND ( + CASE + WHEN $6=TRUE THEN (state = 0 OR state = 3) + ELSE TRUE + END +) +ORDER BY + CASE + WHEN $7 = FALSE THEN id + ELSE NULL + END ASC, + CASE + WHEN $7 = TRUE THEN id + ELSE NULL + END DESC +LIMIT $9 OFFSET $8 +` + +type FilterInvoicesParams struct { + AddIndexGet sql.NullInt32 + AddIndexLet sql.NullInt32 + State sql.NullInt16 + CreatedAfter sql.NullTime + CreatedBefore sql.NullTime + PendingOnly interface{} + Reverse interface{} + NumOffset int32 + NumLimit int32 +} + +func (q *Queries) FilterInvoices(ctx context.Context, arg FilterInvoicesParams) ([]Invoice, error) { + rows, err := q.db.QueryContext(ctx, filterInvoices, + arg.AddIndexGet, + arg.AddIndexLet, + arg.State, + arg.CreatedAfter, + arg.CreatedBefore, + arg.PendingOnly, + arg.Reverse, + arg.NumOffset, + arg.NumLimit, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Invoice + for rows.Next() { + var i Invoice + if err := rows.Scan( + &i.ID, + &i.Hash, + &i.Preimage, + &i.Memo, + &i.AmountMsat, + &i.CltvDelta, + &i.Expiry, + &i.PaymentAddr, + &i.PaymentRequest, + &i.State, + &i.AmountPaidMsat, + &i.IsAmp, + &i.IsHodl, + &i.IsKeysend, + &i.CreatedAt, + ); 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 getInvoice = `-- name: GetInvoice :many + +SELECT id, hash, preimage, memo, amount_msat, cltv_delta, expiry, payment_addr, payment_request, state, amount_paid_msat, is_amp, is_hodl, is_keysend, created_at +FROM invoices +WHERE ( + id = $1 OR + $1 IS NULL +) AND ( + hash = $2 OR + $2 IS NULL +) AND ( + preimage = $3 OR + $3 IS NULL +) AND ( + payment_addr = $4 OR + $4 IS NULL +) +LIMIT 2 +` + +type GetInvoiceParams struct { + AddIndex sql.NullInt32 + Hash []byte + Preimage []byte + PaymentAddr []byte +} + +// This method may return more than one invoice if filter using multiple fields +// from different invoices. It is the caller's responsibility to ensure that +// we bubble up an error in those cases. +func (q *Queries) GetInvoice(ctx context.Context, arg GetInvoiceParams) ([]Invoice, error) { + rows, err := q.db.QueryContext(ctx, getInvoice, + arg.AddIndex, + arg.Hash, + arg.Preimage, + arg.PaymentAddr, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Invoice + for rows.Next() { + var i Invoice + if err := rows.Scan( + &i.ID, + &i.Hash, + &i.Preimage, + &i.Memo, + &i.AmountMsat, + &i.CltvDelta, + &i.Expiry, + &i.PaymentAddr, + &i.PaymentRequest, + &i.State, + &i.AmountPaidMsat, + &i.IsAmp, + &i.IsHodl, + &i.IsKeysend, + &i.CreatedAt, + ); 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 getInvoiceFeatures = `-- name: GetInvoiceFeatures :many +SELECT feature, invoice_id +FROM invoice_features +WHERE invoice_id = $1 +` + +func (q *Queries) GetInvoiceFeatures(ctx context.Context, invoiceID int32) ([]InvoiceFeature, error) { + rows, err := q.db.QueryContext(ctx, getInvoiceFeatures, invoiceID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []InvoiceFeature + for rows.Next() { + var i InvoiceFeature + if err := rows.Scan(&i.Feature, &i.InvoiceID); 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 getInvoiceHTLCCustomRecords = `-- name: GetInvoiceHTLCCustomRecords :many +SELECT ihcr.htlc_id, key, value +FROM invoice_htlcs ih JOIN invoice_htlc_custom_records ihcr ON ih.id=ihcr.htlc_id +WHERE ih.invoice_id = $1 +` + +type GetInvoiceHTLCCustomRecordsRow struct { + HtlcID int64 + Key int64 + Value []byte +} + +func (q *Queries) GetInvoiceHTLCCustomRecords(ctx context.Context, invoiceID int32) ([]GetInvoiceHTLCCustomRecordsRow, error) { + rows, err := q.db.QueryContext(ctx, getInvoiceHTLCCustomRecords, invoiceID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetInvoiceHTLCCustomRecordsRow + for rows.Next() { + var i GetInvoiceHTLCCustomRecordsRow + if err := rows.Scan(&i.HtlcID, &i.Key, &i.Value); 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 getInvoiceHTLCs = `-- name: GetInvoiceHTLCs :many +SELECT id, htlc_id, chan_id, amount_msat, total_mpp_msat, accept_height, accept_time, expiry_height, state, resolve_time, invoice_id +FROM invoice_htlcs +WHERE invoice_id = $1 +` + +func (q *Queries) GetInvoiceHTLCs(ctx context.Context, invoiceID int32) ([]InvoiceHtlc, error) { + rows, err := q.db.QueryContext(ctx, getInvoiceHTLCs, invoiceID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []InvoiceHtlc + for rows.Next() { + var i InvoiceHtlc + if err := rows.Scan( + &i.ID, + &i.HtlcID, + &i.ChanID, + &i.AmountMsat, + &i.TotalMppMsat, + &i.AcceptHeight, + &i.AcceptTime, + &i.ExpiryHeight, + &i.State, + &i.ResolveTime, + &i.InvoiceID, + ); 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 getInvoicePayments = `-- name: GetInvoicePayments :many +SELECT id, settled_at, amount_paid_msat, invoice_id +FROM invoice_payments +WHERE invoice_id = $1 +` + +func (q *Queries) GetInvoicePayments(ctx context.Context, invoiceID int32) ([]InvoicePayment, error) { + rows, err := q.db.QueryContext(ctx, getInvoicePayments, invoiceID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []InvoicePayment + for rows.Next() { + var i InvoicePayment + if err := rows.Scan( + &i.ID, + &i.SettledAt, + &i.AmountPaidMsat, + &i.InvoiceID, + ); 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 insertInvoice = `-- name: InsertInvoice :one +INSERT INTO invoices ( + hash, preimage, memo, amount_msat, cltv_delta, expiry, payment_addr, + payment_request, state, amount_paid_msat, is_amp, is_hodl, is_keysend, + created_at +) VALUES ( + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14 +) RETURNING id +` + +type InsertInvoiceParams struct { + Hash []byte + Preimage []byte + Memo sql.NullString + AmountMsat int64 + CltvDelta sql.NullInt32 + Expiry int32 + PaymentAddr []byte + PaymentRequest sql.NullString + State int16 + AmountPaidMsat int64 + IsAmp bool + IsHodl bool + IsKeysend bool + CreatedAt time.Time +} + +func (q *Queries) InsertInvoice(ctx context.Context, arg InsertInvoiceParams) (int32, error) { + row := q.db.QueryRowContext(ctx, insertInvoice, + arg.Hash, + arg.Preimage, + arg.Memo, + arg.AmountMsat, + arg.CltvDelta, + arg.Expiry, + arg.PaymentAddr, + arg.PaymentRequest, + arg.State, + arg.AmountPaidMsat, + arg.IsAmp, + arg.IsHodl, + arg.IsKeysend, + arg.CreatedAt, + ) + var id int32 + err := row.Scan(&id) + return id, err +} + +const insertInvoiceFeature = `-- name: InsertInvoiceFeature :exec +INSERT INTO invoice_features ( + invoice_id, feature +) VALUES ( + $1, $2 +) +` + +type InsertInvoiceFeatureParams struct { + InvoiceID int32 + Feature int32 +} + +func (q *Queries) InsertInvoiceFeature(ctx context.Context, arg InsertInvoiceFeatureParams) error { + _, err := q.db.ExecContext(ctx, insertInvoiceFeature, arg.InvoiceID, arg.Feature) + return err +} + +const insertInvoiceHTLC = `-- name: InsertInvoiceHTLC :exec +INSERT INTO invoice_htlcs ( + htlc_id, chan_id, amount_msat, total_mpp_msat, accept_height, accept_time, + expiry_height, state, resolve_time, invoice_id +) VALUES ( + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 +) +` + +type InsertInvoiceHTLCParams struct { + HtlcID int64 + ChanID string + AmountMsat int64 + TotalMppMsat sql.NullInt64 + AcceptHeight int32 + AcceptTime time.Time + ExpiryHeight int32 + State int16 + ResolveTime sql.NullTime + InvoiceID int32 +} + +func (q *Queries) InsertInvoiceHTLC(ctx context.Context, arg InsertInvoiceHTLCParams) error { + _, err := q.db.ExecContext(ctx, insertInvoiceHTLC, + arg.HtlcID, + arg.ChanID, + arg.AmountMsat, + arg.TotalMppMsat, + arg.AcceptHeight, + arg.AcceptTime, + arg.ExpiryHeight, + arg.State, + arg.ResolveTime, + arg.InvoiceID, + ) + return err +} + +const insertInvoiceHTLCCustomRecord = `-- name: InsertInvoiceHTLCCustomRecord :exec +INSERT INTO invoice_htlc_custom_records ( + key, value, htlc_id +) VALUES ( + $1, $2, $3 +) +` + +type InsertInvoiceHTLCCustomRecordParams struct { + Key int64 + Value []byte + HtlcID int64 +} + +func (q *Queries) InsertInvoiceHTLCCustomRecord(ctx context.Context, arg InsertInvoiceHTLCCustomRecordParams) error { + _, err := q.db.ExecContext(ctx, insertInvoiceHTLCCustomRecord, arg.Key, arg.Value, arg.HtlcID) + return err +} + +const insertInvoicePayment = `-- name: InsertInvoicePayment :one +INSERT INTO invoice_payments ( + invoice_id, amount_paid_msat, settled_at +) VALUES ( + $1, $2, $3 +) RETURNING id +` + +type InsertInvoicePaymentParams struct { + InvoiceID int32 + AmountPaidMsat int64 + SettledAt time.Time +} + +func (q *Queries) InsertInvoicePayment(ctx context.Context, arg InsertInvoicePaymentParams) (int32, error) { + row := q.db.QueryRowContext(ctx, insertInvoicePayment, arg.InvoiceID, arg.AmountPaidMsat, arg.SettledAt) + var id int32 + err := row.Scan(&id) + return id, err +} + +const updateInvoice = `-- name: UpdateInvoice :exec +UPDATE invoices +SET preimage=$2, state=$3, amount_paid_msat=$4 +WHERE id=$1 +` + +type UpdateInvoiceParams struct { + ID int32 + Preimage []byte + State int16 + AmountPaidMsat int64 +} + +func (q *Queries) UpdateInvoice(ctx context.Context, arg UpdateInvoiceParams) error { + _, err := q.db.ExecContext(ctx, updateInvoice, + arg.ID, + arg.Preimage, + arg.State, + arg.AmountPaidMsat, + ) + return err +} + +const updateInvoiceHTLC = `-- name: UpdateInvoiceHTLC :exec +UPDATE invoice_htlcs +SET state=$2, resolve_time=$3 +WHERE id = $1 +` + +type UpdateInvoiceHTLCParams struct { + ID int32 + State int16 + ResolveTime sql.NullTime +} + +func (q *Queries) UpdateInvoiceHTLC(ctx context.Context, arg UpdateInvoiceHTLCParams) error { + _, err := q.db.ExecContext(ctx, updateInvoiceHTLC, arg.ID, arg.State, arg.ResolveTime) + return err +} + +const updateInvoiceHTLCs = `-- name: UpdateInvoiceHTLCs :exec +UPDATE invoice_htlcs +SET state=$2, resolve_time=$3 +WHERE invoice_id = $1 AND resolve_time IS NULL +` + +type UpdateInvoiceHTLCsParams struct { + InvoiceID int32 + State int16 + ResolveTime sql.NullTime +} + +func (q *Queries) UpdateInvoiceHTLCs(ctx context.Context, arg UpdateInvoiceHTLCsParams) error { + _, err := q.db.ExecContext(ctx, updateInvoiceHTLCs, arg.InvoiceID, arg.State, arg.ResolveTime) + return err +} diff --git a/sqldb/sqlc/migrations/000001_invoices.down.sql b/sqldb/sqlc/migrations/000001_invoices.down.sql new file mode 100644 index 000000000..0e275b344 --- /dev/null +++ b/sqldb/sqlc/migrations/000001_invoices.down.sql @@ -0,0 +1,19 @@ +DROP INDEX IF EXISTS invoice_payments_invoice_id_idx; +DROP INDEX IF EXISTS invoice_payments_settled_at_idx; +DROP TABLE IF EXISTS invoice_payments; + +DROP INDEX IF EXISTS invoice_htlc_custom_records_htlc_id_idx; +DROP TABLE IF EXISTS invoice_htlc_custom_records; + +DROP INDEX IF EXISTS invoice_htlc_invoice_id_idx; +DROP TABLE IF EXISTS invoice_htlcs; + +DROP INDEX IF EXISTS invoice_feature_invoice_id_idx; +DROP TABLE IF EXISTS invoice_features; + +DROP INDEX IF EXISTS invoices_created_at_idx; +DROP INDEX IF EXISTS invoices_state_idx; +DROP INDEX IF EXISTS invoices_payment_addr_idx; +DROP INDEX IF EXISTS invoices_preimage_idx; +DROP INDEX IF EXISTS invoices_hash_idx; +DROP TABLE IF EXISTS invoices; diff --git a/sqldb/sqlc/migrations/000001_invoices.up.sql b/sqldb/sqlc/migrations/000001_invoices.up.sql new file mode 100644 index 000000000..32b70b9fa --- /dev/null +++ b/sqldb/sqlc/migrations/000001_invoices.up.sql @@ -0,0 +1,154 @@ +-- invoices table contains all the information shared by all the invoice types. +CREATE TABLE IF NOT EXISTS invoices ( + -- The id of the invoice. Translates to the AddIndex. + id INTEGER PRIMARY KEY, + + -- The hash for this invoice. The invoice hash will always identify that + -- invoice. + hash BLOB NOT NULL UNIQUE, + + -- The preimage for the hash in this invoice. Some invoices may have this + -- field empty, like unsettled hodl invoices or AMP invoices. + preimage BLOB, + + -- An optional memo to attach along with the invoice. + memo TEXT, + + -- The amount of the invoice in millisatoshis. + amount_msat BIGINT NOT NULL, + + -- Delta to use for the time-lock of the CLTV extended to the final hop. + -- BOLT12 invoices will have this field set to NULL. + cltv_delta INTEGER, + + -- The time before this invoice expiries, in seconds. + expiry INTEGER NOT NULL, + + -- A randomly generated value include in the MPP record by the sender to + -- prevent probing of the receiver. + -- This field corresponds to the `payment_secret` specified in BOLT 11. + payment_addr BLOB UNIQUE, + + -- The encoded payment request for this invoice. Some invoice types may + -- not have leave this empty, like Keysends. + payment_request TEXT UNIQUE, + + -- The invoice state. + state SMALLINT NOT NULL, + + -- The accumulated value of all the htlcs settled for this invoice. + amount_paid_msat BIGINT NOT NULL, + + -- This field will be true for AMP invoices. + is_amp BOOLEAN NOT NULL, + + -- This field will be true for hodl invoices, independently of they being + -- settled or not. + is_hodl BOOLEAN NOT NULL, + + -- This field will be true for keysend invoices. + is_keysend BOOLEAN NOT NULL, + + -- Timestamp of when this invoice was created. + created_at TIMESTAMP NOT NULL +); + +CREATE INDEX IF NOT EXISTS invoices_hash_idx ON invoices(hash); +CREATE INDEX IF NOT EXISTS invoices_preimage_idx ON invoices(preimage); +CREATE INDEX IF NOT EXISTS invoices_payment_addr_idx ON invoices(payment_addr); +CREATE INDEX IF NOT EXISTS invoices_state_idx ON invoices(state); +CREATE INDEX IF NOT EXISTS invoices_created_at_idx ON invoices(created_at); + +-- invoice_features contains the feature bits of an invoice. +CREATE TABLE IF NOT EXISTS invoice_features ( + -- The feature bit. + feature INTEGER NOT NULL, + + -- The invoice id this feature belongs to. + invoice_id INTEGER NOT NULL REFERENCES invoices(id), + + -- The feature bit is unique per invoice. + UNIQUE (feature, invoice_id) +); + +CREATE INDEX IF NOT EXISTS invoice_feature_invoice_id_idx ON invoice_features(invoice_id); + +-- invoice_htlcs contains the information of a htlcs related to one of the node +-- invocies. +CREATE TABLE IF NOT EXISTS invoice_htlcs ( + -- The id for this htlc. Used in foreign keys instead of the + -- htlc_id/chan_id combination. + id INTEGER PRIMARY KEY, + + -- The uint64 htlc id. This field is a counter so it is safe to store it as + -- int64 in the database. The application layer must check that there is no + -- overflow when storing/loading this column. + htlc_id BIGINT NOT NULL, + + -- Short chan id indicating the htlc's origin. uint64 stored as text. + chan_id TEXT NOT NULL, + + -- The htlc's amount in millisatoshis. + amount_msat BIGINT NOT NULL, + + -- The total amount expected for the htlcs in a multi-path payment. + total_mpp_msat BIGINT, + + -- The block height at which this htlc was accepted. + accept_height INTEGER NOT NULL, + + -- The timestamp at which this htlc was accepted. + accept_time TIMESTAMP NOT NULL, + + -- The block height at which this htlc expiries. + expiry_height INTEGER NOT NULL, + + -- The htlc state. + state SMALLINT NOT NULL, + + -- Timestamp of when this htlc was resolved (settled/canceled). + resolve_time TIMESTAMP, + + -- The id of the invoice this htlc belongs to. + invoice_id INTEGER NOT NULL REFERENCES invoices(id), + + -- The htlc_id and chan_id identify the htlc. + UNIQUE (htlc_id, chan_id) +); + +CREATE INDEX IF NOT EXISTS invoice_htlc_invoice_id_idx ON invoice_htlcs(invoice_id); + +-- invoice_htlc_custom_records stores the custom key/value pairs that +-- accompanied an htlc. +CREATE TABLE IF NOT EXISTS invoice_htlc_custom_records ( + -- The custom type identifier for this record. + -- The range of values for custom records key is defined in BOLT 01. + key BIGINT NOT NULL, + + -- The custom value for this record. + value BLOB NOT NULL, + + -- The htlc id this record belongs to. + htlc_id BIGINT NOT NULL REFERENCES invoice_htlcs(id) +); + +CREATE INDEX IF NOT EXISTS invoice_htlc_custom_records_htlc_id_idx ON invoice_htlc_custom_records(htlc_id); + +-- invoice_payments contains the information of a settled invoice payment. +CREATE TABLE IF NOT EXISTS invoice_payments ( + -- The id for this invoice payment. Translates to SettleIndex. + id INTEGER PRIMARY KEY, + + -- When the payment was settled. + settled_at TIMESTAMP NOT NULL, + + -- The amount of the payment in millisatoshis. This is the sum of all the + -- the htlcs settled for this payment. + amount_paid_msat BIGINT NOT NULL, + + -- The invoice id this payment is for. + invoice_id INTEGER NOT NULL REFERENCES invoices(id) +); + +CREATE INDEX IF NOT EXISTS invoice_payments_settled_at_idx ON invoice_payments(settled_at); +CREATE INDEX IF NOT EXISTS invoice_payments_invoice_id_idx ON invoice_payments(invoice_id); diff --git a/sqldb/sqlc/migrations/000002_amp_invoices.down.sql b/sqldb/sqlc/migrations/000002_amp_invoices.down.sql new file mode 100644 index 000000000..0167bdeb2 --- /dev/null +++ b/sqldb/sqlc/migrations/000002_amp_invoices.down.sql @@ -0,0 +1,8 @@ +DROP INDEX IF EXISTS amp_htlcs_htlc_id_idx; +DROP INDEX IF EXISTS amp_htlcs_invoice_id_idx; +DROP INDEX IF EXISTS amp_htlcs_set_id_idx; +DROP TABLE IF EXISTS amp_invoice_htlcs; + +DROP INDEX IF EXISTS amp_invoice_payments_invoice_id_idx; +DROP TABLE IF EXISTS amp_invoice_payments; + diff --git a/sqldb/sqlc/migrations/000002_amp_invoices.up.sql b/sqldb/sqlc/migrations/000002_amp_invoices.up.sql new file mode 100644 index 000000000..a4daa8c30 --- /dev/null +++ b/sqldb/sqlc/migrations/000002_amp_invoices.up.sql @@ -0,0 +1,54 @@ +-- amp_invoices_payments +CREATE TABLE IF NOT EXISTS amp_invoice_payments ( + -- The set id identifying the payment. + set_id BLOB PRIMARY KEY, + + -- The state of this amp payment. This matches the state for all the htlcs + -- belonging to this set id. The A in AMP stands for Atomic. + state SMALLINT NOT NULL, + + -- Timestamp of when the first htlc for this payment was accepted. + created_at TIMESTAMP NOT NULL, + + -- If settled, the invoice payment related to this set id. + settled_index INTEGER REFERENCES invoice_payments(id), + + -- The invoice id this set id is related to. + invoice_id INTEGER NOT NULL REFERENCES invoices(id) +); + +CREATE INDEX IF NOT EXISTS amp_invoice_payments_invoice_id_idx ON amp_invoice_payments(invoice_id); + +-- amp_invoice_htlcs contains the complementary information for an htlc related +-- to an AMP invoice. +CREATE TABLE IF NOT EXISTS amp_invoice_htlcs ( + -- The set id identifying the payment this htlc belongs to. + set_id BLOB NOT NULL REFERENCES amp_invoice_payments(set_id), + + -- The id of the htlc this entry blongs to. + htlc_id BIGINT NOT NULL REFERENCES invoice_htlcs(id), + + -- The invoice id this entry is related to. + invoice_id INTEGER NOT NULL REFERENCES invoices(id), + + -- The root share for this amp htlc. + root_share BLOB NOT NULL, + + -- The child index for this amp htlc. + child_index BIGINT NOT NULL, + + -- The htlc-level payment hash. An AMP htlc will carry a different payment + -- hash from the invoice it might be satisfying. They are needed to ensure + -- that we reconstruct the preimage correctly. + hash BLOB NOT NULL, + + -- The HTLC-level preimage that satisfies the AMP htlc's Hash. + -- The preimage will be derived either from secret share reconstruction of + -- the shares in the AMP payload. + preimage BLOB +); + +CREATE INDEX IF NOT EXISTS amp_htlcs_set_id_idx ON amp_invoice_htlcs(set_id); +CREATE INDEX IF NOT EXISTS amp_htlcs_invoice_id_idx ON amp_invoice_htlcs(invoice_id); +CREATE INDEX IF NOT EXISTS amp_htlcs_htlc_id_idx ON amp_invoice_htlcs(htlc_id); + diff --git a/sqldb/sqlc/migrations/000003_invoice_events.down.sql b/sqldb/sqlc/migrations/000003_invoice_events.down.sql new file mode 100644 index 000000000..66e554e24 --- /dev/null +++ b/sqldb/sqlc/migrations/000003_invoice_events.down.sql @@ -0,0 +1,9 @@ +DROP TABLE IF EXISTS invoice_events; + +DROP INDEX IF EXISTS invoice_events_created_at_idx; +DROP INDEX IF EXISTS invoice_events_invoice_id_idx; +DROP INDEX IF EXISTS invoice_events_htlc_id_idx; +DROP INDEX IF EXISTS invoice_events_set_id_idx; +DROP INDEX IF EXISTS invoice_events_event_type_idx; + +DROP TABLE IF EXISTS invoice_event_types; diff --git a/sqldb/sqlc/migrations/000003_invoice_events.up.sql b/sqldb/sqlc/migrations/000003_invoice_events.up.sql new file mode 100644 index 000000000..47d2a91d0 --- /dev/null +++ b/sqldb/sqlc/migrations/000003_invoice_events.up.sql @@ -0,0 +1,59 @@ +-- invoice_event_types stores the different types of events that can be emitted +-- for invoices. +CREATE TABLE IF NOT EXISTS invoice_event_types( + id INTEGER PRIMARY KEY, + + description TEXT NOT NULL +); + +-- invoice_events stores all events related to the node invoices. +CREATE TABLE IF NOT EXISTS invoice_events ( + id INTEGER PRIMARY KEY, + + -- created_at is the creation time of this event. + created_at TIMESTAMP NOT NULL, + + -- invoice_id is the reference to the invoice this event was emitted for. + invoice_id INTEGER NOT NULL REFERENCES invoices(id), + + -- htlc_id is the reference to the htlc this event was emitted for, may be + -- null. + htlc_id BIGINT REFERENCES invoice_htlcs(htlc_id), + + -- set_id is the reference to the set_id this event was emitted for, may be + -- null. + set_id BLOB NOT NULL REFERENCES amp_invoice_payments(set_id), + + -- event_type is the type of this event. + event_type INTEGER NOT NULL REFERENCES invoice_event_types(id), + + -- event_metadata is a TLV encoding any relevant information for this kind + -- of events. + event_metadata BLOB +); + +CREATE INDEX IF NOT EXISTS invoice_events_created_at_idx ON invoice_events(created_at); +CREATE INDEX IF NOT EXISTS invoice_events_invoice_id_idx ON invoice_events(invoice_id); +CREATE INDEX IF NOT EXISTS invoice_events_htlc_id_idx ON invoice_events(htlc_id); +CREATE INDEX IF NOT EXISTS invoice_events_set_id_idx ON invoice_events(set_id); +CREATE INDEX IF NOT EXISTS invoice_events_event_type_idx ON invoice_events(event_type); + + +-- invoice_event_types defines the different types of events that can be emitted +-- for an invoice. +INSERT INTO invoice_event_types (id, description) +VALUES + -- invoice_created is the event emitted when an invoice is created. + (0, 'invoice_created'), + -- invoice_canceled is the event emitted when an invoice is canceled. + (1, "invoice_canceled"), + -- invoice_settled is the event emitted when an invoice is settled. + (2, "invoice_settled"), + -- setid_created is the event emitted when the first htlc for the set_id is + -- received. + (3, "setid_created"), + -- setid_canceled is the event emitted when the set_id is canceled. + (4, "setid_canceled"), + -- setid_settled is the event emitted when the set_id is settled. + (5, "setid_settled"); + diff --git a/sqldb/sqlc/models.go b/sqldb/sqlc/models.go new file mode 100644 index 000000000..b0ef3c857 --- /dev/null +++ b/sqldb/sqlc/models.go @@ -0,0 +1,93 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.18.0 + +package sqlc + +import ( + "database/sql" + "time" +) + +type AmpInvoiceHtlc struct { + SetID []byte + HtlcID int64 + InvoiceID int32 + RootShare []byte + ChildIndex int64 + Hash []byte + Preimage []byte +} + +type AmpInvoicePayment struct { + SetID []byte + State int16 + CreatedAt time.Time + SettledIndex sql.NullInt32 + InvoiceID int32 +} + +type Invoice struct { + ID int32 + Hash []byte + Preimage []byte + Memo sql.NullString + AmountMsat int64 + CltvDelta sql.NullInt32 + Expiry int32 + PaymentAddr []byte + PaymentRequest sql.NullString + State int16 + AmountPaidMsat int64 + IsAmp bool + IsHodl bool + IsKeysend bool + CreatedAt time.Time +} + +type InvoiceEvent struct { + ID int32 + CreatedAt time.Time + InvoiceID int32 + HtlcID sql.NullInt64 + SetID []byte + EventType int32 + EventMetadata []byte +} + +type InvoiceEventType struct { + ID int32 + Description string +} + +type InvoiceFeature struct { + Feature int32 + InvoiceID int32 +} + +type InvoiceHtlc struct { + ID int32 + HtlcID int64 + ChanID string + AmountMsat int64 + TotalMppMsat sql.NullInt64 + AcceptHeight int32 + AcceptTime time.Time + ExpiryHeight int32 + State int16 + ResolveTime sql.NullTime + InvoiceID int32 +} + +type InvoiceHtlcCustomRecord struct { + Key int64 + Value []byte + HtlcID int64 +} + +type InvoicePayment struct { + ID int32 + SettledAt time.Time + AmountPaidMsat int64 + InvoiceID int32 +} diff --git a/sqldb/sqlc/querier.go b/sqldb/sqlc/querier.go new file mode 100644 index 000000000..1e766ac63 --- /dev/null +++ b/sqldb/sqlc/querier.go @@ -0,0 +1,51 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.18.0 + +package sqlc + +import ( + "context" +) + +type Querier interface { + DeleteAMPHTLCCustomRecords(ctx context.Context, invoiceID int32) error + DeleteAMPHTLCs(ctx context.Context, invoiceID int32) error + DeleteAMPInvoiceHTLC(ctx context.Context, setID []byte) error + DeleteInvoice(ctx context.Context, arg DeleteInvoiceParams) error + DeleteInvoiceEvents(ctx context.Context, invoiceID int32) error + DeleteInvoiceFeatures(ctx context.Context, invoiceID int32) error + DeleteInvoiceHTLC(ctx context.Context, htlcID int64) error + DeleteInvoiceHTLCCustomRecords(ctx context.Context, invoiceID int32) error + DeleteInvoiceHTLCs(ctx context.Context, invoiceID int32) error + FilterInvoicePayments(ctx context.Context, arg FilterInvoicePaymentsParams) ([]FilterInvoicePaymentsRow, error) + FilterInvoices(ctx context.Context, arg FilterInvoicesParams) ([]Invoice, error) + GetAMPInvoiceHTLCsByInvoiceID(ctx context.Context, invoiceID int32) ([]AmpInvoiceHtlc, error) + GetAMPInvoiceHTLCsBySetID(ctx context.Context, setID []byte) ([]AmpInvoiceHtlc, error) + // This method may return more than one invoice if filter using multiple fields + // from different invoices. It is the caller's responsibility to ensure that + // we bubble up an error in those cases. + GetInvoice(ctx context.Context, arg GetInvoiceParams) ([]Invoice, error) + GetInvoiceFeatures(ctx context.Context, invoiceID int32) ([]InvoiceFeature, error) + GetInvoiceHTLCCustomRecords(ctx context.Context, invoiceID int32) ([]GetInvoiceHTLCCustomRecordsRow, error) + GetInvoiceHTLCs(ctx context.Context, invoiceID int32) ([]InvoiceHtlc, error) + GetInvoicePayments(ctx context.Context, invoiceID int32) ([]InvoicePayment, error) + GetSetIDHTLCsCustomRecords(ctx context.Context, setID []byte) ([]GetSetIDHTLCsCustomRecordsRow, error) + InsertAMPInvoiceHTLC(ctx context.Context, arg InsertAMPInvoiceHTLCParams) error + InsertAMPInvoicePayment(ctx context.Context, arg InsertAMPInvoicePaymentParams) error + InsertInvoice(ctx context.Context, arg InsertInvoiceParams) (int32, error) + InsertInvoiceEvent(ctx context.Context, arg InsertInvoiceEventParams) error + InsertInvoiceFeature(ctx context.Context, arg InsertInvoiceFeatureParams) error + InsertInvoiceHTLC(ctx context.Context, arg InsertInvoiceHTLCParams) error + InsertInvoiceHTLCCustomRecord(ctx context.Context, arg InsertInvoiceHTLCCustomRecordParams) error + InsertInvoicePayment(ctx context.Context, arg InsertInvoicePaymentParams) (int32, error) + SelectAMPInvoicePayments(ctx context.Context, arg SelectAMPInvoicePaymentsParams) ([]SelectAMPInvoicePaymentsRow, error) + SelectInvoiceEvents(ctx context.Context, arg SelectInvoiceEventsParams) ([]InvoiceEvent, error) + UpdateAMPInvoiceHTLC(ctx context.Context, arg UpdateAMPInvoiceHTLCParams) error + UpdateAMPPayment(ctx context.Context, arg UpdateAMPPaymentParams) error + UpdateInvoice(ctx context.Context, arg UpdateInvoiceParams) error + UpdateInvoiceHTLC(ctx context.Context, arg UpdateInvoiceHTLCParams) error + UpdateInvoiceHTLCs(ctx context.Context, arg UpdateInvoiceHTLCsParams) error +} + +var _ Querier = (*Queries)(nil) diff --git a/sqldb/sqlc/queries/amp_invoices.sql b/sqldb/sqlc/queries/amp_invoices.sql new file mode 100644 index 000000000..d965de768 --- /dev/null +++ b/sqldb/sqlc/queries/amp_invoices.sql @@ -0,0 +1,79 @@ +-- name: InsertAMPInvoicePayment :exec +INSERT INTO amp_invoice_payments ( + set_id, state, created_at, settled_index, invoice_id +) VALUES ( + $1, $2, $3, $4, $5 +); + +-- name: SelectAMPInvoicePayments :many +SELECT aip.*, ip.* +FROM amp_invoice_payments aip LEFT JOIN invoice_payments ip ON aip.settled_index = ip.id +WHERE ( + set_id = sqlc.narg('set_id') OR + sqlc.narg('set_id') IS NULL +) AND ( + aip.settled_index = sqlc.narg('settled_index') OR + sqlc.narg('settled_index') IS NULL +) AND ( + aip.invoice_id = sqlc.narg('invoice_id') OR + sqlc.narg('invoice_id') IS NULL +); + +-- name: UpdateAMPPayment :exec +UPDATE amp_invoice_payments +SET state = $1, settled_index = $2 +WHERE state = 0 AND ( + set_id = sqlc.narg('set_id') OR + sqlc.narg('set_id') IS NULL +) AND ( + invoice_id = sqlc.narg('invoice_id') OR + sqlc.narg('invoice_id') IS NULL +); + +-- name: InsertAMPInvoiceHTLC :exec +INSERT INTO amp_invoice_htlcs ( + set_id, htlc_id, root_share, child_index, hash, preimage +) VALUES ( + $1, $2, $3, $4, $5, $6 +); + +-- name: GetAMPInvoiceHTLCsBySetID :many +SELECT * +FROM amp_invoice_htlcs +WHERE set_id = $1; + +-- name: GetAMPInvoiceHTLCsByInvoiceID :many +SELECT * +FROM amp_invoice_htlcs +WHERE invoice_id = $1; + +-- name: GetSetIDHTLCsCustomRecords :many +SELECT ihcr.htlc_id, key, value +FROM amp_invoice_htlcs aih JOIN invoice_htlc_custom_records ihcr ON aih.id=ihcr.htlc_id +WHERE aih.set_id = $1; + +-- name: UpdateAMPInvoiceHTLC :exec +UPDATE amp_invoice_htlcs +SET preimage = $1 +WHERE htlc_id = $2; + +-- name: DeleteAMPHTLCCustomRecords :exec +WITH htlc_ids AS ( + SELECT htlc_id + FROM amp_invoice_htlcs + WHERE invoice_id = $1 +) +DELETE +FROM invoice_htlc_custom_records +WHERE htlc_id IN (SELECT id FROM htlc_ids); + +-- name: DeleteAMPHTLCs :exec +DELETE +FROM amp_invoice_htlcs +WHERE invoice_id = $1; + +-- name: DeleteAMPInvoiceHTLC :exec +DELETE +FROM amp_invoice_htlcs +WHERE set_id = $1; + diff --git a/sqldb/sqlc/queries/invoice_events.sql b/sqldb/sqlc/queries/invoice_events.sql new file mode 100644 index 000000000..4216a2513 --- /dev/null +++ b/sqldb/sqlc/queries/invoice_events.sql @@ -0,0 +1,35 @@ +-- name: InsertInvoiceEvent :exec +INSERT INTO invoice_events ( + created_at, invoice_id, htlc_id, set_id, event_type, event_metadata +) VALUES ( + $1, $2, $3, $4, $5, $6 +); + +-- name: SelectInvoiceEvents :many +SELECT * +FROM invoice_events +WHERE ( + invoice_id = sqlc.narg('invoice_id') OR + sqlc.narg('invoice_id') IS NULL +) AND ( + htlc_id = sqlc.narg('htlc_id') OR + sqlc.narg('htlc_id') IS NULL +) AND ( + set_id = sqlc.narg('set_id') OR + sqlc.narg('set_id') IS NULL +) AND ( + event_type = sqlc.narg('event_type') OR + sqlc.narg('event_type') IS NULL +) AND ( + created_at >= sqlc.narg('created_after') OR + sqlc.narg('created_after') IS NULL +) AND ( + created_at <= sqlc.narg('created_before') OR + sqlc.narg('created_before') IS NULL +) +LIMIT @num_limit OFFSET @num_offset; + +-- name: DeleteInvoiceEvents :exec +DELETE +FROM invoice_events +WHERE invoice_id = $1; diff --git a/sqldb/sqlc/queries/invoices.sql b/sqldb/sqlc/queries/invoices.sql new file mode 100644 index 000000000..335929e05 --- /dev/null +++ b/sqldb/sqlc/queries/invoices.sql @@ -0,0 +1,196 @@ +-- name: InsertInvoice :one +INSERT INTO invoices ( + hash, preimage, memo, amount_msat, cltv_delta, expiry, payment_addr, + payment_request, state, amount_paid_msat, is_amp, is_hodl, is_keysend, + created_at +) VALUES ( + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14 +) RETURNING id; + +-- name: InsertInvoiceFeature :exec +INSERT INTO invoice_features ( + invoice_id, feature +) VALUES ( + $1, $2 +); + +-- name: GetInvoiceFeatures :many +SELECT * +FROM invoice_features +WHERE invoice_id = $1; + +-- name: DeleteInvoiceFeatures :exec +DELETE +FROM invoice_features +WHERE invoice_id = $1; + +-- This method may return more than one invoice if filter using multiple fields +-- from different invoices. It is the caller's responsibility to ensure that +-- we bubble up an error in those cases. + +-- name: GetInvoice :many +SELECT * +FROM invoices +WHERE ( + id = sqlc.narg('add_index') OR + sqlc.narg('add_index') IS NULL +) AND ( + hash = sqlc.narg('hash') OR + sqlc.narg('hash') IS NULL +) AND ( + preimage = sqlc.narg('preimage') OR + sqlc.narg('preimage') IS NULL +) AND ( + payment_addr = sqlc.narg('payment_addr') OR + sqlc.narg('payment_addr') IS NULL +) +LIMIT 2; + +-- name: FilterInvoices :many +SELECT * +FROM invoices +WHERE ( + id >= sqlc.narg('add_index_get') OR + sqlc.narg('add_index_get') IS NULL +) AND ( + id <= sqlc.narg('add_index_let') OR + sqlc.narg('add_index_let') IS NULL +) AND ( + state = sqlc.narg('state') OR + sqlc.narg('state') IS NULL +) AND ( + created_at >= sqlc.narg('created_after') OR + sqlc.narg('created_after') IS NULL +) AND ( + created_at <= sqlc.narg('created_before') OR + sqlc.narg('created_before') IS NULL +) AND ( + CASE + WHEN sqlc.narg('pending_only')=TRUE THEN (state = 0 OR state = 3) + ELSE TRUE + END +) +ORDER BY + CASE + WHEN sqlc.narg('reverse') = FALSE THEN id + ELSE NULL + END ASC, + CASE + WHEN sqlc.narg('reverse') = TRUE THEN id + ELSE NULL + END DESC +LIMIT @num_limit OFFSET @num_offset; + +-- name: UpdateInvoice :exec +UPDATE invoices +SET preimage=$2, state=$3, amount_paid_msat=$4 +WHERE id=$1; + +-- name: DeleteInvoice :exec +DELETE +FROM invoices +WHERE ( + id = sqlc.narg('add_index') OR + sqlc.narg('add_index') IS NULL +) AND ( + hash = sqlc.narg('hash') OR + sqlc.narg('hash') IS NULL +) AND ( + preimage = sqlc.narg('preimage') OR + sqlc.narg('preimage') IS NULL +) AND ( + payment_addr = sqlc.narg('payment_addr') OR + sqlc.narg('payment_addr') IS NULL +); + +-- name: InsertInvoiceHTLC :exec +INSERT INTO invoice_htlcs ( + htlc_id, chan_id, amount_msat, total_mpp_msat, accept_height, accept_time, + expiry_height, state, resolve_time, invoice_id +) VALUES ( + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 +); + +-- name: GetInvoiceHTLCs :many +SELECT * +FROM invoice_htlcs +WHERE invoice_id = $1; + +-- name: UpdateInvoiceHTLC :exec +UPDATE invoice_htlcs +SET state=$2, resolve_time=$3 +WHERE id = $1; + +-- name: UpdateInvoiceHTLCs :exec +UPDATE invoice_htlcs +SET state=$2, resolve_time=$3 +WHERE invoice_id = $1 AND resolve_time IS NULL; + + +-- name: DeleteInvoiceHTLC :exec +DELETE +FROM invoice_htlcs +WHERE htlc_id = $1; + +-- name: DeleteInvoiceHTLCs :exec +DELETE +FROM invoice_htlcs +WHERE invoice_id = $1; + +-- name: InsertInvoiceHTLCCustomRecord :exec +INSERT INTO invoice_htlc_custom_records ( + key, value, htlc_id +) VALUES ( + $1, $2, $3 +); + +-- name: GetInvoiceHTLCCustomRecords :many +SELECT ihcr.htlc_id, key, value +FROM invoice_htlcs ih JOIN invoice_htlc_custom_records ihcr ON ih.id=ihcr.htlc_id +WHERE ih.invoice_id = $1; + +-- name: DeleteInvoiceHTLCCustomRecords :exec +WITH htlc_ids AS ( + SELECT ih.id + FROM invoice_htlcs ih JOIN invoice_htlc_custom_records ihcr ON ih.id=ihcr.htlc_id + WHERE ih.invoice_id = $1 +) +DELETE +FROM invoice_htlc_custom_records +WHERE htlc_id IN (SELECT id FROM htlc_ids); + +-- name: InsertInvoicePayment :one +INSERT INTO invoice_payments ( + invoice_id, amount_paid_msat, settled_at +) VALUES ( + $1, $2, $3 +) RETURNING id; + +-- name: GetInvoicePayments :many +SELECT * +FROM invoice_payments +WHERE invoice_id = $1; + +-- name: FilterInvoicePayments :many +SELECT + ip.id AS settle_index, ip.amount_paid_msat, ip.settled_at AS settle_date, + i.* +FROM invoice_payments ip JOIN invoices i ON ip.invoice_id = i.id +WHERE ( + ip.id >= sqlc.narg('settle_index_get') OR + sqlc.narg('settle_index_get') IS NULL +) AND ( + ip.settled_at >= sqlc.narg('settled_after') OR + sqlc.narg('settled_after') IS NULL +) +ORDER BY + CASE + WHEN sqlc.narg('reverse') = FALSE THEN ip.id + ELSE NULL + END ASC, + CASE + WHEN sqlc.narg('reverse') = TRUE THEN ip.id + ELSE NULL + END DESC +LIMIT @num_limit OFFSET @num_offset; +