2023-06-05 21:54:52 -07:00
|
|
|
package sqldb
|
|
|
|
|
|
|
|
import (
|
2024-11-22 18:29:54 +01:00
|
|
|
"context"
|
2023-06-05 21:54:52 -07:00
|
|
|
"database/sql"
|
2024-06-25 15:18:28 +02:00
|
|
|
"fmt"
|
2023-12-06 16:31:25 +01:00
|
|
|
"net/url"
|
|
|
|
"path"
|
|
|
|
"strings"
|
2023-06-05 21:54:52 -07:00
|
|
|
"time"
|
|
|
|
|
2024-07-03 08:32:48 +02:00
|
|
|
pgx_migrate "github.com/golang-migrate/migrate/v4/database/pgx/v5"
|
2024-11-29 11:16:13 +02:00
|
|
|
_ "github.com/golang-migrate/migrate/v4/source/file" // Read migrations from files. // nolint:ll
|
2024-07-03 08:32:48 +02:00
|
|
|
_ "github.com/jackc/pgx/v5"
|
2023-06-05 21:54:52 -07:00
|
|
|
"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
|
2024-06-25 15:18:28 +02:00
|
|
|
|
|
|
|
// 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",
|
|
|
|
}
|
2024-11-22 18:29:54 +01:00
|
|
|
|
|
|
|
// Make sure PostgresStore implements the MigrationExecutor interface.
|
|
|
|
_ MigrationExecutor = (*PostgresStore)(nil)
|
2024-11-25 20:29:08 +01:00
|
|
|
|
|
|
|
// Make sure PostgresStore implements the DB interface.
|
|
|
|
_ DB = (*PostgresStore)(nil)
|
2023-06-05 21:54:52 -07:00
|
|
|
)
|
|
|
|
|
2023-12-06 16:31:25 +01:00
|
|
|
// 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
|
2023-06-05 21:54:52 -07:00
|
|
|
}
|
|
|
|
|
2023-12-06 16:31:25 +01:00
|
|
|
// 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
|
2023-06-05 21:54:52 -07:00
|
|
|
}
|
|
|
|
|
2023-12-06 16:31:25 +01:00
|
|
|
// 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
|
2023-06-05 21:54:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2024-11-25 20:29:08 +01:00
|
|
|
func NewPostgresStore(cfg *PostgresConfig) (*PostgresStore, error) {
|
2023-12-06 16:31:25 +01:00
|
|
|
sanitizedDSN, err := replacePasswordInDSN(cfg.Dsn)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
log.Infof("Using SQL database '%s'", sanitizedDSN)
|
|
|
|
|
2024-11-22 18:29:54 +01:00
|
|
|
db, err := sql.Open("pgx", cfg.Dsn)
|
2023-06-05 21:54:52 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-11-22 18:29:54 +01:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2023-06-05 21:54:52 -07:00
|
|
|
maxConns := defaultMaxConns
|
2023-12-06 16:31:25 +01:00
|
|
|
if cfg.MaxConnections > 0 {
|
|
|
|
maxConns = cfg.MaxConnections
|
2023-06-05 21:54:52 -07:00
|
|
|
}
|
|
|
|
|
2024-11-22 18:29:54 +01:00
|
|
|
db.SetMaxOpenConns(maxConns)
|
|
|
|
db.SetMaxIdleConns(maxConns)
|
|
|
|
db.SetConnMaxLifetime(connIdleLifetime)
|
2023-06-05 21:54:52 -07:00
|
|
|
|
2024-11-22 18:29:54 +01:00
|
|
|
queries := sqlc.New(db)
|
2024-06-25 15:18:28 +02:00
|
|
|
|
2024-11-25 20:29:08 +01:00
|
|
|
return &PostgresStore{
|
2024-06-25 15:18:28 +02:00
|
|
|
cfg: cfg,
|
|
|
|
BaseDB: &BaseDB{
|
2024-11-22 18:29:54 +01:00
|
|
|
DB: db,
|
2024-06-25 15:18:28 +02:00
|
|
|
Queries: queries,
|
|
|
|
},
|
2024-11-25 20:29:08 +01:00
|
|
|
}, 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 {
|
2024-06-25 15:18:28 +02:00
|
|
|
|
|
|
|
// Execute migrations unless configured to skip them.
|
2024-11-25 20:29:08 +01:00
|
|
|
if s.cfg.SkipMigrations {
|
|
|
|
return nil
|
2024-06-25 15:18:28 +02:00
|
|
|
}
|
2023-06-05 21:54:52 -07:00
|
|
|
|
2024-11-25 20:29:08 +01:00
|
|
|
return ApplyMigrations(ctx, s.BaseDB, s, migrations)
|
2024-06-25 15:18:28 +02:00
|
|
|
}
|
2023-06-05 21:54:52 -07:00
|
|
|
|
2024-06-25 15:18:28 +02:00
|
|
|
// 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
|
2023-06-05 21:54:52 -07:00
|
|
|
}
|
|
|
|
|
2024-07-03 08:32:48 +02:00
|
|
|
driver, err := pgx_migrate.WithInstance(s.DB, &pgx_migrate.Config{})
|
2024-06-25 15:18:28 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error creating postgres migration: %w", err)
|
|
|
|
}
|
2023-06-05 21:54:52 -07:00
|
|
|
|
2024-06-25 15:18:28 +02:00
|
|
|
// 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,
|
|
|
|
)
|
2023-06-05 21:54:52 -07:00
|
|
|
}
|