PostgreSQLパーティションテーブルの外部キー制約が `NOT VALID` のままになるバグを修正

rails/rails

ActiveRecord::FixtureSet.create_fixtures をPostgreSQLのパーティションテーブルで使用すると、外部キー制約が NOT VALID 状態のまま残留するバグが修正されました。対象は ReferentialIntegrity#check_all_foreign_keys_valid!UPDATE 文のスコープです。

背景

ActiveRecord::FixtureSet.create_fixtures を実行すると、参照整合性チェックのために check_all_foreign_keys_valid! が呼び出されます。このメソッドは外部キー制約を一時的に convalidated=false(未検証)状態にしてから VALIDATE CONSTRAINT で再検証するというアプローチをとります。しかし、この実装がPostgreSQLのパーティションテーブルと組み合わさったとき、検証済みに戻るべき制約が NOT VALID のまま残るという問題が生じていました。

具体的な症状として、rails db:migrate を実行するたびに structure.sql の外部キー制約に NOT VALID が付与される形で差分が生じ、スキーマファイルが汚染されていました。

PostgreSQLのパーティションテーブルでは、親テーブルと各パーティションがそれぞれ独立したテーブルとして扱われ、同名の外部キー制約を持ちます。UPDATE pg_catalog.pg_constraint を制約名とスキーマ名だけでフィルタリングすると、親テーブルと全パーティションの convalidated が一括で false に設定されます。ところが VALIDATE CONSTRAINT は1テーブルずつ実行されるため、処理済みのテーブル以外のパーティションは NOT VALID のまま取り残されていました。

技術的な変更

UPDATE pg_catalog.pg_constraintconrelid::regclass によるテーブルスコープを追加することで、1テーブル分の制約のみを NOT VALID に変更するよう修正されました。

変更前:

UPDATE pg_catalog.pg_constraint
  SET convalidated=false
  WHERE conname = '%1$I'
    AND connamespace::regnamespace = '%2$I'::regnamespace;
ALTER TABLE %2$I.%3$I VALIDATE CONSTRAINT %1$I;

変更後:

UPDATE pg_catalog.pg_constraint
  SET convalidated=false
  WHERE conname = '%1$I'
    AND connamespace::regnamespace = '%2$I'::regnamespace
    AND conrelid::regclass = '%3$I'::regclass;  -- テーブルを限定
ALTER TABLE %2$I.%3$I VALIDATE CONSTRAINT %1$I;

この修正は activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb の1行変更です。%3$Itable_name)は元のSQLにすでに引数として存在していたため、新たな引数追加は不要でした。

テストは activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb に追加されました。親テーブル・パーティション・参照先テーブルの3テーブルを作成して check_all_foreign_keys_valid! を実行し、その後 pg_constraint を直接クエリして convalidated = false の件数がゼロであることを assert_equal で検証しています。

設計判断

UPDATE 文のスコープを最小化するという方針が採用されました。

修正の本質は「テーブル1件分の制約だけを NOT VALID にしてから即座に再検証する」サイクルをテーブルごとに繰り返すことです。conrelid::regclass の条件を追加するだけで、ループ構造や検証ロジック自体には一切手を加えていません。また、パーティション自体もPostgreSQLからは独立したテーブルとして扱われるため、各パーティションは個別にループ処理の対象となり、修正後も正しく検証されます。

変更がSQL文の述語追加1行に収まっていることで、既存の動作(パーティションなしのテーブルや複数スキーマにまたがる外部キー)への影響がなく、リグレッションリスクを最小限に抑えています。

まとめ

SQL述語に conrelid::regclass を追加するだけの1行修正ですが、PostgreSQLのパーティションテーブルで外部キー制約が NOT VALID のまま残留するという根本原因を正確に解消しています。パーティションテーブルを利用するアプリケーションでは、フィクスチャ実行後に structure.sql が汚染される問題が解消されます。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
2d9d8f1a

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

「リード文(総論)→背景・技術詳細・設計判断(各論)→まとめ(結論)」という3部構成が明確に適用されています。必須要素はすべて満たしており、理想的な記事構成です。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

PR番号へのリンク記法([PR #53636](URL))が正しく使用されています。ファイル名付きシンタックスハイライトは使用されていませんが、本文中でファイルパスが明記されており、構文上の誤りもないため問題ありません。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

「PostgreSQLパーティションテーブル」「ActiveRecord」「pg_constraint」などの用語が前提知識として扱われており、専門知識を持つエンジニアという対象読者に適合した内容と表現になっています。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論→各論の構成になっており、各段落もトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が守られています。可読性が非常に高いです。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内のSQLコードブロックは、Diff内の`FORMAT`関数の中身を分かりやすく展開したものであり、技術的な内容と完全に一致しています。ファイル名の言及も正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「convalidated」「conrelid::regclass」など、PostgreSQLのシステムカタログに関連する専門用語を正確に使用できています。PR内の用語とも一致しています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

「UPDATE文のスコープがテーブルに限定されていなかった」という問題の根本原因と、「conrelid::regclassでスコープを限定する」という解決策の説明が、DiffとPRの内容に完全に即しており、技術的に正確です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内のすべての主張(例:`structure.sql`が汚染される問題)は、PRのDescriptionで裏付けが取れており、ハルシネーション(創作)は見られません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号「#53636」が正しく記載されています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事タイトルは、PRタイトル「fix referential_integrity for postgres partitioned tables」の内容を、より具体的に分かりやすく要約しており、主題と完全に一致しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

記事の内容はPR情報(Title, Description, Diff)に完全に準拠しており、バージョン情報やリリース予定といったPR外の知識の追加はありません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「問題が生じていました」「修正されました」「問題が解消されます」といった時間表現が、過去の状況、今回の変更、変更後の効果を正確に示しており、PRの内容と矛盾していません。