diff --git a/cmd/pg-schema-diff/plan_cmd.go b/cmd/pg-schema-diff/plan_cmd.go index 6c65722..7d16b2b 100644 --- a/cmd/pg-schema-diff/plan_cmd.go +++ b/cmd/pg-schema-diff/plan_cmd.go @@ -117,6 +117,7 @@ type ( dataPackNewTables bool disablePlanValidation bool noConcurrentIndexOps bool + skipNotImplemented bool statementTimeoutModifiers []string lockTimeoutModifiers []string @@ -227,6 +228,8 @@ func createPlanOptionsFlags(cmd *cobra.Command) *planOptionsFlags { "database with an identical schema to the original, asserting that the generated plan actually migrates the schema to the desired target.") cmd.Flags().BoolVar(&flags.noConcurrentIndexOps, "no-concurrent-index-ops", false, "If set, will disable the use of CONCURRENTLY in CREATE INDEX and DROP INDEX statements. "+ "This may result in longer lock times and potential downtime during migrations.") + cmd.Flags().BoolVar(&flags.skipNotImplemented, "skip-not-implemented", false, "If set, will silently skip any diff operations that are not yet implemented (ErrNotImplemented) "+ + "rather than failing the entire plan. Useful for best-effort diffing of schemas with unsupported features.") timeoutModifierFlagVar(cmd, &flags.statementTimeoutModifiers, "statement", "t") timeoutModifierFlagVar(cmd, &flags.lockTimeoutModifiers, "lock", "l") @@ -329,6 +332,9 @@ func parsePlanOptions(p planOptionsFlags) (planOptions, error) { if p.noConcurrentIndexOps { opts = append(opts, diff.WithNoConcurrentIndexOps()) } + if p.skipNotImplemented { + opts = append(opts, diff.WithSkipNotImplemented()) + } var statementTimeoutModifiers []timeoutModifier for _, s := range p.statementTimeoutModifiers { diff --git a/pkg/diff/plan_generator.go b/pkg/diff/plan_generator.go index 2c2c92b..47c3bdc 100644 --- a/pkg/diff/plan_generator.go +++ b/pkg/diff/plan_generator.go @@ -36,6 +36,7 @@ type ( getSchemaOpts []schema.GetSchemaOpt randReader io.Reader noConcurrentIndexOps bool + skipNotImplemented bool } PlanOpt func(opts *planOptions) @@ -113,6 +114,14 @@ func WithNoConcurrentIndexOps() PlanOpt { } } +// WithSkipNotImplemented configures plan generation to silently skip any diff operations that are not yet +// implemented rather than failing the entire plan. +func WithSkipNotImplemented() PlanOpt { + return func(opts *planOptions) { + opts.skipNotImplemented = true + } +} + // Generate generates a migration plan to migrate the database to the target schema // // Parameters: diff --git a/pkg/diff/sql_generator.go b/pkg/diff/sql_generator.go index 6c31e22..b921deb 100644 --- a/pkg/diff/sql_generator.go +++ b/pkg/diff/sql_generator.go @@ -573,12 +573,14 @@ func (s schemaSQLGenerator) Alter(diff schemaDiff) ([]Statement, error) { var partialGraph partialSQLGraph + skipNI := s.planOptions.skipNotImplemented tablePartialGraph, err := generatePartialGraph(legacyToNewSqlVertexGenerator[schema.Table, tableDiff](&tableSQLVertexGenerator{ randReader: s.randReader, deletedTablesByName: deletedTablesByName, tablesInNewSchemaByName: tablesInNewSchemaByName, tableDiffsByName: buildDiffByNameMap[schema.Table, tableDiff](diff.tableDiffs.alters), - }), diff.tableDiffs) + skipNotImplemented: skipNI, + }), diff.tableDiffs, skipNI) if err != nil { return nil, fmt.Errorf("resolving table diff: %w", err) } @@ -595,14 +597,14 @@ func (s schemaSQLGenerator) Alter(diff schemaDiff) ([]Statement, error) { } attachPartitionGenerator := newAttachPartitionSQLVertexGenerator(diff.new.Indexes, diff.tableDiffs.adds) - attachPartitionsPartialGraph, err := generatePartialGraph(legacyToNewSqlVertexGenerator[schema.Table, tableDiff](attachPartitionGenerator), diff.tableDiffs) + attachPartitionsPartialGraph, err := generatePartialGraph(legacyToNewSqlVertexGenerator[schema.Table, tableDiff](attachPartitionGenerator), diff.tableDiffs, skipNI) if err != nil { return nil, fmt.Errorf("resolving attach partition diff: %w", err) } partialGraph = concatPartialGraphs(partialGraph, attachPartitionsPartialGraph) renameConflictingIndexesGenerator := newRenameConflictingIndexSQLVertexGenerator(s.randReader, buildSchemaObjByNameMap(diff.old.Indexes)) - renameConflictingIndexesPartialGraph, err := generatePartialGraph(legacyToNewSqlVertexGenerator[schema.Index, indexDiff](renameConflictingIndexesGenerator), diff.indexDiffs) + renameConflictingIndexesPartialGraph, err := generatePartialGraph(legacyToNewSqlVertexGenerator[schema.Index, indexDiff](renameConflictingIndexesGenerator), diff.indexDiffs, skipNI) if err != nil { return nil, fmt.Errorf("resolving renaming conflicting indexes diff: %w", err) } @@ -623,14 +625,14 @@ func (s schemaSQLGenerator) Alter(diff schemaDiff) ([]Statement, error) { attachPartitionSQLVertexGenerator: attachPartitionGenerator, planOptions: s.planOptions, }) - indexesPartialGraph, err := generatePartialGraph(indexGenerator, diff.indexDiffs) + indexesPartialGraph, err := generatePartialGraph(indexGenerator, diff.indexDiffs, skipNI) if err != nil { return nil, fmt.Errorf("resolving index diff: %w", err) } partialGraph = concatPartialGraphs(partialGraph, indexesPartialGraph) foreignKeyGenerator := newForeignKeyConstraintSQLVertexGenerator(diff.oldAndNew, diff.tableDiffs) - fkConsPartialGraph, err := generatePartialGraph(foreignKeyGenerator, diff.foreignKeyConstraintDiffs) + fkConsPartialGraph, err := generatePartialGraph(foreignKeyGenerator, diff.foreignKeyConstraintDiffs, skipNI) if err != nil { return nil, fmt.Errorf("resolving foreign key constraint diff: %w", err) } @@ -640,49 +642,49 @@ func (s schemaSQLGenerator) Alter(diff schemaDiff) ([]Statement, error) { deletedTablesByName: deletedTablesByName, tableDiffsByName: buildDiffByNameMap[schema.Table, tableDiff](diff.tableDiffs.alters), }) - sequencesPartialGraph, err := generatePartialGraph(sequenceGenerator, diff.sequenceDiffs) + sequencesPartialGraph, err := generatePartialGraph(sequenceGenerator, diff.sequenceDiffs, skipNI) if err != nil { return nil, fmt.Errorf("resolving sequence diff: %w", err) } partialGraph = concatPartialGraphs(partialGraph, sequencesPartialGraph) sequenceOwnershipGenerator := legacyToNewSqlVertexGenerator[schema.Sequence, sequenceDiff](&sequenceOwnershipSQLVertexGenerator{}) - sequenceOwnershipsPartialGraph, err := generatePartialGraph(sequenceOwnershipGenerator, diff.sequenceDiffs) + sequenceOwnershipsPartialGraph, err := generatePartialGraph(sequenceOwnershipGenerator, diff.sequenceDiffs, skipNI) if err != nil { return nil, fmt.Errorf("resolving sequence ownership diff: %w", err) } partialGraph = concatPartialGraphs(partialGraph, sequenceOwnershipsPartialGraph) functionGenerator := newFunctionSqlVertexGenerator(functionsInNewSchemaByName) - functionsPartialGraph, err := generatePartialGraph(functionGenerator, diff.functionDiffs) + functionsPartialGraph, err := generatePartialGraph(functionGenerator, diff.functionDiffs, skipNI) if err != nil { return nil, fmt.Errorf("resolving function diff: %w", err) } partialGraph = concatPartialGraphs(partialGraph, functionsPartialGraph) procedureGenerator := newProcedureSqlVertexGenerator(diff.new) - proceduresPartialGraph, err := generatePartialGraph(procedureGenerator, diff.proceduresDiffs) + proceduresPartialGraph, err := generatePartialGraph(procedureGenerator, diff.proceduresDiffs, skipNI) if err != nil { return nil, fmt.Errorf("resolving procedure diff: %w", err) } partialGraph = concatPartialGraphs(partialGraph, proceduresPartialGraph) triggerGenerator := newTriggerSqlVertexGenerator(functionsInNewSchemaByName) - triggersPartialGraph, err := generatePartialGraph(triggerGenerator, diff.triggerDiffs) + triggersPartialGraph, err := generatePartialGraph(triggerGenerator, diff.triggerDiffs, skipNI) if err != nil { return nil, fmt.Errorf("resolving trigger diff: %w", err) } partialGraph = concatPartialGraphs(partialGraph, triggersPartialGraph) viewGenerator := newViewSQLVertexGenerator() - viewPartialGraph, err := generatePartialGraph(viewGenerator, diff.viewDiff) + viewPartialGraph, err := generatePartialGraph(viewGenerator, diff.viewDiff, skipNI) if err != nil { return nil, fmt.Errorf("resolving view diff: %w", err) } partialGraph = concatPartialGraphs(partialGraph, viewPartialGraph) materializedViewGenerator := newMaterializedViewSQLVertexGenerator() - materializedViewPartialGraph, err := generatePartialGraph(materializedViewGenerator, diff.materializedViewDiffs) + materializedViewPartialGraph, err := generatePartialGraph(materializedViewGenerator, diff.materializedViewDiffs, skipNI) if err != nil { return nil, fmt.Errorf("resolving materialized view diff: %w", err) } @@ -796,6 +798,7 @@ type tableSQLVertexGenerator struct { deletedTablesByName map[string]schema.Table tablesInNewSchemaByName map[string]schema.Table tableDiffsByName map[string]tableDiff + skipNotImplemented bool } func (t *tableSQLVertexGenerator) Add(table schema.Table) ([]Statement, error) { @@ -992,7 +995,7 @@ func (t *tableSQLVertexGenerator) alterBaseTable(diff tableDiff) ([]Statement, e var partialGraph partialSQLGraph columnGenerator := newColumnSQLVertexGenerator(diff.new.SchemaQualifiedName) - columnsPartialGraph, err := generatePartialGraph(columnGenerator, diff.columnsDiff) + columnsPartialGraph, err := generatePartialGraph(columnGenerator, diff.columnsDiff, t.skipNotImplemented) if err != nil { return nil, fmt.Errorf("resolving index diff: %w", err) } @@ -1006,7 +1009,7 @@ func (t *tableSQLVertexGenerator) alterBaseTable(diff tableDiff) ([]Statement, e deletedColumnsByName: buildSchemaObjByNameMap(diff.columnsDiff.deletes), isNewTable: false, }) - checkConsPartialGraph, err := generatePartialGraph(checkConGenerator, diff.checkConstraintDiff) + checkConsPartialGraph, err := generatePartialGraph(checkConGenerator, diff.checkConstraintDiff, t.skipNotImplemented) if err != nil { return nil, fmt.Errorf("resolving check constraints sql: %w", err) } @@ -1029,14 +1032,14 @@ func (t *tableSQLVertexGenerator) alterBaseTable(diff tableDiff) ([]Statement, e if err != nil { return nil, fmt.Errorf("creating policy sql vertex generator: %w", err) } - policiesPartialGraph, err := generatePartialGraph(policyGenerator, diff.policiesDiff) + policiesPartialGraph, err := generatePartialGraph(policyGenerator, diff.policiesDiff, t.skipNotImplemented) if err != nil { return nil, fmt.Errorf("resolving policy sql: %w", err) } partialGraph = concatPartialGraphs(partialGraph, policiesPartialGraph) privilegeGenerator := newPrivilegeSQLVertexGenerator(diff.new.SchemaQualifiedName) - privilegesPartialGraph, err := generatePartialGraph(privilegeGenerator, diff.privilegesDiff) + privilegesPartialGraph, err := generatePartialGraph(privilegeGenerator, diff.privilegesDiff, t.skipNotImplemented) if err != nil { return nil, fmt.Errorf("resolving privilege sql: %w", err) } diff --git a/pkg/diff/sql_vertex_generator.go b/pkg/diff/sql_vertex_generator.go index 3b54459..07bac57 100644 --- a/pkg/diff/sql_vertex_generator.go +++ b/pkg/diff/sql_vertex_generator.go @@ -1,6 +1,7 @@ package diff import ( + "errors" "fmt" "github.com/stripe/pg-schema-diff/internal/schema" @@ -101,11 +102,14 @@ type sqlVertexGenerator[S schema.Object, Diff diff[S]] interface { } // generatePartialGraph generates a partial for the given schema object list diff using the inutted generator. -func generatePartialGraph[S schema.Object, Diff diff[S]](generator sqlVertexGenerator[S, Diff], listDiff listDiff[S, Diff]) (partialSQLGraph, error) { +func generatePartialGraph[S schema.Object, Diff diff[S]](generator sqlVertexGenerator[S, Diff], listDiff listDiff[S, Diff], skipNotImplemented bool) (partialSQLGraph, error) { var partialGraphs []partialSQLGraph for _, a := range listDiff.adds { v, err := generator.Add(a) if err != nil { + if skipNotImplemented && errors.Is(err, ErrNotImplemented) { + continue + } return partialSQLGraph{}, fmt.Errorf("generating add statements for %s: %w", a.GetName(), err) } partialGraphs = append(partialGraphs, v) @@ -113,6 +117,9 @@ func generatePartialGraph[S schema.Object, Diff diff[S]](generator sqlVertexGene for _, d := range listDiff.deletes { v, err := generator.Delete(d) if err != nil { + if skipNotImplemented && errors.Is(err, ErrNotImplemented) { + continue + } return partialSQLGraph{}, fmt.Errorf("generating delete statements for %s: %w", d.GetName(), err) } partialGraphs = append(partialGraphs, v) @@ -120,6 +127,9 @@ func generatePartialGraph[S schema.Object, Diff diff[S]](generator sqlVertexGene for _, a := range listDiff.alters { v, err := generator.Alter(a) if err != nil { + if skipNotImplemented && errors.Is(err, ErrNotImplemented) { + continue + } return partialSQLGraph{}, fmt.Errorf("generating alter statements for %s: %w", a.GetNew().GetName(), err) } partialGraphs = append(partialGraphs, v)