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

supabase / cli / 14071634967

25 Mar 2025 10:53PM UTC coverage: 51.056%. First build
14071634967

Pull #3355

github

web-flow
Merge 5d47f2320 into c8778df16
Pull Request #3355: fix: account for declared schemas in config file

3 of 18 new or added lines in 1 file covered. (16.67%)

6959 of 13630 relevant lines covered (51.06%)

186.35 hits per line

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

66.01
/internal/db/diff/diff.go
1
package diff
2

3
import (
4
        "context"
5
        _ "embed"
6
        "fmt"
7
        "io"
8
        "io/fs"
9
        "os"
10
        "path/filepath"
11
        "regexp"
12
        "strconv"
13
        "strings"
14
        "time"
15

16
        "github.com/cenkalti/backoff/v4"
17
        "github.com/docker/docker/api/types/container"
18
        "github.com/docker/docker/api/types/network"
19
        "github.com/docker/go-connections/nat"
20
        "github.com/go-errors/errors"
21
        "github.com/jackc/pgconn"
22
        "github.com/jackc/pgx/v4"
23
        "github.com/spf13/afero"
24
        "github.com/supabase/cli/internal/db/start"
25
        "github.com/supabase/cli/internal/gen/keys"
26
        "github.com/supabase/cli/internal/utils"
27
        "github.com/supabase/cli/pkg/migration"
28
        "github.com/supabase/cli/pkg/parser"
29
)
30

31
type DiffFunc func(context.Context, string, string, []string) (string, error)
32

33
func Run(ctx context.Context, schema []string, file string, config pgconn.Config, differ DiffFunc, fsys afero.Fs, options ...func(*pgx.ConnConfig)) (err error) {
3✔
34
        // Sanity checks.
3✔
35
        if utils.IsLocalDatabase(config) {
3✔
NEW
36
                if declared, err := loadDeclaredSchemas(fsys); err != nil {
×
NEW
37
                        return err
×
NEW
38
                } else if container, err := createShadowIfNotExists(ctx, declared); err != nil {
×
39
                        return err
×
40
                } else if len(container) > 0 {
×
41
                        defer utils.DockerRemove(container)
×
42
                        if err := start.WaitForHealthyService(ctx, start.HealthTimeout, container); err != nil {
×
43
                                return err
×
44
                        }
×
NEW
45
                        if err := migrateBaseDatabase(ctx, container, declared, fsys, options...); err != nil {
×
46
                                return err
×
47
                        }
×
48
                }
49
        }
50
        // 1. Load all user defined schemas
51
        if len(schema) == 0 {
4✔
52
                schema, err = loadSchema(ctx, config, options...)
1✔
53
                if err != nil {
2✔
54
                        return err
1✔
55
                }
1✔
56
        }
57
        // 3. Run migra to diff schema
58
        out, err := DiffDatabase(ctx, schema, config, os.Stderr, fsys, differ, options...)
2✔
59
        if err != nil {
3✔
60
                return err
1✔
61
        }
1✔
62
        branch := keys.GetGitBranch(fsys)
1✔
63
        fmt.Fprintln(os.Stderr, "Finished "+utils.Aqua("supabase db diff")+" on branch "+utils.Aqua(branch)+".\n")
1✔
64
        if err := SaveDiff(out, file, fsys); err != nil {
1✔
65
                return err
×
66
        }
×
67
        drops := findDropStatements(out)
1✔
68
        if len(drops) > 0 {
1✔
69
                fmt.Fprintln(os.Stderr, "Found drop statements in schema diff. Please double check if these are expected:")
×
70
                fmt.Fprintln(os.Stderr, utils.Yellow(strings.Join(drops, "\n")))
×
71
        }
×
72
        return nil
1✔
73
}
74

NEW
75
func createShadowIfNotExists(ctx context.Context, migrations []string) (string, error) {
×
NEW
76
        if len(migrations) == 0 {
×
77
                return "", nil
×
78
        }
×
79
        if err := utils.AssertSupabaseDbIsRunning(); !errors.Is(err, utils.ErrNotRunning) {
×
80
                return "", err
×
81
        }
×
NEW
82
        fmt.Fprintln(os.Stderr, "Creating local database from declarative schemas:")
×
NEW
83
        msg := make([]string, len(migrations))
×
NEW
84
        for i, m := range migrations {
×
NEW
85
                msg[i] = fmt.Sprintf(" • %s", utils.Bold(m))
×
NEW
86
        }
×
NEW
87
        fmt.Fprintln(os.Stderr, strings.Join(msg, "\n"))
×
88
        return CreateShadowDatabase(ctx, utils.Config.Db.Port)
×
89
}
90

91
func loadDeclaredSchemas(fsys afero.Fs) ([]string, error) {
1✔
92
        if schemas := utils.Config.Db.Migrations.SchemaPaths; len(schemas) > 0 {
1✔
93
                return schemas.Files(afero.NewIOFS(fsys))
×
94
        }
×
95
        var declared []string
1✔
96
        if err := afero.Walk(fsys, utils.SchemasDir, func(path string, info fs.FileInfo, err error) error {
10✔
97
                if err != nil {
9✔
NEW
98
                        return err
×
NEW
99
                }
×
100
                if info.Mode().IsRegular() && filepath.Ext(info.Name()) == ".sql" {
13✔
101
                        declared = append(declared, path)
4✔
102
                }
4✔
103
                return nil
9✔
104
        }); err != nil {
×
105
                return nil, errors.Errorf("failed to walk dir: %w", err)
×
106
        }
×
107
        return declared, nil
1✔
108
}
109

110
// https://github.com/djrobstep/migra/blob/master/migra/statements.py#L6
111
var dropStatementPattern = regexp.MustCompile(`(?i)drop\s+`)
112

113
func findDropStatements(out string) []string {
2✔
114
        lines, err := parser.SplitAndTrim(strings.NewReader(out))
2✔
115
        if err != nil {
2✔
116
                return nil
×
117
        }
×
118
        var drops []string
2✔
119
        for _, line := range lines {
6✔
120
                if dropStatementPattern.MatchString(line) {
6✔
121
                        drops = append(drops, line)
2✔
122
                }
2✔
123
        }
124
        return drops
2✔
125
}
126

127
func loadSchema(ctx context.Context, config pgconn.Config, options ...func(*pgx.ConnConfig)) ([]string, error) {
1✔
128
        conn, err := utils.ConnectByConfig(ctx, config, options...)
1✔
129
        if err != nil {
1✔
130
                return nil, err
×
131
        }
×
132
        defer conn.Close(context.Background())
1✔
133
        // RLS policies in auth and storage schemas can be included with -s flag
1✔
134
        return migration.ListUserSchemas(ctx, conn)
1✔
135
}
136

137
func CreateShadowDatabase(ctx context.Context, port uint16) (string, error) {
6✔
138
        config := start.NewContainerConfig()
6✔
139
        hostPort := strconv.FormatUint(uint64(port), 10)
6✔
140
        hostConfig := container.HostConfig{
6✔
141
                PortBindings: nat.PortMap{"5432/tcp": []nat.PortBinding{{HostPort: hostPort}}},
6✔
142
                AutoRemove:   true,
6✔
143
        }
6✔
144
        networkingConfig := network.NetworkingConfig{}
6✔
145
        if utils.Config.Db.MajorVersion <= 14 {
10✔
146
                config.Entrypoint = nil
4✔
147
                hostConfig.Tmpfs = map[string]string{"/docker-entrypoint-initdb.d": ""}
4✔
148
        }
4✔
149
        return utils.DockerStart(ctx, config, hostConfig, networkingConfig, "")
6✔
150
}
151

152
func ConnectShadowDatabase(ctx context.Context, timeout time.Duration, options ...func(*pgx.ConnConfig)) (conn *pgx.Conn, err error) {
6✔
153
        // Retry until connected, cancelled, or timeout
6✔
154
        policy := start.NewBackoffPolicy(ctx, timeout)
6✔
155
        config := pgconn.Config{Port: utils.Config.Db.ShadowPort}
6✔
156
        connect := func() (*pgx.Conn, error) {
12✔
157
                return utils.ConnectLocalPostgres(ctx, config, options...)
6✔
158
        }
6✔
159
        return backoff.RetryWithData(connect, policy)
6✔
160
}
161

162
func MigrateShadowDatabase(ctx context.Context, container string, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error {
7✔
163
        migrations, err := migration.ListLocalMigrations(utils.MigrationsDir, afero.NewIOFS(fsys))
7✔
164
        if err != nil {
8✔
165
                return err
1✔
166
        }
1✔
167
        conn, err := ConnectShadowDatabase(ctx, 10*time.Second, options...)
6✔
168
        if err != nil {
7✔
169
                return err
1✔
170
        }
1✔
171
        defer conn.Close(context.Background())
5✔
172
        if err := start.SetupDatabase(ctx, conn, container[:12], os.Stderr, fsys); err != nil {
7✔
173
                return err
2✔
174
        }
2✔
175
        return migration.ApplyMigrations(ctx, migrations, conn, afero.NewIOFS(fsys))
3✔
176
}
177

178
func migrateBaseDatabase(ctx context.Context, container string, migrations []string, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error {
×
179
        conn, err := utils.ConnectLocalPostgres(ctx, pgconn.Config{}, options...)
×
180
        if err != nil {
×
181
                return err
×
182
        }
×
NEW
183
        defer conn.Close(context.Background())
×
184
        if err := start.SetupDatabase(ctx, conn, container[:12], os.Stderr, fsys); err != nil {
×
185
                return err
×
186
        }
×
187
        return migration.SeedGlobals(ctx, migrations, conn, afero.NewIOFS(fsys))
×
188
}
189

190
func DiffDatabase(ctx context.Context, schema []string, config pgconn.Config, w io.Writer, fsys afero.Fs, differ func(context.Context, string, string, []string) (string, error), options ...func(*pgx.ConnConfig)) (string, error) {
6✔
191
        fmt.Fprintln(w, "Creating shadow database...")
6✔
192
        shadow, err := CreateShadowDatabase(ctx, utils.Config.Db.ShadowPort)
6✔
193
        if err != nil {
8✔
194
                return "", err
2✔
195
        }
2✔
196
        defer utils.DockerRemove(shadow)
4✔
197
        if err := start.WaitForHealthyService(ctx, start.HealthTimeout, shadow); err != nil {
5✔
198
                return "", err
1✔
199
        }
1✔
200
        if err := MigrateShadowDatabase(ctx, shadow, fsys, options...); err != nil {
4✔
201
                return "", err
1✔
202
        }
1✔
203
        fmt.Fprintln(w, "Diffing schemas:", strings.Join(schema, ","))
2✔
204
        source := utils.ToPostgresURL(pgconn.Config{
2✔
205
                Host:     utils.Config.Hostname,
2✔
206
                Port:     utils.Config.Db.ShadowPort,
2✔
207
                User:     "postgres",
2✔
208
                Password: utils.Config.Db.Password,
2✔
209
                Database: "postgres",
2✔
210
        })
2✔
211
        target := utils.ToPostgresURL(config)
2✔
212
        return differ(ctx, source, target, schema)
2✔
213
}
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

© 2026 Coveralls, Inc