diff --git a/go.mod b/go.mod index fc63353..6ee0e28 100644 --- a/go.mod +++ b/go.mod @@ -4,5 +4,5 @@ go 1.14 require ( github.com/go-sql-driver/mysql v1.7.0 - gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55 + gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde ) diff --git a/go.sum b/go.sum index 5db00fb..8165163 100644 --- a/go.sum +++ b/go.sum @@ -7,4 +7,8 @@ github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/ gorm.io/gorm v1.25.1 h1:nsSALe5Pr+cM3V1qwwQ7rOkw+6UeLrX5O4v3llhHa64= gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55 h1:sC1Xj4TYrLqg1n3AN10w871An7wJM0gzgcm8jkIkECQ= -gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= \ No newline at end of file +gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.6-0.20231115133256-3207ad6033aa h1:cGDODlWbAjO6jreDwDD34SsdQVhuFE5KjXO1LmpKZYQ= +gorm.io/gorm v1.25.6-0.20231115133256-3207ad6033aa/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde h1:9DShaph9qhkIYw7QF91I/ynrr4cOO2PZra2PFD7Mfeg= +gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= diff --git a/migrator.go b/migrator.go index d29d204..cd492b2 100644 --- a/migrator.go +++ b/migrator.go @@ -47,6 +47,78 @@ func (m Migrator) FullDataTypeOf(field *schema.Field) clause.Expr { return expr } +// MigrateColumnUnique migrate column's UNIQUE constraint. +// In MySQL, ColumnType's Unique is affected by UniqueIndex, so we have to take care of the UniqueIndex. +func (m Migrator) MigrateColumnUnique(value interface{}, field *schema.Field, columnType gorm.ColumnType) error { + unique, ok := columnType.Unique() + if !ok || field.PrimaryKey { + return nil // skip primary key + } + + queryTx, execTx := m.GetQueryAndExecTx() + return m.RunWithValue(value, func(stmt *gorm.Statement) error { + // We're currently only receiving boolean values on `Unique` tag, + // so the UniqueConstraint name is fixed + constraint := m.DB.NamingStrategy.UniqueName(stmt.Table, field.DBName) + if unique { + // Clean up redundant unique indexes + indexes, _ := queryTx.Migrator().GetIndexes(value) + for _, index := range indexes { + if uni, ok := index.Unique(); !ok || !uni { + continue + } + if columns := index.Columns(); len(columns) != 1 || columns[0] != field.DBName { + continue + } + if name := index.Name(); name == constraint || name == field.UniqueIndex { + continue + } + if err := execTx.Migrator().DropIndex(value, index.Name()); err != nil { + return err + } + } + + hasConstraint := queryTx.Migrator().HasConstraint(value, constraint) + switch { + case field.Unique && !hasConstraint: + if field.Unique { + if err := execTx.Migrator().CreateConstraint(value, constraint); err != nil { + return err + } + } + // field isn't Unique but ColumnType's Unique is reported by UniqueConstraint. + case !field.Unique && hasConstraint: + if err := execTx.Migrator().DropConstraint(value, constraint); err != nil { + return err + } + if field.UniqueIndex != "" { + if err := execTx.Migrator().CreateIndex(value, field.UniqueIndex); err != nil { + return err + } + } + } + + if field.UniqueIndex != "" && !queryTx.Migrator().HasIndex(value, field.UniqueIndex) { + if err := execTx.Migrator().CreateIndex(value, field.UniqueIndex); err != nil { + return err + } + } + } else { + if field.Unique { + if err := execTx.Migrator().CreateConstraint(value, constraint); err != nil { + return err + } + } + if field.UniqueIndex != "" { + if err := execTx.Migrator().CreateIndex(value, field.UniqueIndex); err != nil { + return err + } + } + } + return nil + }) +} + func (m Migrator) AddColumn(value interface{}, name string) error { return m.RunWithValue(value, func(stmt *gorm.Statement) error { // avoid using the same name field @@ -209,22 +281,6 @@ func (m Migrator) DropTable(values ...interface{}) error { }) } -func (m Migrator) DropConstraint(value interface{}, name string) error { - return m.RunWithValue(value, func(stmt *gorm.Statement) error { - constraint, chk, table := m.GuessConstraintAndTable(stmt, name) - if chk != nil { - return m.DB.Exec("ALTER TABLE ? DROP CHECK ?", clause.Table{Name: stmt.Table}, clause.Column{Name: chk.Name}).Error - } - if constraint != nil { - name = constraint.Name - } - - return m.DB.Exec( - "ALTER TABLE ? DROP FOREIGN KEY ?", clause.Table{Name: table}, clause.Column{Name: name}, - ).Error - }) -} - // ColumnTypes column types return columnTypes,error func (m Migrator) ColumnTypes(value interface{}) ([]gorm.ColumnType, error) { columnTypes := make([]gorm.ColumnType, 0)