Skip to content
Open
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
6 changes: 6 additions & 0 deletions cmd/pg-schema-diff/plan_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ type (
dataPackNewTables bool
disablePlanValidation bool
noConcurrentIndexOps bool
skipNotImplemented bool

statementTimeoutModifiers []string
lockTimeoutModifiers []string
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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 {
Expand Down
9 changes: 9 additions & 0 deletions pkg/diff/plan_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type (
getSchemaOpts []schema.GetSchemaOpt
randReader io.Reader
noConcurrentIndexOps bool
skipNotImplemented bool
}

PlanOpt func(opts *planOptions)
Expand Down Expand Up @@ -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:
Expand Down
35 changes: 19 additions & 16 deletions pkg/diff/sql_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand All @@ -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)
}
Expand All @@ -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)
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand All @@ -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)
}
Expand Down
12 changes: 11 additions & 1 deletion pkg/diff/sql_vertex_generator.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package diff

import (
"errors"
"fmt"

"github.com/stripe/pg-schema-diff/internal/schema"
Expand Down Expand Up @@ -101,25 +102,34 @@ 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)
}
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)
}
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)
Expand Down