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

supabase / cli / 18403653021

10 Oct 2025 10:20AM UTC coverage: 54.315% (-0.4%) from 54.696%
18403653021

Pull #4283

github

web-flow
Merge d880548e0 into a250719cb
Pull Request #4283: feat: add command to clone project to local

14 of 107 new or added lines in 4 files covered. (13.08%)

23 existing lines in 2 files now uncovered.

6419 of 11818 relevant lines covered (54.32%)

6.04 hits per line

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

68.24
/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, pgconn.Config, pgconn.Config, []string, ...func(*pgx.ConnConfig)) (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) {
2✔
34
        out, err := DiffDatabase(ctx, schema, config, os.Stderr, fsys, differ, options...)
2✔
35
        if err != nil {
3✔
36
                return err
1✔
37
        }
1✔
38
        branch := keys.GetGitBranch(fsys)
1✔
39
        fmt.Fprintln(os.Stderr, "Finished "+utils.Aqua("supabase db diff")+" on branch "+utils.Aqua(branch)+".\n")
1✔
40
        if err := SaveDiff(out, file, fsys); err != nil {
1✔
41
                return err
×
42
        }
×
43
        drops := findDropStatements(out)
1✔
44
        if len(drops) > 0 {
1✔
45
                fmt.Fprintln(os.Stderr, "Found drop statements in schema diff. Please double check if these are expected:")
×
46
                fmt.Fprintln(os.Stderr, utils.Yellow(strings.Join(drops, "\n")))
×
47
        }
×
48
        return nil
1✔
49
}
50

51
func loadDeclaredSchemas(fsys afero.Fs) ([]string, error) {
2✔
52
        if schemas := utils.Config.Db.Migrations.SchemaPaths; len(schemas) > 0 {
2✔
53
                return schemas.Files(afero.NewIOFS(fsys))
×
54
        }
×
55
        if exists, err := afero.DirExists(fsys, utils.SchemasDir); err != nil {
2✔
56
                return nil, errors.Errorf("failed to check schemas: %w", err)
×
57
        } else if !exists {
3✔
58
                return nil, nil
1✔
59
        }
1✔
60
        var declared []string
1✔
61
        if err := afero.Walk(fsys, utils.SchemasDir, func(path string, info fs.FileInfo, err error) error {
10✔
62
                if err != nil {
9✔
63
                        return err
×
64
                }
×
65
                if info.Mode().IsRegular() && filepath.Ext(info.Name()) == ".sql" {
13✔
66
                        declared = append(declared, path)
4✔
67
                }
4✔
68
                return nil
9✔
69
        }); err != nil {
×
70
                return nil, errors.Errorf("failed to walk dir: %w", err)
×
71
        }
×
72
        return declared, nil
1✔
73
}
74

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

78
func findDropStatements(out string) []string {
2✔
79
        lines, err := parser.SplitAndTrim(strings.NewReader(out))
2✔
80
        if err != nil {
2✔
81
                return nil
×
82
        }
×
83
        var drops []string
2✔
84
        for _, line := range lines {
6✔
85
                if dropStatementPattern.MatchString(line) {
6✔
86
                        drops = append(drops, line)
2✔
87
                }
2✔
88
        }
89
        return drops
2✔
90
}
91

92
func CreateShadowDatabase(ctx context.Context, port uint16) (string, error) {
14✔
93
        // Disable background workers in shadow database
14✔
94
        config := start.NewContainerConfig("-c", "max_worker_processes=0")
14✔
95
        hostPort := strconv.FormatUint(uint64(port), 10)
14✔
96
        hostConfig := container.HostConfig{
14✔
97
                PortBindings: nat.PortMap{"5432/tcp": []nat.PortBinding{{HostPort: hostPort}}},
14✔
98
                AutoRemove:   true,
14✔
99
        }
14✔
100
        networkingConfig := network.NetworkingConfig{}
14✔
101
        if utils.Config.Db.MajorVersion <= 14 {
20✔
102
                hostConfig.Tmpfs = map[string]string{"/docker-entrypoint-initdb.d": ""}
6✔
103
        }
6✔
104
        return utils.DockerStart(ctx, config, hostConfig, networkingConfig, "")
14✔
105
}
106

107
func ConnectShadowDatabase(ctx context.Context, timeout time.Duration, options ...func(*pgx.ConnConfig)) (conn *pgx.Conn, err error) {
9✔
108
        // Retry until connected, cancelled, or timeout
9✔
109
        policy := start.NewBackoffPolicy(ctx, timeout)
9✔
110
        config := pgconn.Config{Port: utils.Config.Db.ShadowPort}
9✔
111
        connect := func() (*pgx.Conn, error) {
18✔
112
                return utils.ConnectLocalPostgres(ctx, config, options...)
9✔
113
        }
9✔
114
        return backoff.RetryWithData(connect, policy)
9✔
115
}
116

117
// Required to bypass pg_cron check: https://github.com/citusdata/pg_cron/blob/main/pg_cron.sql#L3
118
const CREATE_TEMPLATE = "CREATE DATABASE contrib_regression TEMPLATE postgres"
119

120
func MigrateShadowDatabase(ctx context.Context, container string, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error {
7✔
121
        migrations, err := migration.ListLocalMigrations(utils.MigrationsDir, afero.NewIOFS(fsys))
7✔
122
        if err != nil {
8✔
123
                return err
1✔
124
        }
1✔
125
        conn, err := ConnectShadowDatabase(ctx, 10*time.Second, options...)
6✔
126
        if err != nil {
7✔
127
                return err
1✔
128
        }
1✔
129
        defer conn.Close(context.Background())
5✔
130
        if err := start.SetupDatabase(ctx, conn, container[:12], os.Stderr, fsys); err != nil {
7✔
131
                return err
2✔
132
        }
2✔
133
        if _, err := conn.Exec(ctx, CREATE_TEMPLATE); err != nil {
3✔
134
                return errors.Errorf("failed to create template database: %w", err)
×
135
        }
×
136
        // Migrations take precedence over declarative schemas
137
        if len(migrations) > 0 {
5✔
138
                return migration.ApplyMigrations(ctx, migrations, conn, afero.NewIOFS(fsys))
2✔
139
        }
2✔
140
        declared, err := loadDeclaredSchemas(fsys)
1✔
141
        if err != nil || len(declared) == 0 {
2✔
142
                return err
1✔
143
        }
1✔
NEW
144
        fmt.Fprintln(os.Stderr, "Creating local database from declarative schemas:")
×
NEW
145
        msg := make([]string, len(declared))
×
NEW
146
        for i, m := range declared {
×
NEW
147
                msg[i] = fmt.Sprintf(" • %s", utils.Bold(m))
×
NEW
148
        }
×
NEW
149
        fmt.Fprintln(os.Stderr, strings.Join(msg, "\n"))
×
NEW
150
        return migration.SeedGlobals(ctx, declared, conn, afero.NewIOFS(fsys))
×
151
}
152

153
func DiffDatabase(ctx context.Context, schema []string, config pgconn.Config, w io.Writer, fsys afero.Fs, differ DiffFunc, options ...func(*pgx.ConnConfig)) (string, error) {
7✔
154
        fmt.Fprintln(w, "Creating shadow database...")
7✔
155
        shadow, err := CreateShadowDatabase(ctx, utils.Config.Db.ShadowPort)
7✔
156
        if err != nil {
10✔
157
                return "", err
3✔
158
        }
3✔
159
        defer utils.DockerRemove(shadow)
4✔
160
        if err := start.WaitForHealthyService(ctx, start.HealthTimeout, shadow); err != nil {
5✔
161
                return "", err
1✔
162
        }
1✔
163
        if err := MigrateShadowDatabase(ctx, shadow, fsys, options...); err != nil {
4✔
164
                return "", err
1✔
165
        }
1✔
166
        shadowConfig := pgconn.Config{
2✔
167
                Host:     utils.Config.Hostname,
2✔
168
                Port:     utils.Config.Db.ShadowPort,
2✔
169
                User:     "postgres",
2✔
170
                Password: utils.Config.Db.Password,
2✔
171
                Database: "postgres",
2✔
172
        }
2✔
173
        if utils.IsLocalDatabase(config) {
2✔
174
                if declared, err := loadDeclaredSchemas(fsys); err != nil {
×
175
                        return "", err
×
176
                } else if len(declared) > 0 {
×
177
                        config = shadowConfig
×
178
                        config.Database = "contrib_regression"
×
179
                        if err := migrateBaseDatabase(ctx, config, declared, fsys, options...); err != nil {
×
180
                                return "", err
×
181
                        }
×
182
                }
183
        }
184
        // Load all user defined schemas
185
        if len(schema) > 0 {
4✔
186
                fmt.Fprintln(w, "Diffing schemas:", strings.Join(schema, ","))
2✔
187
        } else {
2✔
188
                fmt.Fprintln(w, "Diffing schemas...")
×
189
        }
×
190
        return differ(ctx, shadowConfig, config, schema, options...)
2✔
191
}
192

193
func migrateBaseDatabase(ctx context.Context, config pgconn.Config, migrations []string, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error {
×
194
        fmt.Fprintln(os.Stderr, "Creating local database from declarative schemas:")
×
195
        msg := make([]string, len(migrations))
×
196
        for i, m := range migrations {
×
197
                msg[i] = fmt.Sprintf(" • %s", utils.Bold(m))
×
198
        }
×
199
        fmt.Fprintln(os.Stderr, strings.Join(msg, "\n"))
×
200
        conn, err := utils.ConnectLocalPostgres(ctx, config, options...)
×
201
        if err != nil {
×
202
                return err
×
203
        }
×
204
        defer conn.Close(context.Background())
×
205
        return migration.SeedGlobals(ctx, migrations, conn, afero.NewIOFS(fsys))
×
206
}
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