Edited at

Redmineプラグインのマイグレーションを適用した環境でdb:setup, db:resetを実行してはいけない

確認したバージョン: Redmine 4.0.4, 3.4.11(たぶん他のバージョンでも起きる)


  1. Redmineプラグインのマイグレーションを適用済みの環境で


  2. rails db:setuprails db:resetを実行してしまうと


  3. rails redmine:plugins:migrateを実行するとTable already existsといったエラーが出て失敗するようになる。1


問題を起こさない方法


  • 新しいDBにスキーマを適用したい場合にはrails db:migrate redmine:plugins:migrateを実行する

  • DBのスキーマを作り直したい場合にはrails db:migrate:reset redmine:plugins:migrateを実行する2


問題が起きたあとの修正方法



  • rails db:migrate:reset redmine:plugins:migrateでDBを作り直す


  • schema_migrationsテーブルをいじってプラグインのマイグレーションが適用されたことにする

  • プラグインのマイグレーションで追加されたテーブルなどを手動で消す

のどれかで直る。だいたい上のほうが簡単なはず。


原因



  • db:setupdb:resetdb/schema.rbをソースにしてスキーマとマイグレーションの適用状況を復元する。



    • db/schema.rbのスキーマ情報にはRedmine本体のほか、プラグインのマイグレーションの結果も保存されているので、プラグインが追加したテーブルなどは正しく復元される。

    • しかしマイグレーションの適用状況はRedmine本体のものしか保存されていないので、プラグインのマイグレーションの適用情報は失われる。



  • そのため、次にredmine:plugins:migrateを実行するとプラグインのマイグレーションが二重に適用されてしまう。


    • マイグレーションの中でテーブルやカラムを追加していた場合、同名のテーブルなどがすでに存在するため、エラーが起きてマイグレーションが失敗する。



  • 一方、db:migrate:resetdb:migrateでマイグレーションをひとつずつ実行していくので、schema_migrationsテーブルとスキーマの不整合が起きない。


用語メモ



  • db/schema.rb: DBのスキーマ情報を保存したファイル3


    • (↓以下2つは確認してないけどたぶん合ってるはず)

    • マイグレーションの実行が完了するたびに更新される。

    • 最後に実行が完了したマイグレーションのバージョンを保持している。




  • schema_migrationsテーブル: どのマイグレーションを適用したかを記録しておくためのテーブル。


コードリーディングメモ


再現コード

再現確認用のリポジトリを作った。pushするたびにGitHub Actionsで


  1. 再現用のプラグインを用意する

  2. Redmineのソースをダウンロードして展開する

  3. プラグインとdatabase.ymlをRedmineのディレクトリに配置する


  4. rails db:create db:migrate redmine:plugins:migrateを実行してからrails db:reset redmine:plugins:migrateを実行してエラーメッセージを確認する

という感じのことをやっていて、不具合が再現したらコミットのステータスが緑になる。





  1. プラグインのすべてのマイグレーションがロールバックせずに何度でも実行できるなら問題は起きないが、そういうケースはごくまれだと思う。 



  2. ActiveRecord::ProtectedEnvironmentErrorが発生したらRails 5に入ったDB破壊系taskの防止処理について | 日々雑記などを参照。 



  3. 設定によってはdb/schema.rbではなくdb/structure.sqlになる。