diff --git a/docs/release-notes/release-notes-0.16.0.md b/docs/release-notes/release-notes-0.16.0.md
index ed27b2ebdf8..f31e2c8e061 100644
--- a/docs/release-notes/release-notes-0.16.0.md
+++ b/docs/release-notes/release-notes-0.16.0.md
@@ -330,6 +330,12 @@ Keysend](https://github.com/lightningnetwork/lnd/pull/7334).
* [Store AckedUpdates in a more compact
way](https://github.com/lightningnetwork/lnd/pull/7055)
+## DB
+
+* [Add a sqlite backend
+ option](https://github.com/lightningnetwork/lnd/pull/7251) to the kvdb
+ package.
+
## Pathfinding
* [Pathfinding takes capacity of edges into account to improve success
@@ -389,6 +395,7 @@ refactor the itest for code health and maintenance.
* Antoni Spaanderman
* Carla Kirk-Cohen
* Carsten Otto
+* Chris Geihsler
* Conner Babinchak
* cutiful
* Daniel McNally
diff --git a/kvdb/backend.go b/kvdb/backend.go
index 7d4cbc36fd3..76599311bbb 100644
--- a/kvdb/backend.go
+++ b/kvdb/backend.go
@@ -247,12 +247,17 @@ func updateLastCompactionDate(dbFile string) error {
func GetTestBackend(path, name string) (Backend, func(), error) {
empty := func() {}
+ // Note that for tests, we expect only one db backend build flag
+ // (or none) to be set at a time and thus one of the following switch
+ // cases should ever be true
switch {
case PostgresBackend:
key := filepath.Join(path, name)
keyHash := sha256.Sum256([]byte(key))
- f, err := NewPostgresFixture("test_" + hex.EncodeToString(keyHash[:]))
+ f, err := NewPostgresFixture("test_" + hex.EncodeToString(
+ keyHash[:]),
+ )
if err != nil {
return nil, func() {}, err
}
@@ -260,7 +265,31 @@ func GetTestBackend(path, name string) (Backend, func(), error) {
_ = f.DB().Close()
}, nil
- case TestBackend == BoltBackendName:
+ case EtcdBackend:
+ etcdConfig, cancel, err := StartEtcdTestBackend(path, 0, 0, "")
+ if err != nil {
+ return nil, empty, err
+ }
+ backend, err := Open(
+ EtcdBackendName, context.TODO(), etcdConfig,
+ )
+ return backend, cancel, err
+
+ case SqliteBackend:
+ dbPath := filepath.Join(path, name)
+ keyHash := sha256.Sum256([]byte(dbPath))
+ sqliteDb, err := StartSqliteTestBackend(
+ path, name, "test_"+hex.EncodeToString(keyHash[:]),
+ )
+ if err != nil {
+ return nil, empty, err
+ }
+
+ return sqliteDb, func() {
+ _ = sqliteDb.Close()
+ }, nil
+
+ default:
db, err := GetBoltBackend(&BoltBackendConfig{
DBPath: path,
DBFileName: name,
@@ -271,17 +300,6 @@ func GetTestBackend(path, name string) (Backend, func(), error) {
return nil, nil, err
}
return db, empty, nil
-
- case TestBackend == EtcdBackendName:
- etcdConfig, cancel, err := StartEtcdTestBackend(path, 0, 0, "")
- if err != nil {
- return nil, empty, err
- }
- backend, err := Open(
- EtcdBackendName, context.TODO(), etcdConfig,
- )
- return backend, cancel, err
-
}
return nil, nil, fmt.Errorf("unknown backend")
diff --git a/kvdb/config.go b/kvdb/config.go
index b3a92309302..08fcc3ad93d 100644
--- a/kvdb/config.go
+++ b/kvdb/config.go
@@ -18,6 +18,11 @@ const (
// by a live instance of postgres.
PostgresBackendName = "postgres"
+ // SqliteBackendName is the name of the backend that should be passed
+ // into kvdb.Create to initialize a new instance of kvdb.Backend backed
+ // by a live instance of sqlite.
+ SqliteBackendName = "sqlite"
+
// DefaultBoltAutoCompactMinAge is the default minimum time that must
// have passed since a bolt database file was last compacted for the
// compaction to be considered again.
diff --git a/kvdb/go.mod b/kvdb/go.mod
index a75cdac9555..baba6ccfe0e 100644
--- a/kvdb/go.mod
+++ b/kvdb/go.mod
@@ -1,28 +1,113 @@
module github.com/lightningnetwork/lnd/kvdb
require (
- github.com/andybalholm/brotli v1.0.3 // indirect
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
github.com/btcsuite/btcwallet/walletdb v1.3.6-0.20210803004036-eebed51155ec
github.com/davecgh/go-spew v1.1.1
github.com/fergusstrange/embedded-postgres v1.10.0
- github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.0.1
github.com/jackc/pgx/v4 v4.13.0
- github.com/klauspost/compress v1.13.6 // indirect
- github.com/klauspost/pgzip v1.2.5 // indirect
- github.com/lib/pq v1.10.3 // indirect
github.com/lightningnetwork/lnd/healthcheck v1.0.0
- github.com/nwaples/rardecode v1.1.2 // indirect
- github.com/pierrec/lz4/v4 v4.1.8 // indirect
github.com/stretchr/testify v1.7.0
- github.com/ulikunitz/xz v0.5.10 // indirect
go.etcd.io/bbolt v1.3.6
go.etcd.io/etcd/api/v3 v3.5.0
go.etcd.io/etcd/client/pkg/v3 v3.5.0
go.etcd.io/etcd/client/v3 v3.5.0
go.etcd.io/etcd/server/v3 v3.5.0
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
+ modernc.org/sqlite v1.20.3
+)
+
+require (
+ github.com/andybalholm/brotli v1.0.3 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.1.1 // indirect
+ github.com/coreos/go-semver v0.3.0 // indirect
+ github.com/coreos/go-systemd/v22 v22.3.2 // indirect
+ github.com/dsnet/compress v0.0.1 // indirect
+ github.com/dustin/go-humanize v1.0.0 // indirect
+ github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/protobuf v1.5.2 // indirect
+ github.com/golang/snappy v0.0.4 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/gorilla/websocket v1.4.2 // indirect
+ github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
+ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
+ github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
+ github.com/jackc/chunkreader/v2 v2.0.1 // indirect
+ github.com/jackc/pgconn v1.10.0 // indirect
+ github.com/jackc/pgio v1.0.0 // indirect
+ github.com/jackc/pgpassfile v1.0.0 // indirect
+ github.com/jackc/pgproto3/v2 v2.1.1 // indirect
+ github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
+ github.com/jackc/pgtype v1.8.1 // indirect
+ github.com/jonboulle/clockwork v0.2.2 // indirect
+ github.com/json-iterator/go v1.1.11 // indirect
+ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
+ github.com/klauspost/compress v1.13.6 // indirect
+ github.com/klauspost/pgzip v1.2.5 // indirect
+ github.com/lib/pq v1.10.3 // indirect
+ github.com/lightningnetwork/lnd/ticker v1.0.0 // indirect
+ github.com/mattn/go-isatty v0.0.16 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
+ github.com/mholt/archiver/v3 v3.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/nwaples/rardecode v1.1.2 // indirect
+ github.com/pierrec/lz4/v4 v4.1.8 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.11.0 // indirect
+ github.com/prometheus/client_model v0.2.0 // indirect
+ github.com/prometheus/common v0.26.0 // indirect
+ github.com/prometheus/procfs v0.6.0 // indirect
+ github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
+ github.com/sirupsen/logrus v1.7.0 // indirect
+ github.com/soheilhy/cmux v0.1.5 // indirect
+ github.com/spf13/pflag v1.0.5 // indirect
+ github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect
+ github.com/ulikunitz/xz v0.5.10 // indirect
+ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
+ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
+ go.etcd.io/etcd/client/v2 v2.305.0 // indirect
+ go.etcd.io/etcd/pkg/v3 v3.5.0 // indirect
+ go.etcd.io/etcd/raft/v3 v3.5.0 // indirect
+ go.opentelemetry.io/contrib v0.20.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0 // indirect
+ go.opentelemetry.io/otel v0.20.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp v0.20.0 // indirect
+ go.opentelemetry.io/otel/metric v0.20.0 // indirect
+ go.opentelemetry.io/otel/sdk v0.20.0 // indirect
+ go.opentelemetry.io/otel/sdk/export/metric v0.20.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.20.0 // indirect
+ go.opentelemetry.io/otel/trace v0.20.0 // indirect
+ go.opentelemetry.io/proto/otlp v0.7.0 // 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/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
+ golang.org/x/mod v0.4.2 // indirect
+ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
+ golang.org/x/text v0.3.6 // indirect
+ golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
+ golang.org/x/tools v0.1.2 // indirect
+ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
+ google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect
+ google.golang.org/grpc v1.38.0 // indirect
+ google.golang.org/protobuf v1.26.0 // indirect
+ gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
+ gopkg.in/yaml.v2 v2.4.0 // indirect
+ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
+ lukechampine.com/uint128 v1.2.0 // indirect
+ modernc.org/cc/v3 v3.40.0 // indirect
+ modernc.org/ccgo/v3 v3.16.13 // indirect
+ modernc.org/libc v1.22.2 // indirect
+ modernc.org/mathutil v1.5.0 // indirect
+ modernc.org/memory v1.4.0 // indirect
+ modernc.org/opt v0.1.3 // indirect
+ modernc.org/strutil v1.1.3 // indirect
+ modernc.org/token v1.0.1 // indirect
+ sigs.k8s.io/yaml v1.2.0 // indirect
)
// This replace is for https://github.com/advisories/GHSA-w73w-5m7g-f7qc
@@ -35,4 +120,4 @@ replace github.com/ulikunitz/xz => github.com/ulikunitz/xz v0.5.8
// https://deps.dev/advisory/OSV/GO-2021-0053?from=%2Fgo%2Fgithub.com%252Fgogo%252Fprotobuf%2Fv1.3.1
replace github.com/gogo/protobuf => github.com/gogo/protobuf v1.3.2
-go 1.16
+go 1.18
diff --git a/kvdb/go.sum b/kvdb/go.sum
index 1674bbbb17c..30ae3ace3e9 100644
--- a/kvdb/go.sum
+++ b/kvdb/go.sum
@@ -46,7 +46,6 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054 h1:uH66TXeswKn5PW5zdZ39xEwfS9an067BirqA+P4QaLI=
github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
-github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
@@ -68,7 +67,6 @@ github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc
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=
-github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
@@ -150,14 +148,17 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@@ -192,7 +193,6 @@ github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0m
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
-github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
@@ -213,7 +213,6 @@ github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5W
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 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
@@ -251,6 +250,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
+github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
+github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
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/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
@@ -291,6 +292,9 @@ github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mholt/archiver/v3 v3.5.0 h1:nE8gZIrw66cu4osS/U7UW7YDuGMHssxKutU8IfWxwWE=
@@ -353,6 +357,8 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
+github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@@ -509,6 +515,7 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
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.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
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=
@@ -582,8 +589,9 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -711,6 +719,30 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/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=
+lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
+lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
+modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
+modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
+modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
+modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
+modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
+modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
+modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0=
+modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug=
+modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
+modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
+modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk=
+modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
+modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
+modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
+modernc.org/sqlite v1.20.3 h1:SqGJMMxjj1PHusLxdYxeQSodg7Jxn9WWkaAQjKrntZs=
+modernc.org/sqlite v1.20.3/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A=
+modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
+modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
+modernc.org/tcl v1.15.0 h1:oY+JeD11qVVSgVvodMJsu7Edf8tr5E/7tuhF5cNYz34=
+modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
+modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
+modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
diff --git a/kvdb/kvdb_etcd.go b/kvdb/kvdb_etcd.go
index 017388a3fc5..2e07d015283 100644
--- a/kvdb/kvdb_etcd.go
+++ b/kvdb/kvdb_etcd.go
@@ -7,9 +7,9 @@ import (
"github.com/lightningnetwork/lnd/kvdb/etcd"
)
-// TestBackend is conditionally set to etcd when the kvdb_etcd build tag is
+// EtcdBackend is conditionally set to etcd when the kvdb_etcd build tag is
// defined, allowing testing our database code with etcd backend.
-const TestBackend = EtcdBackendName
+const EtcdBackend = true
// GetEtcdTestBackend creates an embedded etcd backend for testing
// storig the database at the passed path.
diff --git a/kvdb/kvdb_no_etcd.go b/kvdb/kvdb_no_etcd.go
index ad56a3775fa..79897635b1d 100644
--- a/kvdb/kvdb_no_etcd.go
+++ b/kvdb/kvdb_no_etcd.go
@@ -9,9 +9,9 @@ import (
"github.com/lightningnetwork/lnd/kvdb/etcd"
)
-// TestBackend is conditionally set to bdb when the kvdb_etcd build tag is
-// not defined, allowing testing our database code with bolt backend.
-const TestBackend = BoltBackendName
+// EtcdBackend is conditionally set to false when the kvdb_etcd build tag is not
+// defined. This will allow testing of other database backends.
+const EtcdBackend = false
var errEtcdNotAvailable = fmt.Errorf("etcd backend not available")
diff --git a/kvdb/kvdb_no_sqlite.go b/kvdb/kvdb_no_sqlite.go
new file mode 100644
index 00000000000..ff265e6caa7
--- /dev/null
+++ b/kvdb/kvdb_no_sqlite.go
@@ -0,0 +1,25 @@
+//go:build !kvdb_sqlite || (windows && (arm || 386)) || (linux && (ppc64 || mips || mipsle || mips64))
+
+package kvdb
+
+import (
+ "fmt"
+ "runtime"
+
+ "github.com/btcsuite/btcwallet/walletdb"
+)
+
+var errSqliteNotAvailable = fmt.Errorf("sqlite backend not available either "+
+ "due to the `kvdb_sqlite` build tag not being set, or due to this "+
+ "OS(%s) and/or architecture(%s) not being supported", runtime.GOOS,
+ runtime.GOARCH)
+
+// SqliteBackend is conditionally set to false when the kvdb_sqlite build tag is
+// not defined. This will allow testing of other database backends.
+const SqliteBackend = false
+
+// StartSqliteTestBackend is a stub returning nil, and errSqliteNotAvailable
+// error.
+func StartSqliteTestBackend(path, name, table string) (walletdb.DB, error) {
+ return nil, errSqliteNotAvailable
+}
diff --git a/kvdb/kvdb_sqlite.go b/kvdb/kvdb_sqlite.go
new file mode 100644
index 00000000000..ec880da63ce
--- /dev/null
+++ b/kvdb/kvdb_sqlite.go
@@ -0,0 +1,39 @@
+//go:build kvdb_sqlite && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64))
+
+package kvdb
+
+import (
+ "context"
+ "os"
+ "time"
+
+ "github.com/btcsuite/btcwallet/walletdb"
+ "github.com/lightningnetwork/lnd/kvdb/sqlbase"
+ "github.com/lightningnetwork/lnd/kvdb/sqlite"
+)
+
+const (
+ // SqliteBackend is conditionally set to true when the kvdb_sqlite build
+ // tag is defined. This will allow testing of other database backends.
+ SqliteBackend = true
+
+ testMaxConnections = 50
+)
+
+// StartSqliteTestBackend starts a sqlite backed wallet.DB instance
+func StartSqliteTestBackend(path, name, table string) (walletdb.DB, error) {
+ if !fileExists(path) {
+ err := os.Mkdir(path, 0700)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ sqlbase.Init(testMaxConnections)
+ return sqlite.NewSqliteBackend(
+ context.Background(), &sqlite.Config{
+ Timeout: time.Second * 30,
+ BusyTimeout: time.Second * 5,
+ }, path, name, table,
+ )
+}
diff --git a/kvdb/log.go b/kvdb/log.go
index 6dc3ad714a0..824715432ce 100644
--- a/kvdb/log.go
+++ b/kvdb/log.go
@@ -2,7 +2,7 @@ package kvdb
import (
"github.com/btcsuite/btclog"
- "github.com/lightningnetwork/lnd/kvdb/postgres"
+ "github.com/lightningnetwork/lnd/kvdb/sqlbase"
)
// log is a logger that is initialized as disabled. This means the package will
@@ -13,5 +13,5 @@ var log = btclog.Disabled
func UseLogger(logger btclog.Logger) {
log = logger
- postgres.UseLogger(log)
+ sqlbase.UseLogger(log)
}
diff --git a/kvdb/postgres/db.go b/kvdb/postgres/db.go
index 7badd143398..90ca8324a8d 100644
--- a/kvdb/postgres/db.go
+++ b/kvdb/postgres/db.go
@@ -1,262 +1,35 @@
//go:build kvdb_postgres
-// +build kvdb_postgres
package postgres
import (
"context"
- "database/sql"
- "errors"
- "fmt"
- "io"
- "sync"
- "time"
"github.com/btcsuite/btcwallet/walletdb"
+ "github.com/lightningnetwork/lnd/kvdb/sqlbase"
)
-const (
- // kvTableName is the name of the table that will contain all the kv
- // pairs.
- kvTableName = "kv"
-)
-
-// KV stores a key/value pair.
-type KV struct {
- key string
- val string
-}
-
-// db holds a reference to the postgres connection connection.
-type db struct {
- // cfg is the postgres connection config.
- cfg *Config
-
- // prefix is the table name prefix that is used to simulate namespaces.
- // We don't use schemas because at least sqlite does not support that.
- prefix string
-
- // ctx is the overall context for the database driver.
- //
- // TODO: This is an anti-pattern that is in place until the kvdb
- // interface supports a context.
- ctx context.Context
-
- // db is the underlying database connection instance.
- db *sql.DB
-
- // lock is the global write lock that ensures single writer.
- lock sync.RWMutex
-
- // table is the name of the table that contains the data for all
- // top-level buckets that have keys that cannot be mapped to a distinct
- // sql table.
- table string
-}
-
-// Enforce db implements the walletdb.DB interface.
-var _ walletdb.DB = (*db)(nil)
-
-// Global set of database connections.
-var dbConns *dbConnSet
-
-// Init initializes the global set of database connections.
-func Init(maxConnections int) {
- dbConns = newDbConnSet(maxConnections)
+// sqliteCmdReplacements defines a mapping from some SQLite keywords and phrases
+// to their postgres counterparts.
+var sqliteCmdReplacements = sqlbase.SQLiteCmdReplacements{
+ "BLOB": "BYTEA",
+ "INTEGER PRIMARY KEY": "BIGSERIAL PRIMARY KEY",
}
// newPostgresBackend returns a db object initialized with the passed backend
-// config. If postgres connection cannot be estabished, then returns error.
+// config. If postgres connection cannot be established, then returns error.
func newPostgresBackend(ctx context.Context, config *Config, prefix string) (
- *db, error) {
-
- if prefix == "" {
- return nil, errors.New("empty postgres prefix")
- }
-
- if dbConns == nil {
- return nil, errors.New("db connection set not initialized")
- }
-
- dbConn, err := dbConns.Open(config.Dsn)
- if err != nil {
- return nil, err
- }
-
- // Compose system table names.
- table := fmt.Sprintf(
- "%s_%s", prefix, kvTableName,
- )
-
- // Execute the create statements to set up a kv table in postgres. Every
- // row points to the bucket that it is one via its parent_id field. A
- // NULL parent_id means that the key belongs to the upper-most bucket in
- // this table. A constraint on parent_id is enforcing referential
- // integrity.
- //
- // Furthermore there is a
_p index on parent_id that is required
- // for the foreign key constraint.
- //
- // Finally there are unique indices on (parent_id, key) to prevent the
- // same key being present in a bucket more than once (_up and
- // _unp). In postgres, a single index wouldn't enforce the unique
- // constraint on rows with a NULL parent_id. Therefore two indices are
- // defined.
- _, err = dbConn.ExecContext(ctx, `
-CREATE SCHEMA IF NOT EXISTS public;
-CREATE TABLE IF NOT EXISTS public.`+table+`
-(
- key bytea NOT NULL,
- value bytea,
- parent_id bigint,
- id bigserial PRIMARY KEY,
- sequence bigint,
- CONSTRAINT `+table+`_parent FOREIGN KEY (parent_id)
- REFERENCES public.`+table+` (id)
- ON UPDATE NO ACTION
- ON DELETE CASCADE
-);
-
-CREATE INDEX IF NOT EXISTS `+table+`_p
- ON public.`+table+` (parent_id);
-
-CREATE UNIQUE INDEX IF NOT EXISTS `+table+`_up
- ON public.`+table+`
- (parent_id, key) WHERE parent_id IS NOT NULL;
-
-CREATE UNIQUE INDEX IF NOT EXISTS `+table+`_unp
- ON public.`+table+` (key) WHERE parent_id IS NULL;
-`)
- if err != nil {
- _ = dbConn.Close()
-
- return nil, err
- }
-
- backend := &db{
- cfg: config,
- prefix: prefix,
- ctx: ctx,
- db: dbConn,
- table: table,
- }
-
- return backend, nil
-}
-
-// getTimeoutCtx gets a timeout context for database requests.
-func (db *db) getTimeoutCtx() (context.Context, func()) {
- if db.cfg.Timeout == time.Duration(0) {
- return db.ctx, func() {}
- }
-
- return context.WithTimeout(db.ctx, db.cfg.Timeout)
-}
-
-// getPrefixedTableName returns a table name for this prefix (namespace).
-func (db *db) getPrefixedTableName(table string) string {
- return fmt.Sprintf("%s_%s", db.prefix, table)
-}
-
-// catchPanic executes the specified function. If a panic occurs, it is returned
-// as an error value.
-func catchPanic(f func() error) (err error) {
- defer func() {
- if r := recover(); r != nil {
- log.Criticalf("Caught unhandled error: %v", r)
-
- switch data := r.(type) {
- case error:
- err = data
-
- default:
- err = errors.New(fmt.Sprintf("%v", data))
- }
- }
- }()
-
- err = f()
-
- return
-}
-
-// View opens a database read transaction and executes the function f with the
-// transaction passed as a parameter. After f exits, the transaction is rolled
-// back. If f errors, its error is returned, not a rollback error (if any
-// occur). The passed reset function is called before the start of the
-// transaction and can be used to reset intermediate state. As callers may
-// expect retries of the f closure (depending on the database backend used), the
-// reset function will be called before each retry respectively.
-func (db *db) View(f func(tx walletdb.ReadTx) error, reset func()) error {
- return db.executeTransaction(
- func(tx walletdb.ReadWriteTx) error {
- return f(tx.(walletdb.ReadTx))
- },
- reset, true,
- )
-}
-
-// Update opens a database read/write transaction and executes the function f
-// with the transaction passed as a parameter. After f exits, if f did not
-// error, the transaction is committed. Otherwise, if f did error, the
-// transaction is rolled back. If the rollback fails, the original error
-// returned by f is still returned. If the commit fails, the commit error is
-// returned. As callers may expect retries of the f closure, the reset function
-// will be called before each retry respectively.
-func (db *db) Update(f func(tx walletdb.ReadWriteTx) error, reset func()) (err error) {
- return db.executeTransaction(f, reset, false)
-}
-
-// executeTransaction creates a new read-only or read-write transaction and
-// executes the given function within it.
-func (db *db) executeTransaction(f func(tx walletdb.ReadWriteTx) error,
- reset func(), readOnly bool) error {
+ walletdb.DB, error) {
- reset()
-
- tx, err := newReadWriteTx(db, readOnly)
- if err != nil {
- return err
- }
-
- err = catchPanic(func() error { return f(tx) })
- if err != nil {
- if rollbackErr := tx.Rollback(); rollbackErr != nil {
- log.Errorf("Error rolling back tx: %v", rollbackErr)
- }
-
- return err
+ cfg := &sqlbase.Config{
+ DriverName: "pgx",
+ Dsn: config.Dsn,
+ Timeout: config.Timeout,
+ Schema: "public",
+ TableNamePrefix: prefix,
+ SQLiteCmdReplacements: sqliteCmdReplacements,
+ WithTxLevelLock: true,
}
- return tx.Commit()
-}
-
-// PrintStats returns all collected stats pretty printed into a string.
-func (db *db) PrintStats() string {
- return "stats not supported by Postgres driver"
-}
-
-// BeginReadWriteTx opens a database read+write transaction.
-func (db *db) BeginReadWriteTx() (walletdb.ReadWriteTx, error) {
- return newReadWriteTx(db, false)
-}
-
-// BeginReadTx opens a database read transaction.
-func (db *db) BeginReadTx() (walletdb.ReadTx, error) {
- return newReadWriteTx(db, true)
-}
-
-// Copy writes a copy of the database to the provided writer. This call will
-// start a read-only transaction to perform all operations.
-// This function is part of the walletdb.Db interface implementation.
-func (db *db) Copy(w io.Writer) error {
- return errors.New("not implemented")
-}
-
-// Close cleanly shuts down the database and syncs all data.
-// This function is part of the walletdb.Db interface implementation.
-func (db *db) Close() error {
- log.Infof("Closing database %v", db.prefix)
-
- return dbConns.Close(db.cfg.Dsn)
+ return sqlbase.NewSqlBackend(ctx, cfg)
}
diff --git a/kvdb/postgres/db_test.go b/kvdb/postgres/db_test.go
index 84c2b65038e..0378b4dbb85 100644
--- a/kvdb/postgres/db_test.go
+++ b/kvdb/postgres/db_test.go
@@ -1,5 +1,4 @@
//go:build kvdb_postgres
-// +build kvdb_postgres
package postgres
@@ -42,7 +41,7 @@ func TestPanic(t *testing.T) {
f, err := NewFixture("")
require.NoError(t, err)
- err = f.Db.(*db).Update(func(tx walletdb.ReadWriteTx) error {
+ err = f.Db.Update(func(tx walletdb.ReadWriteTx) error {
bucket, err := tx.CreateTopLevelBucket([]byte("test"))
require.NoError(t, err)
diff --git a/kvdb/postgres/driver.go b/kvdb/postgres/driver.go
index 1792eb6529a..4bc22052fa7 100644
--- a/kvdb/postgres/driver.go
+++ b/kvdb/postgres/driver.go
@@ -1,5 +1,4 @@
//go:build kvdb_postgres
-// +build kvdb_postgres
package postgres
diff --git a/kvdb/postgres/fixture.go b/kvdb/postgres/fixture.go
index 94356c31a81..26d95c33d34 100644
--- a/kvdb/postgres/fixture.go
+++ b/kvdb/postgres/fixture.go
@@ -1,5 +1,4 @@
//go:build kvdb_postgres
-// +build kvdb_postgres
package postgres
@@ -14,6 +13,7 @@ import (
"github.com/btcsuite/btcwallet/walletdb"
embeddedpostgres "github.com/fergusstrange/embedded-postgres"
+ "github.com/lightningnetwork/lnd/kvdb/sqlbase"
)
const (
@@ -33,7 +33,7 @@ const testMaxConnections = 50
// to be done once, because NewFixture will create random new databases on every
// call. It returns a stop closure that stops the database if called.
func StartEmbeddedPostgres() (func() error, error) {
- Init(testMaxConnections)
+ sqlbase.Init(testMaxConnections)
postgres := embeddedpostgres.NewDatabase(
embeddedpostgres.DefaultConfig().
diff --git a/kvdb/postgres/no_db.go b/kvdb/postgres/no_db.go
deleted file mode 100644
index edac449e41d..00000000000
--- a/kvdb/postgres/no_db.go
+++ /dev/null
@@ -1,6 +0,0 @@
-//go:build !kvdb_postgres
-// +build !kvdb_postgres
-
-package postgres
-
-func Init(maxConnections int) {}
diff --git a/kvdb/sqlbase/db.go b/kvdb/sqlbase/db.go
new file mode 100644
index 00000000000..fda628a7400
--- /dev/null
+++ b/kvdb/sqlbase/db.go
@@ -0,0 +1,267 @@
+//go:build kvdb_postgres || (kvdb_sqlite && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64)))
+
+package sqlbase
+
+import (
+ "context"
+ "database/sql"
+ "errors"
+ "fmt"
+ "io"
+ "sync"
+ "time"
+
+ "github.com/btcsuite/btcwallet/walletdb"
+)
+
+const (
+ // kvTableName is the name of the table that will contain all the kv
+ // pairs.
+ kvTableName = "kv"
+)
+
+// Config holds a set of configuration options of a sql database connection.
+type Config struct {
+ // DriverName is the string that defines the registered sql driver that
+ // is to be used.
+ DriverName string
+
+ // Dsn is the database connection string that will be used to connect
+ // to the db.
+ Dsn string
+
+ // Timeout is the time after which a query to the db will be canceled if
+ // it has not yet completed.
+ Timeout time.Duration
+
+ // Schema is the name of the schema under which the sql tables should be
+ // created. It should be left empty for backends like sqlite that do not
+ // support having more than one schema.
+ Schema string
+
+ // TableNamePrefix is the name that should be used as a table name
+ // prefix when constructing the KV style table.
+ TableNamePrefix string
+
+ // SQLiteCmdReplacements define a one-to-one string mapping of sql
+ // keywords to the strings that should replace those keywords in any
+ // commands. Note that the sqlite keywords to be replaced are
+ // case-sensitive.
+ SQLiteCmdReplacements SQLiteCmdReplacements
+
+ // WithTxLevelLock when set will ensure that there is a transaction
+ // level lock.
+ WithTxLevelLock bool
+}
+
+// db holds a reference to the sql db connection.
+type db struct {
+ // cfg is the sql db connection config.
+ cfg *Config
+
+ // prefix is the table name prefix that is used to simulate namespaces.
+ // We don't use schemas because at least sqlite does not support that.
+ prefix string
+
+ // ctx is the overall context for the database driver.
+ //
+ // TODO: This is an anti-pattern that is in place until the kvdb
+ // interface supports a context.
+ ctx context.Context
+
+ // db is the underlying database connection instance.
+ db *sql.DB
+
+ // lock is the global write lock that ensures single writer. This is
+ // only used if cfg.WithTxLevelLock is set.
+ lock sync.RWMutex
+
+ // table is the name of the table that contains the data for all
+ // top-level buckets that have keys that cannot be mapped to a distinct
+ // sql table.
+ table string
+}
+
+// Enforce db implements the walletdb.DB interface.
+var _ walletdb.DB = (*db)(nil)
+
+var (
+ // dbConns is a global set of database connections.
+ dbConns *dbConnSet
+ dbConnsMu sync.Mutex
+)
+
+// Init initializes the global set of database connections.
+func Init(maxConnections int) {
+ dbConnsMu.Lock()
+ defer dbConnsMu.Unlock()
+
+ if dbConns != nil {
+ return
+ }
+
+ dbConns = newDbConnSet(maxConnections)
+}
+
+// NewSqlBackend returns a db object initialized with the passed backend
+// config. If database connection cannot be established, then returns error.
+func NewSqlBackend(ctx context.Context, cfg *Config) (*db, error) {
+ dbConnsMu.Lock()
+ defer dbConnsMu.Unlock()
+
+ if dbConns == nil {
+ return nil, errors.New("db connection set not initialized")
+ }
+
+ if cfg.TableNamePrefix == "" {
+ return nil, errors.New("empty table name prefix")
+ }
+
+ table := fmt.Sprintf("%s_%s", cfg.TableNamePrefix, kvTableName)
+
+ query := newKVSchemaCreationCmd(
+ table, cfg.Schema, cfg.SQLiteCmdReplacements,
+ )
+
+ dbConn, err := dbConns.Open(cfg.DriverName, cfg.Dsn)
+ if err != nil {
+ return nil, err
+ }
+
+ _, err = dbConn.ExecContext(ctx, query)
+ if err != nil {
+ _ = dbConn.Close()
+
+ return nil, err
+ }
+
+ return &db{
+ cfg: cfg,
+ ctx: ctx,
+ db: dbConn,
+ table: table,
+ prefix: cfg.TableNamePrefix,
+ }, nil
+}
+
+// getTimeoutCtx gets a timeout context for database requests.
+func (db *db) getTimeoutCtx() (context.Context, func()) {
+ if db.cfg.Timeout == time.Duration(0) {
+ return db.ctx, func() {}
+ }
+
+ return context.WithTimeout(db.ctx, db.cfg.Timeout)
+}
+
+// getPrefixedTableName returns a table name for this prefix (namespace).
+func (db *db) getPrefixedTableName(table string) string {
+ return fmt.Sprintf("%s_%s", db.prefix, table)
+}
+
+// catchPanic executes the specified function. If a panic occurs, it is returned
+// as an error value.
+func catchPanic(f func() error) (err error) {
+ defer func() {
+ if r := recover(); r != nil {
+ log.Criticalf("Caught unhandled error: %v", r)
+
+ switch data := r.(type) {
+ case error:
+ err = data
+
+ default:
+ err = errors.New(fmt.Sprintf("%v", data))
+ }
+ }
+ }()
+
+ err = f()
+
+ return
+}
+
+// View opens a database read transaction and executes the function f with the
+// transaction passed as a parameter. After f exits, the transaction is rolled
+// back. If f errors, its error is returned, not a rollback error (if any
+// occur). The passed reset function is called before the start of the
+// transaction and can be used to reset intermediate state. As callers may
+// expect retries of the f closure (depending on the database backend used), the
+// reset function will be called before each retry respectively.
+func (db *db) View(f func(tx walletdb.ReadTx) error, reset func()) error {
+ return db.executeTransaction(
+ func(tx walletdb.ReadWriteTx) error {
+ return f(tx.(walletdb.ReadTx))
+ },
+ reset, true,
+ )
+}
+
+// Update opens a database read/write transaction and executes the function f
+// with the transaction passed as a parameter. After f exits, if f did not
+// error, the transaction is committed. Otherwise, if f did error, the
+// transaction is rolled back. If the rollback fails, the original error
+// returned by f is still returned. If the commit fails, the commit error is
+// returned. As callers may expect retries of the f closure, the reset function
+// will be called before each retry respectively.
+func (db *db) Update(f func(tx walletdb.ReadWriteTx) error,
+ reset func()) error {
+
+ return db.executeTransaction(f, reset, false)
+}
+
+// executeTransaction creates a new read-only or read-write transaction and
+// executes the given function within it.
+func (db *db) executeTransaction(f func(tx walletdb.ReadWriteTx) error,
+ reset func(), readOnly bool) error {
+
+ reset()
+
+ tx, err := newReadWriteTx(db, readOnly)
+ if err != nil {
+ return err
+ }
+
+ err = catchPanic(func() error { return f(tx) })
+ if err != nil {
+ if rollbackErr := tx.Rollback(); rollbackErr != nil {
+ log.Errorf("Error rolling back tx: %v", rollbackErr)
+ }
+
+ return err
+ }
+
+ return tx.Commit()
+}
+
+// PrintStats returns all collected stats pretty printed into a string.
+func (db *db) PrintStats() string {
+ return "stats not supported by SQL driver"
+}
+
+// BeginReadWriteTx opens a database read+write transaction.
+func (db *db) BeginReadWriteTx() (walletdb.ReadWriteTx, error) {
+ return newReadWriteTx(db, false)
+}
+
+// BeginReadTx opens a database read transaction.
+func (db *db) BeginReadTx() (walletdb.ReadTx, error) {
+ return newReadWriteTx(db, true)
+}
+
+// Copy writes a copy of the database to the provided writer. This call will
+// start a read-only transaction to perform all operations.
+// This function is part of the walletdb.Db interface implementation.
+func (db *db) Copy(w io.Writer) error {
+ return errors.New("not implemented")
+}
+
+// Close cleanly shuts down the database and syncs all data.
+// This function is part of the walletdb.Db interface implementation.
+func (db *db) Close() error {
+ dbConnsMu.Lock()
+ defer dbConnsMu.Unlock()
+
+ log.Infof("Closing database %v", db.prefix)
+
+ return dbConns.Close(db.cfg.Dsn)
+}
diff --git a/kvdb/postgres/db_conn_set.go b/kvdb/sqlbase/db_conn_set.go
similarity index 86%
rename from kvdb/postgres/db_conn_set.go
rename to kvdb/sqlbase/db_conn_set.go
index 062a977dc7e..ee360a97397 100644
--- a/kvdb/postgres/db_conn_set.go
+++ b/kvdb/sqlbase/db_conn_set.go
@@ -1,4 +1,4 @@
-package postgres
+package sqlbase
import (
"database/sql"
@@ -19,7 +19,8 @@ type dbConnSet struct {
dbConn map[string]*dbConn
maxConnections int
- sync.Mutex
+ // mu is used to guard access to the dbConn map.
+ mu sync.Mutex
}
// newDbConnSet initializes a new set of connections.
@@ -32,9 +33,9 @@ func newDbConnSet(maxConnections int) *dbConnSet {
// Open opens a new database connection. If a connection already exists for the
// given dsn, the existing connection is returned.
-func (d *dbConnSet) Open(dsn string) (*sql.DB, error) {
- d.Lock()
- defer d.Unlock()
+func (d *dbConnSet) Open(driver, dsn string) (*sql.DB, error) {
+ d.mu.Lock()
+ defer d.mu.Unlock()
if dbConn, ok := d.dbConn[dsn]; ok {
dbConn.count++
@@ -42,7 +43,7 @@ func (d *dbConnSet) Open(dsn string) (*sql.DB, error) {
return dbConn.db, nil
}
- db, err := sql.Open("pgx", dsn)
+ db, err := sql.Open(driver, dsn)
if err != nil {
return nil, err
}
@@ -66,8 +67,8 @@ func (d *dbConnSet) Open(dsn string) (*sql.DB, error) {
// Close closes the connection with the given dsn. If there are still other
// users of the same connection, this function does nothing.
func (d *dbConnSet) Close(dsn string) error {
- d.Lock()
- defer d.Unlock()
+ d.mu.Lock()
+ defer d.mu.Unlock()
dbConn, ok := d.dbConn[dsn]
if !ok {
diff --git a/kvdb/postgres/log.go b/kvdb/sqlbase/log.go
similarity index 95%
rename from kvdb/postgres/log.go
rename to kvdb/sqlbase/log.go
index 5a27e9eac0f..c6cbe6577be 100644
--- a/kvdb/postgres/log.go
+++ b/kvdb/sqlbase/log.go
@@ -1,4 +1,4 @@
-package postgres
+package sqlbase
import "github.com/btcsuite/btclog"
diff --git a/kvdb/sqlbase/no_sql.go b/kvdb/sqlbase/no_sql.go
new file mode 100644
index 00000000000..6661757820a
--- /dev/null
+++ b/kvdb/sqlbase/no_sql.go
@@ -0,0 +1,5 @@
+//go:build !kvdb_postgres && (!kvdb_sqlite || (windows && (arm || 386)) || (linux && (ppc64 || mips || mipsle || mips64)))
+
+package sqlbase
+
+func Init(maxConnections int) {}
diff --git a/kvdb/postgres/readwrite_bucket.go b/kvdb/sqlbase/readwrite_bucket.go
similarity index 95%
rename from kvdb/postgres/readwrite_bucket.go
rename to kvdb/sqlbase/readwrite_bucket.go
index 933769919fc..f8913723f86 100644
--- a/kvdb/postgres/readwrite_bucket.go
+++ b/kvdb/sqlbase/readwrite_bucket.go
@@ -1,7 +1,6 @@
-//go:build kvdb_postgres
-// +build kvdb_postgres
+//go:build kvdb_postgres || (kvdb_sqlite && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64)))
-package postgres
+package sqlbase
import (
"database/sql"
@@ -90,6 +89,15 @@ func (b *readWriteBucket) Get(key []byte) []byte {
panic(err)
}
+ // When an empty byte array is stored as the value, Sqlite will decode
+ // that into nil whereas postgres will decode that as an empty byte
+ // array. Since returning nil is taken to mean that no value has ever
+ // been written, we ensure here that we at least return an empty array
+ // so that nil checks will fail.
+ if len(*value) == 0 {
+ return []byte{}
+ }
+
return *value
}
diff --git a/kvdb/postgres/readwrite_cursor.go b/kvdb/sqlbase/readwrite_cursor.go
similarity index 97%
rename from kvdb/postgres/readwrite_cursor.go
rename to kvdb/sqlbase/readwrite_cursor.go
index 80e321e061f..3124f915c0e 100644
--- a/kvdb/postgres/readwrite_cursor.go
+++ b/kvdb/sqlbase/readwrite_cursor.go
@@ -1,7 +1,6 @@
-//go:build kvdb_postgres
-// +build kvdb_postgres
+//go:build kvdb_postgres || (kvdb_sqlite && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64)))
-package postgres
+package sqlbase
import (
"database/sql"
diff --git a/kvdb/postgres/readwrite_tx.go b/kvdb/sqlbase/readwrite_tx.go
similarity index 80%
rename from kvdb/postgres/readwrite_tx.go
rename to kvdb/sqlbase/readwrite_tx.go
index e0c3e23710e..7286f1db9bb 100644
--- a/kvdb/postgres/readwrite_tx.go
+++ b/kvdb/sqlbase/readwrite_tx.go
@@ -1,7 +1,6 @@
-//go:build kvdb_postgres
-// +build kvdb_postgres
+//go:build kvdb_postgres || (kvdb_sqlite && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64)))
-package postgres
+package sqlbase
import (
"context"
@@ -29,14 +28,17 @@ type readWriteTx struct {
// newReadWriteTx creates an rw transaction using a connection from the
// specified pool.
func newReadWriteTx(db *db, readOnly bool) (*readWriteTx, error) {
- // Obtain the global lock instance. An alternative here is to obtain a
- // database lock from Postgres. Unfortunately there is no database-level
- // lock in Postgres, meaning that each table would need to be locked
- // individually. Perhaps an advisory lock could perform this function
- // too.
- var locker sync.Locker = &db.lock
- if readOnly {
- locker = db.lock.RLocker()
+ locker := newNoopLocker()
+ if db.cfg.WithTxLevelLock {
+ // Obtain the global lock instance. An alternative here is to
+ // obtain a database lock from Postgres. Unfortunately there is
+ // no database-level lock in Postgres, meaning that each table
+ // would need to be locked individually. Perhaps an advisory
+ // lock could perform this function too.
+ locker = &db.lock
+ if readOnly {
+ locker = db.lock.RLocker()
+ }
}
locker.Lock()
@@ -108,7 +110,9 @@ func (tx *readWriteTx) ReadWriteBucket(key []byte) walletdb.ReadWriteBucket {
// CreateTopLevelBucket creates the top level bucket for a key if it
// does not exist. The newly-created bucket it returned.
-func (tx *readWriteTx) CreateTopLevelBucket(key []byte) (walletdb.ReadWriteBucket, error) {
+func (tx *readWriteTx) CreateTopLevelBucket(key []byte) (
+ walletdb.ReadWriteBucket, error) {
+
if len(key) == 0 {
return nil, walletdb.ErrBucketNameRequired
}
@@ -199,3 +203,25 @@ func (tx *readWriteTx) Exec(query string, args ...interface{}) (sql.Result,
return tx.tx.ExecContext(ctx, query, args...)
}
+
+// noopLocker is an implementation of a no-op sync.Locker.
+type noopLocker struct{}
+
+// newNoopLocker creates a new noopLocker.
+func newNoopLocker() sync.Locker {
+ return &noopLocker{}
+}
+
+// Lock is a noop.
+//
+// NOTE: this is part of the sync.Locker interface.
+func (n *noopLocker) Lock() {
+}
+
+// Unlock is a noop.
+//
+// NOTE: this is part of the sync.Locker interface.
+func (n *noopLocker) Unlock() {
+}
+
+var _ sync.Locker = (*noopLocker)(nil)
diff --git a/kvdb/sqlbase/schema.go b/kvdb/sqlbase/schema.go
new file mode 100644
index 00000000000..1ff3aefb942
--- /dev/null
+++ b/kvdb/sqlbase/schema.go
@@ -0,0 +1,74 @@
+//go:build kvdb_postgres || (kvdb_sqlite && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64)))
+
+package sqlbase
+
+import (
+ "fmt"
+ "strings"
+)
+
+// SQLiteCmdReplacements is a one to one mapping of sqlite keywords that should
+// be replaced by the mapped strings in any command. Note that the sqlite
+// keywords to be replaced are case-sensitive.
+type SQLiteCmdReplacements map[string]string
+
+func newKVSchemaCreationCmd(table, schema string,
+ replacements SQLiteCmdReplacements) string {
+
+ var (
+ tableInSchema = table
+ finalCmd string
+ )
+ if schema != "" {
+ finalCmd = fmt.Sprintf(
+ `CREATE SCHEMA IF NOT EXISTS ` + schema + `;`,
+ )
+
+ tableInSchema = fmt.Sprintf("%s.%s", schema, table)
+ }
+
+ // Construct the sql statements to set up a kv table in postgres. Every
+ // row points to the bucket that it is one via its parent_id field. A
+ // NULL parent_id means that the key belongs to the uppermost bucket in
+ // this table. A constraint on parent_id is enforcing referential
+ // integrity.
+ //
+ // Furthermore, there is a _p index on parent_id that is required
+ // for the foreign key constraint.
+ //
+ // Finally, there are unique indices on (parent_id, key) to prevent the
+ // same key being present in a bucket more than once (_up and
+ // _unp). In postgres, a single index wouldn't enforce the unique
+ // constraint on rows with a NULL parent_id. Therefore, two indices are
+ // defined.
+ //
+ // The replacements map can be used to replace any sqlite keywords.
+ // Callers should note that the sqlite keywords are case-sensitive.
+ finalCmd += fmt.Sprintf(`
+CREATE TABLE IF NOT EXISTS ` + tableInSchema + `
+(
+ key BLOB NOT NULL,
+ value BLOB,
+ parent_id BIGINT,
+ id INTEGER PRIMARY KEY,
+ sequence BIGINT,
+ CONSTRAINT ` + table + `_parent FOREIGN KEY (parent_id)
+ REFERENCES ` + tableInSchema + ` (id)
+ ON UPDATE NO ACTION
+ ON DELETE CASCADE
+);
+CREATE INDEX IF NOT EXISTS ` + table + `_p
+ ON ` + tableInSchema + ` (parent_id);
+CREATE UNIQUE INDEX IF NOT EXISTS ` + table + `_up
+ ON ` + tableInSchema + `
+ (parent_id, key) WHERE parent_id IS NOT NULL;
+CREATE UNIQUE INDEX IF NOT EXISTS ` + table + `_unp
+ ON ` + tableInSchema + ` (key) WHERE parent_id IS NULL;
+`)
+
+ for from, to := range replacements {
+ finalCmd = strings.Replace(finalCmd, from, to, -1)
+ }
+
+ return finalCmd
+}
diff --git a/kvdb/sqlite/config.go b/kvdb/sqlite/config.go
new file mode 100644
index 00000000000..f60abcfc009
--- /dev/null
+++ b/kvdb/sqlite/config.go
@@ -0,0 +1,13 @@
+package sqlite
+
+import "time"
+
+// Config holds sqlite configuration data.
+//
+//nolint:lll
+type Config 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."`
+}
diff --git a/kvdb/sqlite/db.go b/kvdb/sqlite/db.go
new file mode 100644
index 00000000000..f04bec3be02
--- /dev/null
+++ b/kvdb/sqlite/db.go
@@ -0,0 +1,83 @@
+//go:build kvdb_sqlite && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64))
+
+package sqlite
+
+import (
+ "context"
+ "fmt"
+ "net/url"
+ "path/filepath"
+
+ "github.com/btcsuite/btcwallet/walletdb"
+ "github.com/lightningnetwork/lnd/kvdb/sqlbase"
+ _ "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"
+)
+
+// NewSqliteBackend returns a db object initialized with the passed backend
+// config. If a sqlite connection cannot be established, then an error is
+// returned.
+func NewSqliteBackend(ctx context.Context, cfg *Config, dbPath, fileName,
+ prefix string) (walletdb.DB, error) {
+
+ // First, we add a set of mandatory pragma options to the query.
+ pragmaOptions := []struct {
+ name string
+ value string
+ }{
+ {
+ name: "busy_timeout",
+ value: fmt.Sprintf(
+ "%d", cfg.BusyTimeout.Milliseconds(),
+ ),
+ },
+ {
+ name: "foreign_keys",
+ value: "on",
+ },
+ {
+ name: "journal_mode",
+ value: "WAL",
+ },
+ }
+ sqliteOptions := make(url.Values)
+ for _, option := range pragmaOptions {
+ sqliteOptions.Add(
+ sqliteOptionPrefix,
+ fmt.Sprintf("%v=%v", option.name, option.value),
+ )
+ }
+
+ // Then we add any user specified pragma options. Note that these can
+ // be of the form: "key=value", "key(N)" or "key".
+ for _, option := range cfg.PragmaOptions {
+ sqliteOptions.Add(sqliteOptionPrefix, option)
+ }
+
+ // 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", filepath.Join(dbPath, fileName),
+ sqliteOptions.Encode(), sqliteTxLockImmediate,
+ )
+ sqlCfg := &sqlbase.Config{
+ DriverName: "sqlite",
+ Dsn: dsn,
+ Timeout: cfg.Timeout,
+ TableNamePrefix: prefix,
+ }
+
+ return sqlbase.NewSqlBackend(ctx, sqlCfg)
+}
diff --git a/kvdb/sqlite/db_test.go b/kvdb/sqlite/db_test.go
new file mode 100644
index 00000000000..f12acf47f7c
--- /dev/null
+++ b/kvdb/sqlite/db_test.go
@@ -0,0 +1,35 @@
+//go:build kvdb_sqlite && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64))
+
+package sqlite
+
+import (
+ "testing"
+ "time"
+
+ "github.com/btcsuite/btcwallet/walletdb/walletdbtest"
+ "github.com/lightningnetwork/lnd/kvdb/sqlbase"
+ "github.com/stretchr/testify/require"
+ "golang.org/x/net/context"
+)
+
+// TestInterface performs all interfaces tests for this database driver.
+func TestInterface(t *testing.T) {
+ // dbType is the database type name for this driver.
+ dir := t.TempDir()
+ ctx := context.Background()
+
+ sqlbase.Init(0)
+
+ cfg := &Config{
+ BusyTimeout: time.Second * 5,
+ }
+
+ sqlDB, err := NewSqliteBackend(ctx, cfg, dir, "tmp.db", "table")
+ require.NoError(t, err)
+
+ t.Cleanup(func() {
+ require.NoError(t, sqlDB.Close())
+ })
+
+ walletdbtest.TestInterface(t, dbType, ctx, cfg, dir, "tmp.db", "temp")
+}
diff --git a/kvdb/sqlite/driver.go b/kvdb/sqlite/driver.go
new file mode 100644
index 00000000000..efd7892b18d
--- /dev/null
+++ b/kvdb/sqlite/driver.go
@@ -0,0 +1,97 @@
+//go:build kvdb_sqlite && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64))
+
+package sqlite
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/btcsuite/btcwallet/walletdb"
+)
+
+const (
+ dbType = "sqlite"
+)
+
+// parseArgs parses the arguments from the walletdb Open/Create methods.
+func parseArgs(funcName string, args ...interface{}) (context.Context, *Config,
+ string, string, string, error) {
+
+ if len(args) != 5 {
+ return nil, nil, "", "", "", fmt.Errorf("invalid number of "+
+ "arguments to %s.%s -- expected: context.Context, "+
+ "sql.Config, string, string, string", dbType, funcName)
+ }
+
+ ctx, ok := args[0].(context.Context)
+ if !ok {
+ return nil, nil, "", "", "", fmt.Errorf("argument 0 to %s.%s "+
+ "is invalid -- expected: context.Context", dbType,
+ funcName)
+ }
+
+ config, ok := args[1].(*Config)
+ if !ok {
+ return nil, nil, "", "", "", fmt.Errorf("argument 1 to %s.%s "+
+ "is invalid -- expected: sqlite.Config", dbType,
+ funcName)
+ }
+
+ dbPath, ok := args[2].(string)
+ if !ok {
+ return nil, nil, "", "", "", fmt.Errorf("argument 2 to %s.%s "+
+ "is invalid -- expected string", dbType, dbPath)
+ }
+
+ fileName, ok := args[3].(string)
+ if !ok {
+ return nil, nil, "", "", "", fmt.Errorf("argument 3 to %s.%s "+
+ "is invalid -- expected string", dbType, funcName)
+ }
+
+ prefix, ok := args[4].(string)
+ if !ok {
+ return nil, nil, "", "", "", fmt.Errorf("argument 4 to %s.%s "+
+ "is invalid -- expected string", dbType, funcName,
+ )
+ }
+
+ return ctx, config, dbPath, fileName, prefix, nil
+}
+
+// createDBDriver is the callback provided during driver registration that
+// creates, initializes, and opens a database for use.
+func createDBDriver(args ...interface{}) (walletdb.DB, error) {
+ ctx, config, dbPath, filename, prefix, err := parseArgs(
+ "Create", args...,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ return NewSqliteBackend(ctx, config, dbPath, filename, prefix)
+}
+
+// openDBDriver is the callback provided during driver registration that opens
+// an existing database for use.
+func openDBDriver(args ...interface{}) (walletdb.DB, error) {
+ ctx, config, dbPath, filename, prefix, err := parseArgs("Open", args...)
+ if err != nil {
+ return nil, err
+ }
+
+ return NewSqliteBackend(ctx, config, dbPath, filename, prefix)
+}
+
+func init() {
+ // Register the driver.
+ driver := walletdb.Driver{
+ DbType: dbType,
+ Create: createDBDriver,
+ Open: openDBDriver,
+ }
+ if err := walletdb.RegisterDriver(driver); err != nil {
+ panic(fmt.Sprintf("Failed to regiser database driver '%s': %v",
+ dbType, err))
+ }
+}