Database migrations must be designed to run safely multiple times without causing errors or data corruption. This involves using structured migration APIs instead of raw SQL, implementing proper checks for existing state, and avoiding unnecessary complexity that could lead to failures.
Key practices:
NewDropColumn
, NewAddColumn
instead of executing raw SQL queries for better safety and maintainabilityIF NOT EXISTS
clauses)Example of idempotent migration pattern:
func (migration *example) Up(ctx context.Context, db *bun.DB) error {
// Use structured API with safety checks
_, err := tx.NewCreateTable().
Model((*ExampleModel)(nil)).
IfNotExists(). // Idempotent check
Exec(ctx)
// Avoid unnecessary complexity - only pass required constraints
column := &sqlschema.Column{
Name: sqlschema.ColumnName("new_field"),
DataType: sqlschema.DataTypeText,
Nullable: true,
}
// Pass nil for constraints if none needed, don't extract unused ones
sqls := migration.sqlschema.Operator().AddColumn(table, nil, column, nil)
}
func (migration *example) Down(ctx context.Context, db *bun.DB) error {
// Simply return nil if down migrations aren't used
return nil
}
This approach ensures migrations can be run reliably across different environments and deployment scenarios without manual intervention or risk of failure.
Enter the URL of a public GitHub repository