GitLab CIでRailsアプリをお手軽CI開発する - Tech Inside Drecom で書ききれなかったおまけです。
tl;dr
- activerecord 5.0.0.beta1以降では治ってる
- 4系以前では下記のコミットをモンキーパッチとして入れれば治る
あらすじ
GitLab CIでRailsアプリをお手軽CI開発する - Tech Inside Drecom を書いてる時に .gitlab-ci.yml
で
services: - mysql:5.5
って書いていたのになぜか activerecordがmysql 5.7以降でしか使えないDDLを発行してmigrationが失敗するという事象が発生。
エラーの状況
migrationファイル
こんな感じのmigrationを実行したかった
class TableRenaemStatusToProfile < ActiveRecord::Migration def change rename_table :user_statuses, :user_profiles end end
ローカル (MySQL 5.5)
問題なし
Migrating to TableRenaemStatusToProfile (20140126044757) (25.6ms) RENAME TABLE `user_statuses` TO `user_profiles` (13.4ms) CREATE UNIQUE INDEX `index_user_profiles_on_user_id` ON `user_profiles` (`user_id`) (8.7ms) DROP INDEX `index_user_statuses_on_user_id` ON `user_profiles`
リネーム対象のテーブルにあったindexも新テーブル名に即したindex名に変更された。(activerecord賢い)
GitLab CI
シンタックスエラー(;´Д`)
Mysql2::Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'INDEX `index_user_statuses_on_user_id` TO `index_user_profiles_on_user_id`' at line 1: ALTER TABLE `user_profiles` RENAME INDEX `index_user_statuses_on_user_id` TO `index_user_profiles_on_user_id`
user_statuses
の user_id
というカラムにunique indexが貼られていたため rename_table
内でindex名変更のDDLが発行されていたのですが、MySQL 5.7以降でしか使えない RENAME INDEX
が発行されてエラーになってる模様。いやお前5.5だろ。。。
activerecordどの〜!
原因
原因はactiverecord はmysql serverのバージョンではなく、Dockerイメージで使ってるlibmysqlclientのバージョンが5.7で、activerecordはそこからMySQLのバージョンを取得していたためでした。*1
ためしにビルドスクリプトでデバッグログを出したところ、activerecord上は確かに5.7でした
+ bundle exec rails r 'puts ActiveRecord::Base.connection.send(:full_version)' 5.7.12
今回使ってたDockerのイメージの中を調べたらlibmysqlclientとかのバージョンとも完全一致
$ docker run -i -t drecom/ubuntu-ruby bash root@8c92dcb22de8:/# dpkg -l | grep mysql ii libmysqlclient-dev 5.7.12-0ubuntu1 amd64 MySQL database development files ii libmysqlclient20:amd64 5.7.12-0ubuntu1 amd64 MySQL database client library ii libqt4-sql-mysql:amd64 4:4.8.7+dfsg-5ubuntu2 amd64 Qt 4 MySQL database driver ii mysql-client 5.7.12-0ubuntu1 all MySQL database client (metapackage depending on the latest version) ii mysql-client-5.7 5.7.12-0ubuntu1 amd64 MySQL database client binaries ii mysql-client-core-5.7 5.7.12-0ubuntu1 amd64 MySQL database core client binaries ii mysql-common 5.7.12-0ubuntu1 all MySQL database common files, e.g. /etc/mysql/my.cnf
MySQLのバージョンによるmigrationの挙動の違い
activerecordのソースを読んだところ、MySQL 5.7以降だと RENAME INDEX
を使って、MySQL 5.6以下だと CREATE INDEX
してから DROP INDEX
しているようでした
- https://github.com/rails/rails/blob/v4.2.6/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb#L505
- https://github.com/rails/rails/blob/v4.2.6/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb#L612-L613
対処法
冒頭に書いた通りRails 5系では治ってます。Rails 4系でこの問題に直面したら config/initializers/activerecord_mysql_version_patch.rb
のようなモンキーパッチを入れれば治ります
# activerecordでクライアントではなくサーバのMySQLのバージョンを使用するためのモンキーパッチ # via. https://github.com/rails/rails/commit/977ffe880624bbd05f5ee1cc6e4fa51a999884ab current_version = ActiveRecord.version if Gem::Version.new("5.0.0.beta1") <= current_version raise "This monkeypatch's lifetime is over. Check ActiveRecord version." end module ActiveRecord module ConnectionAdapters class Mysql2Adapter < AbstractMysqlAdapter def full_version @full_version ||= @connection.server_info[:version] end end end end
Rails 5系では治っているので、Rails 5系に上げた後にモンキーパッチが残り続けないようにわざとエラーを投げるようにしています。(いわゆる「モンキーパッチの賞味期限」というやつ)
モンキーパッチの賞味期限は下記のスライドが詳しいです
https://speakerdeck.com/eagletmt/activerecord-3-dot-2-4-dot-1?slide=26
まとめ
mysql clientとmysql serverのバージョンが食い違うことは早々ないと思いますが上記のようなやり方で回避は可能です
これはGitLab CI以外にも、Dockerコンテナ採用しているWerckerでも同じ現象が起こりそうな予感。
kamipoさんには足を向けて眠れません m(_ _)m https://github.com/rails/rails/commit/977ffe880624bbd05f5ee1cc6e4fa51a999884ab
*1:activerecord 5系だとmysql serverからバージョンを取得している