インデックス名変更時に属性を保持するよう rename_index を修正
Rails の抽象的な rename_index 実装が、SQLite や旧 MySQL/MariaDB で部分インデックスの属性を失っていた問題を解消しました。これにより、インデックス名を変更しても WHERE 句やソート順などのメタ情報が保持されます。
背景
rename_index のフォールバック実装は、add_index を呼び出す際に columns と unique だけを渡す単純なロジックでした。その結果、部分インデックスで指定した WHERE clause やカラムのソート順(orders など)が名前変更時に失われました。
このロジックは、ネイティブな名前変更構文を持たない SQLite と MySQL 5.7.6 未満 / MariaDB 10.5.2 未満のアダプタで使用されます。PostgreSQL や新しい MySQL/MariaDB では ALTER INDEX … RENAME が直接実行されるため影響はありません。
インデックス名変更は rename_column から内部的に呼び出されることが多く、特に一意制約付きの部分インデックスで行うと、制約が適用される行集合が変わってしまうリスクがあります。したがって、属性を保持しないまま名前を変えることはデータ整合性に直結する重大な欠陥と言えます。
技術的な変更
rename_index のフォールバックロジックに、旧インデックス定義から取得した where(および他属性)を新しい add_index 呼び出しへ引き継ぐ処理が追加されました。これにより、属性が失われずに再作成されます。
@@
- add_index(table_name, old_index_def.columns, name: new_name, unique: old_index_def.unique)
+ add_index(table_name, old_index_def.columns, name: new_name, unique: old_index_def.unique, where: old_index_def.where)
実装は old_index_def.where が存在する場合にのみ渡す形でガードされ、既存の add_index 呼び出しとの互換性を保っています。コメントでは他の属性(orders, lengths, opclasses, using, type, include, nulls_not_distinct, comment)も同様に条件付きで引き継ぐことが言及されていますが、今回の差分では where の追加が中心です。
テストも新規に追加され、supports_partial_index? が有効な環境で WHERE 句が保持されることを検証しています。テストは add_index 後に取得したインデックスオブジェクトの where がリネーム前と等しいことを確認し、SQLite での失敗ケースを防ぎます。
結果として、SQLite と旧 MySQL/MariaDB では属性保持が保証され、他のアダプタは従来通りネイティブ実装を利用するため動作に変化はありません。
設計判断
今回の修正は、抽象実装に属性伝搬ロジックを組み込むことで 後方互換性 を維持しつつ機能拡張を実現しています。新しい設定キーを導入せず、既存の rename_index 流れに最小限の変更で対応した点が評価されます。
属性の引き継ぎは old_index_def から取得し、nil でない場合にだけ add_index に渡す guard 条件で実装されているため、ネイティブリネームを使用するデータベース側のパスには一切影響しません。これにより、パフォーマンスや挙動の差異が導入されるリスクを回避しています。
この設計は、抽象層での汎用性とアダプタごとの最適化を両立させる典型的なパターンであり、将来的に他のインデックス属性が追加されても同様のガード方式で拡張できる柔軟性を持ちます。
まとめ
rename_index のフォールバック実装が部分インデックスのメタ情報を保持しないバグを修正し、SQLite と古い MySQL/MariaDB でもインデックス名変更時に属性が失われません。最小限のコード変更で後方互換性を保ちつつ、データ整合性のリスクを排除した点が本変更の主な意義です。