Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dry run auto migrate #5645

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 13 additions & 4 deletions gorm.go
Expand Up @@ -31,6 +31,8 @@ type Config struct {
NowFunc func() time.Time
// DryRun generate sql without execute
DryRun bool
// DryRunMigration prevent AutoMigrate() to change the schema
DryRunMigration bool
// PrepareStmt executes the given query in cached statement
PrepareStmt bool
// DisableAutomaticPing
Expand Down Expand Up @@ -97,6 +99,7 @@ type DB struct {
// Session session config when create session with Session() method
type Session struct {
DryRun bool
DryRunMigration bool
PrepareStmt bool
NewDB bool
Initialized bool
Expand Down Expand Up @@ -274,6 +277,10 @@ func (db *DB) Session(config *Session) *DB {
tx.Config.DryRun = true
}

if config.DryRunMigration {
tx.Config.DryRunMigration = true
}

if config.QueryFields {
tx.Config.QueryFields = true
}
Expand Down Expand Up @@ -457,10 +464,12 @@ func (db *DB) Use(plugin Plugin) error {
// ToSQL for generate SQL string.
//
// db.ToSQL(func(tx *gorm.DB) *gorm.DB {
// return tx.Model(&User{}).Where(&User{Name: "foo", Age: 20})
// .Limit(10).Offset(5)
// .Order("name ASC")
// .First(&User{})
//
// return tx.Model(&User{}).Where(&User{Name: "foo", Age: 20})
// .Limit(10).Offset(5)
// .Order("name ASC")
// .First(&User{})
//
// })
func (db *DB) ToSQL(queryFn func(tx *DB) *DB) string {
tx := queryFn(db.Session(&Session{DryRun: true, SkipDefaultTransaction: true}))
Expand Down
24 changes: 24 additions & 0 deletions migrator/migrator.go
Expand Up @@ -94,6 +94,10 @@ func (m Migrator) AutoMigrate(values ...interface{}) error {
for _, value := range m.ReorderModels(values, true) {
tx := m.DB.Session(&gorm.Session{})
if !tx.Migrator().HasTable(value) {
if tx.DryRunMigration {
return fmt.Errorf("create table for model %T: %w", value, gorm.ErrDryRunModeUnsupported)
}

if err := tx.Migrator().CreateTable(value); err != nil {
return err
}
Expand All @@ -117,6 +121,10 @@ func (m Migrator) AutoMigrate(values ...interface{}) error {

if foundColumn == nil {
// not found, add column
if tx.DryRunMigration {
return fmt.Errorf("create column for model %T: %w", value, gorm.ErrDryRunModeUnsupported)
}

if err := tx.Migrator().AddColumn(value, dbName); err != nil {
return err
}
Expand All @@ -130,6 +138,10 @@ func (m Migrator) AutoMigrate(values ...interface{}) error {
if !m.DB.Config.DisableForeignKeyConstraintWhenMigrating {
if constraint := rel.ParseConstraint(); constraint != nil &&
constraint.Schema == stmt.Schema && !tx.Migrator().HasConstraint(value, constraint.Name) {
if tx.DryRunMigration {
return fmt.Errorf("create constraint %s for model %T: %w", constraint.Name, value, gorm.ErrDryRunModeUnsupported)
}

if err := tx.Migrator().CreateConstraint(value, constraint.Name); err != nil {
return err
}
Expand All @@ -139,6 +151,10 @@ func (m Migrator) AutoMigrate(values ...interface{}) error {

for _, chk := range stmt.Schema.ParseCheckConstraints() {
if !tx.Migrator().HasConstraint(value, chk.Name) {
if tx.DryRunMigration {
return fmt.Errorf("create constraint %s for model %T: %w", chk.Name, value, gorm.ErrDryRunModeUnsupported)
}

if err := tx.Migrator().CreateConstraint(value, chk.Name); err != nil {
return err
}
Expand All @@ -147,6 +163,10 @@ func (m Migrator) AutoMigrate(values ...interface{}) error {

for _, idx := range stmt.Schema.ParseIndexes() {
if !tx.Migrator().HasIndex(value, idx.Name) {
if tx.DryRunMigration {
return fmt.Errorf("create index %s for model %T: %w", idx.Name, value, gorm.ErrDryRunModeUnsupported)
}

if err := tx.Migrator().CreateIndex(value, idx.Name); err != nil {
return err
}
Expand Down Expand Up @@ -478,6 +498,10 @@ func (m Migrator) MigrateColumn(value interface{}, field *schema.Field, columnTy
}

if alterColumn && !field.IgnoreMigration {
if m.DB.DryRunMigration {
return fmt.Errorf("alter column %s for model %T: %w", field.Name, value, gorm.ErrDryRunModeUnsupported)
}

return m.DB.Migrator().AlterColumn(value, field.Name)
}

Expand Down
62 changes: 62 additions & 0 deletions tests/migrate_test.go
@@ -1,6 +1,7 @@
package tests_test

import (
"errors"
"fmt"
"math/rand"
"reflect"
Expand Down Expand Up @@ -959,3 +960,64 @@ func TestMigrateArrayTypeModel(t *testing.T) {
AssertEqual(t, nil, err)
AssertEqual(t, "integer[]", ct.DatabaseTypeName())
}

type Origin struct {
ID int64 `gorm:"primaryKey"`
Data string `gorm:"null"`
}

func (Origin) TableName() string {
return "test"
}

func TestDryRunAutoMigrate(t *testing.T) {
type ChangeColumn struct {
Origin `gorm:"-"`
ID int64 `gorm:"primaryKey"`
Data int64 `gorm:""`
}

type AddIndex struct {
Origin `gorm:"-"`
ID int64 `gorm:"primaryKey"`
Data string `gorm:"null;index"`
}

type AddConstraint struct {
Origin `gorm:"-"`
ID int64 `gorm:"primaryKey"`
Data string `gorm:"null;check:,data <> 'migrate'"`
}

var tests = []struct {
from, to interface{}
dryrunErr bool
}{
{&Origin{}, &Origin{}, false},
{&Origin{}, &ChangeColumn{}, true},
{&Origin{}, &AddIndex{}, true},
{&Origin{}, &AddConstraint{}, true},
}

for _, test := range tests {
name := strings.ReplaceAll(fmt.Sprintf("%T", test.to), "*", "")
t.Run(name, func(t *testing.T) {
DB.Migrator().DropTable(test.from, test.to)
t.Cleanup(func() {
DB.Migrator().DropTable(test.from, test.to)
})

err := DB.Migrator().CreateTable(test.from)
AssertEqual(t, nil, err)

err = DB.Session(&gorm.Session{DryRunMigration: true}).AutoMigrate(test.to)
if err != nil {
t.Log("migrate error:", err)
}
AssertEqual(t, test.dryrunErr, errors.Is(err, gorm.ErrDryRunModeUnsupported))

err = DB.AutoMigrate(test.to)
AssertEqual(t, false, errors.Is(err, gorm.ErrDryRunModeUnsupported))
})
}
}