From 72f86394299de353f5c15632d1f63d75811e60ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Tigerstr=C3=B6m?= Date: Fri, 29 Aug 2025 11:28:37 +0200 Subject: [PATCH 1/9] sqldb/v2: add base for sqldb/v2 module In the upcoming commits, we will introduce a new sqldb module, sqldb version 2. The intention of the new sqldb module, is to make it generalizable so that it contains no `lnd` specific code, to ensure that it can be reused in other projects. This commit adds the base of the new module, but does not include any implementation yet, as that will be done in the upcoming commits. --- sqldb/v2/go.mod | 7 +++++++ sqldb/v2/go.sum | 4 ++++ sqldb/v2/log.go | 24 ++++++++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 sqldb/v2/go.mod create mode 100644 sqldb/v2/go.sum create mode 100644 sqldb/v2/log.go diff --git a/sqldb/v2/go.mod b/sqldb/v2/go.mod new file mode 100644 index 00000000000..1fadb13be5b --- /dev/null +++ b/sqldb/v2/go.mod @@ -0,0 +1,7 @@ +module github.com/lightningnetwork/lnd/sqldb/v2 + +require github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084 + +require github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c // indirect + +go 1.23.12 \ No newline at end of file diff --git a/sqldb/v2/go.sum b/sqldb/v2/go.sum new file mode 100644 index 00000000000..fc3f44cfc90 --- /dev/null +++ b/sqldb/v2/go.sum @@ -0,0 +1,4 @@ +github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c h1:4HxD1lBUGUddhzgaNgrCPsFWd7cGYNpeFUgd9ZIgyM0= +github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c/go.mod h1:w7xnGOhwT3lmrS4H3b/D1XAXxvh+tbhUm8xeHN2y3TQ= +github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084 h1:y3bvkt8ki0KX35eUEU8XShRHusz1S+55QwXUTmxn888= +github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084/go.mod h1:XItGUfVOxotJL8kkuk2Hj3EVow5KCugXl3wWfQ6K0AE= diff --git a/sqldb/v2/log.go b/sqldb/v2/log.go new file mode 100644 index 00000000000..19f43f7018b --- /dev/null +++ b/sqldb/v2/log.go @@ -0,0 +1,24 @@ +package sqldb + +import "github.com/btcsuite/btclog/v2" + +// Subsystem defines the logging code for this subsystem. +const Subsystem = "SQL2" + +// log is a logger that is initialized with no output filters. This +// means the package will not perform any logging by default until the caller +// requests it. +var log = btclog.Disabled + +// DisableLog disables all library log output. Logging output is disabled +// by default until UseLogger is called. +func DisableLog() { + UseLogger(btclog.Disabled) +} + +// UseLogger uses a specified Logger to output package logging info. +// This should be used in preference to SetLogWriter if the caller is also +// using btclog. +func UseLogger(logger btclog.Logger) { + log = logger +} From 212186bbacf39a697de4a792769d7910def9dead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Tigerstr=C3=B6m?= Date: Mon, 1 Sep 2025 17:03:20 +0200 Subject: [PATCH 2/9] sqldb/v2: move all non lnd-specific v1 code to v2 This commit moves all non lnd-specific code of sqldb/v1 to the new sqldb/v2 module. Note however, that without additional changes, this package still needs to reference lnd, as references to the lnd `sqlc` package is required without further changes. Those changes will be introduced in the upcoming commits, to fully decouple the new sqldb/v2 module from lnd. --- sqldb/v2/config.go | 71 +++++ sqldb/v2/go.mod | 66 ++++- sqldb/v2/go.sum | 265 +++++++++++++++++++ sqldb/v2/interfaces.go | 418 ++++++++++++++++++++++++++++++ sqldb/v2/migrations.go | 444 ++++++++++++++++++++++++++++++++ sqldb/v2/no_sqlite.go | 40 +++ sqldb/v2/paginate.go | 318 +++++++++++++++++++++++ sqldb/v2/postgres.go | 212 +++++++++++++++ sqldb/v2/postgres_fixture.go | 192 ++++++++++++++ sqldb/v2/postgres_test.go | 37 +++ sqldb/v2/schemas.go | 8 + sqldb/v2/sqlerrors.go | 170 ++++++++++++ sqldb/v2/sqlerrors_no_sqlite.go | 86 +++++++ sqldb/v2/sqlite.go | 275 ++++++++++++++++++++ sqldb/v2/sqlite_test.go | 27 ++ sqldb/v2/sqlutils.go | 92 +++++++ 16 files changed, 2718 insertions(+), 3 deletions(-) create mode 100644 sqldb/v2/config.go create mode 100644 sqldb/v2/interfaces.go create mode 100644 sqldb/v2/migrations.go create mode 100644 sqldb/v2/no_sqlite.go create mode 100644 sqldb/v2/paginate.go create mode 100644 sqldb/v2/postgres.go create mode 100644 sqldb/v2/postgres_fixture.go create mode 100644 sqldb/v2/postgres_test.go create mode 100644 sqldb/v2/schemas.go create mode 100644 sqldb/v2/sqlerrors.go create mode 100644 sqldb/v2/sqlerrors_no_sqlite.go create mode 100644 sqldb/v2/sqlite.go create mode 100644 sqldb/v2/sqlite_test.go create mode 100644 sqldb/v2/sqlutils.go diff --git a/sqldb/v2/config.go b/sqldb/v2/config.go new file mode 100644 index 00000000000..34de293c185 --- /dev/null +++ b/sqldb/v2/config.go @@ -0,0 +1,71 @@ +package sqldb + +import ( + "fmt" + "net/url" + "time" +) + +const ( + // defaultMaxConns is the number of permitted active and idle + // connections. We want to limit this so it isn't unlimited. We use the + // same value for the number of idle connections as, this can speed up + // queries given a new connection doesn't need to be established each + // time. + defaultMaxConns = 25 + + // connIdleLifetime is the amount of time a connection can be idle. + connIdleLifetime = 5 * time.Minute +) + +// SqliteConfig holds all the config arguments needed to interact with our +// sqlite DB. +// +//nolint:ll +type SqliteConfig struct { + Timeout time.Duration `long:"timeout" description:"The time after which a database query should be timed out."` + BusyTimeout time.Duration `long:"busytimeout" description:"The maximum amount of time to wait for a database connection to become available for a query."` + MaxConnections int `long:"maxconnections" description:"The maximum number of open connections to the database. Set to zero for unlimited."` + PragmaOptions []string `long:"pragmaoptions" description:"A list of pragma options to set on a database connection. For example, 'auto_vacuum=incremental'. Note that the flag must be specified multiple times if multiple options are to be set."` + SkipMigrations bool `long:"skipmigrations" description:"Skip applying migrations on startup."` + QueryConfig `group:"query" namespace:"query"` +} + +// Validate checks that the SqliteConfig values are valid. +func (p *SqliteConfig) Validate() error { + if err := p.QueryConfig.Validate(true); err != nil { + return fmt.Errorf("invalid query config: %w", err) + } + + return nil +} + +// PostgresConfig holds the postgres database configuration. +// +//nolint:ll +type PostgresConfig struct { + Dsn string `long:"dsn" description:"Database connection string."` + Timeout time.Duration `long:"timeout" description:"Database connection timeout. Set to zero to disable."` + MaxConnections int `long:"maxconnections" description:"The maximum number of open connections to the database. Set to zero for unlimited."` + SkipMigrations bool `long:"skipmigrations" description:"Skip applying migrations on startup."` + QueryConfig `group:"query" namespace:"query"` +} + +// Validate checks that the PostgresConfig values are valid. +func (p *PostgresConfig) Validate() error { + if p.Dsn == "" { + return fmt.Errorf("DSN is required") + } + + // Parse the DSN as a URL. + _, err := url.Parse(p.Dsn) + if err != nil { + return fmt.Errorf("invalid DSN: %w", err) + } + + if err := p.QueryConfig.Validate(false); err != nil { + return fmt.Errorf("invalid query config: %w", err) + } + + return nil +} diff --git a/sqldb/v2/go.mod b/sqldb/v2/go.mod index 1fadb13be5b..2abd84f2d77 100644 --- a/sqldb/v2/go.mod +++ b/sqldb/v2/go.mod @@ -1,7 +1,67 @@ module github.com/lightningnetwork/lnd/sqldb/v2 -require github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084 +require ( + github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084 + github.com/davecgh/go-spew v1.1.1 + github.com/golang-migrate/migrate/v4 v4.19.0 + github.com/jackc/pgconn v1.14.3 + github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 + github.com/jackc/pgx/v5 v5.5.4 + github.com/lightningnetwork/lnd/sqldb v1.0.10 + github.com/ory/dockertest/v3 v3.10.0 + github.com/pmezard/go-difflib v1.0.0 + github.com/stretchr/testify v1.10.0 + golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b + modernc.org/sqlite v1.38.2 +) -require github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c // indirect +require ( + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect + github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c // indirect + github.com/cenkalti/backoff/v4 v4.1.3 // indirect + github.com/containerd/continuity v0.3.0 // indirect + github.com/docker/cli v20.10.17+incompatible // indirect + github.com/docker/docker v28.3.3+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/imdario/mergo v0.3.12 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.3 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/opencontainers/runc v1.1.5 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/sync v0.15.0 // indirect + golang.org/x/sys v0.34.0 // indirect + golang.org/x/text v0.23.0 // indirect + gopkg.in/yaml.v2 v2.3.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + modernc.org/libc v1.66.3 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect +) -go 1.23.12 \ No newline at end of file +go 1.23.12 diff --git a/sqldb/v2/go.sum b/sqldb/v2/go.sum index fc3f44cfc90..60b542e58d2 100644 --- a/sqldb/v2/go.sum +++ b/sqldb/v2/go.sum @@ -1,4 +1,269 @@ +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c h1:4HxD1lBUGUddhzgaNgrCPsFWd7cGYNpeFUgd9ZIgyM0= github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c/go.mod h1:w7xnGOhwT3lmrS4H3b/D1XAXxvh+tbhUm8xeHN2y3TQ= github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084 h1:y3bvkt8ki0KX35eUEU8XShRHusz1S+55QwXUTmxn888= github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084/go.mod h1:XItGUfVOxotJL8kkuk2Hj3EVow5KCugXl3wWfQ6K0AE= +github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= +github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= +github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= +github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dhui/dktest v0.4.6 h1:+DPKyScKSEp3VLtbMDHcUq6V5Lm5zfZZVb0Sk7Ahom4= +github.com/dhui/dktest v0.4.6/go.mod h1:JHTSYDtKkvFNFHJKqCzVzqXecyv+tKt8EzceOmQOgbU= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M= +github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= +github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-migrate/migrate/v4 v4.19.0 h1:RcjOnCGz3Or6HQYEJ/EEVLfWnmw9KnoigPSjzhCuaSE= +github.com/golang-migrate/migrate/v4 v4.19.0/go.mod h1:9dyEcu+hO+G9hPSw8AIg50yg622pXJsoHItQnDGZkI0= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= +github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0= +github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8= +github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lightningnetwork/lnd/sqldb v1.0.10 h1:ZLV7TGwjnKupVfCd+DJ43MAc9BKVSFCnvhpSPGKdN3M= +github.com/lightningnetwork/lnd/sqldb v1.0.10/go.mod h1:c/vWoQfcxu6FAfHzGajkIQi7CEIeIZFhhH4DYh1BJpc= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs= +github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= +github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= +github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= +gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= +modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM= +modernc.org/cc/v4 v4.26.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= +modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE= +modernc.org/fileutil v1.3.8 h1:qtzNm7ED75pd1C7WgAGcK4edm4fvhtBsEiI/0NQ54YM= +modernc.org/fileutil v1.3.8/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ= +modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek= +modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/sqldb/v2/interfaces.go b/sqldb/v2/interfaces.go new file mode 100644 index 00000000000..e80112378cb --- /dev/null +++ b/sqldb/v2/interfaces.go @@ -0,0 +1,418 @@ +package sqldb + +import ( + "context" + "database/sql" + "fmt" + "github.com/lightningnetwork/lnd/sqldb/sqlc" + "math" + "math/rand" + prand "math/rand" + "time" +) + +var ( + // DefaultStoreTimeout is the default timeout used for any interaction + // with the storage/database. + DefaultStoreTimeout = time.Second * 10 +) + +const ( + // DefaultNumTxRetries is the default number of times we'll retry a + // transaction if it fails with an error that permits transaction + // repetition. + DefaultNumTxRetries = 20 + + // DefaultRetryDelay is the default delay between retries. This will be + // used to generate a random delay between 0 and this value. + DefaultRetryDelay = time.Millisecond * 50 + + // DefaultMaxRetryDelay is the default maximum delay between retries. + DefaultMaxRetryDelay = time.Second +) + +// TxOptions represents a set of options one can use to control what type of +// database transaction is created. Transaction can be either read or write. +type TxOptions interface { + // ReadOnly returns true if the transaction should be read only. + ReadOnly() bool +} + +// txOptions is a concrete implementation of the TxOptions interface. +type txOptions struct { + // readOnly indicates if the transaction should be read-only. + readOnly bool +} + +// ReadOnly returns true if the transaction should be read only. +// +// NOTE: This is part of the TxOptions interface. +func (t *txOptions) ReadOnly() bool { + return t.readOnly +} + +// WriteTxOpt returns a TxOptions that indicates that the transaction +// should be a write transaction. +func WriteTxOpt() TxOptions { + return &txOptions{ + readOnly: false, + } +} + +// ReadTxOpt returns a TxOptions that indicates that the transaction +// should be a read-only transaction. +func ReadTxOpt() TxOptions { + return &txOptions{ + readOnly: true, + } +} + +// BatchedTx is a generic interface that represents the ability to execute +// several operations to a given storage interface in a single atomic +// transaction. Typically, Q here will be some subset of the main sqlc.Querier +// interface allowing it to only depend on the routines it needs to implement +// any additional business logic. +type BatchedTx[Q any] interface { + // ExecTx will execute the passed txBody, operating upon generic + // parameter Q (usually a storage interface) in a single transaction. + // + // The set of TxOptions are passed in order to allow the caller to + // specify if a transaction should be read-only and optionally what + // type of concurrency control should be used. + ExecTx(ctx context.Context, txOptions TxOptions, + txBody func(Q) error, reset func()) error +} + +// Tx represents a database transaction that can be committed or rolled back. +type Tx interface { + // Commit commits the database transaction, an error should be returned + // if the commit isn't possible. + Commit() error + + // Rollback rolls back an incomplete database transaction. + // Transactions that were able to be committed can still call this as a + // noop. + Rollback() error +} + +// QueryCreator is a generic function that's used to create a Querier, which is +// a type of interface that implements storage related methods from a database +// transaction. This will be used to instantiate an object callers can use to +// apply multiple modifications to an object interface in a single atomic +// transaction. +type QueryCreator[Q any] func(*sql.Tx) Q + +// BatchedQuerier is a generic interface that allows callers to create a new +// database transaction based on an abstract type that implements the TxOptions +// interface. +type BatchedQuerier interface { + // BeginTx creates a new database transaction given the set of + // transaction options. + BeginTx(ctx context.Context, options TxOptions) (*sql.Tx, error) +} + +// txExecutorOptions is a struct that holds the options for the transaction +// executor. This can be used to do things like retry a transaction due to an +// error a certain amount of times. +type txExecutorOptions struct { + numRetries int + retryDelay time.Duration +} + +// defaultTxExecutorOptions returns the default options for the transaction +// executor. +func defaultTxExecutorOptions() *txExecutorOptions { + return &txExecutorOptions{ + numRetries: DefaultNumTxRetries, + retryDelay: DefaultRetryDelay, + } +} + +// randRetryDelay returns a random retry delay between 0 and the configured max +// delay. +func (t *txExecutorOptions) randRetryDelay() time.Duration { + return time.Duration(prand.Int63n(int64(t.retryDelay))) //nolint:gosec +} + +// TxExecutorOption is a functional option that allows us to pass in optional +// argument when creating the executor. +type TxExecutorOption func(*txExecutorOptions) + +// WithTxRetries is a functional option that allows us to specify the number of +// times a transaction should be retried if it fails with a repeatable error. +func WithTxRetries(numRetries int) TxExecutorOption { + return func(o *txExecutorOptions) { + o.numRetries = numRetries + } +} + +// WithTxRetryDelay is a functional option that allows us to specify the delay +// to wait before a transaction is retried. +func WithTxRetryDelay(delay time.Duration) TxExecutorOption { + return func(o *txExecutorOptions) { + o.retryDelay = delay + } +} + +// TransactionExecutor is a generic struct that abstracts away from the type of +// query a type needs to run under a database transaction, and also the set of +// options for that transaction. The QueryCreator is used to create a query +// given a database transaction created by the BatchedQuerier. +type TransactionExecutor[Query any] struct { + BatchedQuerier + + createQuery QueryCreator[Query] + + opts *txExecutorOptions +} + +// NewTransactionExecutor creates a new instance of a TransactionExecutor given +// a Querier query object and a concrete type for the type of transactions the +// Querier understands. +func NewTransactionExecutor[Querier any](db BatchedQuerier, + createQuery QueryCreator[Querier], + opts ...TxExecutorOption) *TransactionExecutor[Querier] { + + txOpts := defaultTxExecutorOptions() + for _, optFunc := range opts { + optFunc(txOpts) + } + + return &TransactionExecutor[Querier]{ + BatchedQuerier: db, + createQuery: createQuery, + opts: txOpts, + } +} + +// randRetryDelay returns a random retry delay between -50% and +50% of the +// configured delay that is doubled for each attempt and capped at a max value. +func randRetryDelay(initialRetryDelay, maxRetryDelay time.Duration, + attempt int) time.Duration { + + halfDelay := initialRetryDelay / 2 + randDelay := rand.Int63n(int64(initialRetryDelay)) //nolint:gosec + + // 50% plus 0%-100% gives us the range of 50%-150%. + initialDelay := halfDelay + time.Duration(randDelay) + + // If this is the first attempt, we just return the initial delay. + if attempt == 0 { + return initialDelay + } + + // For each subsequent delay, we double the initial delay. This still + // gives us a somewhat random delay, but it still increases with each + // attempt. If we double something n times, that's the same as + // multiplying the value with 2^n. We limit the power to 32 to avoid + // overflows. + factor := time.Duration(math.Pow(2, min(float64(attempt), 32))) + actualDelay := initialDelay * factor + + // Cap the delay at the maximum configured value. + if actualDelay > maxRetryDelay { + return maxRetryDelay + } + + return actualDelay +} + +// MakeTx is a function that creates a new transaction. It returns a Tx and an +// error if the transaction cannot be created. This is used to abstract the +// creation of a transaction from the actual transaction logic in order to be +// able to reuse the transaction retry logic in other packages. +type MakeTx func() (Tx, error) + +// TxBody represents the function type for transactions. It returns an +// error to indicate success or failure. +type TxBody func(tx Tx) error + +// RollbackTx is a function that is called when a transaction needs to be rolled +// back due to a serialization error. By using this intermediate function, we +// can avoid having to return rollback errors that are not actionable by the +// caller. +type RollbackTx func(tx Tx) error + +// OnBackoff is a function that is called when a transaction is retried due to a +// serialization error. The function is called with the retry attempt number and +// the delay before the next retry. +type OnBackoff func(retry int, delay time.Duration) + +// ExecuteSQLTransactionWithRetry is a helper function that executes a +// transaction with retry logic. It will retry the transaction if it fails with +// a serialization error. The function will return an error if the transaction +// fails with a non-retryable error, the context is cancelled or the number of +// retries is exceeded. +func ExecuteSQLTransactionWithRetry(ctx context.Context, makeTx MakeTx, + rollbackTx RollbackTx, txBody TxBody, onBackoff OnBackoff, + numRetries int) error { + + waitBeforeRetry := func(attemptNumber int) bool { + retryDelay := randRetryDelay( + DefaultRetryDelay, DefaultMaxRetryDelay, attemptNumber, + ) + + onBackoff(attemptNumber, retryDelay) + + select { + // Before we try again, we'll wait with a random backoff based + // on the retry delay. + case <-time.After(retryDelay): + return true + + // If the daemon is shutting down, then we'll exit early. + case <-ctx.Done(): + return false + } + } + + for i := 0; i < numRetries; i++ { + tx, err := makeTx() + if err != nil { + dbErr := MapSQLError(err) + log.Tracef("Failed to makeTx: err=%v, dbErr=%v", err, + dbErr) + + if IsSerializationError(dbErr) { + // Nothing to roll back here, since we haven't + // even get a transaction yet. We'll just wait + // and try again. + if waitBeforeRetry(i) { + continue + } + } + + return dbErr + } + + // Rollback is safe to call even if the tx is already closed, + // so if the tx commits successfully, this is a no-op. + defer func() { + _ = tx.Rollback() + }() + + if bodyErr := txBody(tx); bodyErr != nil { + log.Tracef("Error in txBody: %v", bodyErr) + + // Roll back the transaction, then attempt a random + // backoff and try again if the error was a + // serialization error. + if err := rollbackTx(tx); err != nil { + return MapSQLError(err) + } + + dbErr := MapSQLError(bodyErr) + if IsSerializationError(dbErr) { + if waitBeforeRetry(i) { + continue + } + } + + return dbErr + } + + // Commit transaction. + if commitErr := tx.Commit(); commitErr != nil { + log.Tracef("Failed to commit tx: %v", commitErr) + + // Roll back the transaction, then attempt a random + // backoff and try again if the error was a + // serialization error. + if err := rollbackTx(tx); err != nil { + return MapSQLError(err) + } + + dbErr := MapSQLError(commitErr) + if IsSerializationError(dbErr) { + if waitBeforeRetry(i) { + continue + } + } + + return dbErr + } + + return nil + } + + // If we get to this point, then we weren't able to successfully commit + // a tx given the max number of retries. + return ErrRetriesExceeded +} + +// ExecTx is a wrapper for txBody to abstract the creation and commit of a db +// transaction. The db transaction is embedded in a `*Queries` that txBody +// needs to use when executing each one of the queries that need to be applied +// atomically. This can be used by other storage interfaces to parameterize the +// type of query and options run, in order to have access to batched operations +// related to a storage object. +func (t *TransactionExecutor[Q]) ExecTx(ctx context.Context, + txOptions TxOptions, txBody func(Q) error, reset func()) error { + + makeTx := func() (Tx, error) { + return t.BatchedQuerier.BeginTx(ctx, txOptions) + } + + execTxBody := func(tx Tx) error { + sqlTx, ok := tx.(*sql.Tx) + if !ok { + return fmt.Errorf("expected *sql.Tx, got %T", tx) + } + + reset() + return txBody(t.createQuery(sqlTx)) + } + + onBackoff := func(retry int, delay time.Duration) { + log.Tracef("Retrying transaction due to tx serialization "+ + "error, attempt_number=%v, delay=%v", retry, delay) + } + + rollbackTx := func(tx Tx) error { + sqlTx, ok := tx.(*sql.Tx) + if !ok { + return fmt.Errorf("expected *sql.Tx, got %T", tx) + } + + _ = sqlTx.Rollback() + + return nil + } + + return ExecuteSQLTransactionWithRetry( + ctx, makeTx, rollbackTx, execTxBody, onBackoff, + t.opts.numRetries, + ) +} + +// DB is an interface that represents a generic SQL database. It provides +// methods to apply migrations and access the underlying database connection. +type DB interface { + // GetBaseDB returns the underlying BaseDB instance. + GetBaseDB() *BaseDB + + // ApplyAllMigrations applies all migrations to the database including + // both sqlc and custom in-code migrations. + ApplyAllMigrations(ctx context.Context, + customMigrations []MigrationConfig) error +} + +// BaseDB is the base database struct that each implementation can embed to +// gain some common functionality. +type BaseDB struct { + *sql.DB + + *sqlc.Queries +} + +// BeginTx wraps the normal sql specific BeginTx method with the TxOptions +// interface. This interface is then mapped to the concrete sql tx options +// struct. +func (s *BaseDB) BeginTx(ctx context.Context, opts TxOptions) (*sql.Tx, error) { + sqlOptions := sql.TxOptions{ + Isolation: sql.LevelSerializable, + ReadOnly: opts.ReadOnly(), + } + + return s.DB.BeginTx(ctx, &sqlOptions) +} diff --git a/sqldb/v2/migrations.go b/sqldb/v2/migrations.go new file mode 100644 index 00000000000..bf4b39ff0a7 --- /dev/null +++ b/sqldb/v2/migrations.go @@ -0,0 +1,444 @@ +package sqldb + +import ( + "bytes" + "context" + "database/sql" + "errors" + "fmt" + "io" + "io/fs" + "net/http" + "reflect" + "strings" + "time" + + "github.com/btcsuite/btclog/v2" + "github.com/davecgh/go-spew/spew" + "github.com/golang-migrate/migrate/v4" + "github.com/golang-migrate/migrate/v4/database" + "github.com/golang-migrate/migrate/v4/source/httpfs" + "github.com/lightningnetwork/lnd/sqldb/sqlc" + "github.com/pmezard/go-difflib/difflib" +) + +var ( + // ErrMigrationMismatch is returned when a migrated record does not + // match the original record. + ErrMigrationMismatch = fmt.Errorf("migrated record does not match " + + "original record") +) + +// MigrationConfig is a configuration struct that describes SQL migrations. Each +// migration is associated with a specific schema version and a global database +// version. Migrations are applied in the order of their global database +// version. If a migration includes a non-nil MigrationFn, it is executed after +// the SQL schema has been migrated to the corresponding schema version. +type MigrationConfig struct { + // Name is the name of the migration. + Name string + + // Version represents the "global" database version for this migration. + // Unlike the schema version tracked by golang-migrate, it encompasses + // all migrations, including those managed by golang-migrate as well + // as custom in-code migrations. + Version int + + // SchemaVersion represents the schema version tracked by golang-migrate + // at which the migration is applied. + SchemaVersion int + + // MigrationFn is the function executed for custom migrations at the + // specified version. It is used to handle migrations that cannot be + // performed through SQL alone. If set to nil, no custom migration is + // applied. + MigrationFn func(tx *sqlc.Queries) error +} + +// MigrationTarget is a functional option that can be passed to applyMigrations +// to specify a target version to migrate to. +type MigrationTarget func(mig *migrate.Migrate) error + +// MigrationExecutor is an interface that abstracts the migration functionality. +type MigrationExecutor interface { + // ExecuteMigrations runs database migrations up to the specified target + // version or all migrations if no target is specified. A migration may + // include a schema change, a custom migration function, or both. + // Developers must ensure that migrations are defined in the correct + // order. Migration details are stored in the global variable + // migrationConfig. + ExecuteMigrations(target MigrationTarget) error + + // GetSchemaVersion returns the current schema version of the database. + GetSchemaVersion() (int, bool, error) + + // SetSchemaVersion sets the schema version of the database. + // + // NOTE: This alters the internal database schema tracker. USE WITH + // CAUTION!!! + SetSchemaVersion(version int, dirty bool) error +} + +var ( + // TargetLatest is a MigrationTarget that migrates to the latest + // version available. + TargetLatest = func(mig *migrate.Migrate) error { + return mig.Up() + } + + // TargetVersion is a MigrationTarget that migrates to the given + // version. + TargetVersion = func(version uint) MigrationTarget { + return func(mig *migrate.Migrate) error { + return mig.Migrate(version) + } + } +) + +// migrationLogger is a logger that wraps the passed btclog.Logger so it can be +// used to log migrations. +type migrationLogger struct { + log btclog.Logger +} + +// Printf is like fmt.Printf. We map this to the target logger based on the +// current log level. +func (m *migrationLogger) Printf(format string, v ...interface{}) { + // Trim trailing newlines from the format. + format = strings.TrimRight(format, "\n") + + switch m.log.Level() { + case btclog.LevelTrace: + m.log.Tracef(format, v...) + case btclog.LevelDebug: + m.log.Debugf(format, v...) + case btclog.LevelInfo: + m.log.Infof(format, v...) + case btclog.LevelWarn: + m.log.Warnf(format, v...) + case btclog.LevelError: + m.log.Errorf(format, v...) + case btclog.LevelCritical: + m.log.Criticalf(format, v...) + case btclog.LevelOff: + } +} + +// Verbose should return true when verbose logging output is wanted +func (m *migrationLogger) Verbose() bool { + return m.log.Level() <= btclog.LevelDebug +} + +// applyMigrations executes all database migration files found in the given file +// system under the given path, using the passed database driver and database +// name. +func applyMigrations(fs fs.FS, driver database.Driver, path, + dbName string, targetVersion MigrationTarget) error { + + // With the migrate instance open, we'll create a new migration source + // using the embedded file system stored in sqlSchemas. The library + // we're using can't handle a raw file system interface, so we wrap it + // in this intermediate layer. + migrateFileServer, err := httpfs.New(http.FS(fs), path) + if err != nil { + return err + } + + // Finally, we'll run the migration with our driver above based on the + // open DB, and also the migration source stored in the file system + // above. + sqlMigrate, err := migrate.NewWithInstance( + "migrations", migrateFileServer, dbName, driver, + ) + if err != nil { + return err + } + + migrationVersion, _, err := sqlMigrate.Version() + if err != nil && !errors.Is(err, migrate.ErrNilVersion) { + log.Errorf("Unable to determine current migration version: %v", + err) + + return err + } + + log.Infof("Applying migrations from version=%v", migrationVersion) + + // Apply our local logger to the migration instance. + sqlMigrate.Log = &migrationLogger{log} + + // Execute the migration based on the target given. + err = targetVersion(sqlMigrate) + if err != nil && !errors.Is(err, migrate.ErrNoChange) { + return err + } + + return nil +} + +// replacerFS is an implementation of a fs.FS virtual file system that wraps an +// existing file system but does a search-and-replace operation on each file +// when it is opened. +type replacerFS struct { + parentFS fs.FS + replaces map[string]string +} + +// A compile-time assertion to make sure replacerFS implements the fs.FS +// interface. +var _ fs.FS = (*replacerFS)(nil) + +// newReplacerFS creates a new replacer file system, wrapping the given parent +// virtual file system. Each file within the file system is undergoing a +// search-and-replace operation when it is opened, using the given map where the +// key denotes the search term and the value the term to replace each occurrence +// with. +func newReplacerFS(parent fs.FS, replaces map[string]string) *replacerFS { + return &replacerFS{ + parentFS: parent, + replaces: replaces, + } +} + +// Open opens a file in the virtual file system. +// +// NOTE: This is part of the fs.FS interface. +func (t *replacerFS) Open(name string) (fs.File, error) { + f, err := t.parentFS.Open(name) + if err != nil { + return nil, err + } + + stat, err := f.Stat() + if err != nil { + return nil, err + } + + if stat.IsDir() { + return f, err + } + + return newReplacerFile(f, t.replaces) +} + +type replacerFile struct { + parentFile fs.File + buf bytes.Buffer +} + +// A compile-time assertion to make sure replacerFile implements the fs.File +// interface. +var _ fs.File = (*replacerFile)(nil) + +func newReplacerFile(parent fs.File, replaces map[string]string) (*replacerFile, + error) { + + content, err := io.ReadAll(parent) + if err != nil { + return nil, err + } + + contentStr := string(content) + for from, to := range replaces { + contentStr = strings.ReplaceAll(contentStr, from, to) + } + + var buf bytes.Buffer + _, err = buf.WriteString(contentStr) + if err != nil { + return nil, err + } + + return &replacerFile{ + parentFile: parent, + buf: buf, + }, nil +} + +// Stat returns statistics/info about the file. +// +// NOTE: This is part of the fs.File interface. +func (t *replacerFile) Stat() (fs.FileInfo, error) { + return t.parentFile.Stat() +} + +// Read reads as many bytes as possible from the file into the given slice. +// +// NOTE: This is part of the fs.File interface. +func (t *replacerFile) Read(bytes []byte) (int, error) { + return t.buf.Read(bytes) +} + +// Close closes the underlying file. +// +// NOTE: This is part of the fs.File interface. +func (t *replacerFile) Close() error { + // We already fully read and then closed the file when creating this + // instance, so there's nothing to do for us here. + return nil +} + +// ApplyMigrations applies the provided migrations to the database in sequence. +// It ensures migrations are executed in the correct order, applying both custom +// migration functions and SQL migrations as needed. +func ApplyMigrations(ctx context.Context, db *BaseDB, + migrator MigrationExecutor, migrations []MigrationConfig) error { + + // Ensure that the migrations are sorted by version. + for i := 0; i < len(migrations); i++ { + if migrations[i].Version != i+1 { + return fmt.Errorf("migration version %d is out of "+ + "order. Expected %d", migrations[i].Version, + i+1) + } + } + // Construct a transaction executor to apply custom migrations. + executor := NewTransactionExecutor(db, func(tx *sql.Tx) *sqlc.Queries { + return db.WithTx(tx) + }) + + currentVersion := 0 + version, err := db.GetDatabaseVersion(ctx) + if !errors.Is(err, sql.ErrNoRows) { + if err != nil { + return fmt.Errorf("error getting current database "+ + "version: %w", err) + } + + currentVersion = int(version) + } else { + // Since we don't have a version tracked by our own table yet, + // we'll use the schema version reported by sqlc to determine + // the current version. + // + // NOTE: This is safe because the first in-code migration was + // introduced in version 7. This is only possible if the user + // has a schema version <= 4. + var dirty bool + currentVersion, dirty, err = migrator.GetSchemaVersion() + if err != nil { + return err + } + + log.Infof("No database version found, using schema version %d "+ + "(dirty=%v) as base version", currentVersion, dirty) + } + + // Due to an a migration issue in v0.19.0-rc1 we may be at version 2 and + // have a dirty schema due to failing migration 3. If this is indeed the + // case, we need to reset the dirty flag to be able to apply the fixed + // migration. + // NOTE: this could be removed as soon as we drop v0.19.0-beta. + if version == 2 { + schemaVersion, dirty, err := migrator.GetSchemaVersion() + if err != nil { + return err + } + + if schemaVersion == 3 && dirty { + log.Warnf("Schema version %d is dirty. This is "+ + "likely a consequence of a failed migration "+ + "in v0.19.0-rc1. Attempting to recover by "+ + "resetting the dirty flag", schemaVersion) + + err = migrator.SetSchemaVersion(4, false) + if err != nil { + return err + } + } + } + + for _, migration := range migrations { + if migration.Version <= currentVersion { + log.Infof("Skipping migration '%s' (version %d) as it "+ + "has already been applied", migration.Name, + migration.Version) + + continue + } + + log.Infof("Migrating SQL schema to version %d", + migration.SchemaVersion) + + // Execute SQL schema migrations up to the target version. + err = migrator.ExecuteMigrations( + TargetVersion(uint(migration.SchemaVersion)), + ) + if err != nil { + return fmt.Errorf("error executing schema migrations "+ + "to target version %d: %w", + migration.SchemaVersion, err) + } + + opts := WriteTxOpt() + + // Run the custom migration as a transaction to ensure + // atomicity. If successful, mark the migration as complete in + // the migration tracker table. + err = executor.ExecTx(ctx, opts, func(tx *sqlc.Queries) error { + // Apply the migration function if one is provided. + if migration.MigrationFn != nil { + log.Infof("Applying custom migration '%v' "+ + "(version %d) to schema version %d", + migration.Name, migration.Version, + migration.SchemaVersion) + + err = migration.MigrationFn(tx) + if err != nil { + return fmt.Errorf("error applying "+ + "migration '%v' (version %d) "+ + "to schema version %d: %w", + migration.Name, + migration.Version, + migration.SchemaVersion, err) + } + + log.Infof("Migration '%v' (version %d) "+ + "applied ", migration.Name, + migration.Version) + } + + // Mark the migration as complete by adding the version + // to the migration tracker table along with the current + // timestamp. + err = tx.SetMigration(ctx, sqlc.SetMigrationParams{ + Version: int32(migration.Version), + MigrationTime: time.Now(), + }) + if err != nil { + return fmt.Errorf("error setting migration "+ + "version %d: %w", migration.Version, + err) + } + + return nil + }, func() {}) + if err != nil { + return err + } + } + + return nil +} + +// CompareRecords checks if the original and migrated objects are equal. If +// they are not, it returns an error with a unified diff of the two objects. +func CompareRecords(original, migrated any, identifier string) error { + if reflect.DeepEqual(original, migrated) { + return nil + } + + diff := difflib.UnifiedDiff{ + A: difflib.SplitLines(spew.Sdump(original)), + B: difflib.SplitLines(spew.Sdump(migrated)), + FromFile: "Expected", + FromDate: "", + ToFile: "Actual", + ToDate: "", + Context: 3, + } + diffText, _ := difflib.GetUnifiedDiffString(diff) + + return fmt.Errorf("%w: %s.\n%v", ErrMigrationMismatch, identifier, + diffText) +} diff --git a/sqldb/v2/no_sqlite.go b/sqldb/v2/no_sqlite.go new file mode 100644 index 00000000000..ad0cae6e4f8 --- /dev/null +++ b/sqldb/v2/no_sqlite.go @@ -0,0 +1,40 @@ +//go:build js || (windows && (arm || 386)) || (linux && (ppc64 || mips || mipsle || mips64)) + +package sqldb + +import ( + "context" + "fmt" +) + +var ( + // Make sure SqliteStore implements the DB interface. + _ DB = (*SqliteStore)(nil) +) + +// SqliteStore is a database store implementation that uses a sqlite backend. +type SqliteStore struct { + cfg *SqliteConfig + + *BaseDB +} + +// NewSqliteStore attempts to open a new sqlite database based on the passed +// config. +func NewSqliteStore(cfg *SqliteConfig, dbPath string) (*SqliteStore, error) { + return nil, fmt.Errorf("SQLite backend not supported in WebAssembly") +} + +// GetBaseDB returns the underlying BaseDB instance for the SQLite store. +// It is a trivial helper method to comply with the sqldb.DB interface. +func (s *SqliteStore) GetBaseDB() *BaseDB { + return s.BaseDB +} + +// ApplyAllMigrations applies both the SQLC and custom in-code migrations to +// the SQLite database. +func (s *SqliteStore) ApplyAllMigrations(context.Context, + []MigrationConfig) error { + + return fmt.Errorf("SQLite backend not supported in WebAssembly") +} diff --git a/sqldb/v2/paginate.go b/sqldb/v2/paginate.go new file mode 100644 index 00000000000..4fd2a9d4db7 --- /dev/null +++ b/sqldb/v2/paginate.go @@ -0,0 +1,318 @@ +package sqldb + +import ( + "context" + "fmt" +) + +const ( + // maxSQLiteBatchSize is the maximum number of items that can be + // included in a batch query IN clause for SQLite. This was determined + // using the TestSQLSliceQueries test. + maxSQLiteBatchSize = 32766 + + // maxPostgresBatchSize is the maximum number of items that can be + // included in a batch query IN clause for Postgres. This was determined + // using the TestSQLSliceQueries test. + maxPostgresBatchSize = 65535 + + // defaultSQLitePageSize is the default page size for SQLite queries. + defaultSQLitePageSize = 100 + + // defaultPostgresPageSize is the default page size for Postgres + // queries. + defaultPostgresPageSize = 10500 + + // defaultSQLiteBatchSize is the default batch size for SQLite queries. + defaultSQLiteBatchSize = 250 + + // defaultPostgresBatchSize is the default batch size for Postgres + // queries. + defaultPostgresBatchSize = 5000 +) + +// QueryConfig holds configuration values for SQL queries. +// +//nolint:ll +type QueryConfig struct { + // MaxBatchSize is the maximum number of items included in a batch + // query IN clauses list. + MaxBatchSize uint32 `long:"max-batch-size" description:"The maximum number of items to include in a batch query IN clause. This is used for queries that fetch results based on a list of identifiers."` + + // MaxPageSize is the maximum number of items returned in a single page + // of results. This is used for paginated queries. + MaxPageSize uint32 `long:"max-page-size" description:"The maximum number of items to return in a single page of results. This is used for paginated queries."` +} + +// Validate checks that the QueryConfig values are valid. +func (c *QueryConfig) Validate(sqlite bool) error { + if c.MaxBatchSize <= 0 { + return fmt.Errorf("max batch size must be greater than "+ + "zero, got %d", c.MaxBatchSize) + } + if c.MaxPageSize <= 0 { + return fmt.Errorf("max page size must be greater than "+ + "zero, got %d", c.MaxPageSize) + } + + if sqlite { + if c.MaxBatchSize > maxSQLiteBatchSize { + return fmt.Errorf("max batch size for SQLite cannot "+ + "exceed %d, got %d", maxSQLiteBatchSize, + c.MaxBatchSize) + } + } else { + if c.MaxBatchSize > maxPostgresBatchSize { + return fmt.Errorf("max batch size for Postgres cannot "+ + "exceed %d, got %d", maxPostgresBatchSize, + c.MaxBatchSize) + } + } + + return nil +} + +// DefaultSQLiteConfig returns a default configuration for SQL queries to a +// SQLite backend. +func DefaultSQLiteConfig() *QueryConfig { + return &QueryConfig{ + MaxBatchSize: defaultSQLiteBatchSize, + MaxPageSize: defaultSQLitePageSize, + } +} + +// DefaultPostgresConfig returns a default configuration for SQL queries to a +// Postgres backend. +func DefaultPostgresConfig() *QueryConfig { + return &QueryConfig{ + MaxBatchSize: defaultPostgresBatchSize, + MaxPageSize: defaultPostgresPageSize, + } +} + +// BatchQueryFunc represents a function that takes a batch of converted items +// and returns results. +type BatchQueryFunc[T any, R any] func(context.Context, []T) ([]R, error) + +// ItemCallbackFunc represents a function that processes individual results. +type ItemCallbackFunc[R any] func(context.Context, R) error + +// ConvertFunc represents a function that converts from input type to query type +// for the batch query. +type ConvertFunc[I any, T any] func(I) T + +// ExecuteBatchQuery executes a query in batches over a slice of input items. +// It converts the input items to a query type using the provided convertFunc, +// executes the query in batches using the provided queryFunc, and applies +// the callback to each result. This is useful for queries using the +// "WHERE x IN []slice" pattern. It takes that slice, splits it into batches of +// size MaxBatchSize, and executes the query for each batch. +// +// NOTE: it is the caller's responsibility to ensure that the expected return +// results are unique across all pages. Meaning that if the input items are +// split up, a result that is returned in one page should not be expected to +// be returned in another page. +func ExecuteBatchQuery[I any, T any, R any](ctx context.Context, + cfg *QueryConfig, inputItems []I, convertFunc ConvertFunc[I, T], + queryFunc BatchQueryFunc[T, R], callback ItemCallbackFunc[R]) error { + + if len(inputItems) == 0 { + return nil + } + + // Process items in pages. + for i := 0; i < len(inputItems); i += int(cfg.MaxBatchSize) { + // Calculate the end index for this page. + end := i + int(cfg.MaxBatchSize) + if end > len(inputItems) { + end = len(inputItems) + } + + // Get the page slice of input items. + inputPage := inputItems[i:end] + + // Convert only the items needed for this page. + convertedPage := make([]T, len(inputPage)) + for j, inputItem := range inputPage { + convertedPage[j] = convertFunc(inputItem) + } + + // Execute the query for this page. + results, err := queryFunc(ctx, convertedPage) + if err != nil { + return fmt.Errorf("query failed for page "+ + "starting at %d: %w", i, err) + } + + // Apply the callback to each result. + for _, result := range results { + if err := callback(ctx, result); err != nil { + return fmt.Errorf("callback failed for "+ + "result: %w", err) + } + } + } + + return nil +} + +// PagedQueryFunc represents a function that fetches a page of results using a +// cursor. It returns the fetched items and should return an empty slice when no +// more results. +type PagedQueryFunc[C any, T any] func(context.Context, C, int32) ([]T, error) + +// CursorExtractFunc represents a function that extracts the cursor value from +// an item. This cursor will be used for the next page fetch. +type CursorExtractFunc[T any, C any] func(T) C + +// ItemProcessFunc represents a function that processes individual items. +type ItemProcessFunc[T any] func(context.Context, T) error + +// ExecutePaginatedQuery executes a cursor-based paginated query. It continues +// fetching pages until no more results are returned, processing each item with +// the provided callback. +// +// Parameters: +// - initialCursor: the starting cursor value (e.g., 0, -1, "", etc.). +// - queryFunc: function that fetches a page given cursor and limit. +// - extractCursor: function that extracts cursor from an item for next page. +// - processItem: function that processes each individual item. +// +// NOTE: it is the caller's responsibility to "undo" any processing done on +// items if the query fails on a later page. +func ExecutePaginatedQuery[C any, T any](ctx context.Context, cfg *QueryConfig, + initialCursor C, queryFunc PagedQueryFunc[C, T], + extractCursor CursorExtractFunc[T, C], + processItem ItemProcessFunc[T]) error { + + cursor := initialCursor + + for { + // Fetch the next page. + items, err := queryFunc(ctx, cursor, int32(cfg.MaxPageSize)) + if err != nil { + return fmt.Errorf("failed to fetch page with "+ + "cursor %v: %w", cursor, err) + } + + // If no items returned, we're done. + if len(items) == 0 { + break + } + + // Process each item in the page. + for _, item := range items { + if err := processItem(ctx, item); err != nil { + return fmt.Errorf("failed to process item: %w", + err) + } + + // Update cursor for next iteration. + cursor = extractCursor(item) + } + + // If the number of items is less than the max page size, + // we assume there are no more items to fetch. + if len(items) < int(cfg.MaxPageSize) { + break + } + } + + return nil +} + +// CollectAndBatchDataQueryFunc represents a function that batch loads +// additional data for collected identifiers, returning the batch data that +// applies to all items. +type CollectAndBatchDataQueryFunc[ID any, BatchData any] func(context.Context, + []ID) (BatchData, error) + +// ItemWithBatchDataProcessFunc represents a function that processes individual +// items along with shared batch data. +type ItemWithBatchDataProcessFunc[T any, BatchData any] func(context.Context, + T, BatchData) error + +// CollectFunc represents a function that extracts an identifier from a +// paginated item. +type CollectFunc[T any, ID any] func(T) (ID, error) + +// ExecuteCollectAndBatchWithSharedDataQuery implements a page-by-page +// processing pattern where each page is immediately processed with batch-loaded +// data before moving to the next page. +// +// It: +// 1. Fetches a page of items using cursor-based pagination +// 2. Collects identifiers from that page and batch loads shared data +// 3. Processes each item in the page with the shared batch data +// 4. Moves to the next page and repeats +// +// Parameters: +// - initialCursor: starting cursor for pagination +// - pageQueryFunc: fetches a page of items +// - extractPageCursor: extracts cursor from paginated item for next page +// - collectFunc: extracts identifier from paginated item +// - batchDataFunc: batch loads shared data from collected IDs for one page +// - processItem: processes each item with the shared batch data +func ExecuteCollectAndBatchWithSharedDataQuery[C any, T any, I any, D any]( + ctx context.Context, cfg *QueryConfig, initialCursor C, + pageQueryFunc PagedQueryFunc[C, T], + extractPageCursor CursorExtractFunc[T, C], + collectFunc CollectFunc[T, I], + batchDataFunc CollectAndBatchDataQueryFunc[I, D], + processItem ItemWithBatchDataProcessFunc[T, D]) error { + + cursor := initialCursor + + for { + // Step 1: Fetch the next page of items. + items, err := pageQueryFunc(ctx, cursor, int32(cfg.MaxPageSize)) + if err != nil { + return fmt.Errorf("failed to fetch page with "+ + "cursor %v: %w", cursor, err) + } + + // If no items returned, we're done. + if len(items) == 0 { + break + } + + // Step 2: Collect identifiers from this page and batch load + // data. + pageIDs := make([]I, len(items)) + for i, item := range items { + pageIDs[i], err = collectFunc(item) + if err != nil { + return fmt.Errorf("failed to collect "+ + "identifier from item: %w", err) + } + } + + // Batch load shared data for this page. + batchData, err := batchDataFunc(ctx, pageIDs) + if err != nil { + return fmt.Errorf("failed to load batch data for "+ + "page: %w", err) + } + + // Step 3: Process each item in this page with the shared batch + // data. + for _, item := range items { + err := processItem(ctx, item, batchData) + if err != nil { + return fmt.Errorf("failed to process item "+ + "with batch data: %w", err) + } + + // Update cursor for next page. + cursor = extractPageCursor(item) + } + + // If the number of items is less than the max page size, + // we assume there are no more items to fetch. + if len(items) < int(cfg.MaxPageSize) { + break + } + } + + return nil +} diff --git a/sqldb/v2/postgres.go b/sqldb/v2/postgres.go new file mode 100644 index 00000000000..bc745735bf2 --- /dev/null +++ b/sqldb/v2/postgres.go @@ -0,0 +1,212 @@ +package sqldb + +import ( + "context" + "database/sql" + "fmt" + "net/url" + "path" + "strings" + "time" + + pgx_migrate "github.com/golang-migrate/migrate/v4/database/pgx/v5" + _ "github.com/golang-migrate/migrate/v4/source/file" // Read migrations from files. // nolint:ll + _ "github.com/jackc/pgx/v5" + "github.com/lightningnetwork/lnd/sqldb/sqlc" +) + +var ( + // DefaultPostgresFixtureLifetime is the default maximum time a Postgres + // test fixture is being kept alive. After that time the docker + // container will be terminated forcefully, even if the tests aren't + // fully executed yet. So this time needs to be chosen correctly to be + // longer than the longest expected individual test run time. + DefaultPostgresFixtureLifetime = 10 * time.Minute + + // postgresSchemaReplacements is a map of schema strings that need to be + // replaced for postgres. This is needed because we write the schemas to + // work with sqlite primarily but in sqlc's own dialect, and postgres + // has some differences. + postgresSchemaReplacements = map[string]string{ + "BLOB": "BYTEA", + "INTEGER PRIMARY KEY": "BIGSERIAL PRIMARY KEY", + "TIMESTAMP": "TIMESTAMP WITHOUT TIME ZONE", + } + + // Make sure PostgresStore implements the MigrationExecutor interface. + _ MigrationExecutor = (*PostgresStore)(nil) + + // Make sure PostgresStore implements the DB interface. + _ DB = (*PostgresStore)(nil) +) + +// replacePasswordInDSN takes a DSN string and returns it with the password +// replaced by "***". +func replacePasswordInDSN(dsn string) (string, error) { + // Parse the DSN as a URL + u, err := url.Parse(dsn) + if err != nil { + return "", err + } + + // Check if the URL has a user info part + if u.User != nil { + username := u.User.Username() + + // Reconstruct user info with "***" as password + userInfo := username + ":***@" + + // Rebuild the DSN with the modified user info + sanitizeDSN := strings.Replace( + dsn, u.User.String()+"@", userInfo, 1, + ) + + return sanitizeDSN, nil + } + + // Return the original DSN if no user info is present + return dsn, nil +} + +// getDatabaseNameFromDSN extracts the database name from a DSN string. +func getDatabaseNameFromDSN(dsn string) (string, error) { + // Parse the DSN as a URL + u, err := url.Parse(dsn) + if err != nil { + return "", err + } + + // The database name is the last segment of the path. Trim leading slash + // and return the last segment. + return path.Base(u.Path), nil +} + +// PostgresStore is a database store implementation that uses a Postgres +// backend. +type PostgresStore struct { + cfg *PostgresConfig + + *BaseDB +} + +// NewPostgresStore creates a new store that is backed by a Postgres database +// backend. +func NewPostgresStore(cfg *PostgresConfig) (*PostgresStore, error) { + sanitizedDSN, err := replacePasswordInDSN(cfg.Dsn) + if err != nil { + return nil, err + } + log.Infof("Using SQL database '%s'", sanitizedDSN) + + db, err := sql.Open("pgx", cfg.Dsn) + if err != nil { + return nil, err + } + + // Ensure the migration tracker table exists before running migrations. + // This table tracks migration progress and ensures compatibility with + // SQLC query generation. If the table is already created by an SQLC + // migration, this operation becomes a no-op. + migrationTrackerSQL := ` + CREATE TABLE IF NOT EXISTS migration_tracker ( + version INTEGER UNIQUE NOT NULL, + migration_time TIMESTAMP NOT NULL + );` + + _, err = db.Exec(migrationTrackerSQL) + if err != nil { + return nil, fmt.Errorf("error creating migration tracker: %w", + err) + } + + maxConns := defaultMaxConns + if cfg.MaxConnections > 0 { + maxConns = cfg.MaxConnections + } + + db.SetMaxOpenConns(maxConns) + db.SetMaxIdleConns(maxConns) + db.SetConnMaxLifetime(connIdleLifetime) + + queries := sqlc.New(db) + + return &PostgresStore{ + cfg: cfg, + BaseDB: &BaseDB{ + DB: db, + Queries: queries, + }, + }, nil +} + +// GetBaseDB returns the underlying BaseDB instance for the Postgres store. +// It is a trivial helper method to comply with the sqldb.DB interface. +func (s *PostgresStore) GetBaseDB() *BaseDB { + return s.BaseDB +} + +// ApplyAllMigrations applies both the SQLC and custom in-code migrations to the +// Postgres database. +func (s *PostgresStore) ApplyAllMigrations(ctx context.Context, + migrations []MigrationConfig) error { + + // Execute migrations unless configured to skip them. + if s.cfg.SkipMigrations { + return nil + } + + return ApplyMigrations(ctx, s.BaseDB, s, migrations) +} + +func errPostgresMigration(err error) error { + return fmt.Errorf("error creating postgres migration: %w", err) +} + +// ExecuteMigrations runs migrations for the Postgres database, depending on the +// target given, either all migrations or up to a given version. +func (s *PostgresStore) ExecuteMigrations(target MigrationTarget) error { + dbName, err := getDatabaseNameFromDSN(s.cfg.Dsn) + if err != nil { + return err + } + + driver, err := pgx_migrate.WithInstance(s.DB, &pgx_migrate.Config{}) + if err != nil { + return errPostgresMigration(err) + } + + // Populate the database with our set of schemas based on our embedded + // in-memory file system. + postgresFS := newReplacerFS(sqlSchemas, postgresSchemaReplacements) + return applyMigrations( + postgresFS, driver, "../sqlc/migrations", dbName, target, + ) +} + +// GetSchemaVersion returns the current schema version of the Postgres database. +func (s *PostgresStore) GetSchemaVersion() (int, bool, error) { + driver, err := pgx_migrate.WithInstance(s.DB, &pgx_migrate.Config{}) + if err != nil { + return 0, false, errPostgresMigration(err) + + } + + version, dirty, err := driver.Version() + if err != nil { + return 0, false, err + } + + return version, dirty, nil +} + +// SetSchemaVersion sets the schema version of the Postgres database. +// +// NOTE: This alters the internal database schema tracker. USE WITH CAUTION!!! +func (s *PostgresStore) SetSchemaVersion(version int, dirty bool) error { + driver, err := pgx_migrate.WithInstance(s.DB, &pgx_migrate.Config{}) + if err != nil { + return errPostgresMigration(err) + } + + return driver.SetVersion(version, dirty) +} diff --git a/sqldb/v2/postgres_fixture.go b/sqldb/v2/postgres_fixture.go new file mode 100644 index 00000000000..d6d5c271e5e --- /dev/null +++ b/sqldb/v2/postgres_fixture.go @@ -0,0 +1,192 @@ +//go:build !js && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64)) && !(netbsd || openbsd) + +package sqldb + +import ( + "context" + "crypto/rand" + "database/sql" + "encoding/hex" + "fmt" + "strconv" + "strings" + "testing" + "time" + + _ "github.com/jackc/pgx/v5" + "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" + "github.com/stretchr/testify/require" +) + +const ( + testPgUser = "test" + testPgPass = "test" + testPgDBName = "test" + PostgresTag = "11" +) + +// TestPgFixture is a test fixture that starts a Postgres 11 instance in a +// docker container. +type TestPgFixture struct { + db *sql.DB + pool *dockertest.Pool + resource *dockertest.Resource + host string + port int +} + +// NewTestPgFixture constructs a new TestPgFixture starting up a docker +// container running Postgres 11. The started container will expire in after +// the passed duration. +func NewTestPgFixture(t testing.TB, expiry time.Duration) *TestPgFixture { + // Use a sensible default on Windows (tcp/http) and linux/osx (socket) + // by specifying an empty endpoint. + pool, err := dockertest.NewPool("") + require.NoError(t, err, "Could not connect to docker") + + // Pulls an image, creates a container based on it and runs it. + resource, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "postgres", + Tag: PostgresTag, + Env: []string{ + fmt.Sprintf("POSTGRES_USER=%v", testPgUser), + fmt.Sprintf("POSTGRES_PASSWORD=%v", testPgPass), + fmt.Sprintf("POSTGRES_DB=%v", testPgDBName), + "listen_addresses='*'", + }, + Cmd: []string{ + "postgres", + "-c", "log_statement=all", + "-c", "log_destination=stderr", + "-c", "max_connections=5000", + }, + }, func(config *docker.HostConfig) { + // Set AutoRemove to true so that stopped container goes away + // by itself. + config.AutoRemove = true + config.RestartPolicy = docker.RestartPolicy{Name: "no"} + }) + require.NoError(t, err, "Could not start resource") + + hostAndPort := resource.GetHostPort("5432/tcp") + parts := strings.Split(hostAndPort, ":") + host := parts[0] + port, err := strconv.ParseInt(parts[1], 10, 64) + require.NoError(t, err) + + fixture := &TestPgFixture{ + host: host, + port: int(port), + } + databaseURL := fixture.GetConfig(testPgDBName).Dsn + log.Infof("Connecting to Postgres fixture: %v\n", databaseURL) + + // Tell docker to hard kill the container in "expiry" seconds. + require.NoError(t, resource.Expire(uint(expiry.Seconds()))) + + // Exponential backoff-retry, because the application in the container + // might not be ready to accept connections yet. + pool.MaxWait = 120 * time.Second + + var testDB *sql.DB + err = pool.Retry(func() error { + testDB, err = sql.Open("pgx", databaseURL) + if err != nil { + return err + } + + return testDB.Ping() + }) + require.NoError(t, err, "Could not connect to docker") + + // Now fill in the rest of the fixture. + fixture.db = testDB + fixture.pool = pool + fixture.resource = resource + + return fixture +} + +// GetConfig returns the full config of the Postgres node. +func (f *TestPgFixture) GetConfig(dbName string) *PostgresConfig { + return &PostgresConfig{ + Dsn: fmt.Sprintf( + "postgres://%v:%v@%v:%v/%v?sslmode=disable", + testPgUser, testPgPass, f.host, f.port, dbName, + ), + } +} + +// TearDown stops the underlying docker container. +func (f *TestPgFixture) TearDown(t testing.TB) { + err := f.pool.Purge(f.resource) + require.NoError(t, err, "Could not purge resource") +} + +// randomDBName generates a random database name. +func randomDBName(t testing.TB) string { + randBytes := make([]byte, 8) + _, err := rand.Read(randBytes) + require.NoError(t, err) + + return "test_" + hex.EncodeToString(randBytes) +} + +// NewTestPostgresDB is a helper function that creates a Postgres database for +// testing using the given fixture. +// +// NOTE: This function differs from the one in sqldb/postgres_fixture.go as that +// function does not expect any migrations to be passed in, and instead always +// applies the lnd specific migrations. +func NewTestPostgresDB(t testing.TB, fixture *TestPgFixture, + migrations []MigrationConfig) *PostgresStore { + + t.Helper() + + dbName := randomDBName(t) + + t.Logf("Creating new Postgres DB '%s' for testing", dbName) + + _, err := fixture.db.ExecContext( + context.Background(), "CREATE DATABASE "+dbName, + ) + require.NoError(t, err) + + cfg := fixture.GetConfig(dbName) + store, err := NewPostgresStore(cfg) + require.NoError(t, err) + + require.NoError(t, store.ApplyAllMigrations( + context.Background(), migrations), + ) + + return store +} + +// NewTestPostgresDBWithVersion is a helper function that creates a Postgres +// database for testing and migrates it to the given version. +func NewTestPostgresDBWithVersion(t *testing.T, fixture *TestPgFixture, + version uint) *PostgresStore { + + t.Helper() + + t.Logf("Creating new Postgres DB for testing, migrating to version %d", + version) + + dbName := randomDBName(t) + _, err := fixture.db.ExecContext( + context.Background(), "CREATE DATABASE "+dbName, + ) + require.NoError(t, err) + + storeCfg := fixture.GetConfig(dbName) + storeCfg.SkipMigrations = true + store, err := NewPostgresStore(storeCfg) + require.NoError(t, err) + + err = store.ExecuteMigrations(TargetVersion(version)) + require.NoError(t, err) + + return store +} diff --git a/sqldb/v2/postgres_test.go b/sqldb/v2/postgres_test.go new file mode 100644 index 00000000000..cabf8cb2b7f --- /dev/null +++ b/sqldb/v2/postgres_test.go @@ -0,0 +1,37 @@ +//go:build test_db_postgres +// +build test_db_postgres + +package sqldb + +import ( + "testing" +) + +// isSQLite is false if the build tag is set to test_db_postgres. It is used in +// tests that compile for both SQLite and Postgres databases to determine +// which database implementation is being used. +// +// TODO(elle): once we've updated to using sqldbv2, we can remove this since +// then we will have access to the DatabaseType on the BaseDB struct at runtime. +const isSQLite = false + +// NewTestDB is a helper function that creates a Postgres database for testing. +func NewTestDB(t *testing.T, migrations []MigrationConfig) *PostgresStore { + pgFixture := NewTestPgFixture(t, DefaultPostgresFixtureLifetime) + t.Cleanup(func() { + pgFixture.TearDown(t) + }) + + return NewTestPostgresDB(t, pgFixture, migrations) +} + +// NewTestDBWithVersion is a helper function that creates a Postgres database +// for testing and migrates it to the given version. +func NewTestDBWithVersion(t *testing.T, version uint) *PostgresStore { + pgFixture := NewTestPgFixture(t, DefaultPostgresFixtureLifetime) + t.Cleanup(func() { + pgFixture.TearDown(t) + }) + + return NewTestPostgresDBWithVersion(t, pgFixture, version) +} diff --git a/sqldb/v2/schemas.go b/sqldb/v2/schemas.go new file mode 100644 index 00000000000..ec81fa80e98 --- /dev/null +++ b/sqldb/v2/schemas.go @@ -0,0 +1,8 @@ +package sqldb + +import ( + "embed" +) + +//go:embed ../sqlc/migrations/*.up.sql +var sqlSchemas embed.FS diff --git a/sqldb/v2/sqlerrors.go b/sqldb/v2/sqlerrors.go new file mode 100644 index 00000000000..59729910ee3 --- /dev/null +++ b/sqldb/v2/sqlerrors.go @@ -0,0 +1,170 @@ +//go:build !js && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64)) + +package sqldb + +import ( + "errors" + "fmt" + "strings" + + "github.com/jackc/pgconn" + "github.com/jackc/pgerrcode" + "modernc.org/sqlite" + sqlite3 "modernc.org/sqlite/lib" +) + +var ( + // ErrRetriesExceeded is returned when a transaction is retried more + // than the max allowed valued without a success. + ErrRetriesExceeded = errors.New("db tx retries exceeded") + + // postgresErrMsgs are strings that signify retriable errors resulting + // from serialization failures. + postgresErrMsgs = []string{ + "could not serialize access", + "current transaction is aborted", + "not enough elements in RWConflictPool", + "deadlock detected", + "commit unexpectedly resulted in rollback", + } +) + +// MapSQLError attempts to interpret a given error as a database agnostic SQL +// error. +func MapSQLError(err error) error { + if err == nil { + return nil + } + + // Attempt to interpret the error as a sqlite error. + var sqliteErr *sqlite.Error + if errors.As(err, &sqliteErr) { + return parseSqliteError(sqliteErr) + } + + // Attempt to interpret the error as a postgres error. + var pqErr *pgconn.PgError + if errors.As(err, &pqErr) { + return parsePostgresError(pqErr) + } + + // Sometimes the error won't be properly wrapped, so we'll need to + // inspect raw error itself to detect something we can wrap properly. + // This handles a postgres variant of the error. + for _, postgresErrMsg := range postgresErrMsgs { + if strings.Contains(err.Error(), postgresErrMsg) { + return &ErrSerializationError{ + DBError: err, + } + } + } + + // We'll also attempt to catch this for sqlite, that uses a slightly + // different error message. This is taken from: + // https://gitlab.com/cznic/sqlite/-/blob/v1.25.0/sqlite.go#L75. + const sqliteErrMsg = "SQLITE_BUSY" + if strings.Contains(err.Error(), sqliteErrMsg) { + return &ErrSerializationError{ + DBError: err, + } + } + + // Return original error if it could not be classified as a database + // specific error. + return err +} + +// parsePostgresError attempts to parse a sqlite error as a database agnostic +// SQL error. +func parseSqliteError(sqliteErr *sqlite.Error) error { + switch sqliteErr.Code() { + // Handle unique constraint violation error. + case sqlite3.SQLITE_CONSTRAINT_UNIQUE: + return &ErrSQLUniqueConstraintViolation{ + DBError: sqliteErr, + } + + case sqlite3.SQLITE_CONSTRAINT_PRIMARYKEY: + return &ErrSQLUniqueConstraintViolation{ + DBError: sqliteErr, + } + + // Database is currently busy, so we'll need to try again. + case sqlite3.SQLITE_BUSY: + return &ErrSerializationError{ + DBError: sqliteErr, + } + + default: + return fmt.Errorf("unknown sqlite error: %w", sqliteErr) + } +} + +// parsePostgresError attempts to parse a postgres error as a database agnostic +// SQL error. +func parsePostgresError(pqErr *pgconn.PgError) error { + switch pqErr.Code { + // Handle unique constraint violation error. + case pgerrcode.UniqueViolation: + return &ErrSQLUniqueConstraintViolation{ + DBError: pqErr, + } + + // Unable to serialize the transaction, so we'll need to try again. + case pgerrcode.SerializationFailure: + return &ErrSerializationError{ + DBError: pqErr, + } + + // In failed SQL transaction because we didn't catch a previous + // serialization error, so return this one as a serialization error. + case pgerrcode.InFailedSQLTransaction: + return &ErrSerializationError{ + DBError: pqErr, + } + + // Deadlock detedted because of a serialization error, so return this + // one as a serialization error. + case pgerrcode.DeadlockDetected: + return &ErrSerializationError{ + DBError: pqErr, + } + + default: + return fmt.Errorf("unknown postgres error: %w", pqErr) + } +} + +// ErrSQLUniqueConstraintViolation is an error type which represents a database +// agnostic SQL unique constraint violation. +type ErrSQLUniqueConstraintViolation struct { + DBError error +} + +func (e ErrSQLUniqueConstraintViolation) Error() string { + return fmt.Sprintf("sql unique constraint violation: %v", e.DBError) +} + +// ErrSerializationError is an error type which represents a database agnostic +// error that a transaction couldn't be serialized with other concurrent db +// transactions. +type ErrSerializationError struct { + DBError error +} + +// Unwrap returns the wrapped error. +func (e ErrSerializationError) Unwrap() error { + return e.DBError +} + +// Error returns the error message. +func (e ErrSerializationError) Error() string { + return e.DBError.Error() +} + +// IsSerializationError returns true if the given error is a serialization +// error. +func IsSerializationError(err error) bool { + var serializationError *ErrSerializationError + return errors.As(err, &serializationError) +} diff --git a/sqldb/v2/sqlerrors_no_sqlite.go b/sqldb/v2/sqlerrors_no_sqlite.go new file mode 100644 index 00000000000..717d94152d7 --- /dev/null +++ b/sqldb/v2/sqlerrors_no_sqlite.go @@ -0,0 +1,86 @@ +//go:build js || (windows && (arm || 386)) || (linux && (ppc64 || mips || mipsle || mips64)) + +package sqldb + +import ( + "errors" + "fmt" + + "github.com/jackc/pgconn" + "github.com/jackc/pgerrcode" +) + +var ( + // ErrRetriesExceeded is returned when a transaction is retried more + // than the max allowed valued without a success. + ErrRetriesExceeded = errors.New("db tx retries exceeded") +) + +// MapSQLError attempts to interpret a given error as a database agnostic SQL +// error. +func MapSQLError(err error) error { + // Attempt to interpret the error as a postgres error. + var pqErr *pgconn.PgError + if errors.As(err, &pqErr) { + return parsePostgresError(pqErr) + } + + // Return original error if it could not be classified as a database + // specific error. + return err +} + +// parsePostgresError attempts to parse a postgres error as a database agnostic +// SQL error. +func parsePostgresError(pqErr *pgconn.PgError) error { + switch pqErr.Code { + // Handle unique constraint violation error. + case pgerrcode.UniqueViolation: + return &ErrSQLUniqueConstraintViolation{ + DBError: pqErr, + } + + // Unable to serialize the transaction, so we'll need to try again. + case pgerrcode.SerializationFailure: + return &ErrSerializationError{ + DBError: pqErr, + } + + default: + return fmt.Errorf("unknown postgres error: %w", pqErr) + } +} + +// ErrSQLUniqueConstraintViolation is an error type which represents a database +// agnostic SQL unique constraint violation. +type ErrSQLUniqueConstraintViolation struct { + DBError error +} + +func (e ErrSQLUniqueConstraintViolation) Error() string { + return fmt.Sprintf("sql unique constraint violation: %v", e.DBError) +} + +// ErrSerializationError is an error type which represents a database agnostic +// error that a transaction couldn't be serialized with other concurrent db +// transactions. +type ErrSerializationError struct { + DBError error +} + +// Unwrap returns the wrapped error. +func (e ErrSerializationError) Unwrap() error { + return e.DBError +} + +// Error returns the error message. +func (e ErrSerializationError) Error() string { + return e.DBError.Error() +} + +// IsSerializationError returns true if the given error is a serialization +// error. +func IsSerializationError(err error) bool { + var serializationError *ErrSerializationError + return errors.As(err, &serializationError) +} diff --git a/sqldb/v2/sqlite.go b/sqldb/v2/sqlite.go new file mode 100644 index 00000000000..e4b8be99073 --- /dev/null +++ b/sqldb/v2/sqlite.go @@ -0,0 +1,275 @@ +//go:build !js && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64)) + +package sqldb + +import ( + "context" + "database/sql" + "fmt" + "net/url" + "path/filepath" + "testing" + + sqlite_migrate "github.com/golang-migrate/migrate/v4/database/sqlite" + "github.com/lightningnetwork/lnd/sqldb/sqlc" + "github.com/stretchr/testify/require" + _ "modernc.org/sqlite" // Register relevant drivers. +) + +const ( + // sqliteOptionPrefix is the string prefix sqlite uses to set various + // options. This is used in the following format: + // * sqliteOptionPrefix || option_name = option_value. + sqliteOptionPrefix = "_pragma" + + // sqliteTxLockImmediate is a dsn option used to ensure that write + // transactions are started immediately. + sqliteTxLockImmediate = "_txlock=immediate" +) + +var ( + // sqliteSchemaReplacements maps schema strings to their SQLite + // compatible replacements. Currently, no replacements are needed as our + // SQL schema definition files are designed for SQLite compatibility. + sqliteSchemaReplacements = map[string]string{} + + // Make sure SqliteStore implements the MigrationExecutor interface. + _ MigrationExecutor = (*SqliteStore)(nil) + + // Make sure SqliteStore implements the DB interface. + _ DB = (*SqliteStore)(nil) +) + +// pragmaOption holds a key-value pair for a SQLite pragma setting. +type pragmaOption struct { + name string + value string +} + +// SqliteStore is a database store implementation that uses a sqlite backend. +type SqliteStore struct { + cfg *SqliteConfig + + *BaseDB +} + +// NewSqliteStore attempts to open a new sqlite database based on the passed +// config. +func NewSqliteStore(cfg *SqliteConfig, dbPath string) (*SqliteStore, error) { + // The set of pragma options are accepted using query options. For now + // we only want to ensure that foreign key constraints are properly + // enforced. + pragmaOptions := []pragmaOption{ + { + name: "foreign_keys", + value: "on", + }, + { + name: "journal_mode", + value: "WAL", + }, + { + name: "busy_timeout", + value: "5000", + }, + { + // With the WAL mode, this ensures that we also do an + // extra WAL sync after each transaction. The normal + // sync mode skips this and gives better performance, + // but risks durability. + name: "synchronous", + value: "full", + }, + { + // This is used to ensure proper durability for users + // running on Mac OS. It uses the correct fsync system + // call to ensure items are fully flushed to disk. + name: "fullfsync", + value: "true", + }, + { + name: "auto_vacuum", + value: "incremental", + }, + } + sqliteOptions := make(url.Values) + for _, option := range pragmaOptions { + sqliteOptions.Add( + sqliteOptionPrefix, + fmt.Sprintf("%v=%v", option.name, option.value), + ) + } + + // Construct the DSN which is just the database file name, appended + // with the series of pragma options as a query URL string. For more + // details on the formatting here, see the modernc.org/sqlite docs: + // https://pkg.go.dev/modernc.org/sqlite#Driver.Open. + dsn := fmt.Sprintf( + "%v?%v&%v", dbPath, sqliteOptions.Encode(), + sqliteTxLockImmediate, + ) + db, err := sql.Open("sqlite", dsn) + if err != nil { + return nil, err + } + + // Create the migration tracker table before starting migrations to + // ensure it can be used to track migration progress. Note that a + // corresponding SQLC migration also creates this table, making this + // operation a no-op in that context. Its purpose is to ensure + // compatibility with SQLC query generation. + migrationTrackerSQL := ` + CREATE TABLE IF NOT EXISTS migration_tracker ( + version INTEGER UNIQUE NOT NULL, + migration_time TIMESTAMP NOT NULL + );` + + _, err = db.Exec(migrationTrackerSQL) + if err != nil { + return nil, fmt.Errorf("error creating migration tracker: %w", + err) + } + + db.SetMaxOpenConns(defaultMaxConns) + db.SetMaxIdleConns(defaultMaxConns) + db.SetConnMaxLifetime(connIdleLifetime) + queries := sqlc.New(db) + + s := &SqliteStore{ + cfg: cfg, + BaseDB: &BaseDB{ + DB: db, + Queries: queries, + }, + } + + return s, nil +} + +// GetBaseDB returns the underlying BaseDB instance for the SQLite store. +// It is a trivial helper method to comply with the sqldb.DB interface. +func (s *SqliteStore) GetBaseDB() *BaseDB { + return s.BaseDB +} + +// ApplyAllMigrations applies both the SQLC and custom in-code migrations to the +// SQLite database. +func (s *SqliteStore) ApplyAllMigrations(ctx context.Context, + migrations []MigrationConfig) error { + + // Execute migrations unless configured to skip them. + if s.cfg.SkipMigrations { + return nil + } + + return ApplyMigrations(ctx, s.BaseDB, s, migrations) +} + +func errSqliteMigration(err error) error { + return fmt.Errorf("error creating sqlite migration: %w", err) +} + +// ExecuteMigrations runs migrations for the sqlite database, depending on the +// target given, either all migrations or up to a given version. +func (s *SqliteStore) ExecuteMigrations(target MigrationTarget) error { + driver, err := sqlite_migrate.WithInstance( + s.DB, &sqlite_migrate.Config{}, + ) + if err != nil { + return errSqliteMigration(err) + } + + // Populate the database with our set of schemas based on our embedded + // in-memory file system. + sqliteFS := newReplacerFS(sqlSchemas, sqliteSchemaReplacements) + return applyMigrations( + sqliteFS, driver, "../sqlc/migrations", "sqlite", target, + ) +} + +// GetSchemaVersion returns the current schema version of the SQLite database. +func (s *SqliteStore) GetSchemaVersion() (int, bool, error) { + driver, err := sqlite_migrate.WithInstance( + s.DB, &sqlite_migrate.Config{}, + ) + if err != nil { + return 0, false, errSqliteMigration(err) + } + + version, dirty, err := driver.Version() + if err != nil { + return 0, dirty, err + } + + return version, dirty, nil +} + +// SetSchemaVersion sets the schema version of the SQLite database. +// +// NOTE: This alters the internal database schema tracker. USE WITH CAUTION!!! +func (s *SqliteStore) SetSchemaVersion(version int, dirty bool) error { + driver, err := sqlite_migrate.WithInstance( + s.DB, &sqlite_migrate.Config{}, + ) + if err != nil { + return errSqliteMigration(err) + } + + return driver.SetVersion(version, dirty) +} + +// NewTestSqliteDB is a helper function that creates an SQLite database for +// testing. +// +// NOTE: This function differs from the one in sqldb/sqlite.go as that +// function does not expect any migrations to be passed in, and instead always +// applies the lnd specific migrations. +func NewTestSqliteDB(t testing.TB, migrations []MigrationConfig) *SqliteStore { + t.Helper() + + t.Logf("Creating new SQLite DB for testing") + + // TODO(roasbeef): if we pass :memory: for the file name, then we get + // an in mem version to speed up tests + dbFileName := filepath.Join(t.TempDir(), "tmp.db") + sqlDB, err := NewSqliteStore(&SqliteConfig{ + SkipMigrations: false, + }, dbFileName) + require.NoError(t, err) + + require.NoError(t, sqlDB.ApplyAllMigrations( + context.Background(), migrations), + ) + + t.Cleanup(func() { + require.NoError(t, sqlDB.DB.Close()) + }) + + return sqlDB +} + +// NewTestSqliteDBWithVersion is a helper function that creates an SQLite +// database for testing and migrates it to the given version. +func NewTestSqliteDBWithVersion(t *testing.T, version uint) *SqliteStore { + t.Helper() + + t.Logf("Creating new SQLite DB for testing, migrating to version %d", + version) + + // TODO(roasbeef): if we pass :memory: for the file name, then we get + // an in mem version to speed up tests + dbFileName := filepath.Join(t.TempDir(), "tmp.db") + sqlDB, err := NewSqliteStore(&SqliteConfig{ + SkipMigrations: true, + }, dbFileName) + require.NoError(t, err) + + err = sqlDB.ExecuteMigrations(TargetVersion(version)) + require.NoError(t, err) + + t.Cleanup(func() { + require.NoError(t, sqlDB.DB.Close()) + }) + + return sqlDB +} diff --git a/sqldb/v2/sqlite_test.go b/sqldb/v2/sqlite_test.go new file mode 100644 index 00000000000..4901dd0ffde --- /dev/null +++ b/sqldb/v2/sqlite_test.go @@ -0,0 +1,27 @@ +//go:build !test_db_postgres +// +build !test_db_postgres + +package sqldb + +import ( + "testing" +) + +// isSQLite is true if the build tag is set to test_db_sqlite. It is used in +// tests that compile for both SQLite and Postgres databases to determine +// which database implementation is being used. +// +// TODO(elle): once we've updated to using sqldbv2, we can remove this since +// then we will have access to the DatabaseType on the BaseDB struct at runtime. +const isSQLite = true + +// NewTestDB is a helper function that creates an SQLite database for testing. +func NewTestDB(t *testing.T, migrations []MigrationConfig) *SqliteStore { + return NewTestSqliteDB(t, migrations) +} + +// NewTestDBWithVersion is a helper function that creates an SQLite database +// for testing and migrates it to the given version. +func NewTestDBWithVersion(t *testing.T, version uint) *SqliteStore { + return NewTestSqliteDBWithVersion(t, version) +} diff --git a/sqldb/v2/sqlutils.go b/sqldb/v2/sqlutils.go new file mode 100644 index 00000000000..ce99eb682d5 --- /dev/null +++ b/sqldb/v2/sqlutils.go @@ -0,0 +1,92 @@ +package sqldb + +import ( + "database/sql" + "time" + + "golang.org/x/exp/constraints" +) + +// NoOpReset is a no-op function that can be used as a default +// reset function ExecTx calls. +var NoOpReset = func() {} + +// SQLInt16 turns a numerical integer type into the NullInt16 that sql/sqlc +// uses when an integer field can be permitted to be NULL. +// +// We use this constraints.Integer constraint here which maps to all signed and +// unsigned integer types. +func SQLInt16[T constraints.Integer](num T) sql.NullInt16 { + return sql.NullInt16{ + Int16: int16(num), + Valid: true, + } +} + +// SQLInt32 turns a numerical integer type into the NullInt32 that sql/sqlc +// uses when an integer field can be permitted to be NULL. +// +// We use this constraints.Integer constraint here which maps to all signed and +// unsigned integer types. +func SQLInt32[T constraints.Integer](num T) sql.NullInt32 { + return sql.NullInt32{ + Int32: int32(num), + Valid: true, + } +} + +// SQLInt64 turns a numerical integer type into the NullInt64 that sql/sqlc +// uses when an integer field can be permitted to be NULL. +// +// We use this constraints.Integer constraint here which maps to all signed and +// unsigned integer types. +func SQLInt64[T constraints.Integer](num T) sql.NullInt64 { + return sql.NullInt64{ + Int64: int64(num), + Valid: true, + } +} + +// SQLStr turns a string into the NullString that sql/sqlc uses when a string +// can be permitted to be NULL. +// +// NOTE: If the input string is empty, it returns a NullString with Valid set to +// false. If this is not the desired behavior, consider using SQLStrValid +// instead. +func SQLStr(s string) sql.NullString { + if s == "" { + return sql.NullString{} + } + + return sql.NullString{ + String: s, + Valid: true, + } +} + +// SQLStrValid turns a string into the NullString that sql/sqlc uses when a +// string can be permitted to be NULL. +// +// NOTE: Valid is always set to true, even if the input string is empty. +func SQLStrValid(s string) sql.NullString { + return sql.NullString{ + String: s, + Valid: true, + } +} + +// SQLTime turns a time.Time into the NullTime that sql/sqlc uses when a time +// can be permitted to be NULL. +func SQLTime(t time.Time) sql.NullTime { + return sql.NullTime{ + Time: t, + Valid: true, + } +} + +// ExtractSqlInt16 turns a NullInt16 into a numerical type. This can be useful +// when reading directly from the database, as this function handles extracting +// the inner value from the "option"-like struct. +func ExtractSqlInt16[T constraints.Integer](num sql.NullInt16) T { + return T(num.Int16) +} From 1fcf9a098890f30143986b880369afd61f76779c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Tigerstr=C3=B6m?= Date: Tue, 2 Sep 2025 01:57:31 +0200 Subject: [PATCH 3/9] sqldb/v2: introduce `MigrationStream` This commit introduces a new struct named `MigrationStream`, which defines a structure for migrations SQL migrations. The `MigrationStream` struct contains the SQL migrations which will be applied, as well as corresponding post-migration code migrations which will be executed afterwards. The struct also contains fields which define how the execution of the migrations are tracked. Importantly, it is also possible to define multiple different `MigrationStream`s which are executed, to for example define one `prod` and one `dev` migration stream. --- sqldb/v2/go.mod | 7 +++++++ sqldb/v2/go.sum | 31 ++++++++++++++++--------------- sqldb/v2/migrations.go | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 15 deletions(-) diff --git a/sqldb/v2/go.mod b/sqldb/v2/go.mod index 2abd84f2d77..05108ad68fb 100644 --- a/sqldb/v2/go.mod +++ b/sqldb/v2/go.mod @@ -22,6 +22,8 @@ require ( github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/containerd/continuity v0.3.0 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/docker/cli v20.10.17+incompatible // indirect github.com/docker/docker v28.3.3+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect @@ -53,6 +55,7 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect + go.uber.org/atomic v1.7.0 // indirect golang.org/x/crypto v0.36.0 // indirect golang.org/x/sync v0.15.0 // indirect golang.org/x/sys v0.34.0 // indirect @@ -64,4 +67,8 @@ require ( modernc.org/memory v1.11.0 // indirect ) +// We are using a fork of the migration library with custom functionality that +// did not yet make it into the upstream repository. +replace github.com/golang-migrate/migrate/v4 => github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2 + go 1.23.12 diff --git a/sqldb/v2/go.sum b/sqldb/v2/go.sum index 60b542e58d2..8b0f3340f5a 100644 --- a/sqldb/v2/go.sum +++ b/sqldb/v2/go.sum @@ -28,8 +28,8 @@ github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxG github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dhui/dktest v0.4.6 h1:+DPKyScKSEp3VLtbMDHcUq6V5Lm5zfZZVb0Sk7Ahom4= -github.com/dhui/dktest v0.4.6/go.mod h1:JHTSYDtKkvFNFHJKqCzVzqXecyv+tKt8EzceOmQOgbU= +github.com/dhui/dktest v0.4.5 h1:uUfYBIVREmj/Rw6MvgmqNAYzTiKOHJak+enB5Di73MM= +github.com/dhui/dktest v0.4.5/go.mod h1:tmcyeHDKagvlDrz7gDKq4UAJOLIfVZYkfD5OnHDwcCo= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M= @@ -46,8 +46,8 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= @@ -56,8 +56,6 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-migrate/migrate/v4 v4.19.0 h1:RcjOnCGz3Or6HQYEJ/EEVLfWnmw9KnoigPSjzhCuaSE= -github.com/golang-migrate/migrate/v4 v4.19.0/go.mod h1:9dyEcu+hO+G9hPSw8AIg50yg622pXJsoHItQnDGZkI0= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -103,10 +101,13 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2 h1:eFjp1dIB2BhhQp/THKrjLdlYuPugO9UU4kDqu91OX/Q= +github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2/go.mod h1:99BKpIi6ruaaXRM1A77eqZ+FWPQ3cfRa+ZVy5bmWMaY= github.com/lightningnetwork/lnd/sqldb v1.0.10 h1:ZLV7TGwjnKupVfCd+DJ43MAc9BKVSFCnvhpSPGKdN3M= github.com/lightningnetwork/lnd/sqldb v1.0.10/go.mod h1:c/vWoQfcxu6FAfHzGajkIQi7CEIeIZFhhH4DYh1BJpc= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -166,16 +167,16 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= -go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= -go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= -go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= -go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= -go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= -go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= diff --git a/sqldb/v2/migrations.go b/sqldb/v2/migrations.go index bf4b39ff0a7..736d27259a0 100644 --- a/sqldb/v2/migrations.go +++ b/sqldb/v2/migrations.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "database/sql" + "embed" "errors" "fmt" "io" @@ -55,6 +56,43 @@ type MigrationConfig struct { MigrationFn func(tx *sqlc.Queries) error } +// MigrationStream encapsulates all necessary information to manage and apply +// a series of SQL migrations, and corresponding code migrations, to a database. +type MigrationStream struct { + // MigrateTableName is the name of the table used by golang-migrate to + // track the current schema version. + MigrateTableName string + + // Schemas is the embedded file system containing the SQL migration + // files. + Schemas embed.FS + + // SQLFileDirectory is the directory containing the SQL migration files. + SQLFileDirectory string + + // MakePostMigrationChecks is a function that returns a map of + // code migrations that will be executed after the corresponding SQL + // migration has been applied. The key of the map is the migration + // version, and the value is the function to be executed after the + // SQL migration. + MakePostMigrationChecks func( + *BaseDB) (map[uint]migrate.PostStepCallback, error) + + // LatestMigrationVersion is the latest migration version of the + // database. This is used to implement downgrade protection for the + // daemon. + LatestMigrationVersion uint + + // Configs defines a list of migrations to be applied to the + // database. Each migration is assigned a version number, determining + // its execution order. + // The schema version, tracked by golang-migrate, ensures migrations are + // applied to the correct schema. For migrations involving only schema + // changes, the migration function can be left nil. For custom + // migrations an implemented migration function is required. + Configs []MigrationConfig +} + // MigrationTarget is a functional option that can be passed to applyMigrations // to specify a target version to migrate to. type MigrationTarget func(mig *migrate.Migrate) error From 9c1df3d86bd9d6e21dc6f932b29efd4489a65c00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Tigerstr=C3=B6m?= Date: Tue, 2 Sep 2025 02:30:09 +0200 Subject: [PATCH 4/9] sqldb/v2: Use `MigrationStream` for migrations This commit updates the `sqldb/v2` package to utilize the new `MigrationStream` type for executing migrations, instead of passing `[]MigrationConfig`'s directly. --- sqldb/v2/config.go | 7 +- sqldb/v2/go.mod | 1 + sqldb/v2/go.sum | 2 + sqldb/v2/interfaces.go | 7 +- sqldb/v2/migrations.go | 267 ++++++++++++++--------------------- sqldb/v2/no_sqlite.go | 2 +- sqldb/v2/postgres.go | 48 ++++--- sqldb/v2/postgres_fixture.go | 24 ++-- sqldb/v2/postgres_test.go | 10 +- sqldb/v2/schemas.go | 8 -- sqldb/v2/sqlite.go | 140 ++++++++++++++---- sqldb/v2/sqlite_test.go | 11 +- 12 files changed, 281 insertions(+), 246 deletions(-) delete mode 100644 sqldb/v2/schemas.go diff --git a/sqldb/v2/config.go b/sqldb/v2/config.go index 34de293c185..e893152dbdc 100644 --- a/sqldb/v2/config.go +++ b/sqldb/v2/config.go @@ -28,7 +28,12 @@ type SqliteConfig struct { MaxConnections int `long:"maxconnections" description:"The maximum number of open connections to the database. Set to zero for unlimited."` PragmaOptions []string `long:"pragmaoptions" description:"A list of pragma options to set on a database connection. For example, 'auto_vacuum=incremental'. Note that the flag must be specified multiple times if multiple options are to be set."` SkipMigrations bool `long:"skipmigrations" description:"Skip applying migrations on startup."` - QueryConfig `group:"query" namespace:"query"` + + // SkipMigrationDbBackup if true, then a backup of the database will not + // be created before applying migrations. + SkipMigrationDbBackup bool `long:"skipmigrationdbbackup" description:"Skip creating a backup of the database before applying migrations."` + + QueryConfig `group:"query" namespace:"query"` } // Validate checks that the SqliteConfig values are valid. diff --git a/sqldb/v2/go.mod b/sqldb/v2/go.mod index 05108ad68fb..a06c202a2f5 100644 --- a/sqldb/v2/go.mod +++ b/sqldb/v2/go.mod @@ -7,6 +7,7 @@ require ( github.com/jackc/pgconn v1.14.3 github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 github.com/jackc/pgx/v5 v5.5.4 + github.com/lightningnetwork/lnd/fn/v2 v2.0.8 github.com/lightningnetwork/lnd/sqldb v1.0.10 github.com/ory/dockertest/v3 v3.10.0 github.com/pmezard/go-difflib v1.0.0 diff --git a/sqldb/v2/go.sum b/sqldb/v2/go.sum index 8b0f3340f5a..c44b6f3d98c 100644 --- a/sqldb/v2/go.sum +++ b/sqldb/v2/go.sum @@ -108,6 +108,8 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2 h1:eFjp1dIB2BhhQp/THKrjLdlYuPugO9UU4kDqu91OX/Q= github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2/go.mod h1:99BKpIi6ruaaXRM1A77eqZ+FWPQ3cfRa+ZVy5bmWMaY= +github.com/lightningnetwork/lnd/fn/v2 v2.0.8 h1:r2SLz7gZYQPVc3IZhU82M66guz3Zk2oY+Rlj9QN5S3g= +github.com/lightningnetwork/lnd/fn/v2 v2.0.8/go.mod h1:TOzwrhjB/Azw1V7aa8t21ufcQmdsQOQMDtxVOQWNl8s= github.com/lightningnetwork/lnd/sqldb v1.0.10 h1:ZLV7TGwjnKupVfCd+DJ43MAc9BKVSFCnvhpSPGKdN3M= github.com/lightningnetwork/lnd/sqldb v1.0.10/go.mod h1:c/vWoQfcxu6FAfHzGajkIQi7CEIeIZFhhH4DYh1BJpc= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= diff --git a/sqldb/v2/interfaces.go b/sqldb/v2/interfaces.go index e80112378cb..b9cf552ae32 100644 --- a/sqldb/v2/interfaces.go +++ b/sqldb/v2/interfaces.go @@ -388,13 +388,10 @@ func (t *TransactionExecutor[Q]) ExecTx(ctx context.Context, // DB is an interface that represents a generic SQL database. It provides // methods to apply migrations and access the underlying database connection. type DB interface { + MigrationExecutor + // GetBaseDB returns the underlying BaseDB instance. GetBaseDB() *BaseDB - - // ApplyAllMigrations applies all migrations to the database including - // both sqlc and custom in-code migrations. - ApplyAllMigrations(ctx context.Context, - customMigrations []MigrationConfig) error } // BaseDB is the base database struct that each implementation can embed to diff --git a/sqldb/v2/migrations.go b/sqldb/v2/migrations.go index 736d27259a0..b6e33808043 100644 --- a/sqldb/v2/migrations.go +++ b/sqldb/v2/migrations.go @@ -2,8 +2,6 @@ package sqldb import ( "bytes" - "context" - "database/sql" "embed" "errors" "fmt" @@ -12,14 +10,13 @@ import ( "net/http" "reflect" "strings" - "time" "github.com/btcsuite/btclog/v2" "github.com/davecgh/go-spew/spew" "github.com/golang-migrate/migrate/v4" "github.com/golang-migrate/migrate/v4/database" "github.com/golang-migrate/migrate/v4/source/httpfs" - "github.com/lightningnetwork/lnd/sqldb/sqlc" + "github.com/lightningnetwork/lnd/fn/v2" "github.com/pmezard/go-difflib/difflib" ) @@ -48,12 +45,6 @@ type MigrationConfig struct { // SchemaVersion represents the schema version tracked by golang-migrate // at which the migration is applied. SchemaVersion int - - // MigrationFn is the function executed for custom migrations at the - // specified version. It is used to handle migrations that cannot be - // performed through SQL alone. If set to nil, no custom migration is - // applied. - MigrationFn func(tx *sqlc.Queries) error } // MigrationStream encapsulates all necessary information to manage and apply @@ -94,8 +85,12 @@ type MigrationStream struct { } // MigrationTarget is a functional option that can be passed to applyMigrations -// to specify a target version to migrate to. -type MigrationTarget func(mig *migrate.Migrate) error +// to specify a target version to migrate to. `currentDbVersion` is the current +// (migration) version of the database, or None if unknown. +// `maxMigrationVersion` is the maximum migration version known to the driver, +// or None if unknown. +type MigrationTarget func(mig *migrate.Migrate, + currentDbVersion int, maxMigrationVersion uint) error // MigrationExecutor is an interface that abstracts the migration functionality. type MigrationExecutor interface { @@ -105,34 +100,78 @@ type MigrationExecutor interface { // Developers must ensure that migrations are defined in the correct // order. Migration details are stored in the global variable // migrationConfig. - ExecuteMigrations(target MigrationTarget) error + ExecuteMigrations(target MigrationTarget, stream MigrationStream) error - // GetSchemaVersion returns the current schema version of the database. - GetSchemaVersion() (int, bool, error) + // DefaultTarget returns the default migration target. + DefaultTarget() MigrationTarget - // SetSchemaVersion sets the schema version of the database. - // - // NOTE: This alters the internal database schema tracker. USE WITH - // CAUTION!!! - SetSchemaVersion(version int, dirty bool) error + // SkipMigrations indicates if the SQL and corresponding code migrations + // will be skipped. + SkipMigrations() bool } var ( // TargetLatest is a MigrationTarget that migrates to the latest // version available. - TargetLatest = func(mig *migrate.Migrate) error { + TargetLatest = func(mig *migrate.Migrate, _ int, _ uint) error { return mig.Up() } // TargetVersion is a MigrationTarget that migrates to the given // version. TargetVersion = func(version uint) MigrationTarget { - return func(mig *migrate.Migrate) error { + return func(mig *migrate.Migrate, _ int, _ uint) error { return mig.Migrate(version) } } + + // ErrMigrationDowngrade is returned when a database downgrade is + // detected. + ErrMigrationDowngrade = errors.New("database downgrade detected") ) +// migrationOption is a functional option that can be passed to migrate related +// methods to modify their behavior. +type migrateOptions struct { + latestVersion fn.Option[uint] + postStepCallbacks map[uint]migrate.PostStepCallback +} + +// defaultMigrateOptions returns a new migrateOptions instance with default +// settings. +func defaultMigrateOptions() *migrateOptions { + return &migrateOptions{ + postStepCallbacks: make(map[uint]migrate.PostStepCallback), + } +} + +// MigrateOpt is a functional option that can be passed to migrate related +// methods to modify behavior. +type MigrateOpt func(*migrateOptions) + +// WithLatestVersion allows callers to override the default latest version +// setting. +func WithLatestVersion(version uint) MigrateOpt { + return func(o *migrateOptions) { + o.latestVersion = fn.Some(version) + } +} + +// WithPostStepCallbacks is an option that can be used to set a map of +// PostStepCallback functions that can be used to execute a Golang based +// migration step after a SQL based migration step has been executed. The key is +// the migration version and the value is the callback function that should be +// run _after_ the step was executed (but before the version is marked as +// cleanly executed). An error returned from the callback will cause the +// migration to fail and the step to be marked as dirty. +func WithPostStepCallbacks( + postStepCallbacks map[uint]migrate.PostStepCallback) MigrateOpt { + + return func(o *migrateOptions) { + o.postStepCallbacks = postStepCallbacks + } +} + // migrationLogger is a logger that wraps the passed btclog.Logger so it can be // used to log migrations. type migrationLogger struct { @@ -171,7 +210,8 @@ func (m *migrationLogger) Verbose() bool { // system under the given path, using the passed database driver and database // name. func applyMigrations(fs fs.FS, driver database.Driver, path, - dbName string, targetVersion MigrationTarget) error { + dbName string, targetVersion MigrationTarget, + opts *migrateOptions) error { // With the migrate instance open, we'll create a new migration source // using the embedded file system stored in sqlSchemas. The library @@ -187,30 +227,54 @@ func applyMigrations(fs fs.FS, driver database.Driver, path, // above. sqlMigrate, err := migrate.NewWithInstance( "migrations", migrateFileServer, dbName, driver, + migrate.WithPostStepCallbacks(opts.postStepCallbacks), ) if err != nil { return err } - migrationVersion, _, err := sqlMigrate.Version() - if err != nil && !errors.Is(err, migrate.ErrNilVersion) { - log.Errorf("Unable to determine current migration version: %v", - err) + migrationVersion, _, _ := sqlMigrate.Version() - return err + // As the down migrations may end up *dropping* data, we want to + // prevent that without explicit accounting. + latestVersion, err := opts.latestVersion.UnwrapOrErr( + fmt.Errorf("latest version not set"), + ) + if err != nil { + return fmt.Errorf("unable to get latest version: %w", err) + } + if migrationVersion > latestVersion { + return fmt.Errorf("%w: database version is newer than the "+ + "latest migration version, preventing downgrade: "+ + "db_version=%v, latest_migration_version=%v", + ErrMigrationDowngrade, migrationVersion, latestVersion) } - log.Infof("Applying migrations from version=%v", migrationVersion) + // Report the current version of the database before the migration. + currentDbVersion, _, err := driver.Version() + if err != nil { + return fmt.Errorf("unable to get current db version: %w", err) + } + log.Infof("Attempting to apply migration(s) "+ + "(current_db_version=%v, latest_migration_version=%v)", + currentDbVersion, latestVersion) // Apply our local logger to the migration instance. sqlMigrate.Log = &migrationLogger{log} // Execute the migration based on the target given. - err = targetVersion(sqlMigrate) + err = targetVersion(sqlMigrate, currentDbVersion, latestVersion) if err != nil && !errors.Is(err, migrate.ErrNoChange) { return err } + // Report the current version of the database after the migration. + currentDbVersion, _, err = driver.Version() + if err != nil { + return fmt.Errorf("unable to get current db version: %w", err) + } + log.Infof("Database version after migration: %v", currentDbVersion) + return nil } @@ -316,143 +380,22 @@ func (t *replacerFile) Close() error { return nil } -// ApplyMigrations applies the provided migrations to the database in sequence. -// It ensures migrations are executed in the correct order, applying both custom -// migration functions and SQL migrations as needed. -func ApplyMigrations(ctx context.Context, db *BaseDB, - migrator MigrationExecutor, migrations []MigrationConfig) error { - - // Ensure that the migrations are sorted by version. - for i := 0; i < len(migrations); i++ { - if migrations[i].Version != i+1 { - return fmt.Errorf("migration version %d is out of "+ - "order. Expected %d", migrations[i].Version, - i+1) - } - } - // Construct a transaction executor to apply custom migrations. - executor := NewTransactionExecutor(db, func(tx *sql.Tx) *sqlc.Queries { - return db.WithTx(tx) - }) - - currentVersion := 0 - version, err := db.GetDatabaseVersion(ctx) - if !errors.Is(err, sql.ErrNoRows) { - if err != nil { - return fmt.Errorf("error getting current database "+ - "version: %w", err) - } - - currentVersion = int(version) - } else { - // Since we don't have a version tracked by our own table yet, - // we'll use the schema version reported by sqlc to determine - // the current version. - // - // NOTE: This is safe because the first in-code migration was - // introduced in version 7. This is only possible if the user - // has a schema version <= 4. - var dirty bool - currentVersion, dirty, err = migrator.GetSchemaVersion() - if err != nil { - return err - } - - log.Infof("No database version found, using schema version %d "+ - "(dirty=%v) as base version", currentVersion, dirty) - } - - // Due to an a migration issue in v0.19.0-rc1 we may be at version 2 and - // have a dirty schema due to failing migration 3. If this is indeed the - // case, we need to reset the dirty flag to be able to apply the fixed - // migration. - // NOTE: this could be removed as soon as we drop v0.19.0-beta. - if version == 2 { - schemaVersion, dirty, err := migrator.GetSchemaVersion() - if err != nil { - return err - } - - if schemaVersion == 3 && dirty { - log.Warnf("Schema version %d is dirty. This is "+ - "likely a consequence of a failed migration "+ - "in v0.19.0-rc1. Attempting to recover by "+ - "resetting the dirty flag", schemaVersion) +// ApplyAllMigrations applies both the SQLC and custom in-code migrations to the +// SQLite database. +func ApplyAllMigrations(executor MigrationExecutor, + streams []MigrationStream) error { - err = migrator.SetSchemaVersion(4, false) - if err != nil { - return err - } - } + // Execute migrations unless configured to skip them. + if executor.SkipMigrations() { + return nil } - for _, migration := range migrations { - if migration.Version <= currentVersion { - log.Infof("Skipping migration '%s' (version %d) as it "+ - "has already been applied", migration.Name, - migration.Version) - - continue - } - - log.Infof("Migrating SQL schema to version %d", - migration.SchemaVersion) - - // Execute SQL schema migrations up to the target version. - err = migrator.ExecuteMigrations( - TargetVersion(uint(migration.SchemaVersion)), + for _, stream := range streams { + err := executor.ExecuteMigrations( + executor.DefaultTarget(), stream, ) if err != nil { - return fmt.Errorf("error executing schema migrations "+ - "to target version %d: %w", - migration.SchemaVersion, err) - } - - opts := WriteTxOpt() - - // Run the custom migration as a transaction to ensure - // atomicity. If successful, mark the migration as complete in - // the migration tracker table. - err = executor.ExecTx(ctx, opts, func(tx *sqlc.Queries) error { - // Apply the migration function if one is provided. - if migration.MigrationFn != nil { - log.Infof("Applying custom migration '%v' "+ - "(version %d) to schema version %d", - migration.Name, migration.Version, - migration.SchemaVersion) - - err = migration.MigrationFn(tx) - if err != nil { - return fmt.Errorf("error applying "+ - "migration '%v' (version %d) "+ - "to schema version %d: %w", - migration.Name, - migration.Version, - migration.SchemaVersion, err) - } - - log.Infof("Migration '%v' (version %d) "+ - "applied ", migration.Name, - migration.Version) - } - - // Mark the migration as complete by adding the version - // to the migration tracker table along with the current - // timestamp. - err = tx.SetMigration(ctx, sqlc.SetMigrationParams{ - Version: int32(migration.Version), - MigrationTime: time.Now(), - }) - if err != nil { - return fmt.Errorf("error setting migration "+ - "version %d: %w", migration.Version, - err) - } - - return nil - }, func() {}) - if err != nil { - return err + return fmt.Errorf("error applying migrations: %w", err) } } diff --git a/sqldb/v2/no_sqlite.go b/sqldb/v2/no_sqlite.go index ad0cae6e4f8..7ff33f7e46a 100644 --- a/sqldb/v2/no_sqlite.go +++ b/sqldb/v2/no_sqlite.go @@ -34,7 +34,7 @@ func (s *SqliteStore) GetBaseDB() *BaseDB { // ApplyAllMigrations applies both the SQLC and custom in-code migrations to // the SQLite database. func (s *SqliteStore) ApplyAllMigrations(context.Context, - []MigrationConfig) error { + []MigrationStream) error { return fmt.Errorf("SQLite backend not supported in WebAssembly") } diff --git a/sqldb/v2/postgres.go b/sqldb/v2/postgres.go index bc745735bf2..5b16d30472a 100644 --- a/sqldb/v2/postgres.go +++ b/sqldb/v2/postgres.go @@ -1,7 +1,6 @@ package sqldb import ( - "context" "database/sql" "fmt" "net/url" @@ -12,6 +11,7 @@ import ( pgx_migrate "github.com/golang-migrate/migrate/v4/database/pgx/v5" _ "github.com/golang-migrate/migrate/v4/source/file" // Read migrations from files. // nolint:ll _ "github.com/jackc/pgx/v5" + "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/sqldb/sqlc" ) @@ -145,41 +145,45 @@ func (s *PostgresStore) GetBaseDB() *BaseDB { return s.BaseDB } -// ApplyAllMigrations applies both the SQLC and custom in-code migrations to the -// Postgres database. -func (s *PostgresStore) ApplyAllMigrations(ctx context.Context, - migrations []MigrationConfig) error { - - // Execute migrations unless configured to skip them. - if s.cfg.SkipMigrations { - return nil - } - - return ApplyMigrations(ctx, s.BaseDB, s, migrations) -} - func errPostgresMigration(err error) error { return fmt.Errorf("error creating postgres migration: %w", err) } // ExecuteMigrations runs migrations for the Postgres database, depending on the // target given, either all migrations or up to a given version. -func (s *PostgresStore) ExecuteMigrations(target MigrationTarget) error { +func (s *PostgresStore) ExecuteMigrations(target MigrationTarget, + stream MigrationStream) error { + dbName, err := getDatabaseNameFromDSN(s.cfg.Dsn) if err != nil { return err } - driver, err := pgx_migrate.WithInstance(s.DB, &pgx_migrate.Config{}) + driver, err := pgx_migrate.WithInstance(s.DB, &pgx_migrate.Config{ + MigrationsTable: stream.MigrateTableName, + }) if err != nil { return errPostgresMigration(err) } + opts := &migrateOptions{ + latestVersion: fn.Some(stream.LatestMigrationVersion), + } + + if stream.MakePostMigrationChecks != nil { + postMigSteps, err := stream.MakePostMigrationChecks(s.BaseDB) + if err != nil { + return errPostgresMigration(err) + } + opts.postStepCallbacks = postMigSteps + } + // Populate the database with our set of schemas based on our embedded // in-memory file system. - postgresFS := newReplacerFS(sqlSchemas, postgresSchemaReplacements) + postgresFS := newReplacerFS(stream.Schemas, postgresSchemaReplacements) return applyMigrations( - postgresFS, driver, "../sqlc/migrations", dbName, target, + postgresFS, driver, stream.SQLFileDirectory, dbName, target, + opts, ) } @@ -210,3 +214,11 @@ func (s *PostgresStore) SetSchemaVersion(version int, dirty bool) error { return driver.SetVersion(version, dirty) } + +func (s *PostgresStore) DefaultTarget() MigrationTarget { + return TargetLatest +} + +func (s *PostgresStore) SkipMigrations() bool { + return s.cfg.SkipMigrations +} diff --git a/sqldb/v2/postgres_fixture.go b/sqldb/v2/postgres_fixture.go index d6d5c271e5e..562f91fdf27 100644 --- a/sqldb/v2/postgres_fixture.go +++ b/sqldb/v2/postgres_fixture.go @@ -124,8 +124,8 @@ func (f *TestPgFixture) TearDown(t testing.TB) { require.NoError(t, err, "Could not purge resource") } -// randomDBName generates a random database name. -func randomDBName(t testing.TB) string { +// RandomDBName generates a random database name. +func RandomDBName(t testing.TB) string { randBytes := make([]byte, 8) _, err := rand.Read(randBytes) require.NoError(t, err) @@ -135,16 +135,12 @@ func randomDBName(t testing.TB) string { // NewTestPostgresDB is a helper function that creates a Postgres database for // testing using the given fixture. -// -// NOTE: This function differs from the one in sqldb/postgres_fixture.go as that -// function does not expect any migrations to be passed in, and instead always -// applies the lnd specific migrations. func NewTestPostgresDB(t testing.TB, fixture *TestPgFixture, - migrations []MigrationConfig) *PostgresStore { + streams []MigrationStream) *PostgresStore { t.Helper() - dbName := randomDBName(t) + dbName := RandomDBName(t) t.Logf("Creating new Postgres DB '%s' for testing", dbName) @@ -157,24 +153,22 @@ func NewTestPostgresDB(t testing.TB, fixture *TestPgFixture, store, err := NewPostgresStore(cfg) require.NoError(t, err) - require.NoError(t, store.ApplyAllMigrations( - context.Background(), migrations), - ) + require.NoError(t, ApplyAllMigrations(store, streams)) return store } // NewTestPostgresDBWithVersion is a helper function that creates a Postgres // database for testing and migrates it to the given version. -func NewTestPostgresDBWithVersion(t *testing.T, fixture *TestPgFixture, - version uint) *PostgresStore { +func NewTestPostgresDBWithVersion(t testing.TB, fixture *TestPgFixture, + stream MigrationStream, version uint) *PostgresStore { t.Helper() t.Logf("Creating new Postgres DB for testing, migrating to version %d", version) - dbName := randomDBName(t) + dbName := RandomDBName(t) _, err := fixture.db.ExecContext( context.Background(), "CREATE DATABASE "+dbName, ) @@ -185,7 +179,7 @@ func NewTestPostgresDBWithVersion(t *testing.T, fixture *TestPgFixture, store, err := NewPostgresStore(storeCfg) require.NoError(t, err) - err = store.ExecuteMigrations(TargetVersion(version)) + err = store.ExecuteMigrations(TargetVersion(version), stream) require.NoError(t, err) return store diff --git a/sqldb/v2/postgres_test.go b/sqldb/v2/postgres_test.go index cabf8cb2b7f..32530603b4c 100644 --- a/sqldb/v2/postgres_test.go +++ b/sqldb/v2/postgres_test.go @@ -16,22 +16,24 @@ import ( const isSQLite = false // NewTestDB is a helper function that creates a Postgres database for testing. -func NewTestDB(t *testing.T, migrations []MigrationConfig) *PostgresStore { +func NewTestDB(t *testing.T, streams []MigrationStream) *PostgresStore { pgFixture := NewTestPgFixture(t, DefaultPostgresFixtureLifetime) t.Cleanup(func() { pgFixture.TearDown(t) }) - return NewTestPostgresDB(t, pgFixture, migrations) + return NewTestPostgresDB(t, pgFixture, streams) } // NewTestDBWithVersion is a helper function that creates a Postgres database // for testing and migrates it to the given version. -func NewTestDBWithVersion(t *testing.T, version uint) *PostgresStore { +func NewTestDBWithVersion(t *testing.T, version uint, + stream MigrationStream) *PostgresStore { + pgFixture := NewTestPgFixture(t, DefaultPostgresFixtureLifetime) t.Cleanup(func() { pgFixture.TearDown(t) }) - return NewTestPostgresDBWithVersion(t, pgFixture, version) + return NewTestPostgresDBWithVersion(t, pgFixture, stream, version) } diff --git a/sqldb/v2/schemas.go b/sqldb/v2/schemas.go deleted file mode 100644 index ec81fa80e98..00000000000 --- a/sqldb/v2/schemas.go +++ /dev/null @@ -1,8 +0,0 @@ -package sqldb - -import ( - "embed" -) - -//go:embed ../sqlc/migrations/*.up.sql -var sqlSchemas embed.FS diff --git a/sqldb/v2/sqlite.go b/sqldb/v2/sqlite.go index e4b8be99073..cfb20f7050e 100644 --- a/sqldb/v2/sqlite.go +++ b/sqldb/v2/sqlite.go @@ -3,12 +3,14 @@ package sqldb import ( - "context" "database/sql" "fmt" + "github.com/golang-migrate/migrate/v4" + "github.com/lightningnetwork/lnd/fn/v2" "net/url" "path/filepath" "testing" + "time" sqlite_migrate "github.com/golang-migrate/migrate/v4/database/sqlite" "github.com/lightningnetwork/lnd/sqldb/sqlc" @@ -48,7 +50,9 @@ type pragmaOption struct { // SqliteStore is a database store implementation that uses a sqlite backend. type SqliteStore struct { - cfg *SqliteConfig + DbPath string + + Config *SqliteConfig *BaseDB } @@ -136,7 +140,8 @@ func NewSqliteStore(cfg *SqliteConfig, dbPath string) (*SqliteStore, error) { queries := sqlc.New(db) s := &SqliteStore{ - cfg: cfg, + Config: cfg, + DbPath: dbPath, BaseDB: &BaseDB{ DB: db, Queries: queries, @@ -152,41 +157,126 @@ func (s *SqliteStore) GetBaseDB() *BaseDB { return s.BaseDB } -// ApplyAllMigrations applies both the SQLC and custom in-code migrations to the -// SQLite database. -func (s *SqliteStore) ApplyAllMigrations(ctx context.Context, - migrations []MigrationConfig) error { +func errSqliteMigration(err error) error { + return fmt.Errorf("error creating sqlite migration: %w", err) +} - // Execute migrations unless configured to skip them. - if s.cfg.SkipMigrations { - return nil +// backupSqliteDatabase creates a backup of the given SQLite database. +func backupSqliteDatabase(srcDB *sql.DB, dbFullFilePath string) error { + if srcDB == nil { + return fmt.Errorf("backup source database is nil") + } + + // Create a database backup file full path from the given source + // database full file path. + // + // Get the current time and format it as a Unix timestamp in + // nanoseconds. + timestamp := time.Now().UnixNano() + + // Add the timestamp to the backup name. + backupFullFilePath := fmt.Sprintf( + "%s.%d.backup", dbFullFilePath, timestamp, + ) + + log.Infof("Creating backup of database file: %v -> %v", + dbFullFilePath, backupFullFilePath) + + // Create the database backup. + vacuumIntoQuery := "VACUUM INTO ?;" + stmt, err := srcDB.Prepare(vacuumIntoQuery) + if err != nil { + return err } + defer stmt.Close() - return ApplyMigrations(ctx, s.BaseDB, s, migrations) + _, err = stmt.Exec(backupFullFilePath) + if err != nil { + return err + } + + return nil } -func errSqliteMigration(err error) error { - return fmt.Errorf("error creating sqlite migration: %w", err) +// backupAndMigrate is a helper function that creates a database backup before +// initiating the migration, and then migrates the database to the latest +// version. +func (s *SqliteStore) backupAndMigrate(mig *migrate.Migrate, + currentDbVersion int, maxMigrationVersion uint) error { + + // Determine if a database migration is necessary given the current + // database version and the maximum migration version. + versionUpgradePending := currentDbVersion < int(maxMigrationVersion) + if !versionUpgradePending { + log.Infof("Current database version is up-to-date, skipping "+ + "migration attempt and backup creation "+ + "(current_db_version=%v, max_migration_version=%v)", + currentDbVersion, maxMigrationVersion) + return nil + } + + // At this point, we know that a database migration is necessary. + // Create a backup of the database before starting the migration. + if !s.Config.SkipMigrationDbBackup { + log.Infof("Creating database backup (before applying " + + "migration(s))") + + err := backupSqliteDatabase(s.DB, s.DbPath) + if err != nil { + return err + } + } else { + log.Infof("Skipping database backup creation before applying " + + "migration(s)") + } + + log.Infof("Applying migrations to database") + return mig.Up() } // ExecuteMigrations runs migrations for the sqlite database, depending on the // target given, either all migrations or up to a given version. -func (s *SqliteStore) ExecuteMigrations(target MigrationTarget) error { +func (s *SqliteStore) ExecuteMigrations(target MigrationTarget, + stream MigrationStream) error { + driver, err := sqlite_migrate.WithInstance( - s.DB, &sqlite_migrate.Config{}, + s.DB, &sqlite_migrate.Config{ + MigrationsTable: stream.MigrateTableName, + }, ) if err != nil { return errSqliteMigration(err) } + opts := &migrateOptions{ + latestVersion: fn.Some(stream.LatestMigrationVersion), + } + + if stream.MakePostMigrationChecks != nil { + postMigSteps, err := stream.MakePostMigrationChecks(s.BaseDB) + if err != nil { + return errPostgresMigration(err) + } + opts.postStepCallbacks = postMigSteps + } + // Populate the database with our set of schemas based on our embedded // in-memory file system. - sqliteFS := newReplacerFS(sqlSchemas, sqliteSchemaReplacements) + sqliteFS := newReplacerFS(stream.Schemas, sqliteSchemaReplacements) return applyMigrations( - sqliteFS, driver, "../sqlc/migrations", "sqlite", target, + sqliteFS, driver, stream.SQLFileDirectory, "sqlite", target, + opts, ) } +func (s *SqliteStore) DefaultTarget() MigrationTarget { + return s.backupAndMigrate +} + +func (s *SqliteStore) SkipMigrations() bool { + return s.Config.SkipMigrations +} + // GetSchemaVersion returns the current schema version of the SQLite database. func (s *SqliteStore) GetSchemaVersion() (int, bool, error) { driver, err := sqlite_migrate.WithInstance( @@ -220,11 +310,7 @@ func (s *SqliteStore) SetSchemaVersion(version int, dirty bool) error { // NewTestSqliteDB is a helper function that creates an SQLite database for // testing. -// -// NOTE: This function differs from the one in sqldb/sqlite.go as that -// function does not expect any migrations to be passed in, and instead always -// applies the lnd specific migrations. -func NewTestSqliteDB(t testing.TB, migrations []MigrationConfig) *SqliteStore { +func NewTestSqliteDB(t testing.TB, streams []MigrationStream) *SqliteStore { t.Helper() t.Logf("Creating new SQLite DB for testing") @@ -237,9 +323,7 @@ func NewTestSqliteDB(t testing.TB, migrations []MigrationConfig) *SqliteStore { }, dbFileName) require.NoError(t, err) - require.NoError(t, sqlDB.ApplyAllMigrations( - context.Background(), migrations), - ) + require.NoError(t, ApplyAllMigrations(sqlDB, streams)) t.Cleanup(func() { require.NoError(t, sqlDB.DB.Close()) @@ -250,7 +334,9 @@ func NewTestSqliteDB(t testing.TB, migrations []MigrationConfig) *SqliteStore { // NewTestSqliteDBWithVersion is a helper function that creates an SQLite // database for testing and migrates it to the given version. -func NewTestSqliteDBWithVersion(t *testing.T, version uint) *SqliteStore { +func NewTestSqliteDBWithVersion(t *testing.T, stream MigrationStream, + version uint) *SqliteStore { + t.Helper() t.Logf("Creating new SQLite DB for testing, migrating to version %d", @@ -264,7 +350,7 @@ func NewTestSqliteDBWithVersion(t *testing.T, version uint) *SqliteStore { }, dbFileName) require.NoError(t, err) - err = sqlDB.ExecuteMigrations(TargetVersion(version)) + err = sqlDB.ExecuteMigrations(TargetVersion(version), stream) require.NoError(t, err) t.Cleanup(func() { diff --git a/sqldb/v2/sqlite_test.go b/sqldb/v2/sqlite_test.go index 4901dd0ffde..9ab3b0c3210 100644 --- a/sqldb/v2/sqlite_test.go +++ b/sqldb/v2/sqlite_test.go @@ -1,5 +1,4 @@ //go:build !test_db_postgres -// +build !test_db_postgres package sqldb @@ -16,12 +15,14 @@ import ( const isSQLite = true // NewTestDB is a helper function that creates an SQLite database for testing. -func NewTestDB(t *testing.T, migrations []MigrationConfig) *SqliteStore { - return NewTestSqliteDB(t, migrations) +func NewTestDB(t *testing.T, streams []MigrationStream) *SqliteStore { + return NewTestSqliteDB(t, streams) } // NewTestDBWithVersion is a helper function that creates an SQLite database // for testing and migrates it to the given version. -func NewTestDBWithVersion(t *testing.T, version uint) *SqliteStore { - return NewTestSqliteDBWithVersion(t, version) +func NewTestDBWithVersion(t *testing.T, stream MigrationStream, + version uint) *SqliteStore { + + return NewTestSqliteDBWithVersion(t, stream, version) } From a1d335987cd7330eb501bc87ac5531ff25e8f367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Tigerstr=C3=B6m?= Date: Tue, 2 Sep 2025 02:57:16 +0200 Subject: [PATCH 5/9] sqldb/v2: introduce sqldb/v2 `BaseDB` This commit updates the definition of the `BaseDB` struct to decouple it from lnd`s `sqlc` package. We also introduce new fields to the struct to make it possible to track the database type used at runtime. --- sqldb/v2/go.mod | 1 - sqldb/v2/go.sum | 2 -- sqldb/v2/interfaces.go | 45 ++++++++++++++++++++++++++++++++++----- sqldb/v2/postgres.go | 8 +++---- sqldb/v2/postgres_test.go | 8 ------- sqldb/v2/sqlite.go | 7 +++--- sqldb/v2/sqlite_test.go | 8 ------- 7 files changed, 46 insertions(+), 33 deletions(-) diff --git a/sqldb/v2/go.mod b/sqldb/v2/go.mod index a06c202a2f5..33e240b5c9e 100644 --- a/sqldb/v2/go.mod +++ b/sqldb/v2/go.mod @@ -8,7 +8,6 @@ require ( github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 github.com/jackc/pgx/v5 v5.5.4 github.com/lightningnetwork/lnd/fn/v2 v2.0.8 - github.com/lightningnetwork/lnd/sqldb v1.0.10 github.com/ory/dockertest/v3 v3.10.0 github.com/pmezard/go-difflib v1.0.0 github.com/stretchr/testify v1.10.0 diff --git a/sqldb/v2/go.sum b/sqldb/v2/go.sum index c44b6f3d98c..01dea175ead 100644 --- a/sqldb/v2/go.sum +++ b/sqldb/v2/go.sum @@ -110,8 +110,6 @@ github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2 h1:eFjp1dIB2BhhQp github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2/go.mod h1:99BKpIi6ruaaXRM1A77eqZ+FWPQ3cfRa+ZVy5bmWMaY= github.com/lightningnetwork/lnd/fn/v2 v2.0.8 h1:r2SLz7gZYQPVc3IZhU82M66guz3Zk2oY+Rlj9QN5S3g= github.com/lightningnetwork/lnd/fn/v2 v2.0.8/go.mod h1:TOzwrhjB/Azw1V7aa8t21ufcQmdsQOQMDtxVOQWNl8s= -github.com/lightningnetwork/lnd/sqldb v1.0.10 h1:ZLV7TGwjnKupVfCd+DJ43MAc9BKVSFCnvhpSPGKdN3M= -github.com/lightningnetwork/lnd/sqldb v1.0.10/go.mod h1:c/vWoQfcxu6FAfHzGajkIQi7CEIeIZFhhH4DYh1BJpc= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= diff --git a/sqldb/v2/interfaces.go b/sqldb/v2/interfaces.go index b9cf552ae32..4c9102f31a0 100644 --- a/sqldb/v2/interfaces.go +++ b/sqldb/v2/interfaces.go @@ -4,7 +4,6 @@ import ( "context" "database/sql" "fmt" - "github.com/lightningnetwork/lnd/sqldb/sqlc" "math" "math/rand" prand "math/rand" @@ -31,6 +30,21 @@ const ( DefaultMaxRetryDelay = time.Second ) +// BackendType is an enum that represents the type of database backend we're +// using. +type BackendType uint8 + +const ( + // BackendTypeUnknown indicates we're using an unknown backend. + BackendTypeUnknown BackendType = iota + + // BackendTypeSqlite indicates we're using a SQLite backend. + BackendTypeSqlite + + // BackendTypePostgres indicates we're using a Postgres backend. + BackendTypePostgres +) + // TxOptions represents a set of options one can use to control what type of // database transaction is created. Transaction can be either read or write. type TxOptions interface { @@ -67,12 +81,20 @@ func ReadTxOpt() TxOptions { } } +// BaseQuerier is a generic interface that represents the base methods that any +// database backend implementation which uses a Querier for its operations must +// implement. +type BaseQuerier interface { + // Backend returns the type of the database backend used. + Backend() BackendType +} + // BatchedTx is a generic interface that represents the ability to execute // several operations to a given storage interface in a single atomic // transaction. Typically, Q here will be some subset of the main sqlc.Querier // interface allowing it to only depend on the routines it needs to implement // any additional business logic. -type BatchedTx[Q any] interface { +type BatchedTx[Q BaseQuerier] interface { // ExecTx will execute the passed txBody, operating upon generic // parameter Q (usually a storage interface) in a single transaction. // @@ -81,6 +103,9 @@ type BatchedTx[Q any] interface { // type of concurrency control should be used. ExecTx(ctx context.Context, txOptions TxOptions, txBody func(Q) error, reset func()) error + + // Backend returns the type of the database backend used. + Backend() BackendType } // Tx represents a database transaction that can be committed or rolled back. @@ -158,7 +183,7 @@ func WithTxRetryDelay(delay time.Duration) TxExecutorOption { // query a type needs to run under a database transaction, and also the set of // options for that transaction. The QueryCreator is used to create a query // given a database transaction created by the BatchedQuerier. -type TransactionExecutor[Query any] struct { +type TransactionExecutor[Query BaseQuerier] struct { BatchedQuerier createQuery QueryCreator[Query] @@ -169,7 +194,7 @@ type TransactionExecutor[Query any] struct { // NewTransactionExecutor creates a new instance of a TransactionExecutor given // a Querier query object and a concrete type for the type of transactions the // Querier understands. -func NewTransactionExecutor[Querier any](db BatchedQuerier, +func NewTransactionExecutor[Querier BaseQuerier](db BatchedQuerier, createQuery QueryCreator[Querier], opts ...TxExecutorOption) *TransactionExecutor[Querier] { @@ -399,7 +424,12 @@ type DB interface { type BaseDB struct { *sql.DB - *sqlc.Queries + // BackendType defines the type of database backend the database is. + BackendType BackendType + + // SkipMigrations can be set to true to skip running any migrations + // during the iinitialization of the database. + SkipMigrations bool } // BeginTx wraps the normal sql specific BeginTx method with the TxOptions @@ -413,3 +443,8 @@ func (s *BaseDB) BeginTx(ctx context.Context, opts TxOptions) (*sql.Tx, error) { return s.DB.BeginTx(ctx, &sqlOptions) } + +// Backend returns the type of the database backend used. +func (s *BaseDB) Backend() BackendType { + return s.BackendType +} diff --git a/sqldb/v2/postgres.go b/sqldb/v2/postgres.go index 5b16d30472a..7961363e7a5 100644 --- a/sqldb/v2/postgres.go +++ b/sqldb/v2/postgres.go @@ -12,7 +12,6 @@ import ( _ "github.com/golang-migrate/migrate/v4/source/file" // Read migrations from files. // nolint:ll _ "github.com/jackc/pgx/v5" "github.com/lightningnetwork/lnd/fn/v2" - "github.com/lightningnetwork/lnd/sqldb/sqlc" ) var ( @@ -128,13 +127,12 @@ func NewPostgresStore(cfg *PostgresConfig) (*PostgresStore, error) { db.SetMaxIdleConns(maxConns) db.SetConnMaxLifetime(connIdleLifetime) - queries := sqlc.New(db) - return &PostgresStore{ cfg: cfg, BaseDB: &BaseDB{ - DB: db, - Queries: queries, + DB: db, + BackendType: BackendTypePostgres, + SkipMigrations: cfg.SkipMigrations, }, }, nil } diff --git a/sqldb/v2/postgres_test.go b/sqldb/v2/postgres_test.go index 32530603b4c..ecd9c598784 100644 --- a/sqldb/v2/postgres_test.go +++ b/sqldb/v2/postgres_test.go @@ -7,14 +7,6 @@ import ( "testing" ) -// isSQLite is false if the build tag is set to test_db_postgres. It is used in -// tests that compile for both SQLite and Postgres databases to determine -// which database implementation is being used. -// -// TODO(elle): once we've updated to using sqldbv2, we can remove this since -// then we will have access to the DatabaseType on the BaseDB struct at runtime. -const isSQLite = false - // NewTestDB is a helper function that creates a Postgres database for testing. func NewTestDB(t *testing.T, streams []MigrationStream) *PostgresStore { pgFixture := NewTestPgFixture(t, DefaultPostgresFixtureLifetime) diff --git a/sqldb/v2/sqlite.go b/sqldb/v2/sqlite.go index cfb20f7050e..d561da62ff2 100644 --- a/sqldb/v2/sqlite.go +++ b/sqldb/v2/sqlite.go @@ -13,7 +13,6 @@ import ( "time" sqlite_migrate "github.com/golang-migrate/migrate/v4/database/sqlite" - "github.com/lightningnetwork/lnd/sqldb/sqlc" "github.com/stretchr/testify/require" _ "modernc.org/sqlite" // Register relevant drivers. ) @@ -137,14 +136,14 @@ func NewSqliteStore(cfg *SqliteConfig, dbPath string) (*SqliteStore, error) { db.SetMaxOpenConns(defaultMaxConns) db.SetMaxIdleConns(defaultMaxConns) db.SetConnMaxLifetime(connIdleLifetime) - queries := sqlc.New(db) s := &SqliteStore{ Config: cfg, DbPath: dbPath, BaseDB: &BaseDB{ - DB: db, - Queries: queries, + DB: db, + BackendType: BackendTypeSqlite, + SkipMigrations: cfg.SkipMigrations, }, } diff --git a/sqldb/v2/sqlite_test.go b/sqldb/v2/sqlite_test.go index 9ab3b0c3210..2404714ce10 100644 --- a/sqldb/v2/sqlite_test.go +++ b/sqldb/v2/sqlite_test.go @@ -6,14 +6,6 @@ import ( "testing" ) -// isSQLite is true if the build tag is set to test_db_sqlite. It is used in -// tests that compile for both SQLite and Postgres databases to determine -// which database implementation is being used. -// -// TODO(elle): once we've updated to using sqldbv2, we can remove this since -// then we will have access to the DatabaseType on the BaseDB struct at runtime. -const isSQLite = true - // NewTestDB is a helper function that creates an SQLite database for testing. func NewTestDB(t *testing.T, streams []MigrationStream) *SqliteStore { return NewTestSqliteDB(t, streams) From fafd451f35e60aaa47023f198cef1904f2e33e54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Tigerstr=C3=B6m?= Date: Tue, 2 Sep 2025 03:10:08 +0200 Subject: [PATCH 6/9] sqldb/v2: drop GetSchemaVersion & SetSchemaVersion Drop the GetSchemaVersion and SetSchemaVersion methods for the PostgresStore and SqliteStore structs in the sqldb/v2 package. --- sqldb/v2/postgres.go | 28 ---------------------------- sqldb/v2/sqlite.go | 31 ------------------------------- 2 files changed, 59 deletions(-) diff --git a/sqldb/v2/postgres.go b/sqldb/v2/postgres.go index 7961363e7a5..6166831da2d 100644 --- a/sqldb/v2/postgres.go +++ b/sqldb/v2/postgres.go @@ -185,34 +185,6 @@ func (s *PostgresStore) ExecuteMigrations(target MigrationTarget, ) } -// GetSchemaVersion returns the current schema version of the Postgres database. -func (s *PostgresStore) GetSchemaVersion() (int, bool, error) { - driver, err := pgx_migrate.WithInstance(s.DB, &pgx_migrate.Config{}) - if err != nil { - return 0, false, errPostgresMigration(err) - - } - - version, dirty, err := driver.Version() - if err != nil { - return 0, false, err - } - - return version, dirty, nil -} - -// SetSchemaVersion sets the schema version of the Postgres database. -// -// NOTE: This alters the internal database schema tracker. USE WITH CAUTION!!! -func (s *PostgresStore) SetSchemaVersion(version int, dirty bool) error { - driver, err := pgx_migrate.WithInstance(s.DB, &pgx_migrate.Config{}) - if err != nil { - return errPostgresMigration(err) - } - - return driver.SetVersion(version, dirty) -} - func (s *PostgresStore) DefaultTarget() MigrationTarget { return TargetLatest } diff --git a/sqldb/v2/sqlite.go b/sqldb/v2/sqlite.go index d561da62ff2..de20c5dce16 100644 --- a/sqldb/v2/sqlite.go +++ b/sqldb/v2/sqlite.go @@ -276,37 +276,6 @@ func (s *SqliteStore) SkipMigrations() bool { return s.Config.SkipMigrations } -// GetSchemaVersion returns the current schema version of the SQLite database. -func (s *SqliteStore) GetSchemaVersion() (int, bool, error) { - driver, err := sqlite_migrate.WithInstance( - s.DB, &sqlite_migrate.Config{}, - ) - if err != nil { - return 0, false, errSqliteMigration(err) - } - - version, dirty, err := driver.Version() - if err != nil { - return 0, dirty, err - } - - return version, dirty, nil -} - -// SetSchemaVersion sets the schema version of the SQLite database. -// -// NOTE: This alters the internal database schema tracker. USE WITH CAUTION!!! -func (s *SqliteStore) SetSchemaVersion(version int, dirty bool) error { - driver, err := sqlite_migrate.WithInstance( - s.DB, &sqlite_migrate.Config{}, - ) - if err != nil { - return errSqliteMigration(err) - } - - return driver.SetVersion(version, dirty) -} - // NewTestSqliteDB is a helper function that creates an SQLite database for // testing. func NewTestSqliteDB(t testing.TB, streams []MigrationStream) *SqliteStore { From 88c1ffa6c5b6cd59a57885ce36c1539c76bc6745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Tigerstr=C3=B6m?= Date: Tue, 2 Sep 2025 03:12:16 +0200 Subject: [PATCH 7/9] sqldb/v2: sync features with tapd's sqldb package In order to make it possible to replace `tapd`'s internal `sqldb` package with the new generic `sqldb/v2` package, we need to make sure that all features and functionality that currently exist in the `tapd` package are also present in the new `sqldb/v2` package. This commit adds such additional missing features to the `sqldb/v2` package. --- sqldb/v2/config.go | 26 ++++++--- sqldb/v2/go.mod | 24 +++++---- sqldb/v2/go.sum | 102 +++++++++++------------------------ sqldb/v2/interfaces.go | 47 ++++++++-------- sqldb/v2/migrations.go | 11 ++++ sqldb/v2/postgres.go | 27 ++++++++-- sqldb/v2/postgres_fixture.go | 6 ++- sqldb/v2/postgres_test.go | 1 - sqldb/v2/sqlerrors.go | 79 ++++++++++++++++++++++++++- sqldb/v2/sqlite.go | 29 ++++++++-- sqldb/v2/sqlutils.go | 58 ++++++++++++++++++++ 11 files changed, 285 insertions(+), 125 deletions(-) diff --git a/sqldb/v2/config.go b/sqldb/v2/config.go index e893152dbdc..cffadaa91e2 100644 --- a/sqldb/v2/config.go +++ b/sqldb/v2/config.go @@ -14,8 +14,16 @@ const ( // time. defaultMaxConns = 25 - // connIdleLifetime is the amount of time a connection can be idle. - connIdleLifetime = 5 * time.Minute + // defaultMaxIdleConns is the number of permitted idle connections. + defaultMaxIdleConns = 6 + + // defaultConnMaxIdleTime is the amount of time a connection can be + // idle before it is closed. + defaultConnMaxIdleTime = 5 * time.Minute + + // defaultConnMaxLifetime is the maximum amount of time a connection can + // be reused for before it is closed. + defaultConnMaxLifetime = 10 * time.Minute ) // SqliteConfig holds all the config arguments needed to interact with our @@ -49,11 +57,15 @@ func (p *SqliteConfig) Validate() error { // //nolint:ll type PostgresConfig struct { - Dsn string `long:"dsn" description:"Database connection string."` - Timeout time.Duration `long:"timeout" description:"Database connection timeout. Set to zero to disable."` - MaxConnections int `long:"maxconnections" description:"The maximum number of open connections to the database. Set to zero for unlimited."` - SkipMigrations bool `long:"skipmigrations" description:"Skip applying migrations on startup."` - QueryConfig `group:"query" namespace:"query"` + Dsn string `long:"dsn" description:"Database connection string."` + Timeout time.Duration `long:"timeout" description:"Database connection timeout. Set to zero to disable."` + MaxOpenConnections int `long:"maxconnections" description:"Max open connections to keep alive to the database server. Set to zero for unlimited."` + MaxIdleConnections int `long:"maxidleconnections" description:"Max number of idle connections to keep in the connection pool. Set to zero for unlimited."` + ConnMaxLifetime time.Duration `long:"connmaxlifetime" description:"Max amount of time a connection can be reused for before it is closed. Valid time units are {s, m, h}."` + ConnMaxIdleTime time.Duration `long:"connmaxidletime" description:"Max amount of time a connection can be idle for before it is closed. Valid time units are {s, m, h}."` + RequireSSL bool `long:"requiressl" description:"Whether to require using SSL (mode: require) when connecting to the server."` + SkipMigrations bool `long:"skipmigrations" description:"Skip applying migrations on startup."` + QueryConfig `group:"query" namespace:"query"` } // Validate checks that the PostgresConfig values are valid. diff --git a/sqldb/v2/go.mod b/sqldb/v2/go.mod index 33e240b5c9e..49604cbcafe 100644 --- a/sqldb/v2/go.mod +++ b/sqldb/v2/go.mod @@ -6,7 +6,7 @@ require ( github.com/golang-migrate/migrate/v4 v4.19.0 github.com/jackc/pgconn v1.14.3 github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 - github.com/jackc/pgx/v5 v5.5.4 + github.com/jackc/pgx/v5 v5.7.4 github.com/lightningnetwork/lnd/fn/v2 v2.0.8 github.com/ory/dockertest/v3 v3.10.0 github.com/pmezard/go-difflib v1.0.0 @@ -16,6 +16,7 @@ require ( ) require ( + dario.cat/mergo v1.0.2 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect @@ -24,45 +25,46 @@ require ( github.com/containerd/continuity v0.3.0 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect - github.com/docker/cli v20.10.17+incompatible // indirect + github.com/docker/cli v28.1.1+incompatible // indirect github.com/docker/docker v28.3.3+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/go-viper/mapstructure/v2 v2.3.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/imdario/mergo v0.3.12 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgproto3/v2 v2.3.3 // indirect - github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mitchellh/mapstructure v1.4.1 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/term v0.5.0 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/opencontainers/runc v1.1.5 // indirect + github.com/opencontainers/runc v1.1.14 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect + go.opentelemetry.io/otel/trace v1.35.0 // indirect go.uber.org/atomic v1.7.0 // indirect - golang.org/x/crypto v0.36.0 // indirect + golang.org/x/crypto v0.37.0 // indirect golang.org/x/sync v0.15.0 // indirect golang.org/x/sys v0.34.0 // indirect - golang.org/x/text v0.23.0 // indirect - gopkg.in/yaml.v2 v2.3.0 // indirect + golang.org/x/text v0.24.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - modernc.org/libc v1.66.3 // indirect + modernc.org/libc v1.66.3 // indirect; indirectv modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect ) diff --git a/sqldb/v2/go.sum b/sqldb/v2/go.sum index 01dea175ead..83aa4aea177 100644 --- a/sqldb/v2/go.sum +++ b/sqldb/v2/go.sum @@ -1,6 +1,7 @@ +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= @@ -11,20 +12,14 @@ github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084 h1:y3bvkt8ki0 github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084/go.mod h1:XItGUfVOxotJL8kkuk2Hj3EVow5KCugXl3wWfQ6K0AE= github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= -github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= -github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -32,35 +27,30 @@ github.com/dhui/dktest v0.4.5 h1:uUfYBIVREmj/Rw6MvgmqNAYzTiKOHJak+enB5Di73MM= github.com/dhui/dktest v0.4.5/go.mod h1:tmcyeHDKagvlDrz7gDKq4UAJOLIfVZYkfD5OnHDwcCo= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M= -github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v28.1.1+incompatible h1:eyUemzeI45DY7eDPuwUcmDyDj1pM98oD5MdSpiItp8k= +github.com/docker/cli v28.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk= +github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= @@ -72,8 +62,6 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= @@ -89,19 +77,16 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8= -github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= -github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= -github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg= +github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= @@ -112,26 +97,20 @@ github.com/lightningnetwork/lnd/fn/v2 v2.0.8 h1:r2SLz7gZYQPVc3IZhU82M66guz3Zk2oY github.com/lightningnetwork/lnd/fn/v2 v2.0.8/go.mod h1:TOzwrhjB/Azw1V7aa8t21ufcQmdsQOQMDtxVOQWNl8s= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs= -github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= -github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= +github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w= +github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA= github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -142,23 +121,14 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= @@ -167,21 +137,23 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -192,7 +164,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -200,25 +171,16 @@ golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -229,14 +191,10 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/sqldb/v2/interfaces.go b/sqldb/v2/interfaces.go index 4c9102f31a0..89e9dacb621 100644 --- a/sqldb/v2/interfaces.go +++ b/sqldb/v2/interfaces.go @@ -6,7 +6,6 @@ import ( "fmt" "math" "math/rand" - prand "math/rand" "time" ) @@ -22,12 +21,16 @@ const ( // repetition. DefaultNumTxRetries = 20 - // DefaultRetryDelay is the default delay between retries. This will be - // used to generate a random delay between 0 and this value. - DefaultRetryDelay = time.Millisecond * 50 + // DefaultInitialRetryDelay is the default initial delay between + // retries. This will be used to generate a random delay between -50% + // and +50% of this value, so 20 to 60 milliseconds. The retry will be + // doubled after each attempt until we reach DefaultMaxRetryDelay. We + // start with a random value to avoid multiple goroutines that are + // created at the same time to effectively retry at the same time. + DefaultInitialRetryDelay = time.Millisecond * 40 // DefaultMaxRetryDelay is the default maximum delay between retries. - DefaultMaxRetryDelay = time.Second + DefaultMaxRetryDelay = time.Second * 3 ) // BackendType is an enum that represents the type of database backend we're @@ -140,25 +143,21 @@ type BatchedQuerier interface { // executor. This can be used to do things like retry a transaction due to an // error a certain amount of times. type txExecutorOptions struct { - numRetries int - retryDelay time.Duration + numRetries int + initialRetryDelay time.Duration + maxRetryDelay time.Duration } // defaultTxExecutorOptions returns the default options for the transaction // executor. func defaultTxExecutorOptions() *txExecutorOptions { return &txExecutorOptions{ - numRetries: DefaultNumTxRetries, - retryDelay: DefaultRetryDelay, + numRetries: DefaultNumTxRetries, + initialRetryDelay: DefaultInitialRetryDelay, + maxRetryDelay: DefaultMaxRetryDelay, } } -// randRetryDelay returns a random retry delay between 0 and the configured max -// delay. -func (t *txExecutorOptions) randRetryDelay() time.Duration { - return time.Duration(prand.Int63n(int64(t.retryDelay))) //nolint:gosec -} - // TxExecutorOption is a functional option that allows us to pass in optional // argument when creating the executor. type TxExecutorOption func(*txExecutorOptions) @@ -175,7 +174,7 @@ func WithTxRetries(numRetries int) TxExecutorOption { // to wait before a transaction is retried. func WithTxRetryDelay(delay time.Duration) TxExecutorOption { return func(o *txExecutorOptions) { - o.retryDelay = delay + o.initialRetryDelay = delay } } @@ -270,11 +269,12 @@ type OnBackoff func(retry int, delay time.Duration) // retries is exceeded. func ExecuteSQLTransactionWithRetry(ctx context.Context, makeTx MakeTx, rollbackTx RollbackTx, txBody TxBody, onBackoff OnBackoff, - numRetries int) error { + opts *txExecutorOptions) error { waitBeforeRetry := func(attemptNumber int) bool { retryDelay := randRetryDelay( - DefaultRetryDelay, DefaultMaxRetryDelay, attemptNumber, + opts.initialRetryDelay, opts.maxRetryDelay, + attemptNumber, ) onBackoff(attemptNumber, retryDelay) @@ -291,14 +291,14 @@ func ExecuteSQLTransactionWithRetry(ctx context.Context, makeTx MakeTx, } } - for i := 0; i < numRetries; i++ { + for i := 0; i < opts.numRetries; i++ { tx, err := makeTx() if err != nil { dbErr := MapSQLError(err) log.Tracef("Failed to makeTx: err=%v, dbErr=%v", err, dbErr) - if IsSerializationError(dbErr) { + if IsSerializationOrDeadlockError(dbErr) { // Nothing to roll back here, since we haven't // even get a transaction yet. We'll just wait // and try again. @@ -327,7 +327,7 @@ func ExecuteSQLTransactionWithRetry(ctx context.Context, makeTx MakeTx, } dbErr := MapSQLError(bodyErr) - if IsSerializationError(dbErr) { + if IsSerializationOrDeadlockError(dbErr) { if waitBeforeRetry(i) { continue } @@ -348,7 +348,7 @@ func ExecuteSQLTransactionWithRetry(ctx context.Context, makeTx MakeTx, } dbErr := MapSQLError(commitErr) - if IsSerializationError(dbErr) { + if IsSerializationOrDeadlockError(dbErr) { if waitBeforeRetry(i) { continue } @@ -405,8 +405,7 @@ func (t *TransactionExecutor[Q]) ExecTx(ctx context.Context, } return ExecuteSQLTransactionWithRetry( - ctx, makeTx, rollbackTx, execTxBody, onBackoff, - t.opts.numRetries, + ctx, makeTx, rollbackTx, execTxBody, onBackoff, t.opts, ) } diff --git a/sqldb/v2/migrations.go b/sqldb/v2/migrations.go index b6e33808043..9bb8c6c8148 100644 --- a/sqldb/v2/migrations.go +++ b/sqldb/v2/migrations.go @@ -380,6 +380,17 @@ func (t *replacerFile) Close() error { return nil } +// MigrationTxOptions is the implementation of the TxOptions interface for +// migration transactions. +type MigrationTxOptions struct { +} + +// ReadOnly returns false to indicate that migration transactions are not read +// only. +func (m *MigrationTxOptions) ReadOnly() bool { + return false +} + // ApplyAllMigrations applies both the SQLC and custom in-code migrations to the // SQLite database. func ApplyAllMigrations(executor MigrationExecutor, diff --git a/sqldb/v2/postgres.go b/sqldb/v2/postgres.go index 6166831da2d..dd7b80e478a 100644 --- a/sqldb/v2/postgres.go +++ b/sqldb/v2/postgres.go @@ -9,7 +9,7 @@ import ( "time" pgx_migrate "github.com/golang-migrate/migrate/v4/database/pgx/v5" - _ "github.com/golang-migrate/migrate/v4/source/file" // Read migrations from files. // nolint:ll + _ "github.com/golang-migrate/migrate/v4/source/file" _ "github.com/jackc/pgx/v5" "github.com/lightningnetwork/lnd/fn/v2" ) @@ -30,6 +30,7 @@ var ( "BLOB": "BYTEA", "INTEGER PRIMARY KEY": "BIGSERIAL PRIMARY KEY", "TIMESTAMP": "TIMESTAMP WITHOUT TIME ZONE", + "UNHEX": "DECODE", } // Make sure PostgresStore implements the MigrationExecutor interface. @@ -119,13 +120,29 @@ func NewPostgresStore(cfg *PostgresConfig) (*PostgresStore, error) { } maxConns := defaultMaxConns - if cfg.MaxConnections > 0 { - maxConns = cfg.MaxConnections + if cfg.MaxOpenConnections > 0 { + maxConns = cfg.MaxOpenConnections + } + + maxIdleConns := defaultMaxIdleConns + if cfg.MaxIdleConnections > 0 { + maxIdleConns = cfg.MaxIdleConnections + } + + connMaxLifetime := defaultConnMaxLifetime + if cfg.ConnMaxLifetime > 0 { + connMaxLifetime = cfg.ConnMaxLifetime + } + + connMaxIdleTime := defaultConnMaxIdleTime + if cfg.ConnMaxIdleTime > 0 { + connMaxIdleTime = cfg.ConnMaxIdleTime } db.SetMaxOpenConns(maxConns) - db.SetMaxIdleConns(maxConns) - db.SetConnMaxLifetime(connIdleLifetime) + db.SetMaxIdleConns(maxIdleConns) + db.SetConnMaxLifetime(connMaxLifetime) + db.SetConnMaxIdleTime(connMaxIdleTime) return &PostgresStore{ cfg: cfg, diff --git a/sqldb/v2/postgres_fixture.go b/sqldb/v2/postgres_fixture.go index 562f91fdf27..0a0c0cd7907 100644 --- a/sqldb/v2/postgres_fixture.go +++ b/sqldb/v2/postgres_fixture.go @@ -23,7 +23,7 @@ const ( testPgUser = "test" testPgPass = "test" testPgDBName = "test" - PostgresTag = "11" + PostgresTag = "15" ) // TestPgFixture is a test fixture that starts a Postgres 11 instance in a @@ -124,6 +124,10 @@ func (f *TestPgFixture) TearDown(t testing.TB) { require.NoError(t, err, "Could not purge resource") } +func (f *TestPgFixture) DB() *sql.DB { + return f.db +} + // RandomDBName generates a random database name. func RandomDBName(t testing.TB) string { randBytes := make([]byte, 8) diff --git a/sqldb/v2/postgres_test.go b/sqldb/v2/postgres_test.go index ecd9c598784..ee97ffa9704 100644 --- a/sqldb/v2/postgres_test.go +++ b/sqldb/v2/postgres_test.go @@ -1,5 +1,4 @@ //go:build test_db_postgres -// +build test_db_postgres package sqldb diff --git a/sqldb/v2/sqlerrors.go b/sqldb/v2/sqlerrors.go index 59729910ee3..3f5a92fc017 100644 --- a/sqldb/v2/sqlerrors.go +++ b/sqldb/v2/sqlerrors.go @@ -90,11 +90,32 @@ func parseSqliteError(sqliteErr *sqlite.Error) error { } // Database is currently busy, so we'll need to try again. - case sqlite3.SQLITE_BUSY: + case sqlite3.SQLITE_BUSY, sqlite3.SQLITE_BUSY_SNAPSHOT: return &ErrSerializationError{ DBError: sqliteErr, } + // A write operation could not continue because of a conflict within the + // same database connection. + case sqlite3.SQLITE_LOCKED: + return &ErrDeadlockError{ + DbError: sqliteErr, + } + + // Generic error, need to parse the message further. + case sqlite3.SQLITE_ERROR: + errMsg := sqliteErr.Error() + + switch { + case strings.Contains(errMsg, "no such table"): + return &ErrSchemaError{ + DbError: sqliteErr, + } + + default: + return fmt.Errorf("unknown sqlite error: %w", sqliteErr) + } + default: return fmt.Errorf("unknown sqlite error: %w", sqliteErr) } @@ -130,6 +151,12 @@ func parsePostgresError(pqErr *pgconn.PgError) error { DBError: pqErr, } + // Handle schema error. + case pgerrcode.UndefinedColumn, pgerrcode.UndefinedTable: + return &ErrSchemaError{ + DbError: pqErr, + } + default: return fmt.Errorf("unknown postgres error: %w", pqErr) } @@ -168,3 +195,53 @@ func IsSerializationError(err error) bool { var serializationError *ErrSerializationError return errors.As(err, &serializationError) } + +// ErrDeadlockError is an error type which represents a database agnostic +// error where transactions have led to cyclic dependencies in lock acquisition. +type ErrDeadlockError struct { + DbError error +} + +// Unwrap returns the wrapped error. +func (e ErrDeadlockError) Unwrap() error { + return e.DbError +} + +// Error returns the error message. +func (e ErrDeadlockError) Error() string { + return e.DbError.Error() +} + +// IsDeadlockError returns true if the given error is a deadlock error. +func IsDeadlockError(err error) bool { + var deadlockError *ErrDeadlockError + return errors.As(err, &deadlockError) +} + +// IsSerializationOrDeadlockError returns true if the given error is either a +// deadlock error or a serialization error. +func IsSerializationOrDeadlockError(err error) bool { + return IsDeadlockError(err) || IsSerializationError(err) +} + +// ErrSchemaError is an error type which represents a database agnostic error +// that the schema of the database is incorrect for the given query. +type ErrSchemaError struct { + DbError error +} + +// Unwrap returns the wrapped error. +func (e ErrSchemaError) Unwrap() error { + return e.DbError +} + +// Error returns the error message. +func (e ErrSchemaError) Error() string { + return e.DbError.Error() +} + +// IsSchemaError returns true if the given error is a schema error. +func IsSchemaError(err error) bool { + var schemaError *ErrSchemaError + return errors.As(err, &schemaError) +} diff --git a/sqldb/v2/sqlite.go b/sqldb/v2/sqlite.go index de20c5dce16..3c59a08d6e0 100644 --- a/sqldb/v2/sqlite.go +++ b/sqldb/v2/sqlite.go @@ -5,14 +5,14 @@ package sqldb import ( "database/sql" "fmt" - "github.com/golang-migrate/migrate/v4" - "github.com/lightningnetwork/lnd/fn/v2" "net/url" "path/filepath" "testing" "time" + "github.com/golang-migrate/migrate/v4" sqlite_migrate "github.com/golang-migrate/migrate/v4/database/sqlite" + "github.com/lightningnetwork/lnd/fn/v2" "github.com/stretchr/testify/require" _ "modernc.org/sqlite" // Register relevant drivers. ) @@ -135,7 +135,7 @@ func NewSqliteStore(cfg *SqliteConfig, dbPath string) (*SqliteStore, error) { db.SetMaxOpenConns(defaultMaxConns) db.SetMaxIdleConns(defaultMaxConns) - db.SetConnMaxLifetime(connIdleLifetime) + db.SetConnMaxLifetime(defaultConnMaxLifetime) s := &SqliteStore{ Config: cfg, @@ -300,6 +300,29 @@ func NewTestSqliteDB(t testing.TB, streams []MigrationStream) *SqliteStore { return sqlDB } +// NewTestSqliteDBFromPath is a helper function that creates a SQLite database +// for testing from a given database file path. +func NewTestSqliteDBFromPath(t *testing.T, dbPath string, + streams []MigrationStream) *SqliteStore { + + t.Helper() + + t.Logf("Creating new SQLite DB for testing, using DB path %s", dbPath) + + sqlDB, err := NewSqliteStore(&SqliteConfig{ + SkipMigrations: false, + }, dbPath) + require.NoError(t, err) + + require.NoError(t, ApplyAllMigrations(sqlDB, streams)) + + t.Cleanup(func() { + require.NoError(t, sqlDB.DB.Close()) + }) + + return sqlDB +} + // NewTestSqliteDBWithVersion is a helper function that creates an SQLite // database for testing and migrates it to the given version. func NewTestSqliteDBWithVersion(t *testing.T, stream MigrationStream, diff --git a/sqldb/v2/sqlutils.go b/sqldb/v2/sqlutils.go index ce99eb682d5..889d5a79351 100644 --- a/sqldb/v2/sqlutils.go +++ b/sqldb/v2/sqlutils.go @@ -4,9 +4,16 @@ import ( "database/sql" "time" + "github.com/lightningnetwork/lnd/fn/v2" "golang.org/x/exp/constraints" ) +var ( + // MaxValidSQLTime is the maximum valid time that can be rendered as a + // time string and can be used for comparisons in SQL. + MaxValidSQLTime = time.Date(9999, 12, 31, 23, 59, 59, 999999, time.UTC) +) + // NoOpReset is a no-op function that can be used as a default // reset function ExecTx calls. var NoOpReset = func() {} @@ -35,6 +42,17 @@ func SQLInt32[T constraints.Integer](num T) sql.NullInt32 { } } +// SqlOptInt32 turns an option of a numerical integer type into the NullInt32 +// that sql/sqlc uses when an integer field can be permitted to be NULL. +func SqlOptInt32[T constraints.Integer](num fn.Option[T]) sql.NullInt32 { + return fn.MapOptionZ(num, func(num T) sql.NullInt32 { + return sql.NullInt32{ + Int32: int32(num), + Valid: true, + } + }) +} + // SQLInt64 turns a numerical integer type into the NullInt64 that sql/sqlc // uses when an integer field can be permitted to be NULL. // @@ -47,6 +65,15 @@ func SQLInt64[T constraints.Integer](num T) sql.NullInt64 { } } +// SqlBool turns a boolean into the NullBool that sql/sqlc uses when a boolean +// field can be permitted to be NULL. +func SqlBool(b bool) sql.NullBool { + return sql.NullBool{ + Bool: b, + Valid: true, + } +} + // SQLStr turns a string into the NullString that sql/sqlc uses when a string // can be permitted to be NULL. // @@ -84,9 +111,40 @@ func SQLTime(t time.Time) sql.NullTime { } } +// ExtractSqlInt64 turns a NullInt64 into a numerical type. This can be useful +// when reading directly from the database, as this function handles extracting +// the inner value from the "option"-like struct. +func ExtractSqlInt64[T constraints.Integer](num sql.NullInt64) T { + return T(num.Int64) +} + +// ExtractSqlInt32 turns a NullInt32 into a numerical type. This can be useful +// when reading directly from the database, as this function handles extracting +// the inner value from the "option"-like struct. +func ExtractSqlInt32[T constraints.Integer](num sql.NullInt32) T { + return T(num.Int32) +} + +// ExtractOptSqlInt32 turns a NullInt32 into an option of a numerical type. +func ExtractOptSqlInt32[T constraints.Integer](num sql.NullInt32) fn.Option[T] { + if !num.Valid { + return fn.None[T]() + } + + result := T(num.Int32) + return fn.Some(result) +} + // ExtractSqlInt16 turns a NullInt16 into a numerical type. This can be useful // when reading directly from the database, as this function handles extracting // the inner value from the "option"-like struct. func ExtractSqlInt16[T constraints.Integer](num sql.NullInt16) T { return T(num.Int16) } + +// ExtractBool turns a NullBool into a boolean. This can be useful when reading +// directly from the database, as this function handles extracting the inner +// value from the "option"-like struct. +func ExtractBool(b sql.NullBool) bool { + return b.Bool +} From 53d47088f25e296fe5d1eb9c32fdcd60a2592d2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Tigerstr=C3=B6m?= Date: Tue, 2 Sep 2025 01:28:11 +0200 Subject: [PATCH 8/9] sqldb/v2: rename test db helper files Previously, the test db helper files were suffixed with "_test", which would indicate that the files specifically contained tests. However, these files actually contain helper functions to be used in tests, and are not tests themselves. To better reflect their purpose, the files have been renamed to instead be prefixed with "test_". --- sqldb/v2/{postgres_test.go => test_postgres.go} | 0 sqldb/v2/{sqlite_test.go => test_sqlite.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename sqldb/v2/{postgres_test.go => test_postgres.go} (100%) rename sqldb/v2/{sqlite_test.go => test_sqlite.go} (100%) diff --git a/sqldb/v2/postgres_test.go b/sqldb/v2/test_postgres.go similarity index 100% rename from sqldb/v2/postgres_test.go rename to sqldb/v2/test_postgres.go diff --git a/sqldb/v2/sqlite_test.go b/sqldb/v2/test_sqlite.go similarity index 100% rename from sqldb/v2/sqlite_test.go rename to sqldb/v2/test_sqlite.go From 233f1fb39ca9e2d2a330186b0905e69bf5616195 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Tigerstr=C3=B6m?= Date: Mon, 1 Sep 2025 22:03:49 +0200 Subject: [PATCH 9/9] lnd: add sqldb/v2 dependency to lnd --- go.mod | 68 +++++++++++------------ go.sum | 167 +++++++++++++++++++++++++++------------------------------ log.go | 2 + 3 files changed, 114 insertions(+), 123 deletions(-) diff --git a/go.mod b/go.mod index 393dcc3a57b..30fa0fcfe8d 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/gorilla/websocket v1.5.0 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 - github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 github.com/jackc/pgx/v4 v4.18.3 github.com/jackpal/gateway v1.0.5 github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad @@ -40,6 +40,7 @@ require ( github.com/lightningnetwork/lnd/kvdb v1.4.16 github.com/lightningnetwork/lnd/queue v1.1.1 github.com/lightningnetwork/lnd/sqldb v1.0.10 + github.com/lightningnetwork/lnd/sqldb/v2 v2.0.0 github.com/lightningnetwork/lnd/ticker v1.1.1 github.com/lightningnetwork/lnd/tlv v1.3.2 github.com/lightningnetwork/lnd/tor v1.1.6 @@ -52,22 +53,22 @@ require ( go.etcd.io/etcd/client/pkg/v3 v3.5.12 go.etcd.io/etcd/client/v3 v3.5.12 golang.org/x/crypto v0.37.0 - golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 + golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 - golang.org/x/sync v0.13.0 + golang.org/x/sync v0.15.0 golang.org/x/term v0.31.0 - golang.org/x/time v0.3.0 - google.golang.org/grpc v1.59.0 - google.golang.org/protobuf v1.33.0 + golang.org/x/time v0.5.0 + google.golang.org/grpc v1.64.1 + google.golang.org/protobuf v1.34.2 gopkg.in/macaroon-bakery.v2 v2.0.1 gopkg.in/macaroon.v2 v2.0.0 pgregory.net/rapid v1.2.0 ) require ( - dario.cat/mergo v1.0.1 // indirect + dario.cat/mergo v1.0.2 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect @@ -86,11 +87,10 @@ require ( github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect github.com/decred/dcrd/lru v1.1.2 // indirect github.com/docker/cli v28.1.1+incompatible // indirect - github.com/docker/docker v28.1.1+incompatible // indirect - github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/docker v28.3.3+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fergusstrange/embedded-postgres v1.25.0 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/go-logr/logr v1.4.2 // indirect @@ -98,8 +98,8 @@ require ( github.com/go-viper/mapstructure/v2 v2.3.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect - github.com/golang-migrate/migrate/v4 v4.17.0 // indirect - github.com/golang/protobuf v1.5.4 // indirect + github.com/golang-migrate/migrate/v4 v4.19.0 // indirect + github.com/golang/protobuf v1.5.4 github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.0.1 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect @@ -107,7 +107,6 @@ require ( github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgconn v1.14.3 // indirect github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 // indirect @@ -120,7 +119,7 @@ require ( github.com/jackc/puddle v1.3.0 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jonboulle/clockwork v0.2.2 // indirect - github.com/json-iterator/go v1.1.11 // indirect + github.com/json-iterator/go v1.1.12 // indirect github.com/juju/loggo v0.0.0-20210728185423-eebad3a902c4 // indirect github.com/juju/testing v0.0.0-20220203020004-a0ff61f03494 // indirect github.com/klauspost/compress v1.17.9 @@ -132,12 +131,12 @@ require ( github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/onsi/gomega v1.26.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.0.2 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect github.com/opencontainers/runc v1.1.14 // indirect github.com/ory/dockertest/v3 v3.10.0 // indirect github.com/pkg/errors v0.9.1 // indirect @@ -150,7 +149,7 @@ require ( github.com/rogpeppe/fastuuid v1.2.0 // indirect github.com/russross/blackfriday/v2 v2.0.1 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect - github.com/sirupsen/logrus v1.9.2 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/soheilhy/cmux v0.1.5 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.2 // indirect @@ -169,36 +168,31 @@ require ( go.etcd.io/etcd/raft/v3 v3.5.12 // indirect go.etcd.io/etcd/server/v3 v3.5.12 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 // indirect go.opentelemetry.io/otel/metric v1.35.0 // indirect go.opentelemetry.io/otel/sdk v1.35.0 // indirect go.opentelemetry.io/otel/trace v1.35.0 // indirect - go.opentelemetry.io/proto/otlp v1.0.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.17.0 // indirect - golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.39.0 // indirect - golang.org/x/sys v0.32.0 // indirect + golang.org/x/sys v0.34.0 // indirect golang.org/x/text v0.24.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect - google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect + google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect gopkg.in/errgo.v1 v1.0.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect - modernc.org/libc v1.49.3 // indirect - modernc.org/mathutil v1.6.0 // indirect - modernc.org/memory v1.8.0 // indirect - modernc.org/sqlite v1.29.10 // indirect - modernc.org/strutil v1.2.0 // indirect - modernc.org/token v1.1.0 // indirect + modernc.org/libc v1.66.3 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect + modernc.org/sqlite v1.38.2 // indirect sigs.k8s.io/yaml v1.2.0 // indirect ) @@ -206,6 +200,8 @@ require ( // store have been included in a tagged sqldb version. replace github.com/lightningnetwork/lnd/sqldb => ./sqldb +replace github.com/lightningnetwork/lnd/sqldb/v2 => ./sqldb/v2 + // This replace is for https://github.com/advisories/GHSA-25xm-hr59-7c27 replace github.com/ulikunitz/xz => github.com/ulikunitz/xz v0.5.11 @@ -217,6 +213,10 @@ replace github.com/gogo/protobuf => github.com/gogo/protobuf v1.3.2 // allows us to specify that as an option. replace google.golang.org/protobuf => github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display +// We are using a fork of the migration library with custom functionality that +// did not yet make it into the upstream repository. +replace github.com/golang-migrate/migrate/v4 => github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2 + // If you change this please also update docs/INSTALL.md and GO_VERSION in // Makefile (then run `make lint` to see where else it needs to be updated as // well). diff --git a/go.sum b/go.sum index 4f9d613bbc0..93f9fca3656 100644 --- a/go.sum +++ b/go.sum @@ -1,20 +1,15 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y= -cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= -cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= -dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e h1:n+DcnTNkQnHlwpsrHoQtkrJIO7CBx029fw6oR4vIob4= github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e/go.mod h1:Bdzq+51GR4/0DIhaICZEOm+OHvXGwwB2trKZ8B4Y6eQ= github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82 h1:MG93+PZYs9PyEsj/n5/haQu2gK0h4tUtSy9ejtMwWa0= @@ -95,14 +90,16 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101 h1:7To3pQ+pZo0i3dsWEbinPNFs5gPSBOsJtx3wTT94VBY= -github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -129,16 +126,16 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3 github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/decred/dcrd/lru v1.1.2 h1:KdCzlkxppuoIDGEvCGah1fZRicrDH36IipvlB1ROkFY= github.com/decred/dcrd/lru v1.1.2/go.mod h1:gEdCVgXs1/YoBvFWt7Scgknbhwik3FgVSzlnCcXL2N8= -github.com/dhui/dktest v0.4.0 h1:z05UmuXZHO/bgj/ds2bGMBu8FI4WA+Ag/m3ghL+om7M= -github.com/dhui/dktest v0.4.0/go.mod h1:v/Dbz1LgCBOi2Uki2nUqLBGa83hWBGFMu5MrgMDCc78= +github.com/dhui/dktest v0.4.5 h1:uUfYBIVREmj/Rw6MvgmqNAYzTiKOHJak+enB5Di73MM= +github.com/dhui/dktest v0.4.5/go.mod h1:tmcyeHDKagvlDrz7gDKq4UAJOLIfVZYkfD5OnHDwcCo= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/cli v28.1.1+incompatible h1:eyUemzeI45DY7eDPuwUcmDyDj1pM98oD5MdSpiItp8k= github.com/docker/cli v28.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I= -github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= +github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -147,8 +144,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= -github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fergusstrange/embedded-postgres v1.25.0 h1:sa+k2Ycrtz40eCRPOzI7Ry7TtkWXXJ+YRsxpKMDhxK0= @@ -183,11 +178,7 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-migrate/migrate/v4 v4.17.0 h1:rd40H3QXU0AA4IoLllFcEAEo9dYKRHYND2gB4p7xcaU= -github.com/golang-migrate/migrate/v4 v4.17.0/go.mod h1:+Cp2mtLP4/aXDTKb9wmXYitdrNx2HGs45rbWAo6OsKM= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= -github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -211,8 +202,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= -github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= @@ -227,15 +218,13 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92Bcuy github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= -github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= @@ -312,8 +301,9 @@ github.com/jrick/logrotate v1.1.2 h1:6ePk462NCX7TfKtNp5JJ7MbA2YIslkpfgP03TlTYMN0 github.com/jrick/logrotate v1.1.2/go.mod h1:f9tdWggSVK3iqavGpyvegq5IhNois7KXmasU6/N96OQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c h1:3UvYABOQRhJAApj9MdCN+Ydv841ETSoy6xLzdmmr/9A= github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA= @@ -362,6 +352,8 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc= github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk= +github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2 h1:eFjp1dIB2BhhQp/THKrjLdlYuPugO9UU4kDqu91OX/Q= +github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2/go.mod h1:99BKpIi6ruaaXRM1A77eqZ+FWPQ3cfRa+ZVy5bmWMaY= github.com/lightninglabs/neutrino v0.16.1 h1:5Kz4ToxncEVkpKC6fwUjXKtFKJhuxlG3sBB3MdJTJjs= github.com/lightninglabs/neutrino v0.16.1/go.mod h1:L+5UAccpUdyM7yDgmQySgixf7xmwBgJtOfs/IP26jCs= github.com/lightninglabs/neutrino/cache v1.1.2 h1:C9DY/DAPaPxbFC+xNNEI/z1SJY9GS3shmlu5hIQ798g= @@ -415,8 +407,9 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -440,8 +433,8 @@ github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= -github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w= github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -497,8 +490,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= -github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -562,14 +555,14 @@ go.etcd.io/etcd/server/v3 v3.5.12 h1:EtMjsbfyfkwZuA2JlKOiBfuGkFCekv5H178qjXypbG8 go.etcd.io/etcd/server/v3 v3.5.12/go.mod h1:axB0oCjMy+cemo5290/CutIjoxlfA6KVYKD1w0uue10= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 h1:PzIubN4/sjByhDRHLviCjJuweBXWFZWhghjg7cS28+M= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0/go.mod h1:Ct6zzQEuGK3WpJs2n4dn+wfJYzd/+hNnxMRTWjGn30M= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 h1:DeFD0VgTZ+Cj6hxravYYZE2W4GlneVH81iAOPjZkzk8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0/go.mod h1:GijYcYmNpX1KazD5JmWGsi4P7dDTTTnfv1UbGn84MnU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 h1:gvmNvqrPYovvyRmCSygkUDyL8lC5Tl845MLEwqpxhEU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0/go.mod h1:vNUq47TGFioo+ffTSnKNdob241vePmtNZnAODKapKd0= go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= @@ -578,8 +571,8 @@ go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0Qe go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= -go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -617,8 +610,8 @@ golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw= -golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -632,8 +625,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -663,8 +656,6 @@ golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0= -golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -675,8 +666,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -717,8 +708,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -738,8 +729,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -756,8 +747,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -766,26 +757,24 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA= -google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= -google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k= -google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 h1:AB/lmRny7e2pLhFEYIbl5qkDAUt2h0ZRO4wGPhZf+ik= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= +google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 h1:W5Xj/70xIA4x60O/IFyXivR5MGqblAb8R3w26pnD6No= +google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8/go.mod h1:vPrPUTsDCYxXWjP7clS81mZ6/803D8K4iM9Ma27VKas= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 h1:mxSlqyb8ZAHsYDCfiXN1EDdNTdvjUJSLY+OnAUtYNYA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -824,30 +813,30 @@ gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -modernc.org/cc/v4 v4.20.0 h1:45Or8mQfbUqJOG9WaxvlFYOAQO0lQ5RvqBcFCXngjxk= -modernc.org/cc/v4 v4.20.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= -modernc.org/ccgo/v4 v4.16.0 h1:ofwORa6vx2FMm0916/CkZjpFPSR70VwTjUCe2Eg5BnA= -modernc.org/ccgo/v4 v4.16.0/go.mod h1:dkNyWIjFrVIZ68DTo36vHK+6/ShBn4ysU61So6PIqCI= -modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= -modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= -modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= -modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= -modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= -modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= -modernc.org/libc v1.49.3 h1:j2MRCRdwJI2ls/sGbeSk0t2bypOG/uvPZUsGQFDulqg= -modernc.org/libc v1.49.3/go.mod h1:yMZuGkn7pXbKfoT/M35gFJOAEdSKdxL0q64sF7KqCDo= -modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= -modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= -modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= -modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= -modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= -modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= -modernc.org/sqlite v1.29.10 h1:3u93dz83myFnMilBGCOLbr+HjklS6+5rJLx4q86RDAg= -modernc.org/sqlite v1.29.10/go.mod h1:ItX2a1OVGgNsFh6Dv60JQvGfJfTPHPVpV6DF59akYOA= -modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= -modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM= +modernc.org/cc/v4 v4.26.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= +modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE= +modernc.org/fileutil v1.3.8 h1:qtzNm7ED75pd1C7WgAGcK4edm4fvhtBsEiI/0NQ54YM= +modernc.org/fileutil v1.3.8/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ= +modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek= +modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= diff --git a/log.go b/log.go index 2484a35ddb9..e024b936db9 100644 --- a/log.go +++ b/log.go @@ -56,6 +56,7 @@ import ( "github.com/lightningnetwork/lnd/rpcperms" "github.com/lightningnetwork/lnd/signal" "github.com/lightningnetwork/lnd/sqldb" + sqldbv2 "github.com/lightningnetwork/lnd/sqldb/v2" "github.com/lightningnetwork/lnd/sweep" "github.com/lightningnetwork/lnd/tor" "github.com/lightningnetwork/lnd/watchtower" @@ -208,6 +209,7 @@ func SetupLoggers(root *build.SubLoggerManager, interceptor signal.Interceptor) AddSubLogger(root, chainio.Subsystem, interceptor, chainio.UseLogger) AddSubLogger(root, msgmux.Subsystem, interceptor, msgmux.UseLogger) AddSubLogger(root, sqldb.Subsystem, interceptor, sqldb.UseLogger) + AddSubLogger(root, sqldbv2.Subsystem, interceptor, sqldbv2.UseLogger) AddSubLogger( root, paymentsdb.Subsystem, interceptor, paymentsdb.UseLogger, )