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 }