Skip to content

Commit 909f2be

Browse files
authored
Fix postgres ID sequences broken by recreate-table (#15015) (#15029)
Backport #15015 Unfortunately there is a subtle problem with recreatetable on postgres which leads to the sequences not being renamed and not being left at 0. Fix #14725 Signed-off-by: Andrew Thornton <[email protected]>
1 parent 645c0d8 commit 909f2be

File tree

3 files changed

+140
-1
lines changed

3 files changed

+140
-1
lines changed

cmd/doctor.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,23 @@ func runDoctorCheckDBConsistency(ctx *cli.Context) ([]string, error) {
670670
}
671671
}
672672

673+
if setting.Database.UsePostgreSQL {
674+
count, err = models.CountBadSequences()
675+
if err != nil {
676+
return nil, err
677+
}
678+
if count > 0 {
679+
if ctx.Bool("fix") {
680+
err := models.FixBadSequences()
681+
if err != nil {
682+
return nil, err
683+
}
684+
results = append(results, fmt.Sprintf("%d sequences updated", count))
685+
} else {
686+
results = append(results, fmt.Sprintf("%d sequences with incorrect values", count))
687+
}
688+
}
689+
}
673690
//ToDo: function to recalc all counters
674691

675692
return results, nil

models/consistency.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55
package models
66

77
import (
8+
"fmt"
89
"reflect"
10+
"regexp"
911
"strings"
1012
"testing"
1113

14+
"code.gitea.io/gitea/modules/setting"
1215
"github.com/stretchr/testify/assert"
1316
"xorm.io/builder"
1417
)
@@ -295,3 +298,61 @@ func FixNullArchivedRepository() (int64, error) {
295298
IsArchived: false,
296299
})
297300
}
301+
302+
// CountBadSequences looks for broken sequences from recreate-table mistakes
303+
func CountBadSequences() (int64, error) {
304+
if !setting.Database.UsePostgreSQL {
305+
return 0, nil
306+
}
307+
308+
sess := x.NewSession()
309+
defer sess.Close()
310+
311+
var sequences []string
312+
schema := sess.Engine().Dialect().URI().Schema
313+
314+
sess.Engine().SetSchema("")
315+
if err := sess.Table("information_schema.sequences").Cols("sequence_name").Where("sequence_name LIKE 'tmp_recreate__%_id_seq%' AND sequence_catalog = ?", setting.Database.Name).Find(&sequences); err != nil {
316+
return 0, err
317+
}
318+
sess.Engine().SetSchema(schema)
319+
320+
return int64(len(sequences)), nil
321+
}
322+
323+
// FixBadSequences fixes for broken sequences from recreate-table mistakes
324+
func FixBadSequences() error {
325+
if !setting.Database.UsePostgreSQL {
326+
return nil
327+
}
328+
329+
sess := x.NewSession()
330+
defer sess.Close()
331+
if err := sess.Begin(); err != nil {
332+
return err
333+
}
334+
335+
var sequences []string
336+
schema := sess.Engine().Dialect().URI().Schema
337+
338+
sess.Engine().SetSchema("")
339+
if err := sess.Table("information_schema.sequences").Cols("sequence_name").Where("sequence_name LIKE 'tmp_recreate__%_id_seq%' AND sequence_catalog = ?", setting.Database.Name).Find(&sequences); err != nil {
340+
return err
341+
}
342+
sess.Engine().SetSchema(schema)
343+
344+
sequenceRegexp := regexp.MustCompile(`tmp_recreate__(\w+)_id_seq.*`)
345+
346+
for _, sequence := range sequences {
347+
tableName := sequenceRegexp.FindStringSubmatch(sequence)[1]
348+
newSequenceName := tableName + "_id_seq"
349+
if _, err := sess.Exec(fmt.Sprintf("ALTER SEQUENCE `%s` RENAME TO `%s`", sequence, newSequenceName)); err != nil {
350+
return err
351+
}
352+
if _, err := sess.Exec(fmt.Sprintf("SELECT setval('%s', COALESCE((SELECT MAX(id)+1 FROM `%s`), 1), false)", newSequenceName, tableName)); err != nil {
353+
return err
354+
}
355+
}
356+
357+
return sess.Commit()
358+
}

models/migrations/migrations.go

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,31 @@ func recreateTable(sess *xorm.Session, bean interface{}) error {
516516
return err
517517
}
518518
case setting.Database.UsePostgreSQL:
519+
var originalSequences []string
520+
type sequenceData struct {
521+
LastValue int `xorm:"'last_value'"`
522+
IsCalled bool `xorm:"'is_called'"`
523+
}
524+
sequenceMap := map[string]sequenceData{}
525+
526+
schema := sess.Engine().Dialect().URI().Schema
527+
sess.Engine().SetSchema("")
528+
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 {
529+
log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
530+
return err
531+
}
532+
sess.Engine().SetSchema(schema)
533+
534+
for _, sequence := range originalSequences {
535+
sequenceData := sequenceData{}
536+
if _, err := sess.Table(sequence).Cols("last_value", "is_called").Get(&sequenceData); err != nil {
537+
log.Error("Unable to get last_value and is_called from %s. Error: %v", sequence, err)
538+
return err
539+
}
540+
sequenceMap[sequence] = sequenceData
541+
542+
}
543+
519544
// CASCADE causes postgres to drop all the constraints on the old table
520545
if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s` CASCADE", tableName)); err != nil {
521546
log.Error("Unable to drop old table %s. Error: %v", tableName, err)
@@ -529,7 +554,6 @@ func recreateTable(sess *xorm.Session, bean interface{}) error {
529554
}
530555

531556
var indices []string
532-
schema := sess.Engine().Dialect().URI().Schema
533557
sess.Engine().SetSchema("")
534558
if err := sess.Table("pg_indexes").Cols("indexname").Where("tablename = ? ", tableName).Find(&indices); err != nil {
535559
log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
@@ -545,6 +569,43 @@ func recreateTable(sess *xorm.Session, bean interface{}) error {
545569
}
546570
}
547571

572+
var sequences []string
573+
sess.Engine().SetSchema("")
574+
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 {
575+
log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
576+
return err
577+
}
578+
sess.Engine().SetSchema(schema)
579+
580+
for _, sequence := range sequences {
581+
newSequenceName := strings.Replace(sequence, "tmp_recreate__", "", 1)
582+
if _, err := sess.Exec(fmt.Sprintf("ALTER SEQUENCE `%s` RENAME TO `%s`", sequence, newSequenceName)); err != nil {
583+
log.Error("Unable to rename %s sequence to %s. Error: %v", sequence, newSequenceName, err)
584+
return err
585+
}
586+
val, ok := sequenceMap[newSequenceName]
587+
if newSequenceName == tableName+"_id_seq" {
588+
if ok && val.LastValue != 0 {
589+
if _, err := sess.Exec(fmt.Sprintf("SELECT setval('%s', %d, %t)", newSequenceName, val.LastValue, val.IsCalled)); err != nil {
590+
log.Error("Unable to reset %s to %d. Error: %v", newSequenceName, val, err)
591+
return err
592+
}
593+
} else {
594+
// We're going to try to guess this
595+
if _, err := sess.Exec(fmt.Sprintf("SELECT setval('%s', COALESCE((SELECT MAX(id)+1 FROM `%s`), 1), false)", newSequenceName, tableName)); err != nil {
596+
log.Error("Unable to reset %s. Error: %v", newSequenceName, err)
597+
return err
598+
}
599+
}
600+
} else if ok {
601+
if _, err := sess.Exec(fmt.Sprintf("SELECT setval('%s', %d, %t)", newSequenceName, val.LastValue, val.IsCalled)); err != nil {
602+
log.Error("Unable to reset %s to %d. Error: %v", newSequenceName, val, err)
603+
return err
604+
}
605+
}
606+
607+
}
608+
548609
case setting.Database.UseMSSQL:
549610
// MSSQL will drop all the constraints on the old table
550611
if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {

0 commit comments

Comments
 (0)