Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions internal/db/diff/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func Run(ctx context.Context, schema []string, file string, config pgconn.Config
if err != nil {
return err
}
out = filterInheritedConstraints(out)
branch := keys.GetGitBranch(fsys)
fmt.Fprintln(os.Stderr, "Finished "+utils.Aqua("supabase db diff")+" on branch "+utils.Aqua(branch)+".\n")
if err := SaveDiff(out, file, fsys); err != nil {
Expand Down Expand Up @@ -89,6 +90,29 @@ func findDropStatements(out string) []string {
return drops
}

var inheritedConstraintPattern = regexp.MustCompile(`(?i)alter\s+table\s+[^;]+\s+(drop|add)\s+constraint\s+"[^"]*fkey\d+"`)

func filterInheritedConstraints(out string) string {
lines, err := parser.SplitAndTrim(strings.NewReader(out))
if err != nil {
return out
}
var filtered []string
for _, line := range lines {
if !inheritedConstraintPattern.MatchString(line) {
filtered = append(filtered, line)
}
}
if len(filtered) == 0 {
return ""
}
result := strings.Join(filtered, ";\n\n") + ";"
if strings.HasSuffix(out, "\n") {
result += "\n"
}
return result
}

func CreateShadowDatabase(ctx context.Context, port uint16) (string, error) {
// Disable background workers in shadow database
config := start.NewContainerConfig("-c", "max_worker_processes=0")
Expand Down
62 changes: 62 additions & 0 deletions internal/db/diff/diff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,68 @@ func TestDropStatements(t *testing.T) {
assert.Equal(t, []string{"drop table t", "alter table t drop column c"}, drops)
}

func TestFilterInheritedConstraints(t *testing.T) {
t.Run("filters inherited fkey constraints", func(t *testing.T) {
input := `alter table "public"."users" drop constraint "users_avatar_id_fkey1";
alter table "public"."users" drop constraint "users_avatar_id_fkey2";
alter table "public"."users" add constraint "users_avatar_id_fkey1" FOREIGN KEY (avatar_id) REFERENCES photos_avatars(id);
create table test();`
result := filterInheritedConstraints(input)
assert.Equal(t, "create table test();", result)
})

t.Run("preserves non-inherited constraints", func(t *testing.T) {
input := `alter table "public"."users" drop constraint "users_avatar_id_fkey";
alter table "public"."users" add constraint "users_avatar_id_fkey" FOREIGN KEY (avatar_id) REFERENCES photos(id);`
result := filterInheritedConstraints(input)
assert.Contains(t, result, `alter table "public"."users" drop constraint "users_avatar_id_fkey"`)
assert.Contains(t, result, `alter table "public"."users" add constraint "users_avatar_id_fkey" FOREIGN KEY (avatar_id) REFERENCES photos(id)`)
})

t.Run("returns empty string when all statements filtered", func(t *testing.T) {
input := `alter table "public"."users" drop constraint "users_fkey1";
alter table "public"."users" drop constraint "users_fkey2";`
result := filterInheritedConstraints(input)
assert.Equal(t, "", result)
})

t.Run("handles empty input", func(t *testing.T) {
result := filterInheritedConstraints("")
assert.Equal(t, "", result)
})

t.Run("handles mixed statements with partitioned table constraints", func(t *testing.T) {
input := `create table accounts(id text primary key);
alter table "public"."users" drop constraint "users_avatar_id_avatar_bucket_fkey1";
alter table "public"."users" drop constraint "users_avatar_id_avatar_bucket_fkey14";
alter table "public"."companies" drop constraint "companies_logo_id_fkey1";
create index idx_test on test(id);`
result := filterInheritedConstraints(input)
assert.Contains(t, result, "create table accounts(id text primary key)")
assert.Contains(t, result, "create index idx_test on test(id)")
assert.NotContains(t, result, "fkey1")
assert.NotContains(t, result, "fkey14")
})

t.Run("filters exact bug report pattern", func(t *testing.T) {
input := `alter table "public"."users" drop constraint "users_avatar_id_avatar_bucket_fkey1";
alter table "public"."users" drop constraint "users_avatar_id_avatar_bucket_fkey2";
alter table "public"."users" drop constraint "users_avatar_id_avatar_bucket_fkey3";
alter table "public"."users" add constraint "users_avatar_id_avatar_bucket_fkey1" FOREIGN KEY (avatar_id, avatar_bucket) REFERENCES photos_avatars(id, bucket) ON DELETE SET NULL;
alter table "public"."users" add constraint "users_avatar_id_avatar_bucket_fkey2" FOREIGN KEY (avatar_id, avatar_bucket) REFERENCES photos_brands(id, bucket) ON DELETE SET NULL;`
result := filterInheritedConstraints(input)
assert.Equal(t, "", result)
})

t.Run("preserves legitimate constraint operations", func(t *testing.T) {
input := `alter table "public"."users" drop constraint "users_avatar_id_avatar_bucket_fkey";
alter table "public"."users" add constraint "users_avatar_id_avatar_bucket_fkey" FOREIGN KEY (avatar_id, avatar_bucket) REFERENCES photos(id, bucket) ON DELETE SET NULL;`
result := filterInheritedConstraints(input)
assert.Contains(t, result, "users_avatar_id_avatar_bucket_fkey")
assert.NotContains(t, result, "fkey1")
})
}

func TestLoadSchemas(t *testing.T) {
expected := []string{
filepath.Join(utils.SchemasDir, "comment", "model.sql"),
Expand Down
2 changes: 1 addition & 1 deletion internal/db/diff/pgadmin.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func RunPgAdmin(ctx context.Context, schema []string, file string, config pgconn
return err
}

return SaveDiff(output, file, fsys)
return SaveDiff(filterInheritedConstraints(output), file, fsys)
}

var output string
Expand Down
Loading