github.com/systematiccaos/gorm@v1.22.6/migrator/migrator.go (about)

     1  package migrator
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"fmt"
     7  	"reflect"
     8  	"regexp"
     9  	"strings"
    10  
    11  	"github.com/systematiccaos/gorm"
    12  	"github.com/systematiccaos/gorm/clause"
    13  	"github.com/systematiccaos/gorm/schema"
    14  )
    15  
    16  var (
    17  	regRealDataType = regexp.MustCompile(`[^\d](\d+)[^\d]?`)
    18  	regFullDataType = regexp.MustCompile(`[^\d]*(\d+)[^\d]?`)
    19  )
    20  
    21  // Migrator m struct
    22  type Migrator struct {
    23  	Config
    24  }
    25  
    26  // Config schema config
    27  type Config struct {
    28  	CreateIndexAfterCreateTable bool
    29  	DB                          *gorm.DB
    30  	gorm.Dialector
    31  }
    32  
    33  type GormDataTypeInterface interface {
    34  	GormDBDataType(*gorm.DB, *schema.Field) string
    35  }
    36  
    37  func (m Migrator) RunWithValue(value interface{}, fc func(*gorm.Statement) error) error {
    38  	stmt := &gorm.Statement{DB: m.DB}
    39  	if m.DB.Statement != nil {
    40  		stmt.Table = m.DB.Statement.Table
    41  		stmt.TableExpr = m.DB.Statement.TableExpr
    42  	}
    43  
    44  	if table, ok := value.(string); ok {
    45  		stmt.Table = table
    46  	} else if err := stmt.ParseWithSpecialTableName(value, stmt.Table); err != nil {
    47  		return err
    48  	}
    49  
    50  	return fc(stmt)
    51  }
    52  
    53  func (m Migrator) DataTypeOf(field *schema.Field) string {
    54  	fieldValue := reflect.New(field.IndirectFieldType)
    55  	if dataTyper, ok := fieldValue.Interface().(GormDataTypeInterface); ok {
    56  		if dataType := dataTyper.GormDBDataType(m.DB, field); dataType != "" {
    57  			return dataType
    58  		}
    59  	}
    60  
    61  	return m.Dialector.DataTypeOf(field)
    62  }
    63  
    64  func (m Migrator) FullDataTypeOf(field *schema.Field) (expr clause.Expr) {
    65  	expr.SQL = m.DataTypeOf(field)
    66  
    67  	if field.NotNull {
    68  		expr.SQL += " NOT NULL"
    69  	}
    70  
    71  	if field.Unique {
    72  		expr.SQL += " UNIQUE"
    73  	}
    74  
    75  	if field.HasDefaultValue && (field.DefaultValueInterface != nil || field.DefaultValue != "") {
    76  		if field.DefaultValueInterface != nil {
    77  			defaultStmt := &gorm.Statement{Vars: []interface{}{field.DefaultValueInterface}}
    78  			m.Dialector.BindVarTo(defaultStmt, defaultStmt, field.DefaultValueInterface)
    79  			expr.SQL += " DEFAULT " + m.Dialector.Explain(defaultStmt.SQL.String(), field.DefaultValueInterface)
    80  		} else if field.DefaultValue != "(-)" {
    81  			expr.SQL += " DEFAULT " + field.DefaultValue
    82  		}
    83  	}
    84  
    85  	return
    86  }
    87  
    88  // AutoMigrate
    89  func (m Migrator) AutoMigrate(values ...interface{}) error {
    90  	for _, value := range m.ReorderModels(values, true) {
    91  		tx := m.DB.Session(&gorm.Session{})
    92  		if !tx.Migrator().HasTable(value) {
    93  			if err := tx.Migrator().CreateTable(value); err != nil {
    94  				return err
    95  			}
    96  		} else {
    97  			if err := m.RunWithValue(value, func(stmt *gorm.Statement) (errr error) {
    98  				columnTypes, _ := m.DB.Migrator().ColumnTypes(value)
    99  
   100  				for _, field := range stmt.Schema.FieldsByDBName {
   101  					var foundColumn gorm.ColumnType
   102  
   103  					for _, columnType := range columnTypes {
   104  						if columnType.Name() == field.DBName {
   105  							foundColumn = columnType
   106  							break
   107  						}
   108  					}
   109  
   110  					if foundColumn == nil {
   111  						// not found, add column
   112  						if err := tx.Migrator().AddColumn(value, field.DBName); err != nil {
   113  							return err
   114  						}
   115  					} else if err := m.DB.Migrator().MigrateColumn(value, field, foundColumn); err != nil {
   116  						// found, smart migrate
   117  						return err
   118  					}
   119  				}
   120  
   121  				for _, rel := range stmt.Schema.Relationships.Relations {
   122  					if !m.DB.Config.DisableForeignKeyConstraintWhenMigrating {
   123  						if constraint := rel.ParseConstraint(); constraint != nil &&
   124  							constraint.Schema == stmt.Schema && !tx.Migrator().HasConstraint(value, constraint.Name) {
   125  							if err := tx.Migrator().CreateConstraint(value, constraint.Name); err != nil {
   126  								return err
   127  							}
   128  						}
   129  					}
   130  
   131  					for _, chk := range stmt.Schema.ParseCheckConstraints() {
   132  						if !tx.Migrator().HasConstraint(value, chk.Name) {
   133  							if err := tx.Migrator().CreateConstraint(value, chk.Name); err != nil {
   134  								return err
   135  							}
   136  						}
   137  					}
   138  				}
   139  
   140  				for _, idx := range stmt.Schema.ParseIndexes() {
   141  					if !tx.Migrator().HasIndex(value, idx.Name) {
   142  						if err := tx.Migrator().CreateIndex(value, idx.Name); err != nil {
   143  							return err
   144  						}
   145  					}
   146  				}
   147  
   148  				return nil
   149  			}); err != nil {
   150  				return err
   151  			}
   152  		}
   153  	}
   154  
   155  	return nil
   156  }
   157  
   158  func (m Migrator) GetTables() (tableList []string, err error) {
   159  	err = m.DB.Raw("SELECT TABLE_NAME FROM information_schema.tables where TABLE_SCHEMA=?", m.CurrentDatabase()).
   160  		Scan(&tableList).Error
   161  	return
   162  }
   163  
   164  func (m Migrator) CreateTable(values ...interface{}) error {
   165  	for _, value := range m.ReorderModels(values, false) {
   166  		tx := m.DB.Session(&gorm.Session{})
   167  		if err := m.RunWithValue(value, func(stmt *gorm.Statement) (errr error) {
   168  			var (
   169  				createTableSQL          = "CREATE TABLE ? ("
   170  				values                  = []interface{}{m.CurrentTable(stmt)}
   171  				hasPrimaryKeyInDataType bool
   172  			)
   173  
   174  			for _, dbName := range stmt.Schema.DBNames {
   175  				field := stmt.Schema.FieldsByDBName[dbName]
   176  				if !field.IgnoreMigration {
   177  					createTableSQL += "? ?"
   178  					hasPrimaryKeyInDataType = hasPrimaryKeyInDataType || strings.Contains(strings.ToUpper(string(field.DataType)), "PRIMARY KEY")
   179  					values = append(values, clause.Column{Name: dbName}, m.DB.Migrator().FullDataTypeOf(field))
   180  					createTableSQL += ","
   181  				}
   182  			}
   183  
   184  			if !hasPrimaryKeyInDataType && len(stmt.Schema.PrimaryFields) > 0 {
   185  				createTableSQL += "PRIMARY KEY ?,"
   186  				primaryKeys := []interface{}{}
   187  				for _, field := range stmt.Schema.PrimaryFields {
   188  					primaryKeys = append(primaryKeys, clause.Column{Name: field.DBName})
   189  				}
   190  
   191  				values = append(values, primaryKeys)
   192  			}
   193  
   194  			for _, idx := range stmt.Schema.ParseIndexes() {
   195  				if m.CreateIndexAfterCreateTable {
   196  					defer func(value interface{}, name string) {
   197  						if errr == nil {
   198  							errr = tx.Migrator().CreateIndex(value, name)
   199  						}
   200  					}(value, idx.Name)
   201  				} else {
   202  					if idx.Class != "" {
   203  						createTableSQL += idx.Class + " "
   204  					}
   205  					createTableSQL += "INDEX ? ?"
   206  
   207  					if idx.Comment != "" {
   208  						createTableSQL += fmt.Sprintf(" COMMENT '%s'", idx.Comment)
   209  					}
   210  
   211  					if idx.Option != "" {
   212  						createTableSQL += " " + idx.Option
   213  					}
   214  
   215  					createTableSQL += ","
   216  					values = append(values, clause.Expr{SQL: idx.Name}, tx.Migrator().(BuildIndexOptionsInterface).BuildIndexOptions(idx.Fields, stmt))
   217  				}
   218  			}
   219  
   220  			for _, rel := range stmt.Schema.Relationships.Relations {
   221  				if !m.DB.DisableForeignKeyConstraintWhenMigrating {
   222  					if constraint := rel.ParseConstraint(); constraint != nil {
   223  						if constraint.Schema == stmt.Schema {
   224  							sql, vars := buildConstraint(constraint)
   225  							createTableSQL += sql + ","
   226  							values = append(values, vars...)
   227  						}
   228  					}
   229  				}
   230  			}
   231  
   232  			for _, chk := range stmt.Schema.ParseCheckConstraints() {
   233  				createTableSQL += "CONSTRAINT ? CHECK (?),"
   234  				values = append(values, clause.Column{Name: chk.Name}, clause.Expr{SQL: chk.Constraint})
   235  			}
   236  
   237  			createTableSQL = strings.TrimSuffix(createTableSQL, ",")
   238  
   239  			createTableSQL += ")"
   240  
   241  			if tableOption, ok := m.DB.Get("gorm:table_options"); ok {
   242  				createTableSQL += fmt.Sprint(tableOption)
   243  			}
   244  
   245  			errr = tx.Exec(createTableSQL, values...).Error
   246  			return errr
   247  		}); err != nil {
   248  			return err
   249  		}
   250  	}
   251  	return nil
   252  }
   253  
   254  func (m Migrator) DropTable(values ...interface{}) error {
   255  	values = m.ReorderModels(values, false)
   256  	for i := len(values) - 1; i >= 0; i-- {
   257  		tx := m.DB.Session(&gorm.Session{})
   258  		if err := m.RunWithValue(values[i], func(stmt *gorm.Statement) error {
   259  			return tx.Exec("DROP TABLE IF EXISTS ?", m.CurrentTable(stmt)).Error
   260  		}); err != nil {
   261  			return err
   262  		}
   263  	}
   264  	return nil
   265  }
   266  
   267  func (m Migrator) HasTable(value interface{}) bool {
   268  	var count int64
   269  
   270  	m.RunWithValue(value, func(stmt *gorm.Statement) error {
   271  		currentDatabase := m.DB.Migrator().CurrentDatabase()
   272  		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)
   273  	})
   274  
   275  	return count > 0
   276  }
   277  
   278  func (m Migrator) RenameTable(oldName, newName interface{}) error {
   279  	var oldTable, newTable interface{}
   280  	if v, ok := oldName.(string); ok {
   281  		oldTable = clause.Table{Name: v}
   282  	} else {
   283  		stmt := &gorm.Statement{DB: m.DB}
   284  		if err := stmt.Parse(oldName); err == nil {
   285  			oldTable = m.CurrentTable(stmt)
   286  		} else {
   287  			return err
   288  		}
   289  	}
   290  
   291  	if v, ok := newName.(string); ok {
   292  		newTable = clause.Table{Name: v}
   293  	} else {
   294  		stmt := &gorm.Statement{DB: m.DB}
   295  		if err := stmt.Parse(newName); err == nil {
   296  			newTable = m.CurrentTable(stmt)
   297  		} else {
   298  			return err
   299  		}
   300  	}
   301  
   302  	return m.DB.Exec("ALTER TABLE ? RENAME TO ?", oldTable, newTable).Error
   303  }
   304  
   305  func (m Migrator) AddColumn(value interface{}, field string) error {
   306  	return m.RunWithValue(value, func(stmt *gorm.Statement) error {
   307  		// avoid using the same name field
   308  		f := stmt.Schema.LookUpField(field)
   309  		if f == nil {
   310  			return fmt.Errorf("failed to look up field with name: %s", field)
   311  		}
   312  
   313  		if !f.IgnoreMigration {
   314  			return m.DB.Exec(
   315  				"ALTER TABLE ? ADD ? ?",
   316  				m.CurrentTable(stmt), clause.Column{Name: f.DBName}, m.DB.Migrator().FullDataTypeOf(f),
   317  			).Error
   318  		}
   319  
   320  		return nil
   321  	})
   322  }
   323  
   324  func (m Migrator) DropColumn(value interface{}, name string) error {
   325  	return m.RunWithValue(value, func(stmt *gorm.Statement) error {
   326  		if field := stmt.Schema.LookUpField(name); field != nil {
   327  			name = field.DBName
   328  		}
   329  
   330  		return m.DB.Exec(
   331  			"ALTER TABLE ? DROP COLUMN ?", m.CurrentTable(stmt), clause.Column{Name: name},
   332  		).Error
   333  	})
   334  }
   335  
   336  func (m Migrator) AlterColumn(value interface{}, field string) error {
   337  	return m.RunWithValue(value, func(stmt *gorm.Statement) error {
   338  		if field := stmt.Schema.LookUpField(field); field != nil {
   339  			fileType := clause.Expr{SQL: m.DataTypeOf(field)}
   340  			return m.DB.Exec(
   341  				"ALTER TABLE ? ALTER COLUMN ? TYPE ?",
   342  				m.CurrentTable(stmt), clause.Column{Name: field.DBName}, fileType,
   343  			).Error
   344  
   345  		}
   346  		return fmt.Errorf("failed to look up field with name: %s", field)
   347  	})
   348  }
   349  
   350  func (m Migrator) HasColumn(value interface{}, field string) bool {
   351  	var count int64
   352  	m.RunWithValue(value, func(stmt *gorm.Statement) error {
   353  		currentDatabase := m.DB.Migrator().CurrentDatabase()
   354  		name := field
   355  		if field := stmt.Schema.LookUpField(field); field != nil {
   356  			name = field.DBName
   357  		}
   358  
   359  		return m.DB.Raw(
   360  			"SELECT count(*) FROM INFORMATION_SCHEMA.columns WHERE table_schema = ? AND table_name = ? AND column_name = ?",
   361  			currentDatabase, stmt.Table, name,
   362  		).Row().Scan(&count)
   363  	})
   364  
   365  	return count > 0
   366  }
   367  
   368  func (m Migrator) RenameColumn(value interface{}, oldName, newName string) error {
   369  	return m.RunWithValue(value, func(stmt *gorm.Statement) error {
   370  		if field := stmt.Schema.LookUpField(oldName); field != nil {
   371  			oldName = field.DBName
   372  		}
   373  
   374  		if field := stmt.Schema.LookUpField(newName); field != nil {
   375  			newName = field.DBName
   376  		}
   377  
   378  		return m.DB.Exec(
   379  			"ALTER TABLE ? RENAME COLUMN ? TO ?",
   380  			m.CurrentTable(stmt), clause.Column{Name: oldName}, clause.Column{Name: newName},
   381  		).Error
   382  	})
   383  }
   384  
   385  func (m Migrator) MigrateColumn(value interface{}, field *schema.Field, columnType gorm.ColumnType) error {
   386  	// found, smart migrate
   387  	fullDataType := strings.ToLower(m.DB.Migrator().FullDataTypeOf(field).SQL)
   388  	realDataType := strings.ToLower(columnType.DatabaseTypeName())
   389  
   390  	alterColumn := false
   391  
   392  	// check size
   393  	if length, ok := columnType.Length(); length != int64(field.Size) {
   394  		if length > 0 && field.Size > 0 {
   395  			alterColumn = true
   396  		} else {
   397  			// has size in data type and not equal
   398  			// Since the following code is frequently called in the for loop, reg optimization is needed here
   399  			matches := regRealDataType.FindAllStringSubmatch(realDataType, -1)
   400  			matches2 := regFullDataType.FindAllStringSubmatch(fullDataType, -1)
   401  			if (len(matches) == 1 && matches[0][1] != fmt.Sprint(field.Size) || !field.PrimaryKey) &&
   402  				(len(matches2) == 1 && matches2[0][1] != fmt.Sprint(length) && ok) {
   403  				alterColumn = true
   404  			}
   405  		}
   406  	}
   407  
   408  	// check precision
   409  	if precision, _, ok := columnType.DecimalSize(); ok && int64(field.Precision) != precision {
   410  		if regexp.MustCompile(fmt.Sprintf("[^0-9]%d[^0-9]", field.Precision)).MatchString(m.DataTypeOf(field)) {
   411  			alterColumn = true
   412  		}
   413  	}
   414  
   415  	// check nullable
   416  	if nullable, ok := columnType.Nullable(); ok && nullable == field.NotNull {
   417  		// not primary key & database is nullable
   418  		if !field.PrimaryKey && nullable {
   419  			alterColumn = true
   420  		}
   421  	}
   422  
   423  	if alterColumn && !field.IgnoreMigration {
   424  		return m.DB.Migrator().AlterColumn(value, field.Name)
   425  	}
   426  
   427  	return nil
   428  }
   429  
   430  // ColumnTypes return columnTypes []gorm.ColumnType and execErr error
   431  func (m Migrator) ColumnTypes(value interface{}) ([]gorm.ColumnType, error) {
   432  	columnTypes := make([]gorm.ColumnType, 0)
   433  	execErr := m.RunWithValue(value, func(stmt *gorm.Statement) (err error) {
   434  		rows, err := m.DB.Session(&gorm.Session{}).Table(stmt.Table).Limit(1).Rows()
   435  		if err != nil {
   436  			return err
   437  		}
   438  
   439  		defer func() {
   440  			err = rows.Close()
   441  		}()
   442  
   443  		var rawColumnTypes []*sql.ColumnType
   444  		rawColumnTypes, err = rows.ColumnTypes()
   445  		if err != nil {
   446  			return err
   447  		}
   448  
   449  		for _, c := range rawColumnTypes {
   450  			columnTypes = append(columnTypes, c)
   451  		}
   452  
   453  		return
   454  	})
   455  
   456  	return columnTypes, execErr
   457  }
   458  
   459  func (m Migrator) CreateView(name string, option gorm.ViewOption) error {
   460  	return gorm.ErrNotImplemented
   461  }
   462  
   463  func (m Migrator) DropView(name string) error {
   464  	return gorm.ErrNotImplemented
   465  }
   466  
   467  func buildConstraint(constraint *schema.Constraint) (sql string, results []interface{}) {
   468  	sql = "CONSTRAINT ? FOREIGN KEY ? REFERENCES ??"
   469  	if constraint.OnDelete != "" {
   470  		sql += " ON DELETE " + constraint.OnDelete
   471  	}
   472  
   473  	if constraint.OnUpdate != "" {
   474  		sql += " ON UPDATE " + constraint.OnUpdate
   475  	}
   476  
   477  	var foreignKeys, references []interface{}
   478  	for _, field := range constraint.ForeignKeys {
   479  		foreignKeys = append(foreignKeys, clause.Column{Name: field.DBName})
   480  	}
   481  
   482  	for _, field := range constraint.References {
   483  		references = append(references, clause.Column{Name: field.DBName})
   484  	}
   485  	results = append(results, clause.Table{Name: constraint.Name}, foreignKeys, clause.Table{Name: constraint.ReferenceSchema.Table}, references)
   486  	return
   487  }
   488  
   489  func (m Migrator) GuessConstraintAndTable(stmt *gorm.Statement, name string) (_ *schema.Constraint, _ *schema.Check, table string) {
   490  	if stmt.Schema == nil {
   491  		return nil, nil, stmt.Table
   492  	}
   493  
   494  	checkConstraints := stmt.Schema.ParseCheckConstraints()
   495  	if chk, ok := checkConstraints[name]; ok {
   496  		return nil, &chk, stmt.Table
   497  	}
   498  
   499  	getTable := func(rel *schema.Relationship) string {
   500  		switch rel.Type {
   501  		case schema.HasOne, schema.HasMany:
   502  			return rel.FieldSchema.Table
   503  		case schema.Many2Many:
   504  			return rel.JoinTable.Table
   505  		}
   506  		return stmt.Table
   507  	}
   508  
   509  	for _, rel := range stmt.Schema.Relationships.Relations {
   510  		if constraint := rel.ParseConstraint(); constraint != nil && constraint.Name == name {
   511  			return constraint, nil, getTable(rel)
   512  		}
   513  	}
   514  
   515  	if field := stmt.Schema.LookUpField(name); field != nil {
   516  		for k := range checkConstraints {
   517  			if checkConstraints[k].Field == field {
   518  				v := checkConstraints[k]
   519  				return nil, &v, stmt.Table
   520  			}
   521  		}
   522  
   523  		for _, rel := range stmt.Schema.Relationships.Relations {
   524  			if constraint := rel.ParseConstraint(); constraint != nil && rel.Field == field {
   525  				return constraint, nil, getTable(rel)
   526  			}
   527  		}
   528  	}
   529  
   530  	return nil, nil, stmt.Schema.Table
   531  }
   532  
   533  func (m Migrator) CreateConstraint(value interface{}, name string) error {
   534  	return m.RunWithValue(value, func(stmt *gorm.Statement) error {
   535  		constraint, chk, table := m.GuessConstraintAndTable(stmt, name)
   536  		if chk != nil {
   537  			return m.DB.Exec(
   538  				"ALTER TABLE ? ADD CONSTRAINT ? CHECK (?)",
   539  				m.CurrentTable(stmt), clause.Column{Name: chk.Name}, clause.Expr{SQL: chk.Constraint},
   540  			).Error
   541  		}
   542  
   543  		if constraint != nil {
   544  			var vars = []interface{}{clause.Table{Name: table}}
   545  			if stmt.TableExpr != nil {
   546  				vars[0] = stmt.TableExpr
   547  			}
   548  			sql, values := buildConstraint(constraint)
   549  			return m.DB.Exec("ALTER TABLE ? ADD "+sql, append(vars, values...)...).Error
   550  		}
   551  
   552  		return nil
   553  	})
   554  }
   555  
   556  func (m Migrator) DropConstraint(value interface{}, name string) error {
   557  	return m.RunWithValue(value, func(stmt *gorm.Statement) error {
   558  		constraint, chk, table := m.GuessConstraintAndTable(stmt, name)
   559  		if constraint != nil {
   560  			name = constraint.Name
   561  		} else if chk != nil {
   562  			name = chk.Name
   563  		}
   564  		return m.DB.Exec("ALTER TABLE ? DROP CONSTRAINT ?", clause.Table{Name: table}, clause.Column{Name: name}).Error
   565  	})
   566  }
   567  
   568  func (m Migrator) HasConstraint(value interface{}, name string) bool {
   569  	var count int64
   570  	m.RunWithValue(value, func(stmt *gorm.Statement) error {
   571  		currentDatabase := m.DB.Migrator().CurrentDatabase()
   572  		constraint, chk, table := m.GuessConstraintAndTable(stmt, name)
   573  		if constraint != nil {
   574  			name = constraint.Name
   575  		} else if chk != nil {
   576  			name = chk.Name
   577  		}
   578  
   579  		return m.DB.Raw(
   580  			"SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = ? AND table_name = ? AND constraint_name = ?",
   581  			currentDatabase, table, name,
   582  		).Row().Scan(&count)
   583  	})
   584  
   585  	return count > 0
   586  }
   587  
   588  func (m Migrator) BuildIndexOptions(opts []schema.IndexOption, stmt *gorm.Statement) (results []interface{}) {
   589  	for _, opt := range opts {
   590  		str := stmt.Quote(opt.DBName)
   591  		if opt.Expression != "" {
   592  			str = opt.Expression
   593  		} else if opt.Length > 0 {
   594  			str += fmt.Sprintf("(%d)", opt.Length)
   595  		}
   596  
   597  		if opt.Collate != "" {
   598  			str += " COLLATE " + opt.Collate
   599  		}
   600  
   601  		if opt.Sort != "" {
   602  			str += " " + opt.Sort
   603  		}
   604  		results = append(results, clause.Expr{SQL: str})
   605  	}
   606  	return
   607  }
   608  
   609  type BuildIndexOptionsInterface interface {
   610  	BuildIndexOptions([]schema.IndexOption, *gorm.Statement) []interface{}
   611  }
   612  
   613  func (m Migrator) CreateIndex(value interface{}, name string) error {
   614  	return m.RunWithValue(value, func(stmt *gorm.Statement) error {
   615  		if idx := stmt.Schema.LookIndex(name); idx != nil {
   616  			opts := m.DB.Migrator().(BuildIndexOptionsInterface).BuildIndexOptions(idx.Fields, stmt)
   617  			values := []interface{}{clause.Column{Name: idx.Name}, m.CurrentTable(stmt), opts}
   618  
   619  			createIndexSQL := "CREATE "
   620  			if idx.Class != "" {
   621  				createIndexSQL += idx.Class + " "
   622  			}
   623  			createIndexSQL += "INDEX ? ON ??"
   624  
   625  			if idx.Type != "" {
   626  				createIndexSQL += " USING " + idx.Type
   627  			}
   628  
   629  			if idx.Comment != "" {
   630  				createIndexSQL += fmt.Sprintf(" COMMENT '%s'", idx.Comment)
   631  			}
   632  
   633  			if idx.Option != "" {
   634  				createIndexSQL += " " + idx.Option
   635  			}
   636  
   637  			return m.DB.Exec(createIndexSQL, values...).Error
   638  		}
   639  
   640  		return fmt.Errorf("failed to create index with name %s", name)
   641  	})
   642  }
   643  
   644  func (m Migrator) DropIndex(value interface{}, name string) error {
   645  	return m.RunWithValue(value, func(stmt *gorm.Statement) error {
   646  		if idx := stmt.Schema.LookIndex(name); idx != nil {
   647  			name = idx.Name
   648  		}
   649  
   650  		return m.DB.Exec("DROP INDEX ? ON ?", clause.Column{Name: name}, m.CurrentTable(stmt)).Error
   651  	})
   652  }
   653  
   654  func (m Migrator) HasIndex(value interface{}, name string) bool {
   655  	var count int64
   656  	m.RunWithValue(value, func(stmt *gorm.Statement) error {
   657  		currentDatabase := m.DB.Migrator().CurrentDatabase()
   658  		if idx := stmt.Schema.LookIndex(name); idx != nil {
   659  			name = idx.Name
   660  		}
   661  
   662  		return m.DB.Raw(
   663  			"SELECT count(*) FROM information_schema.statistics WHERE table_schema = ? AND table_name = ? AND index_name = ?",
   664  			currentDatabase, stmt.Table, name,
   665  		).Row().Scan(&count)
   666  	})
   667  
   668  	return count > 0
   669  }
   670  
   671  func (m Migrator) RenameIndex(value interface{}, oldName, newName string) error {
   672  	return m.RunWithValue(value, func(stmt *gorm.Statement) error {
   673  		return m.DB.Exec(
   674  			"ALTER TABLE ? RENAME INDEX ? TO ?",
   675  			m.CurrentTable(stmt), clause.Column{Name: oldName}, clause.Column{Name: newName},
   676  		).Error
   677  	})
   678  }
   679  
   680  func (m Migrator) CurrentDatabase() (name string) {
   681  	m.DB.Raw("SELECT DATABASE()").Row().Scan(&name)
   682  	return
   683  }
   684  
   685  // ReorderModels reorder models according to constraint dependencies
   686  func (m Migrator) ReorderModels(values []interface{}, autoAdd bool) (results []interface{}) {
   687  	type Dependency struct {
   688  		*gorm.Statement
   689  		Depends []*schema.Schema
   690  	}
   691  
   692  	var (
   693  		modelNames, orderedModelNames []string
   694  		orderedModelNamesMap          = map[string]bool{}
   695  		parsedSchemas                 = map[*schema.Schema]bool{}
   696  		valuesMap                     = map[string]Dependency{}
   697  		insertIntoOrderedList         func(name string)
   698  		parseDependence               func(value interface{}, addToList bool)
   699  	)
   700  
   701  	parseDependence = func(value interface{}, addToList bool) {
   702  		dep := Dependency{
   703  			Statement: &gorm.Statement{DB: m.DB, Dest: value},
   704  		}
   705  		beDependedOn := map[*schema.Schema]bool{}
   706  		if err := dep.Parse(value); err != nil {
   707  			m.DB.Logger.Error(context.Background(), "failed to parse value %#v, got error %v", value, err)
   708  		}
   709  		if _, ok := parsedSchemas[dep.Statement.Schema]; ok {
   710  			return
   711  		}
   712  		parsedSchemas[dep.Statement.Schema] = true
   713  
   714  		for _, rel := range dep.Schema.Relationships.Relations {
   715  			if c := rel.ParseConstraint(); c != nil && c.Schema == dep.Statement.Schema && c.Schema != c.ReferenceSchema {
   716  				dep.Depends = append(dep.Depends, c.ReferenceSchema)
   717  			}
   718  
   719  			if rel.Type == schema.HasOne || rel.Type == schema.HasMany {
   720  				beDependedOn[rel.FieldSchema] = true
   721  			}
   722  
   723  			if rel.JoinTable != nil {
   724  				// append join value
   725  				defer func(rel *schema.Relationship, joinValue interface{}) {
   726  					if !beDependedOn[rel.FieldSchema] {
   727  						dep.Depends = append(dep.Depends, rel.FieldSchema)
   728  					} else {
   729  						fieldValue := reflect.New(rel.FieldSchema.ModelType).Interface()
   730  						parseDependence(fieldValue, autoAdd)
   731  					}
   732  					parseDependence(joinValue, autoAdd)
   733  				}(rel, reflect.New(rel.JoinTable.ModelType).Interface())
   734  			}
   735  		}
   736  
   737  		valuesMap[dep.Schema.Table] = dep
   738  
   739  		if addToList {
   740  			modelNames = append(modelNames, dep.Schema.Table)
   741  		}
   742  	}
   743  
   744  	insertIntoOrderedList = func(name string) {
   745  		if _, ok := orderedModelNamesMap[name]; ok {
   746  			return // avoid loop
   747  		}
   748  		orderedModelNamesMap[name] = true
   749  
   750  		if autoAdd {
   751  			dep := valuesMap[name]
   752  			for _, d := range dep.Depends {
   753  				if _, ok := valuesMap[d.Table]; ok {
   754  					insertIntoOrderedList(d.Table)
   755  				} else {
   756  					parseDependence(reflect.New(d.ModelType).Interface(), autoAdd)
   757  					insertIntoOrderedList(d.Table)
   758  				}
   759  			}
   760  		}
   761  
   762  		orderedModelNames = append(orderedModelNames, name)
   763  	}
   764  
   765  	for _, value := range values {
   766  		if v, ok := value.(string); ok {
   767  			results = append(results, v)
   768  		} else {
   769  			parseDependence(value, true)
   770  		}
   771  	}
   772  
   773  	for _, name := range modelNames {
   774  		insertIntoOrderedList(name)
   775  	}
   776  
   777  	for _, name := range orderedModelNames {
   778  		results = append(results, valuesMap[name].Statement.Dest)
   779  	}
   780  	return
   781  }
   782  
   783  func (m Migrator) CurrentTable(stmt *gorm.Statement) interface{} {
   784  	if stmt.TableExpr != nil {
   785  		return *stmt.TableExpr
   786  	}
   787  	return clause.Table{Name: stmt.Table}
   788  }