gorm/migrator/migrator.go

675 lines
19 KiB
Go
Raw Normal View History

2020-01-28 18:01:35 +03:00
package migrator
2020-02-20 18:04:03 +03:00
import (
2020-07-16 06:27:04 +03:00
"context"
2020-02-20 18:04:03 +03:00
"database/sql"
"fmt"
2020-02-22 08:09:57 +03:00
"reflect"
2020-08-23 10:40:19 +03:00
"regexp"
2020-02-22 08:09:57 +03:00
"strings"
2020-02-20 18:04:03 +03:00
2020-06-02 04:16:07 +03:00
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/schema"
2020-02-20 18:04:03 +03:00
)
2020-01-28 18:01:35 +03:00
2020-02-22 12:53:57 +03:00
// Migrator m struct
2020-01-28 18:01:35 +03:00
type Migrator struct {
2020-02-22 12:53:57 +03:00
Config
2020-01-28 18:01:35 +03:00
}
// Config schema config
type Config struct {
CreateIndexAfterCreateTable bool
DB *gorm.DB
2020-02-22 12:53:57 +03:00
gorm.Dialector
2020-01-28 18:01:35 +03:00
}
2020-02-20 18:04:03 +03:00
2020-06-06 05:47:32 +03:00
type GormDataTypeInterface interface {
GormDBDataType(*gorm.DB, *schema.Field) string
}
2020-02-22 12:53:57 +03:00
func (m Migrator) RunWithValue(value interface{}, fc func(*gorm.Statement) error) error {
2020-04-28 03:05:22 +03:00
stmt := &gorm.Statement{DB: m.DB}
if m.DB.Statement != nil {
stmt.Table = m.DB.Statement.Table
2020-02-20 18:04:03 +03:00
}
2020-04-28 03:05:22 +03:00
if table, ok := value.(string); ok {
stmt.Table = table
} else if err := stmt.Parse(value); err != nil {
2020-02-20 18:04:03 +03:00
return err
}
return fc(stmt)
}
2020-02-22 12:53:57 +03:00
func (m Migrator) DataTypeOf(field *schema.Field) string {
2020-06-06 05:47:32 +03:00
fieldValue := reflect.New(field.IndirectFieldType)
if dataTyper, ok := fieldValue.Interface().(GormDataTypeInterface); ok {
if dataType := dataTyper.GormDBDataType(m.DB, field); dataType != "" {
return dataType
}
}
2020-02-22 12:53:57 +03:00
return m.Dialector.DataTypeOf(field)
}
2020-05-30 19:42:52 +03:00
func (m Migrator) FullDataTypeOf(field *schema.Field) (expr clause.Expr) {
expr.SQL = m.DataTypeOf(field)
if field.NotNull {
2020-05-30 19:42:52 +03:00
expr.SQL += " NOT NULL"
}
if field.Unique {
2020-05-30 19:42:52 +03:00
expr.SQL += " UNIQUE"
}
if field.HasDefaultValue && (field.DefaultValueInterface != nil || field.DefaultValue != "") {
2020-06-25 03:00:10 +03:00
if field.DefaultValueInterface != nil {
defaultStmt := &gorm.Statement{Vars: []interface{}{field.DefaultValueInterface}}
m.Dialector.BindVarTo(defaultStmt, defaultStmt, field.DefaultValueInterface)
expr.SQL += " DEFAULT " + m.Dialector.Explain(defaultStmt.SQL.String(), field.DefaultValueInterface)
2020-05-30 19:42:52 +03:00
} else {
expr.SQL += " DEFAULT " + field.DefaultValue
}
}
2020-05-30 19:42:52 +03:00
return
}
2020-02-20 18:04:03 +03:00
// AutoMigrate
2020-02-22 12:53:57 +03:00
func (m Migrator) AutoMigrate(values ...interface{}) error {
2020-02-22 19:18:12 +03:00
for _, value := range m.ReorderModels(values, true) {
2020-02-22 18:08:20 +03:00
tx := m.DB.Session(&gorm.Session{})
if !tx.Migrator().HasTable(value) {
if err := tx.Migrator().CreateTable(value); err != nil {
2020-02-22 08:09:57 +03:00
return err
}
} else {
if err := m.RunWithValue(value, func(stmt *gorm.Statement) (errr error) {
2020-08-23 10:40:19 +03:00
columnTypes, _ := m.DB.Migrator().ColumnTypes(value)
2020-02-22 08:09:57 +03:00
for _, field := range stmt.Schema.FieldsByDBName {
2020-08-23 10:40:19 +03:00
var foundColumn *sql.ColumnType
for _, columnType := range columnTypes {
if columnType.Name() == field.DBName {
foundColumn = columnType
break
}
}
if foundColumn == nil {
// not found, add column
2020-02-22 18:08:20 +03:00
if err := tx.Migrator().AddColumn(value, field.DBName); err != nil {
2020-02-22 08:09:57 +03:00
return err
}
2020-08-23 10:40:19 +03:00
} else if err := m.DB.Migrator().MigrateColumn(value, field, foundColumn); err != nil {
// found, smart migrate
return err
2020-02-22 08:09:57 +03:00
}
}
for _, rel := range stmt.Schema.Relationships.Relations {
if !m.DB.Config.DisableForeignKeyConstraintWhenMigrating {
if constraint := rel.ParseConstraint(); constraint != nil {
if constraint.Schema == stmt.Schema {
if !tx.Migrator().HasConstraint(value, constraint.Name) {
if err := tx.Migrator().CreateConstraint(value, constraint.Name); err != nil {
return err
}
2020-06-19 19:48:15 +03:00
}
2020-02-22 08:09:57 +03:00
}
}
}
for _, chk := range stmt.Schema.ParseCheckConstraints() {
2020-02-22 18:08:20 +03:00
if !tx.Migrator().HasConstraint(value, chk.Name) {
if err := tx.Migrator().CreateConstraint(value, chk.Name); err != nil {
2020-02-22 08:09:57 +03:00
return err
}
}
}
}
return nil
}); err != nil {
return err
}
}
}
return nil
2020-02-20 18:04:03 +03:00
}
2020-02-22 12:53:57 +03:00
func (m Migrator) CreateTable(values ...interface{}) error {
2020-02-22 19:18:12 +03:00
for _, value := range m.ReorderModels(values, false) {
2020-02-22 18:08:20 +03:00
tx := m.DB.Session(&gorm.Session{})
if err := m.RunWithValue(value, func(stmt *gorm.Statement) (errr error) {
2020-02-22 08:09:57 +03:00
var (
createTableSQL = "CREATE TABLE ? ("
values = []interface{}{clause.Table{Name: stmt.Table}}
hasPrimaryKeyInDataType bool
)
for _, dbName := range stmt.Schema.DBNames {
field := stmt.Schema.FieldsByDBName[dbName]
2020-07-16 06:27:04 +03:00
createTableSQL += "? ?"
hasPrimaryKeyInDataType = hasPrimaryKeyInDataType || strings.Contains(strings.ToUpper(string(field.DataType)), "PRIMARY KEY")
values = append(values, clause.Column{Name: dbName}, m.DB.Migrator().FullDataTypeOf(field))
2020-02-22 08:09:57 +03:00
createTableSQL += ","
}
2020-05-31 07:52:49 +03:00
if !hasPrimaryKeyInDataType && len(stmt.Schema.PrimaryFields) > 0 {
2020-02-22 08:09:57 +03:00
createTableSQL += "PRIMARY KEY ?,"
primaryKeys := []interface{}{}
for _, field := range stmt.Schema.PrimaryFields {
primaryKeys = append(primaryKeys, clause.Column{Name: field.DBName})
}
values = append(values, primaryKeys)
}
for _, idx := range stmt.Schema.ParseIndexes() {
2020-02-22 15:57:29 +03:00
if m.CreateIndexAfterCreateTable {
defer func(value interface{}, name string) {
errr = tx.Migrator().CreateIndex(value, name)
}(value, idx.Name)
2020-02-22 15:57:29 +03:00
} else {
if idx.Class != "" {
createTableSQL += idx.Class + " "
}
2020-02-22 15:57:29 +03:00
createTableSQL += "INDEX ? ?,"
2020-02-22 18:08:20 +03:00
values = append(values, clause.Expr{SQL: idx.Name}, tx.Migrator().(BuildIndexOptionsInterface).BuildIndexOptions(idx.Fields, stmt))
2020-02-22 15:57:29 +03:00
}
2020-02-22 08:09:57 +03:00
}
for _, rel := range stmt.Schema.Relationships.Relations {
if !m.DB.DisableForeignKeyConstraintWhenMigrating {
if constraint := rel.ParseConstraint(); constraint != nil {
if constraint.Schema == stmt.Schema {
sql, vars := buildConstraint(constraint)
createTableSQL += sql + ","
values = append(values, vars...)
}
2020-06-19 19:48:15 +03:00
}
2020-02-22 08:09:57 +03:00
}
}
for _, chk := range stmt.Schema.ParseCheckConstraints() {
2020-07-08 13:15:45 +03:00
createTableSQL += "CONSTRAINT ? CHECK (?),"
2020-02-22 08:09:57 +03:00
values = append(values, clause.Column{Name: chk.Name}, clause.Expr{SQL: chk.Constraint})
}
createTableSQL = strings.TrimSuffix(createTableSQL, ",")
createTableSQL += ")"
2020-06-14 14:13:16 +03:00
if tableOption, ok := m.DB.Get("gorm:table_options"); ok {
createTableSQL += fmt.Sprint(tableOption)
}
errr = tx.Exec(createTableSQL, values...).Error
return errr
2020-02-22 08:09:57 +03:00
}); err != nil {
return err
}
}
return nil
2020-02-20 18:04:03 +03:00
}
2020-02-22 12:53:57 +03:00
func (m Migrator) DropTable(values ...interface{}) error {
2020-02-22 19:18:12 +03:00
values = m.ReorderModels(values, false)
for i := len(values) - 1; i >= 0; i-- {
2020-06-02 02:28:29 +03:00
tx := m.DB.Session(&gorm.Session{})
if err := m.RunWithValue(values[i], func(stmt *gorm.Statement) error {
return tx.Exec("DROP TABLE IF EXISTS ?", clause.Table{Name: stmt.Table}).Error
}); err != nil {
return err
2020-02-20 18:04:03 +03:00
}
}
return nil
}
2020-02-22 12:53:57 +03:00
func (m Migrator) HasTable(value interface{}) bool {
2020-02-20 18:04:03 +03:00
var count int64
2020-04-28 03:05:22 +03:00
2020-02-22 12:53:57 +03:00
m.RunWithValue(value, func(stmt *gorm.Statement) error {
currentDatabase := m.DB.Migrator().CurrentDatabase()
return m.DB.Raw("SELECT count(*) FROM information_schema.tables WHERE table_schema = ? AND table_name = ? AND table_type = ?", currentDatabase, stmt.Table, "BASE TABLE").Row().Scan(&count)
})
2020-02-20 18:04:03 +03:00
2020-02-22 12:53:57 +03:00
return count > 0
2020-02-20 18:04:03 +03:00
}
2020-05-31 05:24:49 +03:00
func (m Migrator) RenameTable(oldName, newName interface{}) error {
var oldTable, newTable string
if v, ok := oldName.(string); ok {
oldTable = v
} else {
stmt := &gorm.Statement{DB: m.DB}
if err := stmt.Parse(oldName); err == nil {
oldTable = stmt.Table
} else {
return err
}
}
if v, ok := newName.(string); ok {
newTable = v
} else {
stmt := &gorm.Statement{DB: m.DB}
if err := stmt.Parse(newName); err == nil {
newTable = stmt.Table
} else {
return err
}
}
return m.DB.Exec("ALTER TABLE ? RENAME TO ?", clause.Table{Name: oldTable}, clause.Table{Name: newTable}).Error
2020-02-20 18:04:03 +03:00
}
2020-02-22 12:53:57 +03:00
func (m Migrator) AddColumn(value interface{}, field string) error {
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
2020-02-20 18:04:03 +03:00
if field := stmt.Schema.LookUpField(field); field != nil {
2020-02-22 12:53:57 +03:00
return m.DB.Exec(
2020-02-22 06:15:51 +03:00
"ALTER TABLE ? ADD ? ?",
clause.Table{Name: stmt.Table}, clause.Column{Name: field.DBName}, m.DB.Migrator().FullDataTypeOf(field),
2020-02-22 06:15:51 +03:00
).Error
2020-02-20 18:04:03 +03:00
}
return fmt.Errorf("failed to look up field with name: %s", field)
})
}
func (m Migrator) DropColumn(value interface{}, name string) error {
2020-02-22 12:53:57 +03:00
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
if field := stmt.Schema.LookUpField(name); field != nil {
name = field.DBName
2020-02-20 18:04:03 +03:00
}
return m.DB.Exec(
"ALTER TABLE ? DROP COLUMN ?", clause.Table{Name: stmt.Table}, clause.Column{Name: name},
).Error
2020-02-20 18:04:03 +03:00
})
}
2020-02-22 12:53:57 +03:00
func (m Migrator) AlterColumn(value interface{}, field string) error {
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
2020-02-20 18:04:03 +03:00
if field := stmt.Schema.LookUpField(field); field != nil {
fileType := clause.Expr{SQL: m.DataTypeOf(field)}
2020-02-22 12:53:57 +03:00
return m.DB.Exec(
2020-02-22 06:15:51 +03:00
"ALTER TABLE ? ALTER COLUMN ? TYPE ?",
clause.Table{Name: stmt.Table}, clause.Column{Name: field.DBName}, fileType,
2020-02-22 06:15:51 +03:00
).Error
2020-02-20 18:04:03 +03:00
}
return fmt.Errorf("failed to look up field with name: %s", field)
})
}
2020-02-22 12:53:57 +03:00
func (m Migrator) HasColumn(value interface{}, field string) bool {
2020-02-22 08:09:57 +03:00
var count int64
2020-02-22 12:53:57 +03:00
m.RunWithValue(value, func(stmt *gorm.Statement) error {
currentDatabase := m.DB.Migrator().CurrentDatabase()
2020-02-22 08:09:57 +03:00
name := field
if field := stmt.Schema.LookUpField(field); field != nil {
name = field.DBName
}
2020-02-22 12:53:57 +03:00
return m.DB.Raw(
2020-02-22 08:09:57 +03:00
"SELECT count(*) FROM INFORMATION_SCHEMA.columns WHERE table_schema = ? AND table_name = ? AND column_name = ?",
currentDatabase, stmt.Table, name,
2020-02-22 12:53:57 +03:00
).Row().Scan(&count)
2020-02-22 08:09:57 +03:00
})
2020-02-22 12:53:57 +03:00
return count > 0
2020-02-22 08:09:57 +03:00
}
func (m Migrator) RenameColumn(value interface{}, oldName, newName string) error {
2020-02-22 12:53:57 +03:00
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
if field := stmt.Schema.LookUpField(oldName); field != nil {
oldName = field.DBName
2020-02-20 18:04:03 +03:00
}
if field := stmt.Schema.LookUpField(newName); field != nil {
newName = field.DBName
}
return m.DB.Exec(
"ALTER TABLE ? RENAME COLUMN ? TO ?",
clause.Table{Name: stmt.Table}, clause.Column{Name: oldName}, clause.Column{Name: newName},
).Error
2020-02-20 18:04:03 +03:00
})
}
2020-08-23 10:40:19 +03:00
func (m Migrator) MigrateColumn(value interface{}, field *schema.Field, columnType *sql.ColumnType) error {
// found, smart migrate
fullDataType := strings.ToLower(m.DB.Migrator().FullDataTypeOf(field).SQL)
realDataType := strings.ToLower(columnType.DatabaseTypeName())
alterColumn := false
// check size
if length, _ := columnType.Length(); length != int64(field.Size) {
if length > 0 && field.Size > 0 {
alterColumn = true
} else {
// has size in data type and not equal
matches := regexp.MustCompile(`[^\d](\d+)[^\d]`).FindAllStringSubmatch(realDataType, -1)
2020-08-23 10:40:19 +03:00
matches2 := regexp.MustCompile(`[^\d]*(\d+)[^\d]`).FindAllStringSubmatch(fullDataType, -1)
if (len(matches) == 1 && matches[0][1] != fmt.Sprint(field.Size)) && (len(matches2) == 1 && matches2[0][1] != fmt.Sprint(length)) {
2020-08-23 10:40:19 +03:00
alterColumn = true
}
}
}
// check precision
if precision, _, ok := columnType.DecimalSize(); ok && int64(field.Precision) != precision {
if strings.Contains(fullDataType, fmt.Sprint(field.Precision)) {
alterColumn = true
}
}
// check nullable
if nullable, ok := columnType.Nullable(); ok && nullable == field.NotNull {
// not primary key & database is nullable
if !field.PrimaryKey && nullable {
alterColumn = true
}
}
if alterColumn {
return m.DB.Migrator().AlterColumn(value, field.Name)
}
return nil
}
2020-02-22 14:41:01 +03:00
func (m Migrator) ColumnTypes(value interface{}) (columnTypes []*sql.ColumnType, err error) {
err = m.RunWithValue(value, func(stmt *gorm.Statement) error {
rows, err := m.DB.Session(&gorm.Session{}).Raw("select * from ? limit 1", clause.Table{Name: stmt.Table}).Rows()
2020-02-22 14:41:01 +03:00
if err == nil {
columnTypes, err = rows.ColumnTypes()
_ = rows.Close()
2020-02-22 14:41:01 +03:00
}
return err
})
return
2020-02-20 18:04:03 +03:00
}
2020-02-22 12:53:57 +03:00
func (m Migrator) CreateView(name string, option gorm.ViewOption) error {
2020-02-20 18:04:03 +03:00
return gorm.ErrNotImplemented
}
2020-02-22 12:53:57 +03:00
func (m Migrator) DropView(name string) error {
2020-02-20 18:04:03 +03:00
return gorm.ErrNotImplemented
}
2020-02-22 08:09:57 +03:00
func buildConstraint(constraint *schema.Constraint) (sql string, results []interface{}) {
sql = "CONSTRAINT ? FOREIGN KEY ? REFERENCES ??"
if constraint.OnDelete != "" {
sql += " ON DELETE " + constraint.OnDelete
}
if constraint.OnUpdate != "" {
2020-06-19 19:48:15 +03:00
sql += " ON UPDATE " + constraint.OnUpdate
2020-02-22 08:09:57 +03:00
}
var foreignKeys, references []interface{}
for _, field := range constraint.ForeignKeys {
foreignKeys = append(foreignKeys, clause.Column{Name: field.DBName})
}
for _, field := range constraint.References {
references = append(references, clause.Column{Name: field.DBName})
}
2020-02-22 15:57:29 +03:00
results = append(results, clause.Table{Name: constraint.Name}, foreignKeys, clause.Table{Name: constraint.ReferenceSchema.Table}, references)
2020-02-22 08:09:57 +03:00
return
}
2020-02-22 12:53:57 +03:00
func (m Migrator) CreateConstraint(value interface{}, name string) error {
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
2020-02-22 06:15:51 +03:00
checkConstraints := stmt.Schema.ParseCheckConstraints()
if chk, ok := checkConstraints[name]; ok {
2020-02-22 12:53:57 +03:00
return m.DB.Exec(
2020-07-08 13:15:45 +03:00
"ALTER TABLE ? ADD CONSTRAINT ? CHECK (?)",
2020-02-22 06:15:51 +03:00
clause.Table{Name: stmt.Table}, clause.Column{Name: chk.Name}, clause.Expr{SQL: chk.Constraint},
).Error
}
for _, rel := range stmt.Schema.Relationships.Relations {
if constraint := rel.ParseConstraint(); constraint != nil && constraint.Name == name {
2020-02-22 08:09:57 +03:00
sql, values := buildConstraint(constraint)
2020-02-22 12:53:57 +03:00
return m.DB.Exec("ALTER TABLE ? ADD "+sql, append([]interface{}{clause.Table{Name: stmt.Table}}, values...)...).Error
2020-02-22 06:15:51 +03:00
}
}
err := fmt.Errorf("failed to create constraint with name %v", name)
if field := stmt.Schema.LookUpField(name); field != nil {
for _, cc := range checkConstraints {
2020-02-22 15:57:29 +03:00
if err = m.DB.Migrator().CreateIndex(value, cc.Name); err != nil {
2020-02-22 06:15:51 +03:00
return err
}
}
for _, rel := range stmt.Schema.Relationships.Relations {
if constraint := rel.ParseConstraint(); constraint != nil && constraint.Field == field {
2020-02-22 15:57:29 +03:00
if err = m.DB.Migrator().CreateIndex(value, constraint.Name); err != nil {
2020-02-22 06:15:51 +03:00
return err
}
}
}
}
return err
})
2020-02-20 18:04:03 +03:00
}
2020-02-22 12:53:57 +03:00
func (m Migrator) DropConstraint(value interface{}, name string) error {
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
return m.DB.Exec(
2020-02-22 06:15:51 +03:00
"ALTER TABLE ? DROP CONSTRAINT ?",
clause.Table{Name: stmt.Table}, clause.Column{Name: name},
).Error
2020-02-20 18:04:03 +03:00
})
}
2020-02-22 12:53:57 +03:00
func (m Migrator) HasConstraint(value interface{}, name string) bool {
2020-02-22 08:09:57 +03:00
var count int64
2020-02-22 12:53:57 +03:00
m.RunWithValue(value, func(stmt *gorm.Statement) error {
currentDatabase := m.DB.Migrator().CurrentDatabase()
return m.DB.Raw(
"SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = ? AND table_name = ? AND constraint_name = ?",
2020-02-22 08:09:57 +03:00
currentDatabase, stmt.Table, name,
2020-02-22 12:53:57 +03:00
).Row().Scan(&count)
2020-02-22 08:09:57 +03:00
})
2020-02-22 12:53:57 +03:00
return count > 0
2020-02-22 08:09:57 +03:00
}
2020-02-22 12:53:57 +03:00
func (m Migrator) BuildIndexOptions(opts []schema.IndexOption, stmt *gorm.Statement) (results []interface{}) {
2020-02-22 08:09:57 +03:00
for _, opt := range opts {
str := stmt.Quote(opt.DBName)
if opt.Expression != "" {
str = opt.Expression
} else if opt.Length > 0 {
str += fmt.Sprintf("(%d)", opt.Length)
}
2020-02-22 12:53:57 +03:00
if opt.Collate != "" {
str += " COLLATE " + opt.Collate
}
2020-02-22 08:09:57 +03:00
if opt.Sort != "" {
str += " " + opt.Sort
}
results = append(results, clause.Expr{SQL: str})
}
return
}
2020-02-22 12:53:57 +03:00
type BuildIndexOptionsInterface interface {
BuildIndexOptions([]schema.IndexOption, *gorm.Statement) []interface{}
}
func (m Migrator) CreateIndex(value interface{}, name string) error {
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
2020-05-30 17:27:20 +03:00
if idx := stmt.Schema.LookIndex(name); idx != nil {
2020-02-22 12:53:57 +03:00
opts := m.DB.Migrator().(BuildIndexOptionsInterface).BuildIndexOptions(idx.Fields, stmt)
2020-02-22 08:09:57 +03:00
values := []interface{}{clause.Column{Name: idx.Name}, clause.Table{Name: stmt.Table}, opts}
2020-02-22 06:15:51 +03:00
createIndexSQL := "CREATE "
if idx.Class != "" {
createIndexSQL += idx.Class + " "
}
createIndexSQL += "INDEX ? ON ??"
if idx.Type != "" {
createIndexSQL += " USING " + idx.Type
}
2020-02-22 12:53:57 +03:00
return m.DB.Exec(createIndexSQL, values...).Error
2020-02-22 06:15:51 +03:00
}
2020-05-30 17:27:20 +03:00
return fmt.Errorf("failed to create index with name %v", name)
2020-02-22 06:15:51 +03:00
})
2020-02-20 18:04:03 +03:00
}
2020-02-22 12:53:57 +03:00
func (m Migrator) DropIndex(value interface{}, name string) error {
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
2020-05-30 17:27:20 +03:00
if idx := stmt.Schema.LookIndex(name); idx != nil {
name = idx.Name
}
2020-02-22 12:53:57 +03:00
return m.DB.Exec("DROP INDEX ? ON ?", clause.Column{Name: name}, clause.Table{Name: stmt.Table}).Error
2020-02-20 18:04:03 +03:00
})
}
2020-02-22 12:53:57 +03:00
func (m Migrator) HasIndex(value interface{}, name string) bool {
2020-02-20 18:04:03 +03:00
var count int64
2020-02-22 12:53:57 +03:00
m.RunWithValue(value, func(stmt *gorm.Statement) error {
currentDatabase := m.DB.Migrator().CurrentDatabase()
2020-05-30 17:27:20 +03:00
if idx := stmt.Schema.LookIndex(name); idx != nil {
name = idx.Name
}
2020-02-22 12:53:57 +03:00
return m.DB.Raw(
2020-02-22 06:15:51 +03:00
"SELECT count(*) FROM information_schema.statistics WHERE table_schema = ? AND table_name = ? AND index_name = ?",
currentDatabase, stmt.Table, name,
2020-02-22 12:53:57 +03:00
).Row().Scan(&count)
2020-02-20 18:04:03 +03:00
})
2020-02-22 12:53:57 +03:00
return count > 0
2020-02-20 18:04:03 +03:00
}
2020-02-22 12:53:57 +03:00
func (m Migrator) RenameIndex(value interface{}, oldName, newName string) error {
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
return m.DB.Exec(
2020-02-22 06:15:51 +03:00
"ALTER TABLE ? RENAME INDEX ? TO ?",
clause.Table{Name: stmt.Table}, clause.Column{Name: oldName}, clause.Column{Name: newName},
).Error
2020-02-20 18:04:03 +03:00
})
}
2020-02-22 12:53:57 +03:00
func (m Migrator) CurrentDatabase() (name string) {
m.DB.Raw("SELECT DATABASE()").Row().Scan(&name)
2020-02-20 18:04:03 +03:00
return
}
2020-02-22 19:18:12 +03:00
// ReorderModels reorder models according to constraint dependencies
func (m Migrator) ReorderModels(values []interface{}, autoAdd bool) (results []interface{}) {
type Dependency struct {
2020-02-23 03:29:59 +03:00
*gorm.Statement
2020-02-22 19:18:12 +03:00
Depends []*schema.Schema
}
var (
modelNames, orderedModelNames []string
orderedModelNamesMap = map[string]bool{}
parsedSchemas = map[*schema.Schema]bool{}
2020-02-23 03:29:59 +03:00
valuesMap = map[string]Dependency{}
insertIntoOrderedList func(name string)
2020-06-24 11:43:53 +03:00
parseDependence func(value interface{}, addToList bool)
2020-02-22 19:18:12 +03:00
)
2020-06-24 11:43:53 +03:00
parseDependence = func(value interface{}, addToList bool) {
2020-02-23 03:29:59 +03:00
dep := Dependency{
2020-03-09 15:37:01 +03:00
Statement: &gorm.Statement{DB: m.DB, Dest: value},
2020-02-23 03:29:59 +03:00
}
beDependedOn := map[*schema.Schema]bool{}
2020-07-16 06:27:04 +03:00
if err := dep.Parse(value); err != nil {
m.DB.Logger.Error(context.Background(), "failed to parse value %#v, got error %v", value, err)
}
if _, ok := parsedSchemas[dep.Statement.Schema]; ok {
return
}
parsedSchemas[dep.Statement.Schema] = true
2020-02-22 19:18:12 +03:00
2020-02-23 03:29:59 +03:00
for _, rel := range dep.Schema.Relationships.Relations {
2020-06-19 19:48:15 +03:00
if c := rel.ParseConstraint(); c != nil && c.Schema == dep.Statement.Schema && c.Schema != c.ReferenceSchema {
2020-02-23 03:29:59 +03:00
dep.Depends = append(dep.Depends, c.ReferenceSchema)
2020-02-22 19:18:12 +03:00
}
if rel.Type == schema.HasOne || rel.Type == schema.HasMany {
beDependedOn[rel.FieldSchema] = true
}
2020-06-24 11:43:53 +03:00
if rel.JoinTable != nil {
// append join value
defer func(rel *schema.Relationship, joinValue interface{}) {
if !beDependedOn[rel.FieldSchema] {
dep.Depends = append(dep.Depends, rel.FieldSchema)
} else {
fieldValue := reflect.New(rel.FieldSchema.ModelType).Interface()
parseDependence(fieldValue, autoAdd)
}
2020-06-24 11:43:53 +03:00
parseDependence(joinValue, autoAdd)
}(rel, reflect.New(rel.JoinTable.ModelType).Interface())
}
2020-02-22 19:18:12 +03:00
}
2020-02-23 03:29:59 +03:00
valuesMap[dep.Schema.Table] = dep
2020-02-22 19:18:12 +03:00
2020-02-23 03:29:59 +03:00
if addToList {
modelNames = append(modelNames, dep.Schema.Table)
}
2020-02-22 19:18:12 +03:00
}
2020-02-23 03:29:59 +03:00
insertIntoOrderedList = func(name string) {
2020-02-22 19:18:12 +03:00
if _, ok := orderedModelNamesMap[name]; ok {
2020-02-23 03:29:59 +03:00
return // avoid loop
2020-02-22 19:18:12 +03:00
}
orderedModelNamesMap[name] = true
2020-02-22 19:18:12 +03:00
2020-02-23 03:29:59 +03:00
dep := valuesMap[name]
2020-02-22 19:18:12 +03:00
for _, d := range dep.Depends {
if _, ok := valuesMap[d.Table]; ok {
2020-02-23 03:29:59 +03:00
insertIntoOrderedList(d.Table)
2020-02-22 19:18:12 +03:00
} else if autoAdd {
parseDependence(reflect.New(d.ModelType).Interface(), autoAdd)
2020-02-23 03:29:59 +03:00
insertIntoOrderedList(d.Table)
2020-02-22 19:18:12 +03:00
}
}
orderedModelNames = append(orderedModelNames, name)
}
2020-02-23 03:29:59 +03:00
for _, value := range values {
2020-05-23 11:38:13 +03:00
if v, ok := value.(string); ok {
results = append(results, v)
} else {
parseDependence(value, true)
}
2020-02-23 03:29:59 +03:00
}
2020-02-22 19:18:12 +03:00
for _, name := range modelNames {
2020-02-23 03:29:59 +03:00
insertIntoOrderedList(name)
2020-02-22 19:18:12 +03:00
}
for _, name := range orderedModelNames {
2020-02-23 03:29:59 +03:00
results = append(results, valuesMap[name].Statement.Dest)
2020-02-22 19:18:12 +03:00
}
return
}