kvdb: add sqlite

This commit is contained in:
Elle Mouton
2022-12-15 18:13:13 +02:00
parent 3d91bb65f7
commit 74b9c9ce9a
16 changed files with 374 additions and 10 deletions

13
kvdb/sqlite/config.go Normal file
View File

@ -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."`
}

83
kvdb/sqlite/db.go Normal file
View File

@ -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)
}

35
kvdb/sqlite/db_test.go Normal file
View File

@ -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")
}

97
kvdb/sqlite/driver.go Normal file
View File

@ -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))
}
}