lnd/sqldb/postgres.go

140 lines
3.4 KiB
Go

package sqldb
import (
"database/sql"
"net/url"
"path"
"strings"
"time"
postgres_migrate "github.com/golang-migrate/migrate/v4/database/postgres"
_ "github.com/golang-migrate/migrate/v4/source/file" // Read migrations from files. // nolint:lll
"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
)
// 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)
dbName, err := getDatabaseNameFromDSN(cfg.Dsn)
if err != nil {
return nil, err
}
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)
if !cfg.SkipMigrations {
// Now that the database is open, populate the database with
// our set of schemas based on our embedded in-memory file
// system.
//
// First, we'll need to open up a new migration instance for
// our current target database: Postgres.
driver, err := postgres_migrate.WithInstance(
rawDB, &postgres_migrate.Config{},
)
if err != nil {
return nil, err
}
postgresFS := newReplacerFS(sqlSchemas, map[string]string{
"BLOB": "BYTEA",
"INTEGER PRIMARY KEY": "SERIAL PRIMARY KEY",
"BIGINT PRIMARY KEY": "BIGSERIAL PRIMARY KEY",
"TIMESTAMP": "TIMESTAMP WITHOUT TIME ZONE",
})
err = applyMigrations(
postgresFS, driver, "sqlc/migrations", dbName,
)
if err != nil {
return nil, err
}
}
queries := sqlc.New(rawDB)
return &PostgresStore{
cfg: cfg,
BaseDB: &BaseDB{
DB: rawDB,
Queries: queries,
},
}, nil
}