|
|
|
|
@@ -144,7 +144,11 @@ func (w *MigrationWriter) WriteMigration(model *models.Database, current *models
|
|
|
|
|
// Write header
|
|
|
|
|
fmt.Fprintf(w.writer, "-- PostgreSQL Migration Script\n")
|
|
|
|
|
fmt.Fprintf(w.writer, "-- Generated by RelSpec\n")
|
|
|
|
|
fmt.Fprintf(w.writer, "-- Source: %s -> %s\n\n", current.Name, model.Name)
|
|
|
|
|
fmt.Fprintf(w.writer, "-- Source: %s -> %s\n", current.Name, model.Name)
|
|
|
|
|
if w.options.ContinueOnError {
|
|
|
|
|
fmt.Fprintf(w.writer, "\\set ON_ERROR_STOP off\n")
|
|
|
|
|
}
|
|
|
|
|
fmt.Fprintf(w.writer, "\n")
|
|
|
|
|
|
|
|
|
|
// Write scripts
|
|
|
|
|
for _, script := range scripts {
|
|
|
|
|
@@ -171,13 +175,15 @@ func (w *MigrationWriter) generateSchemaScripts(model *models.Schema, current *m
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Phase 1: Drop constraints and indexes that changed (Priority 11-50)
|
|
|
|
|
// Phase 1: Drop constraints and indexes that changed (Priority 5-50)
|
|
|
|
|
var droppedFKs map[string]bool
|
|
|
|
|
if current != nil {
|
|
|
|
|
dropScripts, err := w.generateDropScripts(model, current)
|
|
|
|
|
dropScripts, dropped, err := w.generateDropScripts(model, current)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to generate drop scripts: %w", err)
|
|
|
|
|
}
|
|
|
|
|
scripts = append(scripts, dropScripts...)
|
|
|
|
|
droppedFKs = dropped
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Phase 3: Create/Alter tables and columns (Priority 100-145)
|
|
|
|
|
@@ -195,7 +201,7 @@ func (w *MigrationWriter) generateSchemaScripts(model *models.Schema, current *m
|
|
|
|
|
scripts = append(scripts, indexScripts...)
|
|
|
|
|
|
|
|
|
|
// Phase 5: Create foreign keys (Priority 195)
|
|
|
|
|
fkScripts, err := w.generateForeignKeyScripts(model, current)
|
|
|
|
|
fkScripts, err := w.generateForeignKeyScripts(model, current, droppedFKs)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to generate foreign key scripts: %w", err)
|
|
|
|
|
}
|
|
|
|
|
@@ -211,9 +217,12 @@ func (w *MigrationWriter) generateSchemaScripts(model *models.Schema, current *m
|
|
|
|
|
return scripts, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// generateDropScripts generates DROP scripts using templates
|
|
|
|
|
func (w *MigrationWriter) generateDropScripts(model *models.Schema, current *models.Schema) ([]MigrationScript, error) {
|
|
|
|
|
// generateDropScripts generates DROP scripts using templates.
|
|
|
|
|
// Returns the scripts and a set of FK constraint keys (schema.table.name) that were
|
|
|
|
|
// explicitly dropped because their referenced PK was being dropped, so they can be force-recreated.
|
|
|
|
|
func (w *MigrationWriter) generateDropScripts(model *models.Schema, current *models.Schema) ([]MigrationScript, map[string]bool, error) {
|
|
|
|
|
scripts := make([]MigrationScript, 0)
|
|
|
|
|
droppedFKs := make(map[string]bool)
|
|
|
|
|
|
|
|
|
|
// Build map of model tables for quick lookup
|
|
|
|
|
modelTables := make(map[string]*models.Table)
|
|
|
|
|
@@ -240,6 +249,44 @@ func (w *MigrationWriter) generateDropScripts(model *models.Schema, current *mod
|
|
|
|
|
shouldDrop = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if shouldDrop && currentConstraint.Type == models.PrimaryKeyConstraint {
|
|
|
|
|
// Drop FK constraints that depend on this PK before dropping the PK itself.
|
|
|
|
|
for _, otherTable := range current.Tables {
|
|
|
|
|
for fkName, fkConstraint := range otherTable.Constraints {
|
|
|
|
|
if fkConstraint.Type != models.ForeignKeyConstraint {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
refTable := fkConstraint.ReferencedTable
|
|
|
|
|
refSchema := fkConstraint.ReferencedSchema
|
|
|
|
|
if refSchema == "" {
|
|
|
|
|
refSchema = current.Name
|
|
|
|
|
}
|
|
|
|
|
if strings.EqualFold(refTable, currentTable.Name) && strings.EqualFold(refSchema, current.Name) {
|
|
|
|
|
fkKey := fmt.Sprintf("%s.%s.%s", current.Name, otherTable.Name, fkName)
|
|
|
|
|
if !droppedFKs[fkKey] {
|
|
|
|
|
droppedFKs[fkKey] = true
|
|
|
|
|
sql, err := w.executor.ExecuteDropConstraint(DropConstraintData{
|
|
|
|
|
SchemaName: current.Name,
|
|
|
|
|
TableName: otherTable.Name,
|
|
|
|
|
ConstraintName: fkName,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
scripts = append(scripts, MigrationScript{
|
|
|
|
|
ObjectName: fkKey,
|
|
|
|
|
ObjectType: "drop constraint",
|
|
|
|
|
Schema: current.Name,
|
|
|
|
|
Priority: 5,
|
|
|
|
|
Sequence: len(scripts),
|
|
|
|
|
Body: sql,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if shouldDrop {
|
|
|
|
|
sql, err := w.executor.ExecuteDropConstraint(DropConstraintData{
|
|
|
|
|
SchemaName: current.Name,
|
|
|
|
|
@@ -247,7 +294,7 @@ func (w *MigrationWriter) generateDropScripts(model *models.Schema, current *mod
|
|
|
|
|
ConstraintName: constraintName,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
script := MigrationScript{
|
|
|
|
|
@@ -279,7 +326,7 @@ func (w *MigrationWriter) generateDropScripts(model *models.Schema, current *mod
|
|
|
|
|
IndexName: indexName,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
script := MigrationScript{
|
|
|
|
|
@@ -295,7 +342,7 @@ func (w *MigrationWriter) generateDropScripts(model *models.Schema, current *mod
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return scripts, nil
|
|
|
|
|
return scripts, droppedFKs, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// generateTableScripts generates CREATE/ALTER TABLE scripts using templates
|
|
|
|
|
@@ -631,8 +678,10 @@ func buildIndexColumnExpressions(table *models.Table, index *models.Index, index
|
|
|
|
|
return columnExprs
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// generateForeignKeyScripts generates ADD CONSTRAINT FOREIGN KEY scripts using templates
|
|
|
|
|
func (w *MigrationWriter) generateForeignKeyScripts(model *models.Schema, current *models.Schema) ([]MigrationScript, error) {
|
|
|
|
|
// generateForeignKeyScripts generates ADD CONSTRAINT FOREIGN KEY scripts using templates.
|
|
|
|
|
// forceRecreate is a set of FK constraint keys (schema.table.name) that must be recreated
|
|
|
|
|
// even if unchanged, because their referenced PK was dropped and recreated.
|
|
|
|
|
func (w *MigrationWriter) generateForeignKeyScripts(model *models.Schema, current *models.Schema, forceRecreate map[string]bool) ([]MigrationScript, error) {
|
|
|
|
|
scripts := make([]MigrationScript, 0)
|
|
|
|
|
|
|
|
|
|
// Build map of current tables
|
|
|
|
|
@@ -653,13 +702,16 @@ func (w *MigrationWriter) generateForeignKeyScripts(model *models.Schema, curren
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
shouldCreate := true
|
|
|
|
|
fkKey := fmt.Sprintf("%s.%s.%s", model.Name, modelTable.Name, constraintName)
|
|
|
|
|
shouldCreate := forceRecreate[fkKey]
|
|
|
|
|
|
|
|
|
|
if currentTable != nil {
|
|
|
|
|
if currentConstraint, exists := currentTable.Constraints[constraintName]; exists {
|
|
|
|
|
if constraintsEqual(constraint, currentConstraint) {
|
|
|
|
|
shouldCreate = false
|
|
|
|
|
}
|
|
|
|
|
if !shouldCreate {
|
|
|
|
|
if currentTable == nil {
|
|
|
|
|
shouldCreate = true
|
|
|
|
|
} else if currentConstraint, exists := currentTable.Constraints[constraintName]; !exists {
|
|
|
|
|
shouldCreate = true
|
|
|
|
|
} else if !constraintsEqual(constraint, currentConstraint) {
|
|
|
|
|
shouldCreate = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|