`add_index`の配列構文で式インデックスが使えるように
add_indexメソッドで配列構文を使った式インデックスが正しく機能しない問題が修正されました。これにより、["lower(email)", :status]のような配列形式で式インデックスを定義できるようになります。
背景
add_indexでは従来から式インデックスをサポートしていましたが、文字列形式のみが機能する状態でした。t.index "lower(email)"は動作する一方、t.index ["lower(email)"]はSQLiteであればSQLite3::SQLException: no such column: "lower(email)"というエラーを引き起こしていました。
問題の根本は、配列構文で渡された際の処理にありました。文字列をそのままSQL文に渡す場合は式として解釈されますが、配列として渡すと各要素がカラム名として扱われ、lower(email)がカラム名として検索されてしまいます。#55099でこのバグが報告されています。
配列構文は複数カラムのインデックスでは一般的な記法であり、式インデックスでも同じ書き方ができないことは一貫性を欠いていました。
技術的な変更
index_column_namesメソッドのロジックが変更され、配列内に式が含まれる場合を適切に検出して処理するようになりました。
変更の対象はactiverecord/lib/active_record/connection_adapters/abstract/schema_statements.rb内のindex_column_namesメソッドです。
変更前:
def index_column_names(column_names)
if expression_column_name?(column_names)
column_names
else
Array(column_names)
end
end
変更後:
def index_column_names(column_names)
column_names = Array(column_names)
return column_names.join(", ") if has_expression_column_name?(column_names)
column_names
end
def has_expression_column_name?(column_names)
column_names.any? { |name| expression_column_name?(name) }
end
変更前はexpression_column_name?を配列全体に対して評価していましたが、変更後はまず入力をArray()で統一したうえで、新たに追加したhas_expression_column_name?メソッドによって各要素を個別にチェックします。配列内にひとつでも式が含まれていれば、join(", ")でカンマ区切りの文字列に変換してSQLに渡す形式に統一されます。
これにより、以下のいずれの書き方も正しく動作します:
-
add_index :users, "lower(email)"(従来の文字列形式、変更なし) -
add_index :users, ["lower(email)"](配列に式が1つ) -
add_index :users, ["lower(email)", :status](式とカラム名の混在) -
add_index :users, ["(lower(last_name))", "(upper(first_name))"](複数の式)
設計判断
配列内に式が1つでも含まれていれば、配列全体をjoin(", ")で文字列化してSQLに渡す設計が採用されました。
このアプローチは、式とカラム名が混在した["lower(email)", :status]のようなケースを扱うための自然な拡張です。既存の文字列形式の式インデックス処理(column_namesをそのまま返す)と同じSQL表現に帰着させることで、アダプター側の処理を変更せずに済んでいます。
has_expression_column_name?を独立したメソッドとして切り出したことで、責務の分離と可読性の向上が図られています。テストではsupports_expression_index?によってアダプターのサポート状況を確認したうえで、式単体・式+カラム名・複数式の各パターンが網羅されています。
まとめ
本PRは、配列構文の式インデックスが暗黙的にカラム名として解釈されていた問題を、index_column_namesの入力正規化と要素単位の式検出によって修正しました。これにより、複数カラムインデックスで馴染みのある配列構文が式インデックスでも一貫して使えるようになり、マイグレーションコードの可読性と記述の統一性が向上します。