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

supabase / cli / 8392258799

22 Mar 2024 02:58PM UTC coverage: 58.963% (-0.06%) from 59.021%
8392258799

Pull #2086

github

web-flow
Merge branch 'develop' into inc-pub
Pull Request #2086: fix: include publication in db dump

7 of 13 new or added lines in 2 files covered. (53.85%)

5 existing lines in 1 file now uncovered.

6263 of 10622 relevant lines covered (58.96%)

688.99 hits per line

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

93.55
/internal/migration/list/list.go
1
package list
2

3
import (
4
        "context"
5
        "fmt"
6
        "math"
7
        "os"
8
        "regexp"
9
        "strconv"
10
        "time"
11

12
        "github.com/charmbracelet/glamour"
13
        "github.com/go-errors/errors"
14
        "github.com/jackc/pgconn"
15
        "github.com/jackc/pgerrcode"
16
        "github.com/jackc/pgx/v4"
17
        "github.com/spf13/afero"
18
        "github.com/spf13/viper"
19
        "github.com/supabase/cli/internal/utils"
20
        "github.com/supabase/cli/internal/utils/pgxv5"
21
)
22

23
const LIST_MIGRATION_VERSION = "SELECT version FROM supabase_migrations.schema_migrations ORDER BY version"
24

25
var initSchemaPattern = regexp.MustCompile(`([0-9]{14})_init\.sql`)
26

27
func Run(ctx context.Context, config pgconn.Config, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error {
3✔
28
        remoteVersions, err := loadRemoteVersions(ctx, config, options...)
3✔
29
        if err != nil {
4✔
30
                return err
1✔
31
        }
1✔
32
        localVersions, err := LoadLocalVersions(fsys)
2✔
33
        if err != nil {
3✔
34
                return err
1✔
35
        }
1✔
36
        table := makeTable(remoteVersions, localVersions)
1✔
37
        return RenderTable(table)
1✔
38
}
39

40
func loadRemoteVersions(ctx context.Context, config pgconn.Config, options ...func(*pgx.ConnConfig)) ([]string, error) {
7✔
41
        conn, err := utils.ConnectByConfig(ctx, config, options...)
7✔
42
        if err != nil {
9✔
43
                return nil, err
2✔
44
        }
2✔
45
        defer conn.Close(context.Background())
5✔
46
        return LoadRemoteMigrations(ctx, conn)
5✔
47
}
48

49
func LoadRemoteMigrations(ctx context.Context, conn *pgx.Conn) ([]string, error) {
23✔
50
        versions, err := listMigrationVersions(ctx, conn)
23✔
51
        if err != nil {
27✔
52
                var pgErr *pgconn.PgError
4✔
53
                if errors.As(err, &pgErr) && pgErr.Code == pgerrcode.UndefinedTable {
5✔
54
                        // If migration history table is undefined, the remote project has no migrations
1✔
55
                        return nil, nil
1✔
56
                }
1✔
57
        }
58
        return versions, err
22✔
59
}
60

61
func listMigrationVersions(ctx context.Context, conn *pgx.Conn) ([]string, error) {
23✔
62
        rows, err := conn.Query(ctx, LIST_MIGRATION_VERSION)
23✔
63
        if err != nil {
23✔
64
                return nil, errors.Errorf("failed to query rows: %w", err)
×
65
        }
×
66
        return pgxv5.CollectStrings(rows)
23✔
67
}
68

69
const (
70
        layoutVersion = "20060102150405"
71
        layoutHuman   = "2006-01-02 15:04:05"
72
)
73

74
func formatTimestamp(version string) string {
6✔
75
        timestamp, err := time.Parse(layoutVersion, version)
6✔
76
        if err != nil {
9✔
77
                if viper.GetBool("DEBUG") {
3✔
NEW
78
                        fmt.Fprintln(os.Stderr, err)
×
NEW
79
                }
×
80
                return version
3✔
81
        }
82
        return timestamp.Format(layoutHuman)
3✔
83
}
84

85
func makeTable(remoteMigrations, localMigrations []string) string {
4✔
86
        var err error
4✔
87
        table := "|Local|Remote|Time (UTC)|\n|-|-|-|\n"
4✔
88
        for i, j := 0, 0; i < len(remoteMigrations) || j < len(localMigrations); {
14✔
89
                remoteTimestamp := math.MaxInt
10✔
90
                if i < len(remoteMigrations) {
18✔
91
                        if remoteTimestamp, err = strconv.Atoi(remoteMigrations[i]); err != nil {
10✔
92
                                i++
2✔
93
                                continue
2✔
94
                        }
95
                }
96
                localTimestamp := math.MaxInt
8✔
97
                if j < len(localMigrations) {
14✔
98
                        if localTimestamp, err = strconv.Atoi(localMigrations[j]); err != nil {
8✔
99
                                j++
2✔
100
                                continue
2✔
101
                        }
102
                }
103
                // Top to bottom chronological order
104
                if localTimestamp < remoteTimestamp {
8✔
105
                        table += fmt.Sprintf("|`%s`|` `|`%s`|\n", localMigrations[j], formatTimestamp(localMigrations[j]))
2✔
106
                        j++
2✔
107
                } else if remoteTimestamp < localTimestamp {
8✔
108
                        table += fmt.Sprintf("|` `|`%s`|`%s`|\n", remoteMigrations[i], formatTimestamp(remoteMigrations[i]))
2✔
109
                        i++
2✔
110
                } else {
4✔
111
                        table += fmt.Sprintf("|`%s`|`%s`|`%s`|\n", localMigrations[j], remoteMigrations[i], formatTimestamp(remoteMigrations[i]))
2✔
112
                        i++
2✔
113
                        j++
2✔
114
                }
2✔
115
        }
116
        return table
4✔
117
}
118

119
func RenderTable(markdown string) error {
12✔
120
        r, err := glamour.NewTermRenderer(
12✔
121
                glamour.WithAutoStyle(),
12✔
122
                glamour.WithWordWrap(-1),
12✔
123
        )
12✔
124
        if err != nil {
12✔
125
                return errors.Errorf("failed to initialise terminal renderer: %w", err)
×
126
        }
×
127
        out, err := r.Render(markdown)
12✔
128
        if err != nil {
12✔
129
                return errors.Errorf("failed to render markdown: %w", err)
×
130
        }
×
131
        fmt.Print(out)
12✔
132
        return nil
12✔
133
}
134

135
func LoadLocalVersions(fsys afero.Fs) ([]string, error) {
12✔
136
        names, err := LoadLocalMigrations(fsys)
12✔
137
        if err != nil {
15✔
138
                return nil, err
3✔
139
        }
3✔
140
        var versions []string
9✔
141
        for _, filename := range names {
15✔
142
                // LoadLocalMigrations guarantees we always have a match
6✔
143
                verion := utils.MigrateFilePattern.FindStringSubmatch(filename)[1]
6✔
144
                versions = append(versions, verion)
6✔
145
        }
6✔
146
        return versions, nil
9✔
147
}
148

149
func LoadLocalMigrations(fsys afero.Fs) ([]string, error) {
28✔
150
        return LoadPartialMigrations("", fsys)
28✔
151
}
28✔
152

153
func LoadPartialMigrations(version string, fsys afero.Fs) ([]string, error) {
41✔
154
        localMigrations, err := afero.ReadDir(fsys, utils.MigrationsDir)
41✔
155
        if err != nil && !errors.Is(err, os.ErrNotExist) {
48✔
156
                return nil, errors.Errorf("failed to read directory: %w", err)
7✔
157
        }
7✔
158
        var names []string
34✔
159
        for i, migration := range localMigrations {
67✔
160
                filename := migration.Name()
33✔
161
                if i == 0 && shouldSkip(filename) {
34✔
162
                        fmt.Fprintln(os.Stderr, "Skipping migration "+utils.Bold(filename)+`... (replace "init" with a different file name to apply this migration)`)
1✔
163
                        continue
1✔
164
                }
165
                matches := utils.MigrateFilePattern.FindStringSubmatch(filename)
32✔
166
                if len(matches) == 0 {
33✔
167
                        fmt.Fprintln(os.Stderr, "Skipping migration "+utils.Bold(filename)+`... (file name must match pattern "<timestamp>_name.sql")`)
1✔
168
                        continue
1✔
169
                }
170
                names = append(names, filename)
31✔
171
                if matches[1] == version {
33✔
172
                        break
2✔
173
                }
174
        }
175
        return names, nil
34✔
176
}
177

178
func shouldSkip(name string) bool {
19✔
179
        // NOTE: To handle backward-compatibility. `<timestamp>_init.sql` as
19✔
180
        // the first migration (prev versions of the CLI) is deprecated.
19✔
181
        matches := initSchemaPattern.FindStringSubmatch(name)
19✔
182
        if len(matches) == 2 {
20✔
183
                if timestamp, err := strconv.ParseUint(matches[1], 10, 64); err == nil && timestamp < 20211209000000 {
2✔
184
                        return true
1✔
185
                }
1✔
186
        }
187
        return false
18✔
188
}
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