Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: simplify query for user defined schemas #2206

Merged
merged 2 commits into from
Apr 25, 2024
Merged
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
18 changes: 1 addition & 17 deletions internal/db/diff/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,24 +83,8 @@ func loadSchema(ctx context.Context, config pgconn.Config, options ...func(*pgx.
return nil, err
}
defer conn.Close(context.Background())
return LoadUserSchemas(ctx, conn)
}

func LoadUserSchemas(ctx context.Context, conn *pgx.Conn) ([]string, error) {
// RLS policies in auth and storage schemas can be included with -s flag
exclude := append([]string{
"auth",
// "extensions",
"pgbouncer",
"realtime",
"_realtime",
"storage",
"_analytics",
// Exclude functions because Webhooks support is early alpha
"supabase_functions",
"supabase_migrations",
}, utils.SystemSchemas...)
return reset.ListSchemas(ctx, conn, exclude...)
return reset.LoadUserSchemas(ctx, conn)
}

func CreateShadowDatabase(ctx context.Context) (string, error) {
Expand Down
40 changes: 2 additions & 38 deletions internal/db/diff/diff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,28 +35,10 @@ var dbConfig = pgconn.Config{
}

var escapedSchemas = []string{
"auth",
"pgbouncer",
"realtime",
`\_realtime`,
"storage",
`\_analytics`,
`supabase\_functions`,
`supabase\_migrations`,
"cron",
"dbdev",
"graphql",
`graphql\_public`,
"net",
"pgsodium",
`pgsodium\_masks`,
"pgtle",
"repack",
"tiger",
`tiger\_data`,
`timescaledb\_%`,
`\_timescaledb\_%`,
"topology",
`supabase\_migrations`,
"vault",
`information\_schema`,
`pg\_%`,
Expand Down Expand Up @@ -130,7 +112,7 @@ func TestRun(t *testing.T) {
// Setup mock postgres
conn := pgtest.NewConn()
defer conn.Close(t)
conn.Query(reset.LIST_SCHEMAS, escapedSchemas).
conn.Query(reset.ListSchemas, escapedSchemas).
ReplyError(pgerrcode.DuplicateTable, `relation "test" already exists`)
// Run test
err := Run(context.Background(), []string{}, "", dbConfig, DiffSchemaMigra, fsys, conn.Intercept)
Expand Down Expand Up @@ -356,24 +338,6 @@ At statement 0: create schema public`)
})
}

func TestUserSchema(t *testing.T) {
// Setup mock postgres
conn := pgtest.NewConn()
defer conn.Close(t)
conn.Query(reset.LIST_SCHEMAS, escapedSchemas).
Reply("SELECT 1", []interface{}{"test"})
// Connect to mock
ctx := context.Background()
mock, err := utils.ConnectByConfig(ctx, dbConfig, conn.Intercept)
require.NoError(t, err)
defer mock.Close(ctx)
// Run test
schemas, err := LoadUserSchemas(ctx, mock)
// Check error
assert.NoError(t, err)
assert.ElementsMatch(t, []string{"test"}, schemas)
}

func TestDropStatements(t *testing.T) {
drops := findDropStatements("create table t(); drop table t; alter table t drop column c")
assert.Equal(t, []string{"drop table t", "alter table t drop column c"}, drops)
Expand Down
2 changes: 1 addition & 1 deletion internal/db/dump/templates/dump_schema.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export PGDATABASE="$PGDATABASE"
# - do not include ACL changes on internal schemas
# - do not include RLS policies on cron extension schema
# - do not include event triggers
# - do not include creating publication "supabase_realtime"
# - do not create publication "supabase_realtime"
pg_dump \
--schema-only \
--quote-all-identifier \
Expand Down
2 changes: 1 addition & 1 deletion internal/db/lint/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func LintDatabase(ctx context.Context, conn *pgx.Conn, schema []string) ([]Resul
return nil, errors.Errorf("failed to begin transaction: %w", err)
}
if len(schema) == 0 {
schema, err = reset.ListSchemas(ctx, conn, utils.InternalSchemas...)
schema, err = reset.LoadUserSchemas(ctx, conn)
if err != nil {
return nil, err
}
Expand Down
3 changes: 2 additions & 1 deletion internal/db/pull/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/spf13/afero"
"github.com/supabase/cli/internal/db/diff"
"github.com/supabase/cli/internal/db/dump"
"github.com/supabase/cli/internal/db/reset"
"github.com/supabase/cli/internal/migration/list"
"github.com/supabase/cli/internal/migration/new"
"github.com/supabase/cli/internal/migration/repair"
Expand Down Expand Up @@ -78,7 +79,7 @@ func run(p utils.Program, ctx context.Context, schema []string, path string, con
defaultSchema := len(schema) == 0
if defaultSchema {
var err error
schema, err = diff.LoadUserSchemas(ctx, conn)
schema, err = reset.LoadUserSchemas(ctx, conn)
if err != nil {
return err
}
Expand Down
22 changes: 2 additions & 20 deletions internal/db/pull/pull_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,28 +31,10 @@ var dbConfig = pgconn.Config{
}

var escapedSchemas = []string{
"auth",
"pgbouncer",
"realtime",
`\_realtime`,
"storage",
`\_analytics`,
`supabase\_functions`,
`supabase\_migrations`,
"cron",
"dbdev",
"graphql",
`graphql\_public`,
"net",
"pgsodium",
`pgsodium\_masks`,
"pgtle",
"repack",
"tiger",
`tiger\_data`,
`timescaledb\_%`,
`\_timescaledb\_%`,
"topology",
`supabase\_migrations`,
"vault",
`information\_schema`,
`pg\_%`,
Expand Down Expand Up @@ -196,7 +178,7 @@ func TestPullSchema(t *testing.T) {
defer conn.Close(t)
conn.Query(list.LIST_MIGRATION_VERSION).
Reply("SELECT 1", []interface{}{"0"}).
Query(reset.LIST_SCHEMAS, escapedSchemas).
Query(reset.ListSchemas, escapedSchemas).
ReplyError(pgerrcode.DuplicateTable, `relation "test" already exists`)
// Connect to mock
ctx := context.Background()
Expand Down
3 changes: 2 additions & 1 deletion internal/db/remote/changes/changes.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/jackc/pgconn"
"github.com/spf13/afero"
"github.com/supabase/cli/internal/db/diff"
"github.com/supabase/cli/internal/db/reset"
"github.com/supabase/cli/internal/utils"
)

Expand Down Expand Up @@ -53,5 +54,5 @@ func loadSchema(ctx context.Context, config pgconn.Config, w io.Writer) ([]strin
return nil, err
}
defer conn.Close(context.Background())
return diff.LoadUserSchemas(ctx, conn)
return reset.LoadUserSchemas(ctx, conn)
}
3 changes: 2 additions & 1 deletion internal/db/remote/commit/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/spf13/afero"
"github.com/supabase/cli/internal/db/diff"
"github.com/supabase/cli/internal/db/dump"
"github.com/supabase/cli/internal/db/reset"
"github.com/supabase/cli/internal/migration/list"
"github.com/supabase/cli/internal/migration/repair"
"github.com/supabase/cli/internal/utils"
Expand Down Expand Up @@ -52,7 +53,7 @@ func run(p utils.Program, ctx context.Context, schema []string, config pgconn.Co

// 2. Fetch remote schema changes
if len(schema) == 0 {
schema, err = diff.LoadUserSchemas(ctx, conn)
schema, err = reset.LoadUserSchemas(ctx, conn)
if err != nil {
return err
}
Expand Down
35 changes: 17 additions & 18 deletions internal/db/reset/reset.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,13 @@ import (
"github.com/supabase/cli/internal/utils/pgxv5"
)

const (
SET_POSTGRES_ROLE = "SET ROLE postgres;"
LIST_SCHEMAS = "SELECT schema_name FROM information_schema.schemata WHERE NOT schema_name LIKE ANY($1) ORDER BY schema_name"
)

var (
ErrUnhealthy = errors.New("service not healthy")
serviceTimeout = 30 * time.Second
//go:embed templates/drop.sql
dropObjects string
//go:embed templates/list.sql
ListSchemas string
)

func Run(ctx context.Context, version string, config pgconn.Config, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error {
Expand Down Expand Up @@ -264,39 +261,41 @@ func resetRemote(ctx context.Context, version string, config pgconn.Config, fsys
return err
}
defer conn.Close(context.Background())
// List user defined schemas
excludes := []string{"public"}
for _, schema := range utils.InternalSchemas {
if schema != "supabase_migrations" {
excludes = append(excludes, schema)
}
}
userSchemas, err := ListSchemas(ctx, conn, excludes...)
// Only drop objects in extensions and public schema
excludes := append([]string{
"extensions",
"public",
}, utils.ManagedSchemas...)
userSchemas, err := LoadUserSchemas(ctx, conn, excludes...)
if err != nil {
return err
}
// Drop user defined objects
// Drop all user defined schemas
migration := repair.MigrationFile{}
for _, schema := range userSchemas {
sql := fmt.Sprintf("DROP SCHEMA IF EXISTS %s CASCADE", schema)
migration.Lines = append(migration.Lines, sql)
}
// If an extension uses a schema it doesn't create, dropping the schema will cascade to also
// drop the extension. But if an extension creates its own schema, dropping the schema will
// throw an error. Hence, we drop the extension instead so it cascades to its own schema.
migration.Lines = append(migration.Lines, dropObjects)
if err := migration.ExecBatch(ctx, conn); err != nil {
return err
}
return apply.MigrateAndSeed(ctx, version, conn, fsys)
}

func ListSchemas(ctx context.Context, conn *pgx.Conn, exclude ...string) ([]string, error) {
exclude = LikeEscapeSchema(exclude)
func LoadUserSchemas(ctx context.Context, conn *pgx.Conn, exclude ...string) ([]string, error) {
if len(exclude) == 0 {
exclude = append(exclude, "")
exclude = utils.ManagedSchemas
}
rows, err := conn.Query(ctx, LIST_SCHEMAS, exclude)
exclude = LikeEscapeSchema(exclude)
rows, err := conn.Query(ctx, ListSchemas, exclude)
if err != nil {
return nil, errors.Errorf("failed to list schemas: %w", err)
}
// TODO: show detail and hint from pgconn.PgError
return pgxv5.CollectStrings(rows)
}

Expand Down
27 changes: 5 additions & 22 deletions internal/db/reset/reset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,29 +304,12 @@ func TestRestartDatabase(t *testing.T) {
}

var escapedSchemas = []string{
"public",
"auth",
"extensions",
"public",
"pgbouncer",
"realtime",
`\_realtime`,
"storage",
`\_analytics`,
`supabase\_functions`,
"cron",
"dbdev",
"graphql",
`graphql\_public`,
"net",
"pgsodium",
`pgsodium\_masks`,
"pgtle",
"repack",
"tiger",
`tiger\_data`,
`timescaledb\_%`,
`\_timescaledb\_%`,
"topology",
`supabase\_migrations`,
"vault",
`information\_schema`,
`pg\_%`,
Expand All @@ -347,7 +330,7 @@ func TestResetRemote(t *testing.T) {
// Setup mock postgres
conn := pgtest.NewConn()
defer conn.Close(t)
conn.Query(LIST_SCHEMAS, escapedSchemas).
conn.Query(ListSchemas, escapedSchemas).
Reply("SELECT 1", []interface{}{"private"}).
Query("DROP SCHEMA IF EXISTS private CASCADE").
Reply("DROP SCHEMA").
Expand All @@ -374,7 +357,7 @@ func TestResetRemote(t *testing.T) {
// Setup mock postgres
conn := pgtest.NewConn()
defer conn.Close(t)
conn.Query(LIST_SCHEMAS, escapedSchemas).
conn.Query(ListSchemas, escapedSchemas).
ReplyError(pgerrcode.InsufficientPrivilege, "permission denied for relation information_schema")
// Run test
err := resetRemote(context.Background(), "", dbConfig, fsys, conn.Intercept)
Expand All @@ -388,7 +371,7 @@ func TestResetRemote(t *testing.T) {
// Setup mock postgres
conn := pgtest.NewConn()
defer conn.Close(t)
conn.Query(LIST_SCHEMAS, escapedSchemas).
conn.Query(ListSchemas, escapedSchemas).
Reply("SELECT 0").
Query(dropObjects).
ReplyError(pgerrcode.InsufficientPrivilege, "permission denied for relation supabase_migrations")
Expand Down
4 changes: 2 additions & 2 deletions internal/db/reset/templates/drop.sql
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ begin
execute format('drop table if exists %I.%I cascade', rec.relnamespace::regnamespace::name, rec.relname);
end loop;

-- truncate auth tables
-- truncate tables in auth and migrations schema
for rec in
select *
from pg_class c
where
c.relnamespace::regnamespace::name = 'auth'
c.relnamespace::regnamespace::name in ('auth', 'supabase_migrations')
and c.relkind = 'r'
loop
execute format('truncate %I.%I restart identity cascade', rec.relnamespace::regnamespace::name, rec.relname);
Expand Down
13 changes: 13 additions & 0 deletions internal/db/reset/templates/list.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-- List user defined schemas, excluding
-- Extension created schemas
-- Supabase managed schemas
select pn.nspname
from pg_namespace pn
left join pg_depend pd
on pd.objid = pn.oid
join pg_roles r
on pn.nspowner = r.oid
where pd.deptype is null
and not pn.nspname like any($1)
and r.rolname != 'supabase_admin'
order by pn.nspname
Loading