mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 01:43:16 +01:00
d0c1cec8c1
Completely switch to the better maintained pgx driver.
151 lines
4.0 KiB
Go
151 lines
4.0 KiB
Go
package sqldb
|
|
|
|
import (
|
|
"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:lll
|
|
_ "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": "SERIAL PRIMARY KEY",
|
|
"BIGINT PRIMARY KEY": "BIGSERIAL PRIMARY KEY",
|
|
"TIMESTAMP": "TIMESTAMP WITHOUT TIME ZONE",
|
|
}
|
|
)
|
|
|
|
// 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)
|
|
|
|
rawDB, err := sql.Open("pgx", cfg.Dsn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
maxConns := defaultMaxConns
|
|
if cfg.MaxConnections > 0 {
|
|
maxConns = cfg.MaxConnections
|
|
}
|
|
|
|
rawDB.SetMaxOpenConns(maxConns)
|
|
rawDB.SetMaxIdleConns(maxConns)
|
|
rawDB.SetConnMaxLifetime(connIdleLifetime)
|
|
|
|
queries := sqlc.New(rawDB)
|
|
|
|
s := &PostgresStore{
|
|
cfg: cfg,
|
|
BaseDB: &BaseDB{
|
|
DB: rawDB,
|
|
Queries: queries,
|
|
},
|
|
}
|
|
|
|
// Execute migrations unless configured to skip them.
|
|
if !cfg.SkipMigrations {
|
|
err := s.ExecuteMigrations(TargetLatest)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error executing migrations: %w",
|
|
err)
|
|
}
|
|
}
|
|
|
|
return s, nil
|
|
}
|
|
|
|
// 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 fmt.Errorf("error creating postgres migration: %w", 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,
|
|
)
|
|
}
|