Rails 4.1/Arel 5.0でコネクション切断時にスキーマキャッシュが使われない件について

Rails 4.1/Arel 5.0でactiverecord-refresh_connectionなどを使ってコネクションを切断したとき、切断後にスキーマのキャッシュが使用されない問題があったので、メモしておきます。

再現方法

以下のような簡単なRackアプリで再現できます

# Gemfile:
# source 'https://rubygems.org'
# gem 'rack'
# gem 'activerecord', '~> 4.1.0'
# gem 'mysql2'
# gem 'activerecord-refresh_connection'

require 'active_record'
require 'activerecord-refresh_connection'

ActiveRecord::Base.establish_connection(adapter: 'mysql2', database: 'test')

$buf = []

ActiveSupport::Notifications.subscribe('sql.active_record') do |name, start, finish, id, payload|
  $buf << payload[:sql]
end

use ActiveRecord::ConnectionAdapters::RefreshConnectionManagement

# CREATE TABLE `items` (
#   `id` int(11) NOT NULL AUTO_INCREMENT,
#   `name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
#   `price` int(11) DEFAULT NULL,
#   `description` text COLLATE utf8_unicode_ci,
#   `created_at` datetime DEFAULT NULL,
#   `updated_at` datetime DEFAULT NULL,
#   PRIMARY KEY (`id`)
# )
class Item < ActiveRecord::Base; end

run Proc.new {|env|
  # スキーマキャッシュが使われる
  Item.count

  # スキーマキャッシュが使われない
  #Item.where(name: 'test').to_a

  sqls = $buf.dup
  $buf.clear

  ['200', {'Content-Type' => 'text/plain'}, [sqls.join("\n")]]
}

このアプリをrackupしてlocalhost:9292にアクセスしたとき、Item.countの場合、コネクションが切断されてもスキーマキャッシュが使われる—SHOW FULL SHOW FULL FIELDSは発行されませんが、#Item.where(name: 'test').to_aをuncommentすると、リクエストのたびにSHOW FULL SHOW FULL FIELDSが発行されるようになります。

原因

Item.where(name: 'test').to_aの場合、ArelがModelとは別にスキーマのキャッシュを保持することが原因です。 Arelのキャッシュは再接続で消えるので、再接続のたびにSHOW FULL SHOW FULL FIELDSが発行されることになります。

ArelはASTをたどってSQLを生成するとき、String型の場合、Arel::Visitors::ToSql#quoteを呼び出します。quote()はカラムの型に合わせて値をクォートで囲んだり囲まなかったりという動作をするので、スキーマ情報を取りに行くことになります。

ちなみに、さすがに無駄だと思ったのか、Arel 7.0ではそもそもカラムの型を取得しないように設計が変わっていました。Arel 6.0ではこの問題がまだありましたが、検証したのがちょっと前なのでバックポートされているかも知れません。

対応策

Modelのキャッシュがあったらそっちを使うプルリクエストを投げたのですが、リジェクトされてしまいました…

すでにArel 7.0では問題が再発しないことも考えると、あんまりがんばるのも微妙な気がしたので、とりあえずMonkey patchのgemを作成しました。

困っている人はそんなにはいないと思いますが、この問題をふんじゃったかたの助けになれば幸いです。