code.gitea.io/gitea@v1.21.7/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  
   181  	case setting.Database.Type.IsMySQL():
   182  		// MySQL will drop all the constraints on the old table
   183  		if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
   184  			log.Error("Unable to drop old table %s. Error: %v", tableName, err)
   185  			return err
   186  		}
   187  
   188  		if err := sess.Table(tempTableName).DropIndexes(bean); err != nil {
   189  			log.Error("Unable to drop indexes on temporary table %s. Error: %v", tempTableName, err)
   190  			return err
   191  		}
   192  
   193  		// SQLite and MySQL will move all the constraints from the temporary table to the new table
   194  		if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` RENAME TO `%s`", tempTableName, tableName)); err != nil {
   195  			log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
   196  			return err
   197  		}
   198  
   199  		if err := sess.Table(tableName).CreateIndexes(bean); err != nil {
   200  			log.Error("Unable to recreate indexes on table %s. Error: %v", tableName, err)
   201  			return err
   202  		}
   203  
   204  		if err := sess.Table(tableName).CreateUniques(bean); err != nil {
   205  			log.Error("Unable to recreate uniques on table %s. Error: %v", tableName, err)
   206  			return err
   207  		}
   208  	case setting.Database.Type.IsPostgreSQL():
   209  		var originalSequences []string
   210  		type sequenceData struct {
   211  			LastValue int  `xorm:"'last_value'"`
   212  			IsCalled  bool `xorm:"'is_called'"`
   213  		}
   214  		sequenceMap := map[string]sequenceData{}
   215  
   216  		schema := sess.Engine().Dialect().URI().Schema
   217  		sess.Engine().SetSchema("")
   218  		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 {
   219  			log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
   220  			return err
   221  		}
   222  		sess.Engine().SetSchema(schema)
   223  
   224  		for _, sequence := range originalSequences {
   225  			sequenceData := sequenceData{}
   226  			if _, err := sess.Table(sequence).Cols("last_value", "is_called").Get(&sequenceData); err != nil {
   227  				log.Error("Unable to get last_value and is_called from %s. Error: %v", sequence, err)
   228  				return err
   229  			}
   230  			sequenceMap[sequence] = sequenceData
   231  
   232  		}
   233  
   234  		// CASCADE causes postgres to drop all the constraints on the old table
   235  		if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s` CASCADE", tableName)); err != nil {
   236  			log.Error("Unable to drop old table %s. Error: %v", tableName, err)
   237  			return err
   238  		}
   239  
   240  		// CASCADE causes postgres to move all the constraints from the temporary table to the new table
   241  		if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` RENAME TO `%s`", tempTableName, tableName)); err != nil {
   242  			log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
   243  			return err
   244  		}
   245  
   246  		var indices []string
   247  		sess.Engine().SetSchema("")
   248  		if err := sess.Table("pg_indexes").Cols("indexname").Where("tablename = ? ", tableName).Find(&indices); err != nil {
   249  			log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
   250  			return err
   251  		}
   252  		sess.Engine().SetSchema(schema)
   253  
   254  		for _, index := range indices {
   255  			newIndexName := strings.Replace(index, "tmp_recreate__", "", 1)
   256  			if _, err := sess.Exec(fmt.Sprintf("ALTER INDEX `%s` RENAME TO `%s`", index, newIndexName)); err != nil {
   257  				log.Error("Unable to rename %s to %s. Error: %v", index, newIndexName, err)
   258  				return err
   259  			}
   260  		}
   261  
   262  		var sequences []string
   263  		sess.Engine().SetSchema("")
   264  		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 {
   265  			log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
   266  			return err
   267  		}
   268  		sess.Engine().SetSchema(schema)
   269  
   270  		for _, sequence := range sequences {
   271  			newSequenceName := strings.Replace(sequence, "tmp_recreate__", "", 1)
   272  			if _, err := sess.Exec(fmt.Sprintf("ALTER SEQUENCE `%s` RENAME TO `%s`", sequence, newSequenceName)); err != nil {
   273  				log.Error("Unable to rename %s sequence to %s. Error: %v", sequence, newSequenceName, err)
   274  				return err
   275  			}
   276  			val, ok := sequenceMap[newSequenceName]
   277  			if newSequenceName == tableName+"_id_seq" {
   278  				if ok && val.LastValue != 0 {
   279  					if _, err := sess.Exec(fmt.Sprintf("SELECT setval('%s', %d, %t)", newSequenceName, val.LastValue, val.IsCalled)); err != nil {
   280  						log.Error("Unable to reset %s to %d. Error: %v", newSequenceName, val, err)
   281  						return err
   282  					}
   283  				} else {
   284  					// We're going to try to guess this
   285  					if _, err := sess.Exec(fmt.Sprintf("SELECT setval('%s', COALESCE((SELECT MAX(id)+1 FROM `%s`), 1), false)", newSequenceName, tableName)); err != nil {
   286  						log.Error("Unable to reset %s. Error: %v", newSequenceName, err)
   287  						return err
   288  					}
   289  				}
   290  			} else if ok {
   291  				if _, err := sess.Exec(fmt.Sprintf("SELECT setval('%s', %d, %t)", newSequenceName, val.LastValue, val.IsCalled)); err != nil {
   292  					log.Error("Unable to reset %s to %d. Error: %v", newSequenceName, val, err)
   293  					return err
   294  				}
   295  			}
   296  
   297  		}
   298  
   299  	case setting.Database.Type.IsMSSQL():
   300  		// MSSQL will drop all the constraints on the old table
   301  		if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
   302  			log.Error("Unable to drop old table %s. Error: %v", tableName, err)
   303  			return err
   304  		}
   305  
   306  		// MSSQL sp_rename will move all the constraints from the temporary table to the new table
   307  		if _, err := sess.Exec(fmt.Sprintf("sp_rename `%s`,`%s`", tempTableName, tableName)); err != nil {
   308  			log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
   309  			return err
   310  		}
   311  
   312  	default:
   313  		log.Fatal("Unrecognized DB")
   314  	}
   315  	return nil
   316  }
   317  
   318  // WARNING: YOU MUST COMMIT THE SESSION AT THE END
   319  func DropTableColumns(sess *xorm.Session, tableName string, columnNames ...string) (err error) {
   320  	if tableName == "" || len(columnNames) == 0 {
   321  		return nil
   322  	}
   323  	// TODO: This will not work if there are foreign keys
   324  
   325  	switch {
   326  	case setting.Database.Type.IsSQLite3():
   327  		// First drop the indexes on the columns
   328  		res, errIndex := sess.Query(fmt.Sprintf("PRAGMA index_list(`%s`)", tableName))
   329  		if errIndex != nil {
   330  			return errIndex
   331  		}
   332  		for _, row := range res {
   333  			indexName := row["name"]
   334  			indexRes, err := sess.Query(fmt.Sprintf("PRAGMA index_info(`%s`)", indexName))
   335  			if err != nil {
   336  				return err
   337  			}
   338  			if len(indexRes) != 1 {
   339  				continue
   340  			}
   341  			indexColumn := string(indexRes[0]["name"])
   342  			for _, name := range columnNames {
   343  				if name == indexColumn {
   344  					_, err := sess.Exec(fmt.Sprintf("DROP INDEX `%s`", indexName))
   345  					if err != nil {
   346  						return err
   347  					}
   348  				}
   349  			}
   350  		}
   351  
   352  		// Here we need to get the columns from the original table
   353  		sql := fmt.Sprintf("SELECT sql FROM sqlite_master WHERE tbl_name='%s' and type='table'", tableName)
   354  		res, err := sess.Query(sql)
   355  		if err != nil {
   356  			return err
   357  		}
   358  		tableSQL := string(res[0]["sql"])
   359  
   360  		// Get the string offset for column definitions: `CREATE TABLE ( column-definitions... )`
   361  		columnDefinitionsIndex := strings.Index(tableSQL, "(")
   362  		if columnDefinitionsIndex < 0 {
   363  			return errors.New("couldn't find column definitions")
   364  		}
   365  
   366  		// Separate out the column definitions
   367  		tableSQL = tableSQL[columnDefinitionsIndex:]
   368  
   369  		// Remove the required columnNames
   370  		for _, name := range columnNames {
   371  			tableSQL = regexp.MustCompile(regexp.QuoteMeta("`"+name+"`")+"[^`,)]*?[,)]").ReplaceAllString(tableSQL, "")
   372  		}
   373  
   374  		// Ensure the query is ended properly
   375  		tableSQL = strings.TrimSpace(tableSQL)
   376  		if tableSQL[len(tableSQL)-1] != ')' {
   377  			if tableSQL[len(tableSQL)-1] == ',' {
   378  				tableSQL = tableSQL[:len(tableSQL)-1]
   379  			}
   380  			tableSQL += ")"
   381  		}
   382  
   383  		// Find all the columns in the table
   384  		columns := regexp.MustCompile("`([^`]*)`").FindAllString(tableSQL, -1)
   385  
   386  		tableSQL = fmt.Sprintf("CREATE TABLE `new_%s_new` ", tableName) + tableSQL
   387  		if _, err := sess.Exec(tableSQL); err != nil {
   388  			return err
   389  		}
   390  
   391  		// Now restore the data
   392  		columnsSeparated := strings.Join(columns, ",")
   393  		insertSQL := fmt.Sprintf("INSERT INTO `new_%s_new` (%s) SELECT %s FROM %s", tableName, columnsSeparated, columnsSeparated, tableName)
   394  		if _, err := sess.Exec(insertSQL); err != nil {
   395  			return err
   396  		}
   397  
   398  		// Now drop the old table
   399  		if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
   400  			return err
   401  		}
   402  
   403  		// Rename the table
   404  		if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `new_%s_new` RENAME TO `%s`", tableName, tableName)); err != nil {
   405  			return err
   406  		}
   407  
   408  	case setting.Database.Type.IsPostgreSQL():
   409  		cols := ""
   410  		for _, col := range columnNames {
   411  			if cols != "" {
   412  				cols += ", "
   413  			}
   414  			cols += "DROP COLUMN `" + col + "` CASCADE"
   415  		}
   416  		if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` %s", tableName, cols)); err != nil {
   417  			return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err)
   418  		}
   419  	case setting.Database.Type.IsMySQL():
   420  		// Drop indexes on columns first
   421  		sql := fmt.Sprintf("SHOW INDEX FROM %s WHERE column_name IN ('%s')", tableName, strings.Join(columnNames, "','"))
   422  		res, err := sess.Query(sql)
   423  		if err != nil {
   424  			return err
   425  		}
   426  		for _, index := range res {
   427  			indexName := index["column_name"]
   428  			if len(indexName) > 0 {
   429  				_, err := sess.Exec(fmt.Sprintf("DROP INDEX `%s` ON `%s`", indexName, tableName))
   430  				if err != nil {
   431  					return err
   432  				}
   433  			}
   434  		}
   435  
   436  		// Now drop the columns
   437  		cols := ""
   438  		for _, col := range columnNames {
   439  			if cols != "" {
   440  				cols += ", "
   441  			}
   442  			cols += "DROP COLUMN `" + col + "`"
   443  		}
   444  		if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` %s", tableName, cols)); err != nil {
   445  			return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err)
   446  		}
   447  	case setting.Database.Type.IsMSSQL():
   448  		cols := ""
   449  		for _, col := range columnNames {
   450  			if cols != "" {
   451  				cols += ", "
   452  			}
   453  			cols += "`" + strings.ToLower(col) + "`"
   454  		}
   455  		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'))",
   456  			tableName, strings.ReplaceAll(cols, "`", "'"))
   457  		constraints := make([]string, 0)
   458  		if err := sess.SQL(sql).Find(&constraints); err != nil {
   459  			return fmt.Errorf("Find constraints: %v", err)
   460  		}
   461  		for _, constraint := range constraints {
   462  			if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` DROP CONSTRAINT `%s`", tableName, constraint)); err != nil {
   463  				return fmt.Errorf("Drop table `%s` default constraint `%s`: %v", tableName, constraint, err)
   464  			}
   465  		}
   466  		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'))",
   467  			tableName, strings.ReplaceAll(cols, "`", "'"))
   468  		constraints = make([]string, 0)
   469  		if err := sess.SQL(sql).Find(&constraints); err != nil {
   470  			return fmt.Errorf("Find constraints: %v", err)
   471  		}
   472  		for _, constraint := range constraints {
   473  			if _, err := sess.Exec(fmt.Sprintf("DROP INDEX `%[2]s` ON `%[1]s`", tableName, constraint)); err != nil {
   474  				return fmt.Errorf("Drop index `%[2]s` on `%[1]s`: %v", tableName, constraint, err)
   475  			}
   476  		}
   477  
   478  		if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` DROP COLUMN %s", tableName, cols)); err != nil {
   479  			return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err)
   480  		}
   481  	default:
   482  		log.Fatal("Unrecognized DB")
   483  	}
   484  
   485  	return nil
   486  }
   487  
   488  // ModifyColumn will modify column's type or other property. SQLITE is not supported
   489  func ModifyColumn(x *xorm.Engine, tableName string, col *schemas.Column) error {
   490  	var indexes map[string]*schemas.Index
   491  	var err error
   492  	// MSSQL have to remove index at first, otherwise alter column will fail
   493  	// 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/
   494  	if x.Dialect().URI().DBType == schemas.MSSQL {
   495  		indexes, err = x.Dialect().GetIndexes(x.DB(), context.Background(), tableName)
   496  		if err != nil {
   497  			return err
   498  		}
   499  
   500  		for _, index := range indexes {
   501  			_, err = x.Exec(x.Dialect().DropIndexSQL(tableName, index))
   502  			if err != nil {
   503  				return err
   504  			}
   505  		}
   506  	}
   507  
   508  	defer func() {
   509  		for _, index := range indexes {
   510  			_, err = x.Exec(x.Dialect().CreateIndexSQL(tableName, index))
   511  			if err != nil {
   512  				log.Error("Create index %s on table %s failed: %v", index.Name, tableName, err)
   513  			}
   514  		}
   515  	}()
   516  
   517  	alterSQL := x.Dialect().ModifyColumnSQL(tableName, col)
   518  	if _, err := x.Exec(alterSQL); err != nil {
   519  		return err
   520  	}
   521  	return nil
   522  }
   523  
   524  func removeAllWithRetry(dir string) error {
   525  	var err error
   526  	for i := 0; i < 20; i++ {
   527  		err = os.RemoveAll(dir)
   528  		if err == nil {
   529  			break
   530  		}
   531  		time.Sleep(100 * time.Millisecond)
   532  	}
   533  	return err
   534  }
   535  
   536  func newXORMEngine() (*xorm.Engine, error) {
   537  	if err := db.InitEngine(context.Background()); err != nil {
   538  		return nil, err
   539  	}
   540  	x := unittest.GetXORMEngine()
   541  	return x, nil
   542  }
   543  
   544  func deleteDB() error {
   545  	switch {
   546  	case setting.Database.Type.IsSQLite3():
   547  		if err := util.Remove(setting.Database.Path); err != nil {
   548  			return err
   549  		}
   550  		return os.MkdirAll(path.Dir(setting.Database.Path), os.ModePerm)
   551  
   552  	case setting.Database.Type.IsMySQL():
   553  		db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/",
   554  			setting.Database.User, setting.Database.Passwd, setting.Database.Host))
   555  		if err != nil {
   556  			return err
   557  		}
   558  		defer db.Close()
   559  
   560  		if _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", setting.Database.Name)); err != nil {
   561  			return err
   562  		}
   563  
   564  		if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", setting.Database.Name)); err != nil {
   565  			return err
   566  		}
   567  		return nil
   568  	case setting.Database.Type.IsPostgreSQL():
   569  		db, err := sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/?sslmode=%s",
   570  			setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.SSLMode))
   571  		if err != nil {
   572  			return err
   573  		}
   574  		defer db.Close()
   575  
   576  		if _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", setting.Database.Name)); err != nil {
   577  			return err
   578  		}
   579  
   580  		if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s", setting.Database.Name)); err != nil {
   581  			return err
   582  		}
   583  		db.Close()
   584  
   585  		// Check if we need to setup a specific schema
   586  		if len(setting.Database.Schema) != 0 {
   587  			db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s",
   588  				setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.Name, setting.Database.SSLMode))
   589  			if err != nil {
   590  				return err
   591  			}
   592  			defer db.Close()
   593  
   594  			schrows, err := db.Query(fmt.Sprintf("SELECT 1 FROM information_schema.schemata WHERE schema_name = '%s'", setting.Database.Schema))
   595  			if err != nil {
   596  				return err
   597  			}
   598  			defer schrows.Close()
   599  
   600  			if !schrows.Next() {
   601  				// Create and setup a DB schema
   602  				_, err = db.Exec(fmt.Sprintf("CREATE SCHEMA %s", setting.Database.Schema))
   603  				if err != nil {
   604  					return err
   605  				}
   606  			}
   607  
   608  			// Make the user's default search path the created schema; this will affect new connections
   609  			_, err = db.Exec(fmt.Sprintf(`ALTER USER "%s" SET search_path = %s`, setting.Database.User, setting.Database.Schema))
   610  			if err != nil {
   611  				return err
   612  			}
   613  			return nil
   614  		}
   615  	case setting.Database.Type.IsMSSQL():
   616  		host, port := setting.ParseMSSQLHostPort(setting.Database.Host)
   617  		db, err := sql.Open("mssql", fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;",
   618  			host, port, "master", setting.Database.User, setting.Database.Passwd))
   619  		if err != nil {
   620  			return err
   621  		}
   622  		defer db.Close()
   623  
   624  		if _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS [%s]", setting.Database.Name)); err != nil {
   625  			return err
   626  		}
   627  		if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE [%s]", setting.Database.Name)); err != nil {
   628  			return err
   629  		}
   630  	}
   631  
   632  	return nil
   633  }