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 }