package mysql import ( "database/sql" "fmt" "gorm.io/gorm" "gorm.io/gorm/clause" "gorm.io/gorm/migrator" "gorm.io/gorm/schema" ) type Migrator struct { migrator.Migrator Dialector } type Column struct { name string nullable sql.NullString datatype string maxLen sql.NullInt64 precision sql.NullInt64 scale sql.NullInt64 datetimePrecision sql.NullInt64 } func (c Column) Name() string { return c.name } func (c Column) DatabaseTypeName() string { return c.datatype } func (c Column) Length() (int64, bool) { if c.maxLen.Valid { return c.maxLen.Int64, c.maxLen.Valid } return 0, false } func (c Column) Nullable() (bool, bool) { if c.nullable.Valid { return c.nullable.String == "YES", true } return false, false } // DecimalSize return precision int64, scale int64, ok bool func (c Column) DecimalSize() (int64, int64, bool) { if c.precision.Valid { if c.scale.Valid { return c.precision.Int64, c.scale.Int64, true } return c.precision.Int64, 0, true } if c.datetimePrecision.Valid { return c.datetimePrecision.Int64, 0, true } return 0, 0, false } func (m Migrator) FullDataTypeOf(field *schema.Field) clause.Expr { expr := m.Migrator.FullDataTypeOf(field) if value, ok := field.TagSettings["COMMENT"]; ok { expr.SQL += " COMMENT " + m.Dialector.Explain("?", value) } return expr } func (m Migrator) AlterColumn(value interface{}, field string) error { return m.RunWithValue(value, func(stmt *gorm.Statement) error { if field := stmt.Schema.LookUpField(field); field != nil { return m.DB.Exec( "ALTER TABLE ? MODIFY COLUMN ? ?", clause.Table{Name: stmt.Table}, clause.Column{Name: field.DBName}, m.FullDataTypeOf(field), ).Error } return fmt.Errorf("failed to look up field with name: %s", field) }) } func (m Migrator) RenameColumn(value interface{}, oldName, newName string) error { return m.RunWithValue(value, func(stmt *gorm.Statement) error { if !m.Dialector.DontSupportRenameColumn { return m.Migrator.RenameColumn(value, oldName, newName) } var field *schema.Field if f := stmt.Schema.LookUpField(oldName); f != nil { oldName = f.DBName field = f } if f := stmt.Schema.LookUpField(newName); f != nil { newName = f.DBName field = f } if field != nil { return m.DB.Exec( "ALTER TABLE ? CHANGE ? ? ?", clause.Table{Name: stmt.Table}, clause.Column{Name: oldName}, clause.Column{Name: newName}, m.FullDataTypeOf(field), ).Error } return fmt.Errorf("failed to look up field with name: %s", newName) }) } func (m Migrator) RenameIndex(value interface{}, oldName, newName string) error { if !m.Dialector.DontSupportRenameIndex { return m.RunWithValue(value, func(stmt *gorm.Statement) error { return m.DB.Exec( "ALTER TABLE ? RENAME INDEX ? TO ?", clause.Table{Name: stmt.Table}, clause.Column{Name: oldName}, clause.Column{Name: newName}, ).Error }) } return m.RunWithValue(value, func(stmt *gorm.Statement) error { err := m.DropIndex(value, oldName) if err != nil { return err } if idx := stmt.Schema.LookIndex(newName); idx == nil { if idx = stmt.Schema.LookIndex(oldName); idx != nil { opts := m.BuildIndexOptions(idx.Fields, stmt) values := []interface{}{clause.Column{Name: newName}, clause.Table{Name: stmt.Table}, opts} createIndexSQL := "CREATE " if idx.Class != "" { createIndexSQL += idx.Class + " " } createIndexSQL += "INDEX ? ON ??" if idx.Type != "" { createIndexSQL += " USING " + idx.Type } return m.DB.Exec(createIndexSQL, values...).Error } } return m.CreateIndex(value, newName) }) } func (m Migrator) DropTable(values ...interface{}) error { values = m.ReorderModels(values, false) tx := m.DB.Session(&gorm.Session{}) tx.Exec("SET FOREIGN_KEY_CHECKS = 0;") for i := len(values) - 1; i >= 0; i-- { if err := m.RunWithValue(values[i], func(stmt *gorm.Statement) error { return tx.Exec("DROP TABLE IF EXISTS ? CASCADE", clause.Table{Name: stmt.Table}).Error }); err != nil { return err } } tx.Exec("SET FOREIGN_KEY_CHECKS = 1;") return nil } 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) err := m.RunWithValue(value, func(stmt *gorm.Statement) error { var ( currentDatabase = m.DB.Migrator().CurrentDatabase() columnTypeSQL = "SELECT column_name, is_nullable, data_type, character_maximum_length, numeric_precision, numeric_scale " ) if !m.DisableDatetimePrecision { columnTypeSQL += ", datetime_precision " } columnTypeSQL += "FROM information_schema.columns WHERE table_schema = ? AND table_name = ? ORDER BY ORDINAL_POSITION" columns, rowErr := m.DB.Raw(columnTypeSQL, currentDatabase, stmt.Table).Rows() if rowErr != nil { return rowErr } defer columns.Close() for columns.Next() { var column Column var values = []interface{}{&column.name, &column.nullable, &column.datatype, &column.maxLen, &column.precision, &column.scale} if !m.DisableDatetimePrecision { values = append(values, &column.datetimePrecision) } if scanErr := columns.Scan(values...); scanErr != nil { return scanErr } columnTypes = append(columnTypes, column) } return nil }) return columnTypes, err } func (m Migrator) CurrentDatabase() (name string) { baseName := m.Migrator.CurrentDatabase() m.DB.Raw( "SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE ? ORDER BY SCHEMA_NAME=? DESC limit 1", baseName+"%", baseName).Scan(&name) return } func (m Migrator) GetTables() (tableList []string, err error) { err = m.DB.Raw("SELECT TABLE_NAME FROM information_schema.tables where TABLE_SCHEMA=?", m.CurrentDatabase()). Scan(&tableList).Error return }