Skip to content
24 changes: 18 additions & 6 deletions cmd/pg-schema-diff/dump_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,28 @@ func buildDumpCmd() *cobra.Command {
cmd.Flags().StringArrayVar(&includeSchemas, "include-schema", nil, "Include the specified schema in the dump")
cmd.Flags().StringArrayVar(&excludeSchemas, "exclude-schema", nil, "Exclude the specified schema from the dump")

var excludeTablePatterns []string
cmd.Flags().StringArrayVar(&excludeTablePatterns, "exclude-table", nil,
"Exclude tables matching this Go regexp. The pattern is matched (fully anchored) against both the table "+
"name and the schema-qualified name, e.g., 'tmp_.*' or 'public\\.tmp_.*'. Can be repeated.")

cmd.RunE = func(cmd *cobra.Command, args []string) error {
connConfig, err := parseConnectionFlags(connFlags)
if err != nil {
return err
}

if err := validateExcludeTablePatterns(excludeTablePatterns); err != nil {
return err
}

cmd.SilenceUsage = true

plan, err := generateDump(cmd.Context(), generateDumpParams{
connConfig: connConfig,
includeSchemas: includeSchemas,
excludeSchemas: excludeSchemas,
connConfig: connConfig,
includeSchemas: includeSchemas,
excludeSchemas: excludeSchemas,
excludeTablePatterns: excludeTablePatterns,
})
if err != nil {
return err
Expand All @@ -51,9 +61,10 @@ func buildDumpCmd() *cobra.Command {
}

type generateDumpParams struct {
connConfig *pgx.ConnConfig
includeSchemas []string
excludeSchemas []string
connConfig *pgx.ConnConfig
includeSchemas []string
excludeSchemas []string
excludeTablePatterns []string
}

func generateDump(ctx context.Context, params generateDumpParams) (diff.Plan, error) {
Expand Down Expand Up @@ -82,6 +93,7 @@ func generateDump(ctx context.Context, params generateDumpParams) (diff.Plan, er
diff.WithTempDbFactory(tempDbFactory),
diff.WithIncludeSchemas(params.includeSchemas...),
diff.WithExcludeSchemas(params.excludeSchemas...),
diff.WithExcludeTablePatterns(params.excludeTablePatterns...),
diff.WithDoNotValidatePlan(),
diff.WithNoConcurrentIndexOps(),
)
Expand Down
23 changes: 23 additions & 0 deletions cmd/pg-schema-diff/dump_cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ func (suite *cmdTestSuite) TestDumpCmd() {

// outputContains is a list of substrings that are expected to be contained in the stdout output of the command.
outputContains []string
// outputNotContains is a list of substrings that are expected to NOT be contained in the stdout output.
outputNotContains []string
// expectErrContains is a list of substrings that are expected to be contained in the error returned by
// cmd.RunE. This is DISTINCT from stdErr.
expectErrContains []string
Expand All @@ -28,12 +30,33 @@ func (suite *cmdTestSuite) TestDumpCmd() {
"name",
},
},
{
name: "dump with exclude-table",
args: []string{"--exclude-table", "tmp_.*"},
dynamicArgs: []dArgGenerator{
tempDsnDArg(suite.pgEngine, "dsn", []string{
"CREATE TABLE foobar(id INT PRIMARY KEY)",
"CREATE TABLE tmp_foo(id INT PRIMARY KEY)",
}),
},
outputContains: []string{"foobar"},
outputNotContains: []string{"tmp_foo"},
},
{
name: "dump with invalid exclude-table pattern",
args: []string{"--exclude-table", "["},
dynamicArgs: []dArgGenerator{
tempDsnDArg(suite.pgEngine, "dsn", nil),
},
expectErrContains: []string{"invalid --exclude-table pattern"},
},
} {
suite.Run(tc.name, func() {
suite.runCmdWithAssertions(runCmdWithAssertionsParams{
args: append([]string{"dump"}, tc.args...),
dynamicArgs: tc.dynamicArgs,
outputContains: tc.outputContains,
outputNotContains: tc.outputNotContains,
expectErrContains: tc.expectErrContains,
})
})
Expand Down
5 changes: 5 additions & 0 deletions cmd/pg-schema-diff/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ type runCmdWithAssertionsParams struct {
outputEquals string
// outputContains is a list of substrings that are expected to be contained in the stdout output of the command.
outputContains []string
// outputNotContains is a list of substrings that are expected to NOT be contained in the stdout output.
outputNotContains []string
// expectErrContains is a list of substrings that are expected to be contained in the error returned by
// cmd.RunE. This is DISTINCT from stdErr.
expectErrContains []string
Expand Down Expand Up @@ -74,6 +76,9 @@ func (suite *cmdTestSuite) runCmdWithAssertions(tc runCmdWithAssertionsParams) {
suite.Contains(stdOutStr, o)
}
}
for _, o := range tc.outputNotContains {
suite.NotContains(stdOutStr, o)
}
if len(tc.outputEquals) > 0 {
suite.Equal(tc.outputEquals, stdOutStr)
}
Expand Down
24 changes: 22 additions & 2 deletions cmd/pg-schema-diff/plan_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,9 @@ func buildPlanCmd() *cobra.Command {
type (
// parsePlanOptionsFlags stores the flags that are parsed into planOptions.
planOptionsFlags struct {
includeSchemas []string
excludeSchemas []string
includeSchemas []string
excludeSchemas []string
excludeTablePatterns []string

dataPackNewTables bool
disablePlanValidation bool
Expand Down Expand Up @@ -221,6 +222,9 @@ func createPlanOptionsFlags(cmd *cobra.Command) *planOptionsFlags {

cmd.Flags().StringArrayVar(&flags.includeSchemas, "include-schema", nil, "Include the specified schema in the plan")
cmd.Flags().StringArrayVar(&flags.excludeSchemas, "exclude-schema", nil, "Exclude the specified schema in the plan")
cmd.Flags().StringArrayVar(&flags.excludeTablePatterns, "exclude-table", nil,
"Exclude tables matching this Go regexp. The pattern is matched (fully anchored) against both the table "+
"name and the schema-qualified name, e.g., 'tmp_.*' or 'public\\.tmp_.*'. Can be repeated.")

cmd.Flags().BoolVar(&flags.dataPackNewTables, "data-pack-new-tables", true, "If set, will data pack new tables in the plan to minimize table size (re-arranges columns).")
cmd.Flags().BoolVar(&flags.disablePlanValidation, "disable-plan-validation", false, "If set, will disable plan validation. Plan validation runs the migration against a temporary"+
Expand Down Expand Up @@ -314,10 +318,26 @@ func dsnSchemaSource(connConfig *pgx.ConnConfig) schemaSourceFactory {
}
}

// validateExcludeTablePatterns fail-fast validates regexes before any database work is done. The patterns are
// compiled for real inside schema.GetSchema; this exists purely for a clean CLI error.
func validateExcludeTablePatterns(patterns []string) error {
for _, pattern := range patterns {
if _, err := regexp.Compile(pattern); err != nil {
return fmt.Errorf("invalid --exclude-table pattern %q: %w", pattern, err)
}
}
return nil
}

func parsePlanOptions(p planOptionsFlags) (planOptions, error) {
if err := validateExcludeTablePatterns(p.excludeTablePatterns); err != nil {
return planOptions{}, err
}

opts := []diff.PlanOpt{
diff.WithIncludeSchemas(p.includeSchemas...),
diff.WithExcludeSchemas(p.excludeSchemas...),
diff.WithExcludeTablePatterns(p.excludeTablePatterns...),
}

if p.dataPackNewTables {
Expand Down
19 changes: 19 additions & 0 deletions cmd/pg-schema-diff/plan_cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"regexp"
"testing"
"time"

"github.com/stretchr/testify/require"
)

func (suite *cmdTestSuite) TestPlanCmd() {
Expand Down Expand Up @@ -124,6 +126,15 @@ func (suite *cmdTestSuite) TestPlanCmd() {
},
expectErrContains: []string{"invalid output format"},
},
{
name: "invalid exclude-table pattern",
args: []string{"--exclude-table", "["},
dynamicArgs: []dArgGenerator{
tempDsnDArg(suite.pgEngine, "from-dsn", nil),
tempDsnDArg(suite.pgEngine, "to-dsn", nil),
},
expectErrContains: []string{"invalid --exclude-table pattern"},
},
} {
suite.Run(tc.name, func() {
suite.runCmdWithAssertions(runCmdWithAssertionsParams{
Expand Down Expand Up @@ -245,3 +256,11 @@ func (suite *cmdTestSuite) TestParseInsertStatementStr() {
})
}
}

func TestParsePlanOptionsExcludeTablePatterns(t *testing.T) {
_, err := parsePlanOptions(planOptionsFlags{excludeTablePatterns: []string{"tmp_.*"}})
require.NoError(t, err)

_, err = parsePlanOptions(planOptionsFlags{excludeTablePatterns: []string{"["}})
require.ErrorContains(t, err, `invalid --exclude-table pattern "["`)
}
Loading