supabase-cli/cmd/db.go

346 lines
14 KiB
Go

package cmd
import (
"fmt"
"os"
"os/signal"
"path/filepath"
"github.com/spf13/afero"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/supabase/cli/internal/db/branch/create"
"github.com/supabase/cli/internal/db/branch/delete"
"github.com/supabase/cli/internal/db/branch/list"
"github.com/supabase/cli/internal/db/branch/switch_"
"github.com/supabase/cli/internal/db/diff"
"github.com/supabase/cli/internal/db/dump"
"github.com/supabase/cli/internal/db/lint"
"github.com/supabase/cli/internal/db/pull"
"github.com/supabase/cli/internal/db/push"
"github.com/supabase/cli/internal/db/remote/changes"
"github.com/supabase/cli/internal/db/remote/commit"
"github.com/supabase/cli/internal/db/reset"
"github.com/supabase/cli/internal/db/start"
"github.com/supabase/cli/internal/db/test"
"github.com/supabase/cli/internal/utils"
"github.com/supabase/cli/internal/utils/flags"
)
var (
dbCmd = &cobra.Command{
GroupID: groupLocalDev,
Use: "db",
Short: "Manage Postgres databases",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
cmd.SetContext(ctx)
return cmd.Root().PersistentPreRunE(cmd, args)
},
}
dbBranchCmd = &cobra.Command{
Hidden: true,
Use: "branch",
Short: "Manage local database branches",
Long: "Manage local database branches. Each branch is associated with a separate local database. Forking remote databases is NOT supported.",
}
dbBranchCreateCmd = &cobra.Command{
Deprecated: "use \"branches create <name>\" instead.\n",
Use: "create <branch name>",
Short: "Create a branch",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return create.Run(args[0], afero.NewOsFs())
},
}
dbBranchDeleteCmd = &cobra.Command{
Deprecated: "use \"branches delete <branch-id>\" instead.\n",
Use: "delete <branch name>",
Short: "Delete a branch",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return delete.Run(args[0], afero.NewOsFs())
},
}
dbBranchListCmd = &cobra.Command{
Deprecated: "use \"branches list\" instead.\n",
Use: "list",
Short: "List branches",
RunE: func(cmd *cobra.Command, args []string) error {
return list.Run(afero.NewOsFs(), os.Stdout)
},
}
dbSwitchCmd = &cobra.Command{
Deprecated: "use \"branches create <name>\" instead.\n",
Use: "switch <branch name>",
Short: "Switch the active branch",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return switch_.Run(cmd.Context(), args[0], afero.NewOsFs())
},
}
useMigra bool
usePgAdmin bool
usePgSchema bool
schema []string
file string
dbDiffCmd = &cobra.Command{
Use: "diff",
Short: "Diffs the local database for schema changes",
RunE: func(cmd *cobra.Command, args []string) error {
if usePgAdmin {
return diff.RunPgAdmin(cmd.Context(), schema, file, flags.DbConfig, afero.NewOsFs())
}
differ := diff.DiffSchemaMigra
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 RLS policies, enums, and grants.")
}
return diff.Run(cmd.Context(), schema, file, flags.DbConfig, differ, afero.NewOsFs())
},
}
dataOnly bool
useCopy bool
roleOnly bool
keepComments bool
excludeTable []string
dbDumpCmd = &cobra.Command{
Use: "dump",
Short: "Dumps data or schemas from the remote database",
PreRun: func(cmd *cobra.Command, args []string) {
if useCopy || len(excludeTable) > 0 {
cobra.CheckErr(cmd.MarkFlagRequired("data-only"))
}
},
RunE: func(cmd *cobra.Command, args []string) error {
return dump.Run(cmd.Context(), file, flags.DbConfig, schema, excludeTable, dataOnly, roleOnly, keepComments, useCopy, dryRun, afero.NewOsFs())
},
PostRun: func(cmd *cobra.Command, args []string) {
if len(file) > 0 {
if absPath, err := filepath.Abs(file); err != nil {
fmt.Fprintln(os.Stderr, "Dumped schema to "+utils.Bold(file)+".")
} else {
fmt.Fprintln(os.Stderr, "Dumped schema to "+utils.Bold(absPath)+".")
}
}
},
}
dryRun bool
includeAll bool
includeRoles bool
includeSeed bool
dbPushCmd = &cobra.Command{
Use: "push",
Short: "Push new migrations to the remote database",
RunE: func(cmd *cobra.Command, args []string) error {
return push.Run(cmd.Context(), dryRun, includeAll, includeRoles, includeSeed, flags.DbConfig, afero.NewOsFs())
},
}
dbPullCmd = &cobra.Command{
Use: "pull [migration name]",
Short: "Pull schema from the remote database",
RunE: func(cmd *cobra.Command, args []string) error {
name := "remote_schema"
if len(args) > 0 {
name = args[0]
}
return pull.Run(cmd.Context(), schema, flags.DbConfig, name, afero.NewOsFs())
},
PostRun: func(cmd *cobra.Command, args []string) {
fmt.Println("Finished " + utils.Aqua("supabase db pull") + ".")
},
}
dbRemoteCmd = &cobra.Command{
Hidden: true,
Use: "remote",
Short: "Manage remote databases",
}
dbRemoteChangesCmd = &cobra.Command{
Deprecated: "use \"db diff --use-migra --linked\" instead.\n",
Use: "changes",
Short: "Show changes on the remote database",
Long: "Show changes on the remote database since last migration.",
RunE: func(cmd *cobra.Command, args []string) error {
return changes.Run(cmd.Context(), schema, flags.DbConfig, afero.NewOsFs())
},
}
dbRemoteCommitCmd = &cobra.Command{
Deprecated: "use \"db pull\" instead.\n",
Use: "commit",
Short: "Commit remote changes as a new migration",
RunE: func(cmd *cobra.Command, args []string) error {
return commit.Run(cmd.Context(), schema, flags.DbConfig, afero.NewOsFs())
},
}
noSeed bool
dbResetCmd = &cobra.Command{
Use: "reset",
Short: "Resets the local database to current migrations",
RunE: func(cmd *cobra.Command, args []string) error {
if noSeed {
utils.Config.Db.Seed.Enabled = false
}
return reset.Run(cmd.Context(), migrationVersion, flags.DbConfig, afero.NewOsFs())
},
}
level = utils.EnumFlag{
Allowed: lint.AllowedLevels,
Value: lint.AllowedLevels[0],
}
lintFailOn = utils.EnumFlag{
Allowed: append([]string{"none"}, lint.AllowedLevels...),
Value: "none",
}
dbLintCmd = &cobra.Command{
Use: "lint",
Short: "Checks local database for typing error",
RunE: func(cmd *cobra.Command, args []string) error {
return lint.Run(cmd.Context(), schema, level.Value, lintFailOn.Value, flags.DbConfig, afero.NewOsFs())
},
}
fromBackup string
dbStartCmd = &cobra.Command{
Use: "start",
Short: "Starts local Postgres database",
RunE: func(cmd *cobra.Command, args []string) error {
return start.Run(cmd.Context(), fromBackup, afero.NewOsFs())
},
}
dbTestCmd = &cobra.Command{
Hidden: true,
Use: "test [path] ...",
Short: "Tests local database with pgTAP",
RunE: func(cmd *cobra.Command, args []string) error {
return test.Run(cmd.Context(), args, flags.DbConfig, afero.NewOsFs())
},
}
)
func init() {
// Build branch command
dbBranchCmd.AddCommand(dbBranchCreateCmd)
dbBranchCmd.AddCommand(dbBranchDeleteCmd)
dbBranchCmd.AddCommand(dbBranchListCmd)
dbBranchCmd.AddCommand(dbSwitchCmd)
dbCmd.AddCommand(dbBranchCmd)
// Build diff command
diffFlags := dbDiffCmd.Flags()
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.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.")
dbDiffCmd.MarkFlagsMutuallyExclusive("db-url", "linked", "local")
diffFlags.StringVarP(&file, "file", "f", "", "Saves schema diff to a new migration file.")
diffFlags.StringSliceVarP(&schema, "schema", "s", []string{}, "Comma separated list of schema to include.")
dbCmd.AddCommand(dbDiffCmd)
// Build dump command
dumpFlags := dbDumpCmd.Flags()
dumpFlags.BoolVar(&dryRun, "dry-run", false, "Prints the pg_dump script that would be executed.")
dumpFlags.BoolVar(&dataOnly, "data-only", false, "Dumps only data records.")
dumpFlags.BoolVar(&useCopy, "use-copy", false, "Uses copy statements in place of inserts.")
dumpFlags.StringSliceVarP(&excludeTable, "exclude", "x", []string{}, "List of schema.tables to exclude from data-only dump.")
dumpFlags.BoolVar(&roleOnly, "role-only", false, "Dumps only cluster roles.")
dbDumpCmd.MarkFlagsMutuallyExclusive("role-only", "data-only")
dumpFlags.BoolVar(&keepComments, "keep-comments", false, "Keeps commented lines from pg_dump output.")
dbDumpCmd.MarkFlagsMutuallyExclusive("keep-comments", "data-only")
dumpFlags.StringVarP(&file, "file", "f", "", "File path to save the dumped contents.")
dumpFlags.String("db-url", "", "Dumps from the database specified by the connection string (must be percent-encoded).")
dumpFlags.Bool("linked", true, "Dumps from the linked project.")
dumpFlags.Bool("local", false, "Dumps from the local database.")
dbDumpCmd.MarkFlagsMutuallyExclusive("db-url", "linked", "local")
dumpFlags.StringVarP(&dbPassword, "password", "p", "", "Password to your remote Postgres database.")
cobra.CheckErr(viper.BindPFlag("DB_PASSWORD", dumpFlags.Lookup("password")))
dumpFlags.StringSliceVarP(&schema, "schema", "s", []string{}, "Comma separated list of schema to include.")
dbDumpCmd.MarkFlagsMutuallyExclusive("schema", "role-only")
dbCmd.AddCommand(dbDumpCmd)
// Build push command
pushFlags := dbPushCmd.Flags()
pushFlags.BoolVar(&includeAll, "include-all", false, "Include all migrations not found on remote history table.")
pushFlags.BoolVar(&includeRoles, "include-roles", false, "Include custom roles from "+utils.CustomRolesPath+".")
pushFlags.BoolVar(&includeSeed, "include-seed", false, "Include seed data from your config.")
pushFlags.BoolVar(&dryRun, "dry-run", false, "Print the migrations that would be applied, but don't actually apply them.")
pushFlags.String("db-url", "", "Pushes to the database specified by the connection string (must be percent-encoded).")
pushFlags.Bool("linked", true, "Pushes to the linked project.")
pushFlags.Bool("local", false, "Pushes to the local database.")
dbPushCmd.MarkFlagsMutuallyExclusive("db-url", "linked", "local")
pushFlags.StringVarP(&dbPassword, "password", "p", "", "Password to your remote Postgres database.")
cobra.CheckErr(viper.BindPFlag("DB_PASSWORD", pushFlags.Lookup("password")))
dbCmd.AddCommand(dbPushCmd)
// Build pull command
pullFlags := dbPullCmd.Flags()
pullFlags.StringSliceVarP(&schema, "schema", "s", []string{}, "Comma separated list of schema to include.")
pullFlags.String("db-url", "", "Pulls from the database specified by the connection string (must be percent-encoded).")
pullFlags.Bool("linked", true, "Pulls from the linked project.")
pullFlags.Bool("local", false, "Pulls from the local database.")
dbPullCmd.MarkFlagsMutuallyExclusive("db-url", "linked", "local")
pullFlags.StringVarP(&dbPassword, "password", "p", "", "Password to your remote Postgres database.")
cobra.CheckErr(viper.BindPFlag("DB_PASSWORD", pullFlags.Lookup("password")))
dbCmd.AddCommand(dbPullCmd)
// Build remote command
remoteFlags := dbRemoteCmd.PersistentFlags()
remoteFlags.String("db-url", "", "Connect using the specified Postgres URL (must be percent-encoded).")
remoteFlags.StringVarP(&dbPassword, "password", "p", "", "Password to your remote Postgres database.")
cobra.CheckErr(viper.BindPFlag("DB_PASSWORD", remoteFlags.Lookup("password")))
remoteFlags.StringSliceVarP(&schema, "schema", "s", []string{}, "Comma separated list of schema to include.")
dbRemoteCmd.AddCommand(dbRemoteChangesCmd)
dbRemoteCmd.AddCommand(dbRemoteCommitCmd)
dbCmd.AddCommand(dbRemoteCmd)
// Build reset command
resetFlags := dbResetCmd.Flags()
resetFlags.String("db-url", "", "Resets the database specified by the connection string (must be percent-encoded).")
resetFlags.Bool("linked", false, "Resets the linked project with local migrations.")
resetFlags.Bool("local", true, "Resets the local database with local migrations.")
resetFlags.BoolVar(&noSeed, "no-seed", false, "Skip running the seed script after reset.")
dbResetCmd.MarkFlagsMutuallyExclusive("db-url", "linked", "local")
resetFlags.StringVar(&migrationVersion, "version", "", "Reset up to the specified version.")
dbCmd.AddCommand(dbResetCmd)
// Build lint command
lintFlags := dbLintCmd.Flags()
lintFlags.String("db-url", "", "Lints the database specified by the connection string (must be percent-encoded).")
lintFlags.Bool("linked", false, "Lints the linked project for schema errors.")
lintFlags.Bool("local", true, "Lints the local database for schema errors.")
dbLintCmd.MarkFlagsMutuallyExclusive("db-url", "linked", "local")
lintFlags.StringSliceVarP(&schema, "schema", "s", []string{}, "Comma separated list of schema to include.")
lintFlags.Var(&level, "level", "Error level to emit.")
lintFlags.Var(&lintFailOn, "fail-on", "Error level to exit with non-zero status.")
dbCmd.AddCommand(dbLintCmd)
// Build start command
startFlags := dbStartCmd.Flags()
startFlags.StringVar(&fromBackup, "from-backup", "", "Path to a logical backup file.")
dbCmd.AddCommand(dbStartCmd)
// Build test command
dbCmd.AddCommand(dbTestCmd)
testFlags := dbTestCmd.Flags()
testFlags.String("db-url", "", "Tests the database specified by the connection string (must be percent-encoded).")
testFlags.Bool("linked", false, "Runs pgTAP tests on the linked project.")
testFlags.Bool("local", true, "Runs pgTAP tests on the local database.")
dbTestCmd.MarkFlagsMutuallyExclusive("db-url", "linked", "local")
rootCmd.AddCommand(dbCmd)
}