PostgreSQLのスキーマ修飾オペレータクラス名がschema.rbから欠落するバグを修正
dump_schemasと複数スキーマ構成を併用した場合に、インデックスのオペレータクラスがschema.rbへ出力されなくなる不具合が修正されました。正規表現の拡張と単純名への正規化により、スキーマ修飾された名前でも正しくパースされます。
背景
新たに導入された within_each_schema メソッドが、スキーマダンプ中のschema_search_pathをダンプ対象スキーマのみに絞り込む挙動を持つことが問題の根本原因です。たとえばdump_schemas = 'public'と設定されている場合、ダンプ処理中はschema_search_pathが"public"単体に上書きされます。
この状態でPostgreSQLにpg_get_indexdefの出力を問い合わせると、拡張機能(例:pg_trgm)が別スキーマ(例:shared_extensions)にインストールされている場合、オペレータクラス名がgin_trgm_opsではなくshared_extensions.gin_trgm_opsというスキーマ修飾形式で返されます。within_each_schemaが存在しなかった頃はschema_search_pathがダンプ中に変更されなかったため、この問題は発生しませんでした。
既存の正規表現\w+_opsはドットを含む名前にマッチできないため、オペレータクラス情報が無視され、schema.rbへの出力が静かに欠落していました。再現手順としては、schema_search_path: 'public,shared_extensions'を設定したデータベースでpg_trgmをshared_extensionsスキーマに配置し、GINインデックスを作成した後にスキーマダンプを実行するだけで問題が顕在化します。
技術的な変更
修正箇所は activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb の1行の正規表現と、それに続くオペレータクラス名の取り出し処理です。
変更前:
expressions.scan(/(?<column>\w+)"?\s?(?<opclass>\w+_ops(_\w+)?)?\s?(?<desc>DESC)?\s?(?<nulls>NULLS (?:FIRST|LAST))?/).each do |column, opclass, desc, nulls|
opclasses[column] = opclass.to_sym if opclass
変更後:
expressions.scan(/(?<column>\w+)"?\s?(?<opclass>(?:\w+\.)?\w+_ops(_\w+)?)?\s?(?<desc>DESC)?\s?(?<nulls>NULLS (?:FIRST|LAST))?/).each do |column, opclass, desc, nulls|
opclasses[column] = opclass.split(".").last.to_sym if opclass
オペレータクラスのキャプチャグループに (?:\w+\.)? というオプショナルなプレフィックスが追加され、shared_extensions.gin_trgm_opsのようなスキーマ修飾名にもマッチするようになりました。マッチした後は.split(".").last.to_symでスキーマプレフィックスを除去し、schema.rbには常に非修飾の:gin_trgm_opsが書き出されます。
テストは activerecord/test/cases/adapters/postgresql/schema_test.rb のSchemaIndexOpclassTestクラスに追加されました。新設されたtest_opclass_class_parsing_from_another_schemaはtest_schemaを動的に作成してpg_trgmをそのスキーマに配置し、with_schema_search_path("public,test_schema")配下でダンプした出力にopclass: :gin_trgm_opsが含まれることを検証します。
設計判断
スキーマ修飾名をそのままシンボルに変換するのではなく、末尾の単純名(simple name)に正規化する方針が採用されました。
PR Descriptionに「to extract the simple name, stripping any schema prefix」と明記されているとおり、.split(".").lastによってスキーマプレフィックスを取り除き、schema.rbには常に非修飾のオペレータクラス名のみを記録します。また、正規表現の変更も最小限にとどめられており、既存の_opsサフィックスによるマッチング戦略を維持しつつオプショナルなプレフィックスを追加するだけで対応しているため、非修飾名に対する既存の動作に影響を与えません。
まとめ
本PRはwithin_each_schemaの導入に伴うリグレッションを、正規表現の小さな拡張と名前の正規化という最小コストで解消しています。スキーマ修飾名を受け入れつつ単純名へ正規化するアプローチにより、オペレータクラス情報の欠落という静かな不具合が確実に修正されました。