SQLite3カラムの等価比較にrowid属性を追加
この変更により、SQLite3アダプタのカラム等価比較が修正され、rowid属性が比較対象に含まれるようになりました。これにより、異なるrowid特性を持つカラムが誤って等しいと判定される不具合が解消されます。
背景
ActiveRecord::ConnectionAdapters::SQLite3::Column#==メソッドは、rowid属性を比較対象としていませんでした。一方で#hashメソッドは既にrowidを含めてハッシュ値を計算していたため、Rubyの等価性の基本原則である「等しいオブジェクトは同じハッシュ値を持つべき」という要件に違反していました。
この不整合により、異なるrowid特性を持つカラムが重複排除やスキーマメタデータの整合性チェックで誤って同一と判定される問題がありました。rowidはSQLite3の特殊なカラムで、テーブルの各行に対する一意な整数値を提供する仕組みであり、この属性の違いは設計上重要な意味を持ちます。
技術的な変更
activerecord/lib/active_record/connection_adapters/sqlite3/column.rbの#==メソッドに、rowid属性の比較が追加されました。
変更前:
def ==(other)
other.is_a?(Column) &&
super &&
auto_increment? == other.auto_increment? &&
virtual? == other.virtual?
end
変更後:
def ==(other)
other.is_a?(Column) &&
super &&
auto_increment? == other.auto_increment? &&
rowid == other.rowid &&
virtual? == other.virtual?
end
変更は1行の追加のみで、auto_increment?とvirtual?の比較の間にrowidの比較が挟まれています。alias :eql? :==により、eql?メソッドにも同じ変更が適用されます。
リグレッションテストとして、activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rbにtest_rowid_changes_column_equalityが追加されました。このテストは、rowid属性のみが異なる2つのカラムを構築し、それらが等しくないことを検証します。
def test_rowid_changes_column_equality
cast_type = @conn.lookup_cast_type("integer")
type_metadata = SqlTypeMetadata.new(sql_type: "integer", type: :integer)
rowid_column = SQLite3::Column.new("id", cast_type, nil, type_metadata, true, nil, rowid: true)
regular_column = SQLite3::Column.new("id", cast_type, nil, type_metadata, true, nil, rowid: false)
assert_not_equal rowid_column, regular_column
end
設計判断
既存の比較ロジックにrowidを追加する最小限の修正が採用されました。#hashメソッドが既にrowidを含んでいたことから、等価性の定義を#hashの実装に合わせる方針です。
PR本文では「minor bug fix」と位置づけられ、CHANGELOGへの記載は省略されています。この判断は、バグ修正が既存の動作を変更するものではなく、誤った等価判定を正すものであることに基づいています。ユーザーコードへの影響は限定的で、主にActiveRecord内部のスキーマキャッシュや重複排除処理での正確性が向上します。
#==と#hashの整合性は、Rubyのコレクションクラス(Hash、Setなど)が正しく動作するための前提条件です。この修正により、SQLite3カラムオブジェクトが標準的なRubyの等価性契約に準拠するようになりました。
まとめ
本PRは、SQLite3アダプタのカラム等価比較におけるrowid属性の扱いを修正し、#==と#hashの整合性を回復させる変更です。1行の追加という最小限の修正で、スキーマメタデータの正確性とRubyの等価性契約への準拠を両立させています。