Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
6 changes: 5 additions & 1 deletion cmd/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ var (
useMigra bool
usePgAdmin bool
usePgSchema bool
usePgDelta bool
schema []string
file string

Expand All @@ -101,6 +102,8 @@ var (
if usePgSchema {
differ = diff.DiffPgSchema
fmt.Fprintln(os.Stderr, utils.Yellow("WARNING:"), "--use-pg-schema flag is experimental and may not include all entities, such as views and grants.")
} else if usePgDelta {
differ = diff.DiffPgDelta
}
return diff.Run(cmd.Context(), schema, file, flags.DbConfig, differ, afero.NewOsFs())
},
Expand Down Expand Up @@ -257,7 +260,8 @@ func init() {
diffFlags.BoolVar(&useMigra, "use-migra", true, "Use migra to generate schema diff.")
diffFlags.BoolVar(&usePgAdmin, "use-pgadmin", false, "Use pgAdmin to generate schema diff.")
diffFlags.BoolVar(&usePgSchema, "use-pg-schema", false, "Use pg-schema-diff to generate schema diff.")
dbDiffCmd.MarkFlagsMutuallyExclusive("use-migra", "use-pgadmin")
diffFlags.BoolVar(&usePgDelta, "use-pg-delta", false, "Use pg-delta to generate schema diff.")
dbDiffCmd.MarkFlagsMutuallyExclusive("use-migra", "use-pgadmin", "use-pg-schema", "use-pg-delta")
diffFlags.String("db-url", "", "Diffs against the database specified by the connection string (must be percent-encoded).")
diffFlags.Bool("linked", false, "Diffs local migration files against the linked project.")
diffFlags.Bool("local", true, "Diffs local migration files against the local database.")
Expand Down
31 changes: 3 additions & 28 deletions internal/db/diff/migra.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"github.com/go-errors/errors"
"github.com/jackc/pgconn"
"github.com/jackc/pgx/v4"
"github.com/spf13/viper"
"github.com/supabase/cli/internal/gen/types"
"github.com/supabase/cli/internal/utils"
"github.com/supabase/cli/pkg/config"
Expand Down Expand Up @@ -119,33 +118,9 @@ func DiffSchemaMigra(ctx context.Context, source, target pgconn.Config, schema [
} else {
env = append(env, "EXCLUDED_SCHEMAS="+strings.Join(managedSchemas, ","))
}
cmd := []string{"edge-runtime", "start", "--main-service=."}
if viper.GetBool("DEBUG") {
cmd = append(cmd, "--verbose")
}
cmdString := strings.Join(cmd, " ")
entrypoint := []string{"sh", "-c", `cat <<'EOF' > index.ts && ` + cmdString + `
` + diffSchemaTypeScript + `
EOF
`}
var out, stderr bytes.Buffer
if err := utils.DockerRunOnceWithConfig(
ctx,
container.Config{
Image: utils.Config.EdgeRuntime.Image,
Env: env,
Entrypoint: entrypoint,
},
container.HostConfig{
Binds: []string{utils.EdgeRuntimeId + ":/root/.cache/deno:rw"},
NetworkMode: network.NetworkHost,
},
network.NetworkingConfig{},
"",
&out,
&stderr,
); err != nil && !strings.HasPrefix(stderr.String(), "main worker has been destroyed") {
return "", errors.Errorf("error diffing schema: %w:\n%s", err, stderr.String())
var out bytes.Buffer
if err := diffWithStream(ctx, env, diffSchemaTypeScript, &out); err != nil {
return "", err
}
return out.String(), nil
}
77 changes: 77 additions & 0 deletions internal/db/diff/pgdelta.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package diff

import (
"bytes"
"context"
_ "embed"
"io"
"strings"

"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/go-errors/errors"
"github.com/jackc/pgconn"
"github.com/jackc/pgx/v4"
"github.com/spf13/viper"
"github.com/supabase/cli/internal/gen/types"
"github.com/supabase/cli/internal/utils"
)

//go:embed templates/delta.ts
var pgDeltaScript string

func DiffPgDelta(ctx context.Context, source, target pgconn.Config, schema []string, options ...func(*pgx.ConnConfig)) (string, error) {
env := []string{
"SOURCE=" + utils.ToPostgresURL(source),
"TARGET=" + utils.ToPostgresURL(target),
}
if ca, err := types.GetRootCA(ctx, utils.ToPostgresURL(target), options...); err != nil {
return "", err
} else if len(ca) > 0 {
env = append(env, "PGDELTA_TARGET_SSLROOTCERT="+ca)
}
if len(schema) > 0 {
env = append(env, "INCLUDED_SCHEMAS="+strings.Join(schema, ","))
}
loginRole := strings.Split(target.User, ".")[0]
if strings.EqualFold(loginRole, "cli_login_postgres") {
env = append(env, "ROLE=postgres")
}
var out bytes.Buffer
if err := diffWithStream(ctx, env, pgDeltaScript, &out); err != nil {
return "", err
}
return out.String(), nil
}

func diffWithStream(ctx context.Context, env []string, script string, stdout io.Writer) error {
cmd := []string{"edge-runtime", "start", "--main-service=."}
if viper.GetBool("DEBUG") {
cmd = append(cmd, "--verbose")
}
cmdString := strings.Join(cmd, " ")
entrypoint := []string{"sh", "-c", `cat <<'EOF' > index.ts && ` + cmdString + `
` + script + `
EOF
`}
var stderr bytes.Buffer
if err := utils.DockerRunOnceWithConfig(
ctx,
container.Config{
Image: utils.Config.EdgeRuntime.Image,
Env: env,
Entrypoint: entrypoint,
},
container.HostConfig{
Binds: []string{utils.EdgeRuntimeId + ":/root/.cache/deno:rw"},
NetworkMode: network.NetworkHost,
},
network.NetworkingConfig{},
"",
stdout,
&stderr,
); err != nil && !strings.HasPrefix(stderr.String(), "main worker has been destroyed") {
return errors.Errorf("error diffing schema: %w:\n%s", err, stderr.String())
}
return nil
}
23 changes: 23 additions & 0 deletions internal/db/diff/templates/delta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createPlan } from "npm:@supabase/pg-delta@1.0.0-alpha.1";
import { supabase } from "npm:@supabase/pg-delta@1.0.0-alpha.1/integrations/supabase";

const source = Deno.env.get("SOURCE");
const target = Deno.env.get("TARGET");

const includedSchemas = Deno.env.get("INCLUDED_SCHEMAS");
if (includedSchemas) {
supabase.filter = { schema: includedSchemas.split(",") };
}
supabase.role = Deno.env.get("ROLE");

try {
const result = await createPlan(source, target, supabase);
const statements = result?.plan.statements ?? [];
for (const sql of statements) {
console.log(`${sql};`);
}
} catch (e) {
console.error(e);
// Force close event loop
throw new Error("");
}
Loading