@@ -7,6 +7,7 @@ package migrations
7
7
8
8
import (
9
9
"fmt"
10
+ "reflect"
10
11
"regexp"
11
12
"strings"
12
13
@@ -317,6 +318,153 @@ Please try upgrading to a lower version first (suggested v1.6.4), then upgrade t
317
318
return nil
318
319
}
319
320
321
+ // RecreateTables will recreate the tables for the provided beans using the newly provided bean definition and move all data to that new table
322
+ // WARNING: YOU MUST PROVIDE THE FULL BEAN DEFINITION
323
+ func RecreateTables (beans ... interface {}) func (* xorm.Engine ) error {
324
+ return func (x * xorm.Engine ) error {
325
+ sess := x .NewSession ()
326
+ defer sess .Close ()
327
+ if err := sess .Begin (); err != nil {
328
+ return err
329
+ }
330
+ for _ , bean := range beans {
331
+ log .Info ("Recreating Table: %s for Bean: %s" , x .TableName (bean ), reflect .Indirect (reflect .ValueOf (bean )).Type ().Name ())
332
+ if err := recreateTable (sess , bean ); err != nil {
333
+ return err
334
+ }
335
+ }
336
+ return sess .Commit ()
337
+ }
338
+ }
339
+
340
+ // recreateTable will recreate the table using the newly provided bean definition and move all data to that new table
341
+ // WARNING: YOU MUST PROVIDE THE FULL BEAN DEFINITION
342
+ // WARNING: YOU MUST COMMIT THE SESSION AT THE END
343
+ func recreateTable (sess * xorm.Session , bean interface {}) error {
344
+ // TODO: This will not work if there are foreign keys
345
+
346
+ tableName := sess .Engine ().TableName (bean )
347
+ tempTableName := fmt .Sprintf ("temporary__%s__temporary" , tableName )
348
+
349
+ // We need to move the old table away and create a new one with the correct columns
350
+ // We will need to do this in stages to prevent data loss
351
+ //
352
+ // First let's update the old table to ensure it has all the necessary columns
353
+ if err := sess .CreateTable (bean ); err != nil {
354
+ return err
355
+ }
356
+
357
+ // Now we create the temporary table
358
+ if err := sess .Table (tempTableName ).CreateTable (bean ); err != nil {
359
+ return err
360
+ }
361
+
362
+ // Work out the column names from the bean - these are the columns to select from the old table and install into the new table
363
+ table , err := sess .Engine ().TableInfo (bean )
364
+ if err != nil {
365
+ return err
366
+ }
367
+ newTableColumns := table .Columns ()
368
+ if len (newTableColumns ) == 0 {
369
+ return fmt .Errorf ("no columns in new table" )
370
+ }
371
+
372
+ if setting .Database .UseMSSQL {
373
+ sess .Exec (fmt .Sprintf ("SET IDENTITY_INSERT %s ON" , tempTableName ))
374
+ }
375
+
376
+ sqlStringBuilder := & strings.Builder {}
377
+ _ , _ = sqlStringBuilder .WriteString ("INSERT INTO " )
378
+ _ , _ = sqlStringBuilder .WriteString (tempTableName )
379
+ if setting .Database .UseMSSQL {
380
+ _ , _ = sqlStringBuilder .WriteString (" (`" )
381
+ _ , _ = sqlStringBuilder .WriteString (newTableColumns [0 ].Name )
382
+ _ , _ = sqlStringBuilder .WriteString ("`" )
383
+ for _ , column := range newTableColumns [1 :] {
384
+ _ , _ = sqlStringBuilder .WriteString (", `" )
385
+ _ , _ = sqlStringBuilder .WriteString (column .Name )
386
+ _ , _ = sqlStringBuilder .WriteString ("`" )
387
+ }
388
+ _ , _ = sqlStringBuilder .WriteString (")" )
389
+ }
390
+ _ , _ = sqlStringBuilder .WriteString (" SELECT " )
391
+ if newTableColumns [0 ].Default != "" {
392
+ _ , _ = sqlStringBuilder .WriteString ("COALESCE(`" )
393
+ _ , _ = sqlStringBuilder .WriteString (newTableColumns [0 ].Name )
394
+ _ , _ = sqlStringBuilder .WriteString ("`, " )
395
+ _ , _ = sqlStringBuilder .WriteString (newTableColumns [0 ].Default )
396
+ _ , _ = sqlStringBuilder .WriteString (")" )
397
+ } else {
398
+ _ , _ = sqlStringBuilder .WriteString ("`" )
399
+ _ , _ = sqlStringBuilder .WriteString (newTableColumns [0 ].Name )
400
+ _ , _ = sqlStringBuilder .WriteString ("`" )
401
+ }
402
+
403
+ for _ , column := range newTableColumns [1 :] {
404
+ if column .Default != "" {
405
+ _ , _ = sqlStringBuilder .WriteString (", COALESCE(`" )
406
+ _ , _ = sqlStringBuilder .WriteString (column .Name )
407
+ _ , _ = sqlStringBuilder .WriteString ("`, " )
408
+ _ , _ = sqlStringBuilder .WriteString (column .Default )
409
+ _ , _ = sqlStringBuilder .WriteString (")" )
410
+ } else {
411
+ _ , _ = sqlStringBuilder .WriteString (", `" )
412
+ _ , _ = sqlStringBuilder .WriteString (column .Name )
413
+ _ , _ = sqlStringBuilder .WriteString ("`" )
414
+ }
415
+ }
416
+ _ , _ = sqlStringBuilder .WriteString (" FROM " )
417
+ _ , _ = sqlStringBuilder .WriteString (tableName )
418
+
419
+ if _ , err := sess .Exec (sqlStringBuilder .String ()); err != nil {
420
+ return err
421
+ }
422
+
423
+ if setting .Database .UseMSSQL {
424
+ sess .Exec (fmt .Sprintf ("SET IDENTITY_INSERT %s OFF" , tempTableName ))
425
+ }
426
+
427
+ switch {
428
+ case setting .Database .UseSQLite3 :
429
+ fallthrough
430
+ case setting .Database .UseMySQL :
431
+ // SQLite and MySQL will drop all the constraints on the old table
432
+ if _ , err := sess .Exec (fmt .Sprintf ("DROP TABLE `%s`" , tableName )); err != nil {
433
+ return err
434
+ }
435
+
436
+ // SQLite and MySQL will move all the constraints from the temporary table to the new table
437
+ if _ , err := sess .Exec (fmt .Sprintf ("ALTER TABLE `%s` RENAME TO `%s`" , tempTableName , tableName )); err != nil {
438
+ return err
439
+ }
440
+ case setting .Database .UsePostgreSQL :
441
+ // CASCADE causes postgres to drop all the constraints on the old table
442
+ if _ , err := sess .Exec (fmt .Sprintf ("DROP TABLE `%s` CASCADE" , tableName )); err != nil {
443
+ return err
444
+ }
445
+
446
+ // CASCADE causes postgres to move all the constraints from the temporary table to the new table
447
+ if _ , err := sess .Exec (fmt .Sprintf ("ALTER TABLE `%s` RENAME TO `%s`" , tempTableName , tableName )); err != nil {
448
+ return err
449
+ }
450
+ case setting .Database .UseMSSQL :
451
+ // MSSQL will drop all the constraints on the old table
452
+ if _ , err := sess .Exec (fmt .Sprintf ("DROP TABLE `%s`" , tableName )); err != nil {
453
+ return err
454
+ }
455
+
456
+ // MSSQL sp_rename will move all the constraints from the temporary table to the new table
457
+ if _ , err := sess .Exec (fmt .Sprintf ("sp_rename '[%s]','[%s]'" , tempTableName , tableName )); err != nil {
458
+ return err
459
+ }
460
+
461
+ default :
462
+ log .Fatal ("Unrecognized DB" )
463
+ }
464
+ return nil
465
+ }
466
+
467
+ // WARNING: YOU MUST COMMIT THE SESSION AT THE END
320
468
func dropTableColumns (sess * xorm.Session , tableName string , columnNames ... string ) (err error ) {
321
469
if tableName == "" || len (columnNames ) == 0 {
322
470
return nil
@@ -465,7 +613,6 @@ func dropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin
465
613
return fmt .Errorf ("Drop table `%s` columns %v: %v" , tableName , columnNames , err )
466
614
}
467
615
468
- return sess .Commit ()
469
616
default :
470
617
log .Fatal ("Unrecognized DB" )
471
618
}
0 commit comments