code.gitea.io/gitea@v1.22.3/models/migrations/base/db.go (about)

     1  // Copyright 2022 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package base
     5  
     6  import (
     7  	"context"
     8  	"database/sql"
     9  	"errors"
    10  	"fmt"
    11  	"os"
    12  	"path"
    13  	"reflect"
    14  	"regexp"
    15  	"strings"
    16  	"time"
    17  
    18  	"code.gitea.io/gitea/models/db"
    19  	"code.gitea.io/gitea/models/unittest"
    20  	"code.gitea.io/gitea/modules/log"
    21  	"code.gitea.io/gitea/modules/setting"
    22  	"code.gitea.io/gitea/modules/util"
    23  
    24  	"xorm.io/xorm"
    25  	"xorm.io/xorm/schemas"
    26  )
    27  
    28  // RecreateTables will recreate the tables for the provided beans using the newly provided bean definition and move all data to that new table
    29  // WARNING: YOU MUST PROVIDE THE FULL BEAN DEFINITION
    30  func RecreateTables(beans ...any) func(*xorm.Engine) error {
    31  	return func(x *xorm.Engine) error {
    32  		sess := x.NewSession()
    33  		defer sess.Close()
    34  		if err := sess.Begin(); err != nil {
    35  			return err
    36  		}
    37  		sess = sess.StoreEngine("InnoDB")
    38  		for _, bean := range beans {
    39  			log.Info("Recreating Table: %s for Bean: %s", x.TableName(bean), reflect.Indirect(reflect.ValueOf(bean)).Type().Name())
    40  			if err := RecreateTable(sess, bean); err != nil {
    41  				return err
    42  			}
    43  		}
    44  		return sess.Commit()
    45  	}
    46  }
    47  
    48  // RecreateTable will recreate the table using the newly provided bean definition and move all data to that new table
    49  // WARNING: YOU MUST PROVIDE THE FULL BEAN DEFINITION
    50  // WARNING: YOU MUST COMMIT THE SESSION AT THE END
    51  func RecreateTable(sess *xorm.Session, bean any) error {
    52  	// TODO: This will not work if there are foreign keys
    53  
    54  	tableName := sess.Engine().TableName(bean)
    55  	tempTableName := fmt.Sprintf("tmp_recreate__%s", tableName)
    56  
    57  	// We need to move the old table away and create a new one with the correct columns
    58  	// We will need to do this in stages to prevent data loss
    59  	//
    60  	// First create the temporary table
    61  	if err := sess.Table(tempTableName).CreateTable(bean); err != nil {
    62  		log.Error("Unable to create table %s. Error: %v", tempTableName, err)
    63  		return err
    64  	}
    65  
    66  	if err := sess.Table(tempTableName).CreateUniques(bean); err != nil {
    67  		log.Error("Unable to create uniques for table %s. Error: %v", tempTableName, err)
    68  		return err
    69  	}
    70  
    71  	if err := sess.Table(tempTableName).CreateIndexes(bean); err != nil {
    72  		log.Error("Unable to create indexes for table %s. Error: %v", tempTableName, err)
    73  		return err
    74  	}
    75  
    76  	// Work out the column names from the bean - these are the columns to select from the old table and install into the new table
    77  	table, err := sess.Engine().TableInfo(bean)
    78  	if err != nil {
    79  		log.Error("Unable to get table info. Error: %v", err)
    80  
    81  		return err
    82  	}
    83  	newTableColumns := table.Columns()
    84  	if len(newTableColumns) == 0 {
    85  		return fmt.Errorf("no columns in new table")
    86  	}
    87  	hasID := false
    88  	for _, column := range newTableColumns {
    89  		hasID = hasID || (column.IsPrimaryKey && column.IsAutoIncrement)
    90  	}
    91  
    92  	if hasID && setting.Database.Type.IsMSSQL() {
    93  		if _, err := sess.Exec(fmt.Sprintf("SET IDENTITY_INSERT `%s` ON", tempTableName)); err != nil {
    94  			log.Error("Unable to set identity insert for table %s. Error: %v", tempTableName, err)
    95  			return err
    96  		}
    97  	}
    98  
    99  	sqlStringBuilder := &strings.Builder{}
   100  	_, _ = sqlStringBuilder.WriteString("INSERT INTO `")
   101  	_, _ = sqlStringBuilder.WriteString(tempTableName)
   102  	_, _ = sqlStringBuilder.WriteString("` (`")
   103  	_, _ = sqlStringBuilder.WriteString(newTableColumns[0].Name)
   104  	_, _ = sqlStringBuilder.WriteString("`")
   105  	for _, column := range newTableColumns[1:] {
   106  		_, _ = sqlStringBuilder.WriteString(", `")
   107  		_, _ = sqlStringBuilder.WriteString(column.Name)
   108  		_, _ = sqlStringBuilder.WriteString("`")
   109  	}
   110  	_, _ = sqlStringBuilder.WriteString(")")
   111  	_, _ = sqlStringBuilder.WriteString(" SELECT ")
   112  	if newTableColumns[0].Default != "" {
   113  		_, _ = sqlStringBuilder.WriteString("COALESCE(`")
   114  		_, _ = sqlStringBuilder.WriteString(newTableColumns[0].Name)
   115  		_, _ = sqlStringBuilder.WriteString("`, ")
   116  		_, _ = sqlStringBuilder.WriteString(newTableColumns[0].Default)
   117  		_, _ = sqlStringBuilder.WriteString(")")
   118  	} else {
   119  		_, _ = sqlStringBuilder.WriteString("`")
   120  		_, _ = sqlStringBuilder.WriteString(newTableColumns[0].Name)
   121  		_, _ = sqlStringBuilder.WriteString("`")
   122  	}
   123  
   124  	for _, column := range newTableColumns[1:] {
   125  		if column.Default != "" {
   126  			_, _ = sqlStringBuilder.WriteString(", COALESCE(`")
   127  			_, _ = sqlStringBuilder.WriteString(column.Name)
   128  			_, _ = sqlStringBuilder.WriteString("`, ")
   129  			_, _ = sqlStringBuilder.WriteString(column.Default)
   130  			_, _ = sqlStringBuilder.WriteString(")")
   131  		} else {
   132  			_, _ = sqlStringBuilder.WriteString(", `")
   133  			_, _ = sqlStringBuilder.WriteString(column.Name)
   134  			_, _ = sqlStringBuilder.WriteString("`")
   135  		}
   136  	}
   137  	_, _ = sqlStringBuilder.WriteString(" FROM `")
   138  	_, _ = sqlStringBuilder.WriteString(tableName)
   139  	_, _ = sqlStringBuilder.WriteString("`")
   140  
   141  	if _, err := sess.Exec(sqlStringBuilder.String()); err != nil {
   142  		log.Error("Unable to set copy data in to temp table %s. Error: %v", tempTableName, err)
   143  		return err
   144  	}
   145  
   146  	if hasID && setting.Database.Type.IsMSSQL() {
   147  		if _, err := sess.Exec(fmt.Sprintf("SET IDENTITY_INSERT `%s` OFF", tempTableName)); err != nil {
   148  			log.Error("Unable to switch off identity insert for table %s. Error: %v", tempTableName, err)
   149  			return err
   150  		}
   151  	}
   152  
   153  	switch {
   154  	case setting.Database.Type.IsSQLite3():
   155  		// SQLite will drop all the constraints on the old table
   156  		if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
   157  			log.Error("Unable to drop old table %s. Error: %v", tableName, err)
   158  			return err
   159  		}
   160  
   161  		if err := sess.Table(tempTableName).DropIndexes(bean); err != nil {
   162  			log.Error("Unable to drop indexes on temporary table %s. Error: %v", tempTableName, err)
   163  			return err
   164  		}
   165  
   166  		if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` RENAME TO `%s`", tempTableName, tableName)); err != nil {
   167  			log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
   168  			return err
   169  		}
   170  
   171  		if err := sess.Table(tableName).CreateIndexes(bean); err != nil {
   172  			log.Error("Unable to recreate indexes on table %s. Error: %v", tableName, err)
   173  			return err
   174  		}
   175  
   176  		if err := sess.Table(tableName).CreateUniques(bean); err != nil {
   177  			log.Error("Unable to recreate uniques on table %s. Error: %v", tableName, err)
   178  			return err
   179  		}
   180  	case setting.Database.Type.IsMySQL():
   181  		// MySQL will drop all the constraints on the old table
   182  		if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
   183  			log.Error("Unable to drop old table %s. Error: %v", tableName, err)
   184  			return err
   185  		}
   186  
   187  		if err := sess.Table(tempTableName).DropIndexes(bean); err != nil {
   188  			log.Error("Unable to drop indexes on temporary table %s. Error: %v", tempTableName, err)
   189  			return err
   190  		}
   191  
   192  		// SQLite and MySQL will move all the constraints from the temporary table to the new table
   193  		if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` RENAME TO `%s`", tempTableName, tableName)); err != nil {
   194  			log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
   195  			return err
   196  		}
   197  
   198  		if err := sess.Table(tableName).CreateIndexes(bean); err != nil {
   199  			log.Error("Unable to recreate indexes on table %s. Error: %v", tableName, err)
   200  			return err
   201  		}
   202  
   203  		if err := sess.Table(tableName).CreateUniques(bean); err != nil {
   204  			log.Error("Unable to recreate uniques on table %s. Error: %v", tableName, err)
   205  			return err
   206  		}
   207  	case setting.Database.Type.IsPostgreSQL():
   208  		var originalSequences []string
   209  		type sequenceData struct {
   210  			LastValue int  `xorm:"'last_value'"`
   211  			IsCalled  bool `xorm:"'is_called'"`
   212  		}
   213  		sequenceMap := map[string]sequenceData{}
   214  
   215  		schema := sess.Engine().Dialect().URI().Schema
   216  		sess.Engine().SetSchema("")
   217  		if err := sess.Table("information_schema.sequences").Cols("sequence_name").Where("sequence_name LIKE ? || '_%' AND sequence_catalog = ?", tableName, setting.Database.Name).Find(&originalSequences); err != nil {
   218  			log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
   219  			return err
   220  		}
   221  		sess.Engine().SetSchema(schema)
   222  
   223  		for _, sequence := range originalSequences {
   224  			sequenceData := sequenceData{}
   225  			if _, err := sess.Table(sequence).Cols("last_value", "is_called").Get(&sequenceData); err != nil {
   226  				log.Error("Unable to get last_value and is_called from %s. Error: %v", sequence, err)
   227  				return err
   228  			}
   229  			sequenceMap[sequence] = sequenceData
   230  		}
   231  
   232  		// CASCADE causes postgres to drop all the constraints on the old table
   233  		if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s` CASCADE", tableName)); err != nil {
   234  			log.Error("Unable to drop old table %s. Error: %v", tableName, err)
   235  			return err
   236  		}
   237  
   238  		// CASCADE causes postgres to move all the constraints from the temporary table to the new table
   239  		if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` RENAME TO `%s`", tempTableName, tableName)); err != nil {
   240  			log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
   241  			return err
   242  		}
   243  
   244  		var indices []string
   245  		sess.Engine().SetSchema("")
   246  		if err := sess.Table("pg_indexes").Cols("indexname").Where("tablename = ? ", tableName).Find(&indices); err != nil {
   247  			log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
   248  			return err
   249  		}
   250  		sess.Engine().SetSchema(schema)
   251  
   252  		for _, index := range indices {
   253  			newIndexName := strings.Replace(index, "tmp_recreate__", "", 1)
   254  			if _, err := sess.Exec(fmt.Sprintf("ALTER INDEX `%s` RENAME TO `%s`", index, newIndexName)); err != nil {
   255  				log.Error("Unable to rename %s to %s. Error: %v", index, newIndexName, err)
   256  				return err
   257  			}
   258  		}
   259  
   260  		var sequences []string
   261  		sess.Engine().SetSchema("")
   262  		if err := sess.Table("information_schema.sequences").Cols("sequence_name").Where("sequence_name LIKE 'tmp_recreate__' || ? || '_%' AND sequence_catalog = ?", tableName, setting.Database.Name).Find(&sequences); err != nil {
   263  			log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
   264  			return err
   265  		}
   266  		sess.Engine().SetSchema(schema)
   267  
   268  		for _, sequence := range sequences {
   269  			newSequenceName := strings.Replace(sequence, "tmp_recreate__", "", 1)
   270  			if _, err := sess.Exec(fmt.Sprintf("ALTER SEQUENCE `%s` RENAME TO `%s`", sequence, newSequenceName)); err != nil {
   271  				log.Error("Unable to rename %s sequence to %s. Error: %v", sequence, newSequenceName, err)
   272  				return err
   273  			}
   274  			val, ok := sequenceMap[newSequenceName]
   275  			if newSequenceName == tableName+"_id_seq" {
   276  				if ok && val.LastValue != 0 {
   277  					if _, err := sess.Exec(fmt.Sprintf("SELECT setval('%s', %d, %t)", newSequenceName, val.LastValue, val.IsCalled)); err != nil {
   278  						log.Error("Unable to reset %s to %d. Error: %v", newSequenceName, val, err)
   279  						return err
   280  					}
   281  				} else {
   282  					// We're going to try to guess this
   283  					if _, err := sess.Exec(fmt.Sprintf("SELECT setval('%s', COALESCE((SELECT MAX(id)+1 FROM `%s`), 1), false)", newSequenceName, tableName)); err != nil {
   284  						log.Error("Unable to reset %s. Error: %v", newSequenceName, err)
   285  						return err
   286  					}
   287  				}
   288  			} else if ok {
   289  				if _, err := sess.Exec(fmt.Sprintf("SELECT setval('%s', %d, %t)", newSequenceName, val.LastValue, val.IsCalled)); err != nil {
   290  					log.Error("Unable to reset %s to %d. Error: %v", newSequenceName, val, err)
   291  					return err
   292  				}
   293  			}
   294  		}
   295  	case setting.Database.Type.IsMSSQL():
   296  		// MSSQL will drop all the constraints on the old table
   297  		if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
   298  			log.Error("Unable to drop old table %s. Error: %v", tableName, err)
   299  			return err
   300  		}
   301  
   302  		// MSSQL sp_rename will move all the constraints from the temporary table to the new table
   303  		if _, err := sess.Exec(fmt.Sprintf("sp_rename `%s`,`%s`", tempTableName, tableName)); err != nil {
   304  			log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
   305  			return err
   306  		}
   307  	default:
   308  		log.Fatal("Unrecognized DB")
   309  	}
   310  	return nil
   311  }
   312  
   313  // WARNING: YOU MUST COMMIT THE SESSION AT THE END
   314  func DropTableColumns(sess *xorm.Session, tableName string, columnNames ...string) (err error) {
   315  	if tableName == "" || len(columnNames) == 0 {
   316  		return nil
   317  	}
   318  	// TODO: This will not work if there are foreign keys
   319  
   320  	switch {
   321  	case setting.Database.Type.IsSQLite3():
   322  		// First drop the indexes on the columns
   323  		res, errIndex := sess.Query(fmt.Sprintf("PRAGMA index_list(`%s`)", tableName))
   324  		if errIndex != nil {
   325  			return errIndex
   326  		}
   327  		for _, row := range res {
   328  			indexName := row["name"]
   329  			indexRes, err := sess.Query(fmt.Sprintf("PRAGMA index_info(`%s`)", indexName))
   330  			if err != nil {
   331  				return err
   332  			}
   333  			if len(indexRes) != 1 {
   334  				continue
   335  			}
   336  			indexColumn := string(indexRes[0]["name"])
   337  			for _, name := range columnNames {
   338  				if name == indexColumn {
   339  					_, err := sess.Exec(fmt.Sprintf("DROP INDEX `%s`", indexName))
   340  					if err != nil {
   341  						return err
   342  					}
   343  				}
   344  			}
   345  		}
   346  
   347  		// Here we need to get the columns from the original table
   348  		sql := fmt.Sprintf("SELECT sql FROM sqlite_master WHERE tbl_name='%s' and type='table'", tableName)
   349  		res, err := sess.Query(sql)
   350  		if err != nil {
   351  			return err
   352  		}
   353  		tableSQL := string(res[0]["sql"])
   354  
   355  		// Get the string offset for column definitions: `CREATE TABLE ( column-definitions... )`
   356  		columnDefinitionsIndex := strings.Index(tableSQL, "(")
   357  		if columnDefinitionsIndex < 0 {
   358  			return errors.New("couldn't find column definitions")
   359  		}
   360  
   361  		// Separate out the column definitions
   362  		tableSQL = tableSQL[columnDefinitionsIndex:]
   363  
   364  		// Remove the required columnNames
   365  		for _, name := range columnNames {
   366  			tableSQL = regexp.MustCompile(regexp.QuoteMeta("`"+name+"`")+"[^`,)]*?[,)]").ReplaceAllString(tableSQL, "")
   367  		}
   368  
   369  		// Ensure the query is ended properly
   370  		tableSQL = strings.TrimSpace(tableSQL)
   371  		if tableSQL[len(tableSQL)-1] != ')' {
   372  			if tableSQL[len(tableSQL)-1] == ',' {
   373  				tableSQL = tableSQL[:len(tableSQL)-1]
   374  			}
   375  			tableSQL += ")"
   376  		}
   377  
   378  		// Find all the columns in the table
   379  		columns := regexp.MustCompile("`([^`]*)`").FindAllString(tableSQL, -1)
   380  
   381  		tableSQL = fmt.Sprintf("CREATE TABLE `new_%s_new` ", tableName) + tableSQL
   382  		if _, err := sess.Exec(tableSQL); err != nil {
   383  			return err
   384  		}
   385  
   386  		// Now restore the data
   387  		columnsSeparated := strings.Join(columns, ",")
   388  		insertSQL := fmt.Sprintf("INSERT INTO `new_%s_new` (%s) SELECT %s FROM %s", tableName, columnsSeparated, columnsSeparated, tableName)
   389  		if _, err := sess.Exec(insertSQL); err != nil {
   390  			return err
   391  		}
   392  
   393  		// Now drop the old table
   394  		if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
   395  			return err
   396  		}
   397  
   398  		// Rename the table
   399  		if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `new_%s_new` RENAME TO `%s`", tableName, tableName)); err != nil {
   400  			return err
   401  		}
   402  
   403  	case setting.Database.Type.IsPostgreSQL():
   404  		cols := ""
   405  		for _, col := range columnNames {
   406  			if cols != "" {
   407  				cols += ", "
   408  			}
   409  			cols += "DROP COLUMN `" + col + "` CASCADE"
   410  		}
   411  		if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` %s", tableName, cols)); err != nil {
   412  			return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err)
   413  		}
   414  	case setting.Database.Type.IsMySQL():
   415  		// Drop indexes on columns first
   416  		sql := fmt.Sprintf("SHOW INDEX FROM %s WHERE column_name IN ('%s')", tableName, strings.Join(columnNames, "','"))
   417  		res, err := sess.Query(sql)
   418  		if err != nil {
   419  			return err
   420  		}
   421  		for _, index := range res {
   422  			indexName := index["column_name"]
   423  			if len(indexName) > 0 {
   424  				_, err := sess.Exec(fmt.Sprintf("DROP INDEX `%s` ON `%s`", indexName, tableName))
   425  				if err != nil {
   426  					return err
   427  				}
   428  			}
   429  		}
   430  
   431  		// Now drop the columns
   432  		cols := ""
   433  		for _, col := range columnNames {
   434  			if cols != "" {
   435  				cols += ", "
   436  			}
   437  			cols += "DROP COLUMN `" + col + "`"
   438  		}
   439  		if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` %s", tableName, cols)); err != nil {
   440  			return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err)
   441  		}
   442  	case setting.Database.Type.IsMSSQL():
   443  		cols := ""
   444  		for _, col := range columnNames {
   445  			if cols != "" {
   446  				cols += ", "
   447  			}
   448  			cols += "`" + strings.ToLower(col) + "`"
   449  		}
   450  		sql := fmt.Sprintf("SELECT Name FROM sys.default_constraints WHERE parent_object_id = OBJECT_ID('%[1]s') AND parent_column_id IN (SELECT column_id FROM sys.columns WHERE LOWER(name) IN (%[2]s) AND object_id = OBJECT_ID('%[1]s'))",
   451  			tableName, strings.ReplaceAll(cols, "`", "'"))
   452  		constraints := make([]string, 0)
   453  		if err := sess.SQL(sql).Find(&constraints); err != nil {
   454  			return fmt.Errorf("Find constraints: %v", err)
   455  		}
   456  		for _, constraint := range constraints {
   457  			if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` DROP CONSTRAINT `%s`", tableName, constraint)); err != nil {
   458  				return fmt.Errorf("Drop table `%s` default constraint `%s`: %v", tableName, constraint, err)
   459  			}
   460  		}
   461  		sql = fmt.Sprintf("SELECT DISTINCT Name FROM sys.indexes INNER JOIN sys.index_columns ON indexes.index_id = index_columns.index_id AND indexes.object_id = index_columns.object_id WHERE indexes.object_id = OBJECT_ID('%[1]s') AND index_columns.column_id IN (SELECT column_id FROM sys.columns WHERE LOWER(name) IN (%[2]s) AND object_id = OBJECT_ID('%[1]s'))",
   462  			tableName, strings.ReplaceAll(cols, "`", "'"))
   463  		constraints = make([]string, 0)
   464  		if err := sess.SQL(sql).Find(&constraints); err != nil {
   465  			return fmt.Errorf("Find constraints: %v", err)
   466  		}
   467  		for _, constraint := range constraints {
   468  			if _, err := sess.Exec(fmt.Sprintf("DROP INDEX `%[2]s` ON `%[1]s`", tableName, constraint)); err != nil {
   469  				return fmt.Errorf("Drop index `%[2]s` on `%[1]s`: %v", tableName, constraint, err)
   470  			}
   471  		}
   472  
   473  		if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` DROP COLUMN %s", tableName, cols)); err != nil {
   474  			return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err)
   475  		}
   476  	default:
   477  		log.Fatal("Unrecognized DB")
   478  	}
   479  
   480  	return nil
   481  }
   482  
   483  // ModifyColumn will modify column's type or other property. SQLITE is not supported
   484  func ModifyColumn(x *xorm.Engine, tableName string, col *schemas.Column) error {
   485  	var indexes map[string]*schemas.Index
   486  	var err error
   487  	// MSSQL have to remove index at first, otherwise alter column will fail
   488  	// ref. https://sqlzealots.com/2018/05/09/error-message-the-index-is-dependent-on-column-alter-table-alter-column-failed-because-one-or-more-objects-access-this-column/
   489  	if x.Dialect().URI().DBType == schemas.MSSQL {
   490  		indexes, err = x.Dialect().GetIndexes(x.DB(), context.Background(), tableName)
   491  		if err != nil {
   492  			return err
   493  		}
   494  
   495  		for _, index := range indexes {
   496  			_, err = x.Exec(x.Dialect().DropIndexSQL(tableName, index))
   497  			if err != nil {
   498  				return err
   499  			}
   500  		}
   501  	}
   502  
   503  	defer func() {
   504  		for _, index := range indexes {
   505  			_, err = x.Exec(x.Dialect().CreateIndexSQL(tableName, index))
   506  			if err != nil {
   507  				log.Error("Create index %s on table %s failed: %v", index.Name, tableName, err)
   508  			}
   509  		}
   510  	}()
   511  
   512  	alterSQL := x.Dialect().ModifyColumnSQL(tableName, col)
   513  	if _, err := x.Exec(alterSQL); err != nil {
   514  		return err
   515  	}
   516  	return nil
   517  }
   518  
   519  func removeAllWithRetry(dir string) error {
   520  	var err error
   521  	for i := 0; i < 20; i++ {
   522  		err = os.RemoveAll(dir)
   523  		if err == nil {
   524  			break
   525  		}
   526  		time.Sleep(100 * time.Millisecond)
   527  	}
   528  	return err
   529  }
   530  
   531  func newXORMEngine() (*xorm.Engine, error) {
   532  	if err := db.InitEngine(context.Background()); err != nil {
   533  		return nil, err
   534  	}
   535  	x := unittest.GetXORMEngine()
   536  	return x, nil
   537  }
   538  
   539  func deleteDB() error {
   540  	switch {
   541  	case setting.Database.Type.IsSQLite3():
   542  		if err := util.Remove(setting.Database.Path); err != nil {
   543  			return err
   544  		}
   545  		return os.MkdirAll(path.Dir(setting.Database.Path), os.ModePerm)
   546  
   547  	case setting.Database.Type.IsMySQL():
   548  		db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/",
   549  			setting.Database.User, setting.Database.Passwd, setting.Database.Host))
   550  		if err != nil {
   551  			return err
   552  		}
   553  		defer db.Close()
   554  
   555  		if _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", setting.Database.Name)); err != nil {
   556  			return err
   557  		}
   558  
   559  		if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", setting.Database.Name)); err != nil {
   560  			return err
   561  		}
   562  		return nil
   563  	case setting.Database.Type.IsPostgreSQL():
   564  		db, err := sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/?sslmode=%s",
   565  			setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.SSLMode))
   566  		if err != nil {
   567  			return err
   568  		}
   569  		defer db.Close()
   570  
   571  		if _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", setting.Database.Name)); err != nil {
   572  			return err
   573  		}
   574  
   575  		if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s", setting.Database.Name)); err != nil {
   576  			return err
   577  		}
   578  		db.Close()
   579  
   580  		// Check if we need to setup a specific schema
   581  		if len(setting.Database.Schema) != 0 {
   582  			db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s",
   583  				setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.Name, setting.Database.SSLMode))
   584  			if err != nil {
   585  				return err
   586  			}
   587  			defer db.Close()
   588  
   589  			schrows, err := db.Query(fmt.Sprintf("SELECT 1 FROM information_schema.schemata WHERE schema_name = '%s'", setting.Database.Schema))
   590  			if err != nil {
   591  				return err
   592  			}
   593  			defer schrows.Close()
   594  
   595  			if !schrows.Next() {
   596  				// Create and setup a DB schema
   597  				_, err = db.Exec(fmt.Sprintf("CREATE SCHEMA %s", setting.Database.Schema))
   598  				if err != nil {
   599  					return err
   600  				}
   601  			}
   602  
   603  			// Make the user's default search path the created schema; this will affect new connections
   604  			_, err = db.Exec(fmt.Sprintf(`ALTER USER "%s" SET search_path = %s`, setting.Database.User, setting.Database.Schema))
   605  			if err != nil {
   606  				return err
   607  			}
   608  			return nil
   609  		}
   610  	case setting.Database.Type.IsMSSQL():
   611  		host, port := setting.ParseMSSQLHostPort(setting.Database.Host)
   612  		db, err := sql.Open("mssql", fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;",
   613  			host, port, "master", setting.Database.User, setting.Database.Passwd))
   614  		if err != nil {
   615  			return err
   616  		}
   617  		defer db.Close()
   618  
   619  		if _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS [%s]", setting.Database.Name)); err != nil {
   620  			return err
   621  		}
   622  		if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE [%s]", setting.Database.Name)); err != nil {
   623  			return err
   624  		}
   625  	}
   626  
   627  	return nil
   628  }