SQLite3の生成カラムにおける等価性判定の修正
SQLite3アダプタでは、生成カラム(generated column)の等価性判定が修正されました。stored と virtual の異なる生成タイプを持つカラムが誤って等価と判定される問題が解消され、Rubyのハッシュ/セットコレクションでの正しい動作が保証されます。
背景
eql?/hash 契約の違反 が発生していました。ActiveRecord::ConnectionAdapters::SQLite3::Column#== メソッドは virtual? のみを比較していましたが、#hash メソッドは既に @generated_type を含めてハッシュ値を計算していました。
この不整合により、generated_type: :stored と generated_type: :virtual のカラムが等価と判定される一方で、異なるハッシュ値を生成する状況が生じていました。Rubyでは、eql? が真を返すオブジェクトは同じハッシュ値を持つ必要があるため、この状態はHashやSetの動作を不安定にします。
生成カラムには2つのタイプがあり、stored は値を物理的に保存し、virtual は必要時に計算します。この違いはスキーマ定義において重要な区別であり、カラムの等価性判定でも考慮されるべきでした。
技術的な変更
activerecord/lib/active_record/connection_adapters/sqlite3/column.rb の == メソッドが修正され、virtual? による比較が generated_type による比較に置き換えられました。
変更前:
def ==(other)
super &&
auto_increment? == other.auto_increment? &&
rowid == other.rowid &&
virtual? == other.virtual?
end
変更後:
def ==(other)
super &&
auto_increment? == other.auto_increment? &&
rowid == other.rowid &&
generated_type == other.generated_type
end
generated_type は @generated_type インスタンス変数の値(:stored、:virtual、または nil)を返すため、3つの状態を正確に区別できます。virtual? メソッドは真偽値のみを返すため、stored と virtual の区別ができませんでした。
比較を可能にするため、generated_type の可視性が protected に変更されました。これにより、同じクラスのインスタンス間での比較が可能になり、外部からの不必要なアクセスは制限されます。
設計判断
virtual? メソッドではなく generated_type 属性を直接比較する方式 が採用されました。
virtual? は generated_type == :virtual を返すメソッドですが、:stored の場合も false を返すため、通常のカラム(generated_type == nil)と区別できません。generated_type を直接比較することで、3つの状態(nil、:stored、:virtual)を正確に判定できます。
#hash メソッドは既に @generated_type を含めていたため、== メソッドの修正だけで eql?/hash 契約が満たされます。新たなテストケース test_generated_type_changes_column_equality により、stored と virtual のカラムが等価でないことが保証されます。
まとめ
本PRは、SQLite3アダプタのカラム等価性判定における eql?/hash 契約違反を修正しました。virtual? から generated_type への比較方法の変更により、生成カラムのタイプが正確に区別され、Hashベースのコレクション処理が信頼できるものになります。スキーマの意味論的な違いを正しく反映した変更といえます。