Skip to content

Commit

Permalink
feat: support fetching migration history table to files (#2113)
Browse files Browse the repository at this point in the history
feat: support fetching migration history to local
  • Loading branch information
sweatybridge committed Apr 29, 2024
1 parent 50aa1ab commit cecb69b
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 0 deletions.
16 changes: 16 additions & 0 deletions cmd/migration.go
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/spf13/afero"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/supabase/cli/internal/migration/fetch"
"github.com/supabase/cli/internal/migration/list"
"github.com/supabase/cli/internal/migration/new"
"github.com/supabase/cli/internal/migration/repair"
Expand Down Expand Up @@ -88,6 +89,14 @@ var (
fmt.Println("Local database is up to date.")
},
}

migrationFetchCmd = &cobra.Command{
Use: "fetch",
Short: "Fetch migration files from history table",
RunE: func(cmd *cobra.Command, args []string) error {
return fetch.Run(cmd.Context(), flags.DbConfig, afero.NewOsFs())
},
}
)

func init() {
Expand Down Expand Up @@ -132,6 +141,13 @@ func init() {
upFlags.Bool("local", true, "Applies pending migrations to the local database.")
migrationUpCmd.MarkFlagsMutuallyExclusive("db-url", "linked", "local")
migrationCmd.AddCommand(migrationUpCmd)
// Build up command
fetchFlags := migrationFetchCmd.Flags()
fetchFlags.String("db-url", "", "Fetches migrations from the database specified by the connection string (must be percent-encoded).")
fetchFlags.Bool("linked", true, "Fetches migration history from the linked project.")
fetchFlags.Bool("local", false, "Fetches migration history from the local database.")
migrationFetchCmd.MarkFlagsMutuallyExclusive("db-url", "linked", "local")
migrationCmd.AddCommand(migrationFetchCmd)
// Build new command
migrationCmd.AddCommand(migrationNewCmd)
rootCmd.AddCommand(migrationCmd)
Expand Down
52 changes: 52 additions & 0 deletions internal/migration/fetch/fetch.go
@@ -0,0 +1,52 @@
package fetch

import (
"context"
"fmt"
"path/filepath"
"strings"

"github.com/go-errors/errors"
"github.com/jackc/pgconn"
"github.com/jackc/pgx/v4"
"github.com/spf13/afero"
"github.com/supabase/cli/internal/migration/history"
"github.com/supabase/cli/internal/utils"
)

func Run(ctx context.Context, config pgconn.Config, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error {
if err := utils.MkdirIfNotExistFS(fsys, utils.MigrationsDir); err != nil {
return err
}
if empty, err := afero.IsEmpty(fsys, utils.MigrationsDir); err != nil {
return errors.Errorf("failed to read migrations: %w", err)
} else if !empty {
console := utils.NewConsole()
title := fmt.Sprintf("Do you want to overwrite existing files in %s directory?", utils.Bold(utils.MigrationsDir))
if !console.PromptYesNo(title, true) {
return context.Canceled
}
}
result, err := fetchMigrationHistory(ctx, config, options...)
if err != nil {
return err
}
for _, r := range result {
name := fmt.Sprintf("%s_%s.sql", r.Version, r.Name)
path := filepath.Join(utils.MigrationsDir, name)
contents := strings.Join(r.Statements, ";\n") + ";\n"
if err := afero.WriteFile(fsys, path, []byte(contents), 0644); err != nil {
return errors.Errorf("failed to write migration: %w", err)
}
}
return nil
}

func fetchMigrationHistory(ctx context.Context, config pgconn.Config, options ...func(*pgx.ConnConfig)) ([]history.SchemaMigration, error) {
conn, err := utils.ConnectByConfig(ctx, config, options...)
if err != nil {
return nil, err
}
defer conn.Close(context.Background())
return history.ReadMigrationTable(ctx, conn)
}
16 changes: 16 additions & 0 deletions internal/migration/history/history.go
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/go-errors/errors"
"github.com/jackc/pgconn"
"github.com/jackc/pgx/v4"
"github.com/supabase/cli/internal/utils/pgxv5"
)

const (
Expand All @@ -18,8 +19,15 @@ const (
DELETE_MIGRATION_VERSION = "DELETE FROM supabase_migrations.schema_migrations WHERE version = ANY($1)"
DELETE_MIGRATION_BEFORE = "DELETE FROM supabase_migrations.schema_migrations WHERE version <= $1"
TRUNCATE_VERSION_TABLE = "TRUNCATE supabase_migrations.schema_migrations"
SELECT_VERSION_TABLE = "SELECT * FROM supabase_migrations.schema_migrations"
)

type SchemaMigration struct {
Version string
Name string
Statements []string
}

func CreateMigrationTable(ctx context.Context, conn *pgx.Conn) error {
// This must be run without prepared statements because each statement in the batch depends on
// the previous schema change. The lock timeout will be reset when implicit transaction ends.
Expand All @@ -34,3 +42,11 @@ func CreateMigrationTable(ctx context.Context, conn *pgx.Conn) error {
}
return nil
}

func ReadMigrationTable(ctx context.Context, conn *pgx.Conn) ([]SchemaMigration, error) {
rows, err := conn.Query(ctx, SELECT_VERSION_TABLE)
if err != nil {
return nil, errors.Errorf("failed to read migration table: %w", err)
}
return pgxv5.CollectRows[SchemaMigration](rows)
}

0 comments on commit cecb69b

Please sign in to comment.