Skip to content

Commit 90be78a

Browse files
authored
fix(pg-delta): declarative-sync-no-declarative-dir-set (#5078)
* feat(declarative): add tests for skipping config updates when PgDelta is enabled - These tests verify that the configuration remains unchanged when PgDelta is enabled, ensuring the declarative directory is the source of truth. - Updated the WriteDeclarativeSchemas function to reflect the new behavior regarding PgDelta configuration. * fix(declarative): DSL change due to upgrade
1 parent 7476ee0 commit 90be78a

File tree

4 files changed

+81
-12
lines changed

4 files changed

+81
-12
lines changed

internal/db/declarative/declarative.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -235,10 +235,9 @@ func WriteDeclarativeSchemas(output diff.DeclarativeOutput, fsys afero.Fs) error
235235
return err
236236
}
237237
}
238-
// When pg-delta has its own config section, the declarative path is the single
239-
// source of truth there; do not overwrite [db.migrations] schema_paths.
240-
if utils.IsPgDeltaEnabled() && utils.Config.Experimental.PgDelta != nil &&
241-
len(utils.Config.Experimental.PgDelta.DeclarativeSchemaPath) > 0 {
238+
// When pg-delta is enabled, the declarative directory (default or configured)
239+
// is the source of truth; do not overwrite [db.migrations] schema_paths.
240+
if utils.IsPgDeltaEnabled() {
242241
return nil
243242
}
244243
utils.Config.Db.Migrations.SchemaPaths = []string{

internal/db/declarative/declarative_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,34 @@ func TestWriteDeclarativeSchemas(t *testing.T) {
4848
assert.Contains(t, string(cfg), `"database"`)
4949
}
5050

51+
func TestWriteDeclarativeSchemasSkipsConfigUpdateWhenPgDeltaEnabled(t *testing.T) {
52+
fsys := afero.NewMemMapFs()
53+
originalConfig := "[db]\n"
54+
require.NoError(t, afero.WriteFile(fsys, utils.ConfigPath, []byte(originalConfig), 0644))
55+
original := utils.Config.Experimental.PgDelta
56+
utils.Config.Experimental.PgDelta = &config.PgDeltaConfig{Enabled: true}
57+
t.Cleanup(func() {
58+
utils.Config.Experimental.PgDelta = original
59+
})
60+
61+
output := diff.DeclarativeOutput{
62+
Files: []diff.DeclarativeFile{
63+
{Path: "schemas/public/tables/users.sql", SQL: "create table users(id bigint);"},
64+
},
65+
}
66+
67+
err := WriteDeclarativeSchemas(output, fsys)
68+
require.NoError(t, err)
69+
70+
users, err := afero.ReadFile(fsys, filepath.Join(utils.DeclarativeDir, "schemas", "public", "tables", "users.sql"))
71+
require.NoError(t, err)
72+
assert.Equal(t, "create table users(id bigint);", string(users))
73+
74+
cfg, err := afero.ReadFile(fsys, utils.ConfigPath)
75+
require.NoError(t, err)
76+
assert.Equal(t, originalConfig, string(cfg))
77+
}
78+
5179
func TestTryCacheMigrationsCatalogWritesPrefixedCache(t *testing.T) {
5280
fsys := afero.NewMemMapFs()
5381
original := utils.Config.Experimental.PgDelta
@@ -146,6 +174,38 @@ func TestWriteDeclarativeSchemasUsesConfiguredDir(t *testing.T) {
146174
assert.Contains(t, string(cfg), `db/decl`)
147175
}
148176

177+
func TestWriteDeclarativeSchemasSkipsConfigUpdateForPgDeltaCustomDir(t *testing.T) {
178+
fsys := afero.NewMemMapFs()
179+
originalConfig := "[db]\n"
180+
require.NoError(t, afero.WriteFile(fsys, utils.ConfigPath, []byte(originalConfig), 0644))
181+
original := utils.Config.Experimental.PgDelta
182+
utils.Config.Experimental.PgDelta = &config.PgDeltaConfig{
183+
Enabled: true,
184+
DeclarativeSchemaPath: filepath.Join(utils.SupabaseDirPath, "db", "decl"),
185+
}
186+
t.Cleanup(func() {
187+
utils.Config.Experimental.PgDelta = original
188+
})
189+
190+
output := diff.DeclarativeOutput{
191+
Files: []diff.DeclarativeFile{
192+
{Path: "cluster/roles.sql", SQL: "create role app;"},
193+
},
194+
}
195+
196+
err := WriteDeclarativeSchemas(output, fsys)
197+
require.NoError(t, err)
198+
199+
rolesPath := filepath.Join(utils.SupabaseDirPath, "db", "decl", "cluster", "roles.sql")
200+
roles, err := afero.ReadFile(fsys, rolesPath)
201+
require.NoError(t, err)
202+
assert.Equal(t, "create role app;", string(roles))
203+
204+
cfg, err := afero.ReadFile(fsys, utils.ConfigPath)
205+
require.NoError(t, err)
206+
assert.Equal(t, originalConfig, string(cfg))
207+
}
208+
149209
func TestWriteDeclarativeSchemasRejectsUnsafePath(t *testing.T) {
150210
// Export paths must stay within supabase/declarative to prevent traversal.
151211
fsys := afero.NewMemMapFs()

internal/db/diff/templates/pgdelta.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,14 @@ const target = Deno.env.get("TARGET");
2121

2222
const includedSchemas = Deno.env.get("INCLUDED_SCHEMAS");
2323
if (includedSchemas) {
24-
supabase.filter = { schema: includedSchemas.split(",") };
24+
const schemas = includedSchemas.split(",");
25+
const schemaFilter = {
26+
or: [{ "*/schema": schemas }, { "schema/name": schemas }],
27+
};
28+
// CompositionPattern `and` is valid FilterDSL; Deno's structural typing is strict on `or` branches.
29+
supabase.filter = {
30+
and: [supabase.filter!, schemaFilter],
31+
} as typeof supabase.filter;
2532
}
2633

2734
const formatOptionsRaw = Deno.env.get("FORMAT_OPTIONS");

internal/db/diff/templates/pgdelta_declarative_export.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,23 @@ async function resolveInput(ref: string | undefined) {
2222
const source = Deno.env.get("SOURCE");
2323
const target = Deno.env.get("TARGET");
2424
supabase.filter = {
25-
// Also allow dropped extensions from migrations to be capted in the declarative schema export
25+
// Also allow dropped extensions from migrations to be captured in the declarative schema export
2626
// TODO: fix upstream bug into pgdelta supabase integration
2727
or: [
28-
...supabase.filter.or,
29-
{ type: "extension", operation: "drop", scope: "object" },
28+
...supabase.filter!.or!,
29+
{ objectType: "extension", operation: "drop", scope: "object" },
3030
],
3131
};
3232

3333
const includedSchemas = Deno.env.get("INCLUDED_SCHEMAS");
3434
if (includedSchemas) {
35-
const schemaFilter = { schema: includedSchemas.split(",") };
36-
supabase.filter = supabase.filter
37-
? { and: [supabase.filter, schemaFilter] }
38-
: schemaFilter;
35+
const schemas = includedSchemas.split(",");
36+
const schemaFilter = {
37+
or: [{ "*/schema": schemas }, { "schema/name": schemas }],
38+
};
39+
supabase.filter = {
40+
and: [supabase.filter!, schemaFilter],
41+
} as unknown as typeof supabase.filter;
3942
}
4043

4144
const formatOptionsRaw = Deno.env.get("FORMAT_OPTIONS");

0 commit comments

Comments
 (0)