• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / cli / 18742405472

23 Oct 2025 08:30AM UTC coverage: 54.703% (+0.08%) from 54.621%
18742405472

Pull #4350

github

web-flow
Merge b0fee1729 into c4e179187
Pull Request #4350: fix: deprecate python migra in db pull

1 of 2 new or added lines in 1 file covered. (50.0%)

5 existing lines in 1 file now uncovered.

6386 of 11674 relevant lines covered (54.7%)

6.13 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.81
/internal/db/pull/pull.go
1
package pull
2

3
import (
4
        "context"
5
        _ "embed"
6
        "fmt"
7
        "math"
8
        "os"
9
        "path/filepath"
10
        "strconv"
11
        "strings"
12

13
        "github.com/go-errors/errors"
14
        "github.com/jackc/pgconn"
15
        "github.com/jackc/pgx/v4"
16
        "github.com/spf13/afero"
17
        "github.com/supabase/cli/internal/db/diff"
18
        "github.com/supabase/cli/internal/db/dump"
19
        "github.com/supabase/cli/internal/migration/list"
20
        "github.com/supabase/cli/internal/migration/new"
21
        "github.com/supabase/cli/internal/migration/repair"
22
        "github.com/supabase/cli/internal/utils"
23
        "github.com/supabase/cli/pkg/migration"
24
)
25

26
var (
27
        errMissing     = errors.New("No migrations found")
28
        errInSync      = errors.New("No schema changes found")
29
        errConflict    = errors.Errorf("The remote database's migration history does not match local files in %s directory.", utils.MigrationsDir)
30
        managedSchemas = []string{"auth", "storage", "realtime"}
31
)
32

33
func Run(ctx context.Context, schema []string, config pgconn.Config, name string, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error {
2✔
34
        // 1. Check postgres connection
2✔
35
        conn, err := utils.ConnectByConfig(ctx, config, options...)
2✔
36
        if err != nil {
3✔
37
                return err
1✔
38
        }
1✔
39
        defer conn.Close(context.Background())
1✔
40
        // 2. Pull schema
1✔
41
        timestamp := utils.GetCurrentTimestamp()
1✔
42
        path := new.GetMigrationPath(timestamp, name)
1✔
43
        if err := run(ctx, schema, path, conn, fsys); err != nil {
2✔
44
                return err
1✔
45
        }
1✔
46
        // 3. Insert a row to `schema_migrations`
47
        fmt.Fprintln(os.Stderr, "Schema written to "+utils.Bold(path))
×
48
        if shouldUpdate, err := utils.NewConsole().PromptYesNo(ctx, "Update remote migration history table?", true); err != nil {
×
49
                return err
×
50
        } else if shouldUpdate {
×
51
                return repair.UpdateMigrationTable(ctx, conn, []string{timestamp}, repair.Applied, false, fsys)
×
52
        }
×
53
        return nil
×
54
}
55

56
func run(ctx context.Context, schema []string, path string, conn *pgx.Conn, fsys afero.Fs) error {
3✔
57
        config := conn.Config().Config
3✔
58
        // 1. Assert `supabase/migrations` and `schema_migrations` are in sync.
3✔
59
        if err := assertRemoteInSync(ctx, conn, fsys); errors.Is(err, errMissing) {
4✔
60
                // Ignore schemas flag when working on the initial pull
1✔
61
                if err = dumpRemoteSchema(ctx, path, config, fsys); err != nil {
1✔
62
                        return err
×
63
                }
×
64
                // Pull changes in managed schemas automatically
65
                if err = diffRemoteSchema(ctx, managedSchemas, path, config, fsys); errors.Is(err, errInSync) {
1✔
66
                        err = nil
×
67
                }
×
68
                return err
1✔
69
        } else if err != nil {
3✔
70
                return err
1✔
71
        }
1✔
72
        // 2. Fetch remote schema changes
73
        return diffRemoteSchema(ctx, schema, path, config, fsys)
1✔
74
}
75

76
func dumpRemoteSchema(ctx context.Context, path string, config pgconn.Config, fsys afero.Fs) error {
1✔
77
        // Special case if this is the first migration
1✔
78
        fmt.Fprintln(os.Stderr, "Dumping schema from remote database...")
1✔
79
        if err := utils.MkdirIfNotExistFS(fsys, filepath.Dir(path)); err != nil {
1✔
80
                return err
×
81
        }
×
82
        f, err := fsys.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
1✔
83
        if err != nil {
1✔
84
                return errors.Errorf("failed to open dump file: %w", err)
×
85
        }
×
86
        defer f.Close()
1✔
87
        return migration.DumpSchema(ctx, config, f, dump.DockerExec)
1✔
88
}
89

90
func diffRemoteSchema(ctx context.Context, schema []string, path string, config pgconn.Config, fsys afero.Fs) error {
2✔
91
        // Diff remote db (source) & shadow db (target) and write it as a new migration.
2✔
92
        output, err := diff.DiffDatabase(ctx, schema, config, os.Stderr, fsys, diff.DiffSchemaMigra)
2✔
93
        if err != nil {
4✔
94
                return err
2✔
95
        }
2✔
NEW
96
        if trimmed := strings.TrimSpace(output); len(trimmed) == 0 {
×
97
                return errors.New(errInSync)
×
98
        }
×
99
        // Append to existing migration file since we run this after dump
100
        f, err := fsys.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
×
101
        if err != nil {
×
102
                return errors.Errorf("failed to open migration file: %w", err)
×
103
        }
×
104
        defer f.Close()
×
105
        if _, err := f.WriteString(output); err != nil {
×
106
                return errors.Errorf("failed to write migration file: %w", err)
×
107
        }
×
108
        return nil
×
109
}
110

111
func assertRemoteInSync(ctx context.Context, conn *pgx.Conn, fsys afero.Fs) error {
7✔
112
        remoteMigrations, err := migration.ListRemoteMigrations(ctx, conn)
7✔
113
        if err != nil {
8✔
114
                return err
1✔
115
        }
1✔
116
        localMigrations, err := list.LoadLocalVersions(fsys)
6✔
117
        if err != nil {
7✔
118
                return err
1✔
119
        }
1✔
120
        // Find any mismatch between local and remote migrations
121
        var extraRemote, extraLocal []string
5✔
122
        for i, j := 0, 0; i < len(remoteMigrations) || j < len(localMigrations); {
9✔
123
                remoteTimestamp := math.MaxInt
4✔
124
                if i < len(remoteMigrations) {
7✔
125
                        if remoteTimestamp, err = strconv.Atoi(remoteMigrations[i]); err != nil {
3✔
126
                                i++
×
127
                                continue
×
128
                        }
129
                }
130
                localTimestamp := math.MaxInt
4✔
131
                if j < len(localMigrations) {
7✔
132
                        if localTimestamp, err = strconv.Atoi(localMigrations[j]); err != nil {
3✔
133
                                j++
×
134
                                continue
×
135
                        }
136
                }
137
                // Top to bottom chronological order
138
                if localTimestamp < remoteTimestamp {
6✔
139
                        extraLocal = append(extraLocal, localMigrations[j])
2✔
140
                        j++
2✔
141
                } else if remoteTimestamp < localTimestamp {
5✔
142
                        extraRemote = append(extraRemote, remoteMigrations[i])
1✔
143
                        i++
1✔
144
                } else {
2✔
145
                        i++
1✔
146
                        j++
1✔
147
                }
1✔
148
        }
149
        // Suggest delete local migrations / reset migration history
150
        if len(extraRemote)+len(extraLocal) > 0 {
7✔
151
                utils.CmdSuggestion = suggestMigrationRepair(extraRemote, extraLocal)
2✔
152
                return errors.New(errConflict)
2✔
153
        }
2✔
154
        if len(localMigrations) == 0 {
5✔
155
                return errors.New(errMissing)
2✔
156
        }
2✔
157
        return nil
1✔
158
}
159

160
func suggestMigrationRepair(extraRemote, extraLocal []string) string {
2✔
161
        result := fmt.Sprintln("\nMake sure your local git repo is up-to-date. If the error persists, try repairing the migration history table:")
2✔
162
        for _, version := range extraRemote {
3✔
163
                result += fmt.Sprintln(utils.Bold("supabase migration repair --status reverted " + version))
1✔
164
        }
1✔
165
        for _, version := range extraLocal {
4✔
166
                result += fmt.Sprintln(utils.Bold("supabase migration repair --status applied " + version))
2✔
167
        }
2✔
168
        return result
2✔
169
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc