dbmate/pkg/dbmate/postgres.go
Adrian Macneil b69a3d487a
Fix wait command for servers with no postgres database (#90)
Currently the `dbmate wait` command fails if the `postgres` system database does not exist (which is common on DigitalOcean, and perhaps other hosting providers).

This command is intended to verify that the postgres server is available, and not whether or not the user's database exists. Therefore, we will update this command to simply ignore the `database "foo" does not exist` error and treat this as the server being ready.

Fixes #78
2019-09-19 09:36:34 +12:00

201 lines
4.7 KiB
Go

package dbmate
import (
"bytes"
"database/sql"
"fmt"
"net/url"
"strings"
"github.com/lib/pq"
)
func init() {
RegisterDriver(PostgresDriver{}, "postgres")
RegisterDriver(PostgresDriver{}, "postgresql")
}
// PostgresDriver provides top level database functions
type PostgresDriver struct {
}
// Open creates a new database connection
func (drv PostgresDriver) Open(u *url.URL) (*sql.DB, error) {
return sql.Open("postgres", u.String())
}
func (drv PostgresDriver) openPostgresDB(u *url.URL) (*sql.DB, error) {
// connect to postgres database
postgresURL := *u
postgresURL.Path = "postgres"
return drv.Open(&postgresURL)
}
// CreateDatabase creates the specified database
func (drv PostgresDriver) CreateDatabase(u *url.URL) error {
name := databaseName(u)
fmt.Printf("Creating: %s\n", name)
db, err := drv.openPostgresDB(u)
if err != nil {
return err
}
defer mustClose(db)
_, err = db.Exec(fmt.Sprintf("create database %s",
pq.QuoteIdentifier(name)))
return err
}
// DropDatabase drops the specified database (if it exists)
func (drv PostgresDriver) DropDatabase(u *url.URL) error {
name := databaseName(u)
fmt.Printf("Dropping: %s\n", name)
db, err := drv.openPostgresDB(u)
if err != nil {
return err
}
defer mustClose(db)
_, err = db.Exec(fmt.Sprintf("drop database if exists %s",
pq.QuoteIdentifier(name)))
return err
}
func postgresSchemaMigrationsDump(db *sql.DB) ([]byte, error) {
// load applied migrations
migrations, err := queryColumn(db,
"select quote_literal(version) from public.schema_migrations order by version asc")
if err != nil {
return nil, err
}
// build schema_migrations table data
var buf bytes.Buffer
buf.WriteString("\n--\n-- Dbmate schema migrations\n--\n\n")
if len(migrations) > 0 {
buf.WriteString("INSERT INTO public.schema_migrations (version) VALUES\n (" +
strings.Join(migrations, "),\n (") +
");\n")
}
return buf.Bytes(), nil
}
// DumpSchema returns the current database schema
func (drv PostgresDriver) DumpSchema(u *url.URL, db *sql.DB) ([]byte, error) {
// load schema
schema, err := runCommand("pg_dump", "--format=plain", "--encoding=UTF8",
"--schema-only", "--no-privileges", "--no-owner", u.String())
if err != nil {
return nil, err
}
migrations, err := postgresSchemaMigrationsDump(db)
if err != nil {
return nil, err
}
schema = append(schema, migrations...)
return trimLeadingSQLComments(schema)
}
// DatabaseExists determines whether the database exists
func (drv PostgresDriver) DatabaseExists(u *url.URL) (bool, error) {
name := databaseName(u)
db, err := drv.openPostgresDB(u)
if err != nil {
return false, err
}
defer mustClose(db)
exists := false
err = db.QueryRow("select true from pg_database where datname = $1", name).
Scan(&exists)
if err == sql.ErrNoRows {
return false, nil
}
return exists, err
}
// CreateMigrationsTable creates the schema_migrations table
func (drv PostgresDriver) CreateMigrationsTable(db *sql.DB) error {
_, err := db.Exec("create table if not exists public.schema_migrations " +
"(version varchar(255) primary key)")
return err
}
// SelectMigrations returns a list of applied migrations
// with an optional limit (in descending order)
func (drv PostgresDriver) SelectMigrations(db *sql.DB, limit int) (map[string]bool, error) {
query := "select version from public.schema_migrations order by version desc"
if limit >= 0 {
query = fmt.Sprintf("%s limit %d", query, limit)
}
rows, err := db.Query(query)
if err != nil {
return nil, err
}
defer mustClose(rows)
migrations := map[string]bool{}
for rows.Next() {
var version string
if err := rows.Scan(&version); err != nil {
return nil, err
}
migrations[version] = true
}
return migrations, nil
}
// InsertMigration adds a new migration record
func (drv PostgresDriver) InsertMigration(db Transaction, version string) error {
_, err := db.Exec("insert into public.schema_migrations (version) values ($1)", version)
return err
}
// DeleteMigration removes a migration record
func (drv PostgresDriver) DeleteMigration(db Transaction, version string) error {
_, err := db.Exec("delete from public.schema_migrations where version = $1", version)
return err
}
// Ping verifies a connection to the database server. It does not verify whether the
// specified database exists.
func (drv PostgresDriver) Ping(u *url.URL) error {
// attempt connection to primary database, not "postgres" database
// to support servers with no "postgres" database
// (see https://github.com/amacneil/dbmate/issues/78)
db, err := drv.Open(u)
if err != nil {
return err
}
defer mustClose(db)
err = db.Ping()
if err == nil {
return nil
}
// ignore 'database "foo" does not exist' error
pqErr, ok := err.(*pq.Error)
if ok && pqErr.Code == "3D000" {
return nil
}
return err
}