github.com/supabase/cli@v1.168.1/internal/db/remote/commit/commit.go (about)

     1  package commit
     2  
     3  import (
     4  	"context"
     5  	_ "embed"
     6  	"fmt"
     7  	"path/filepath"
     8  
     9  	"github.com/go-errors/errors"
    10  	"github.com/jackc/pgconn"
    11  	"github.com/jackc/pgx/v4"
    12  	"github.com/spf13/afero"
    13  	"github.com/supabase/cli/internal/db/diff"
    14  	"github.com/supabase/cli/internal/db/dump"
    15  	"github.com/supabase/cli/internal/db/reset"
    16  	"github.com/supabase/cli/internal/migration/list"
    17  	"github.com/supabase/cli/internal/migration/repair"
    18  	"github.com/supabase/cli/internal/utils"
    19  )
    20  
    21  func Run(ctx context.Context, schema []string, config pgconn.Config, fsys afero.Fs) error {
    22  	// Sanity checks.
    23  	if err := utils.LoadConfigFS(fsys); err != nil {
    24  		return err
    25  	}
    26  
    27  	if err := utils.RunProgram(ctx, func(p utils.Program, ctx context.Context) error {
    28  		return run(p, ctx, schema, config, fsys)
    29  	}); err != nil {
    30  		return err
    31  	}
    32  
    33  	fmt.Println("Finished " + utils.Aqua("supabase db remote commit") + ".")
    34  	return nil
    35  }
    36  
    37  func run(p utils.Program, ctx context.Context, schema []string, config pgconn.Config, fsys afero.Fs) error {
    38  	// 1. Assert `supabase/migrations` and `schema_migrations` are in sync.
    39  	w := utils.StatusWriter{Program: p}
    40  	conn, err := utils.ConnectByConfigStream(ctx, config, w)
    41  	if err != nil {
    42  		return err
    43  	}
    44  	defer conn.Close(context.Background())
    45  	if err := assertRemoteInSync(ctx, conn, fsys); err != nil {
    46  		return err
    47  	}
    48  
    49  	// 2. Fetch remote schema changes
    50  	if len(schema) == 0 {
    51  		schema, err = reset.LoadUserSchemas(ctx, conn)
    52  		if err != nil {
    53  			return err
    54  		}
    55  	}
    56  	timestamp := utils.GetCurrentTimestamp()
    57  	if err := fetchRemote(p, ctx, schema, timestamp, config, fsys); err != nil {
    58  		return err
    59  	}
    60  
    61  	// 3. Insert a row to `schema_migrations`
    62  	return repair.UpdateMigrationTable(ctx, conn, []string{timestamp}, repair.Applied, false, fsys)
    63  }
    64  
    65  func fetchRemote(p utils.Program, ctx context.Context, schema []string, timestamp string, config pgconn.Config, fsys afero.Fs) error {
    66  	path := filepath.Join(utils.MigrationsDir, timestamp+"_remote_commit.sql")
    67  	// Special case if this is the first migration
    68  	if migrations, err := list.LoadLocalMigrations(fsys); err != nil {
    69  		return err
    70  	} else if len(migrations) == 0 {
    71  		p.Send(utils.StatusMsg("Committing initial migration on remote database..."))
    72  		return dump.Run(ctx, path, config, nil, nil, false, false, false, false, false, fsys)
    73  	}
    74  
    75  	w := utils.StatusWriter{Program: p}
    76  	// Diff remote db (source) & shadow db (target) and write it as a new migration.
    77  	output, err := diff.DiffDatabase(ctx, schema, config, w, fsys, diff.DiffSchemaMigra)
    78  	if err != nil {
    79  		return err
    80  	}
    81  	if len(output) == 0 {
    82  		return errors.New("No schema changes found")
    83  	}
    84  	return utils.WriteFile(path, []byte(output), fsys)
    85  }
    86  
    87  func assertRemoteInSync(ctx context.Context, conn *pgx.Conn, fsys afero.Fs) error {
    88  	remoteMigrations, err := list.LoadRemoteMigrations(ctx, conn)
    89  	if err != nil {
    90  		return err
    91  	}
    92  	localMigrations, err := list.LoadLocalMigrations(fsys)
    93  	if err != nil {
    94  		return err
    95  	}
    96  
    97  	conflictErr := errors.New("The remote database's migration history is not in sync with the contents of " + utils.Bold(utils.MigrationsDir) + `. Resolve this by:
    98  - Updating the project from version control to get the latest ` + utils.Bold(utils.MigrationsDir) + `,
    99  - Pushing unapplied migrations with ` + utils.Aqua("supabase db push") + `,
   100  - Or failing that, manually editing supabase_migrations.schema_migrations table with ` + utils.Aqua("supabase migration repair") + ".")
   101  	if len(remoteMigrations) != len(localMigrations) {
   102  		return conflictErr
   103  	}
   104  
   105  	for i, remoteTimestamp := range remoteMigrations {
   106  		// LoadLocalMigrations guarantees we always have a match
   107  		localTimestamp := utils.MigrateFilePattern.FindStringSubmatch(localMigrations[i])[1]
   108  		if localTimestamp != remoteTimestamp {
   109  			return conflictErr
   110  		}
   111  	}
   112  
   113  	return nil
   114  }