package database import ( entity "RenewCMS/internal/infrastructure/persistence/models" "fmt" "gorm.io/gorm" ) const ( articleStoriesMarker = "article_stories" articleStoryLanguageIndex = "idx_articles_story_lang" articleStoryLanguageIndexSQL = "CREATE UNIQUE INDEX idx_articles_story_lang ON articles (story_id, lang, retired_id)" articleStoryRetiredMarker = "article_story_language_retired" articleRetiredIDsMarker = "article_retired_ids" ) func BackfillArticleStories(db *gorm.DB) error { return db.Transaction(func(tx *gorm.DB) error { applied, err := markerApplied(tx, articleStoriesMarker) if err != nil || applied { return err } var ids []uint32 if err := tx.Unscoped().Model(&entity.Article{}).Where("story_id = ?", 0).Order("id ASC").Pluck("id", &ids).Error; err != nil { return err } for _, id := range ids { story := entity.Story{} if err := tx.Create(&story).Error; err != nil { return err } if err := tx.Unscoped().Model(&entity.Article{}).Where("id = ?", id).UpdateColumn("story_id", story.ID).Error; err != nil { return err } } return applyMarker(tx, articleStoriesMarker) }) } func BackfillRetiredArticles(db *gorm.DB) error { return db.Transaction(func(tx *gorm.DB) error { applied, err := markerApplied(tx, articleRetiredIDsMarker) if err != nil || applied { return err } if err := tx.Unscoped().Model(&entity.Article{}).Where("deleted_at IS NOT NULL AND retired_id = ?", 0).UpdateColumn("retired_id", gorm.Expr("id")).Error; err != nil { return err } return applyMarker(tx, articleRetiredIDsMarker) }) } type duplicateStoryLanguage struct { StoryID uint32 Lang string RetiredID uint32 Versions int } func checkStoryLanguagesUnique(db *gorm.DB) error { var duplicates []duplicateStoryLanguage err := db.Unscoped().Model(&entity.Article{}). Select("story_id, lang, retired_id, COUNT(*) AS versions"). Group("story_id, lang, retired_id"). Having("COUNT(*) > 1"). Order("story_id ASC, lang ASC"). Limit(1). Scan(&duplicates).Error if err != nil || len(duplicates) == 0 { return err } first := duplicates[0] return fmt.Errorf("cannot create the unique index %s: story %d holds %d versions in language %q; merge or delete the extra versions, then start again", articleStoryLanguageIndex, first.StoryID, first.Versions, first.Lang) } func ensureArticleStoryLanguageIndex(db *gorm.DB) error { replaced, err := markerApplied(db, articleStoryRetiredMarker) if err != nil { return err } indexed := db.Migrator().HasIndex(&entity.Article{}, articleStoryLanguageIndex) if replaced && indexed { return nil } if indexed { if err := db.Migrator().DropIndex(&entity.Article{}, articleStoryLanguageIndex); err != nil { return err } } if err := checkStoryLanguagesUnique(db); err != nil { return err } if err := db.Exec(articleStoryLanguageIndexSQL).Error; err != nil { return err } if replaced { return nil } return applyMarker(db, articleStoryRetiredMarker) }