Skip to content

Commit cb775cb

Browse files
authored
fix: escape drop schema statements (#3877)
1 parent ff2aa95 commit cb775cb

File tree

5 files changed

+22
-60
lines changed

5 files changed

+22
-60
lines changed

internal/migration/down/down_test.go

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,6 @@ func TestMigrationsDown(t *testing.T) {
7676
})
7777
}
7878

79-
var escapedSchemas = append(migration.ManagedSchemas, "extensions", "public")
80-
8179
func TestResetRemote(t *testing.T) {
8280
t.Run("resets remote database", func(t *testing.T) {
8381
// Setup in-memory fs
@@ -87,11 +85,7 @@ func TestResetRemote(t *testing.T) {
8785
// Setup mock postgres
8886
conn := pgtest.NewConn()
8987
defer conn.Close(t)
90-
conn.Query(migration.ListSchemas, escapedSchemas).
91-
Reply("SELECT 1", []interface{}{"private"}).
92-
Query("DROP SCHEMA IF EXISTS private CASCADE").
93-
Reply("DROP SCHEMA").
94-
Query(migration.DropObjects).
88+
conn.Query(migration.DropObjects).
9589
Reply("INSERT 0")
9690
helper.MockMigrationHistory(conn).
9791
Query(migration.INSERT_MIGRATION_VERSION, "0", "schema", nil).
@@ -113,11 +107,7 @@ func TestResetRemote(t *testing.T) {
113107
// Setup mock postgres
114108
conn := pgtest.NewConn()
115109
defer conn.Close(t)
116-
conn.Query(migration.ListSchemas, escapedSchemas).
117-
Reply("SELECT 1", []interface{}{"private"}).
118-
Query("DROP SCHEMA IF EXISTS private CASCADE").
119-
Reply("DROP SCHEMA").
120-
Query(migration.DropObjects).
110+
conn.Query(migration.DropObjects).
121111
Reply("INSERT 0")
122112
helper.MockMigrationHistory(conn).
123113
Query(migration.INSERT_MIGRATION_VERSION, "0", "schema", nil).
@@ -135,9 +125,7 @@ func TestResetRemote(t *testing.T) {
135125
// Setup mock postgres
136126
conn := pgtest.NewConn()
137127
defer conn.Close(t)
138-
conn.Query(migration.ListSchemas, escapedSchemas).
139-
Reply("SELECT 0").
140-
Query(migration.DropObjects).
128+
conn.Query(migration.DropObjects).
141129
ReplyError(pgerrcode.InsufficientPrivilege, "permission denied for relation supabase_migrations")
142130
// Run test
143131
err := ResetAll(context.Background(), "", conn.MockClient(t), fsys)

pkg/migration/drop.go

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package migration
33
import (
44
"context"
55
_ "embed"
6-
"fmt"
76

87
"github.com/go-errors/errors"
98
"github.com/jackc/pgx/v4"
@@ -33,24 +32,7 @@ var (
3332
)
3433

3534
func DropUserSchemas(ctx context.Context, conn *pgx.Conn) error {
36-
// Only drop objects in extensions and public schema
37-
excludes := append(ManagedSchemas,
38-
"extensions",
39-
"public",
40-
)
41-
userSchemas, err := ListUserSchemas(ctx, conn, excludes...)
42-
if err != nil {
43-
return err
44-
}
45-
// Drop all user defined schemas
4635
migration := MigrationFile{}
47-
for _, schema := range userSchemas {
48-
sql := fmt.Sprintf("DROP SCHEMA IF EXISTS %s CASCADE", schema)
49-
migration.Statements = append(migration.Statements, sql)
50-
}
51-
// If an extension uses a schema it doesn't create, dropping the schema will cascade to also
52-
// drop the extension. But if an extension creates its own schema, dropping the schema will
53-
// throw an error. Hence, we drop the extension instead so it cascades to its own schema.
5436
migration.Statements = append(migration.Statements, DropObjects)
5537
return migration.ExecBatch(ctx, conn)
5638
}

pkg/migration/drop_test.go

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,44 +9,24 @@ import (
99
"github.com/supabase/cli/pkg/pgtest"
1010
)
1111

12-
var escapedSchemas = append(ManagedSchemas, "extensions", "public")
13-
1412
func TestDropSchemas(t *testing.T) {
1513
t.Run("resets remote database", func(t *testing.T) {
1614
// Setup mock postgres
1715
conn := pgtest.NewConn()
1816
defer conn.Close(t)
19-
conn.Query(ListSchemas, escapedSchemas).
20-
Reply("SELECT 1", []interface{}{"private"}).
21-
Query("DROP SCHEMA IF EXISTS private CASCADE").
22-
Reply("DROP SCHEMA").
23-
Query(DropObjects).
17+
conn.Query(DropObjects).
2418
Reply("INSERT 0")
2519
// Run test
2620
err := DropUserSchemas(context.Background(), conn.MockClient(t))
2721
// Check error
2822
assert.NoError(t, err)
2923
})
3024

31-
t.Run("throws error on list schema failure", func(t *testing.T) {
32-
// Setup mock postgres
33-
conn := pgtest.NewConn()
34-
defer conn.Close(t)
35-
conn.Query(ListSchemas, escapedSchemas).
36-
ReplyError(pgerrcode.InsufficientPrivilege, "permission denied for relation information_schema")
37-
// Run test
38-
err := DropUserSchemas(context.Background(), conn.MockClient(t))
39-
// Check error
40-
assert.ErrorContains(t, err, "ERROR: permission denied for relation information_schema (SQLSTATE 42501)")
41-
})
42-
4325
t.Run("throws error on drop schema failure", func(t *testing.T) {
4426
// Setup mock postgres
4527
conn := pgtest.NewConn()
4628
defer conn.Close(t)
47-
conn.Query(ListSchemas, escapedSchemas).
48-
Reply("SELECT 0").
49-
Query(DropObjects).
29+
conn.Query(DropObjects).
5030
ReplyError(pgerrcode.InsufficientPrivilege, "permission denied for relation supabase_migrations")
5131
// Run test
5232
err := DropUserSchemas(context.Background(), conn.MockClient(t))

pkg/migration/queries/drop.sql

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
do $$ declare
22
rec record;
33
begin
4+
-- schemas
5+
for rec in
6+
select pn.*
7+
from pg_namespace pn
8+
left join pg_depend pd on pd.objid = pn.oid
9+
where pd.deptype is null
10+
and not pn.nspname like any(array['information\_schema', 'pg\_%', '\_analytics', '\_realtime', '\_supavisor', 'pgbouncer', 'pgmq', 'pgsodium', 'pgtle', 'supabase\_migrations', 'vault', 'extensions', 'public'])
11+
and pn.nspowner::regrole::text != 'supabase_admin'
12+
loop
13+
-- If an extension uses a schema it doesn't create, dropping the schema will cascade to also
14+
-- drop the extension. But if an extension creates its own schema, dropping the schema will
15+
-- throw an error. Hence, we drop schemas first while excluding those created by extensions.
16+
execute format('drop schema if exists %I cascade', rec.nspname);
17+
end loop;
18+
419
-- extensions
520
for rec in
621
select *

pkg/migration/queries/list.sql

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,8 @@
33
-- Supabase managed schemas
44
select pn.nspname
55
from pg_namespace pn
6-
left join pg_depend pd
7-
on pd.objid = pn.oid
8-
join pg_roles r
9-
on pn.nspowner = r.oid
6+
left join pg_depend pd on pd.objid = pn.oid
107
where pd.deptype is null
118
and not pn.nspname like any($1)
12-
and r.rolname != 'supabase_admin'
9+
and pn.nspowner::regrole::text != 'supabase_admin'
1310
order by pn.nspname

0 commit comments

Comments
 (0)