PostgreSQL `cidr`/`inet` カラムのIPv6プレフィックスがスキーマダンプで失われるバグを修正

rails/rails

PostgreSQLの cidr / inet カラムにIPv6アドレスをデフォルト値として設定した場合、schema.rb へのダンプ時にサブネットプレフィックスが失われるサイレントなデータ破壊バグが修正されました。10年来の「IPv4専用」の仮定が原因で、/32 プレフィックスを持つIPv6アドレスが特に影響を受けていました。

背景

OID::Cidr#type_cast_for_schema はスキーマダンパーが schema.rb にデフォルト値を書き出す際に使用されるメソッドですが、2014年の導入(4321cd09a5)以来、IPv4専用の仮定を持ち続けていました。具体的には、プレフィックス長が /32 に等しい場合にプレフィックスを省略する実装になっており、IPv4のフルマスクである /32 を「省略してよい完全アドレス」と同一視していました。

IPv6ではフルマスクは /128 であり、/32 は「ISPや地域に割り当てられるサブネット」として広く使われる有効なプレフィックスです。そのため、"::/32" というデフォルト値は schema.rb"::" として書き出され、db:schema:load 時に IPAddr.new("::")として読み込まれると /128 として解釈されてしまいます。マイグレーションが意図したネットワーク情報が、スキーマのラウンドトリップを経て静かに失われていました。

以下のコードでこの挙動を再現できます:

require "ipaddr"

orig = IPAddr.new("::/32")
dump = orig.prefix == 32 ? %("#{orig}") : %("#{orig}/#{orig.prefix}")
dump      # => "\":::\""

reloaded = IPAddr.new(dump[1..-2])
reloaded.prefix  # => 128  (元は 32)

この問題はIPv6ネットワークを cidr / inet カラムのデフォルト値として使用しているアプリケーションのみに影響し、IPv4のみを使用しているアプリケーションには影響がありません。

技術的な変更

修正は activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rbtype_cast_for_schema メソッド1行の変更に集約されます。プレフィックスの省略判定を「アドレスファミリーに応じたフルマスク長との比較」に改めることで、IPv4・IPv6の両方を正しく扱えるようになっています。

変更前:

def type_cast_for_schema(value)
  # If the subnet mask is equal to /32, don't output it
  if value.prefix == 32
    "\"#{value}\""
  else
    "\"#{value}/#{value.prefix}\""
  end
end

変更後:

def type_cast_for_schema(value)
  # If the subnet mask covers the full address, don't output it
  if value.prefix == (value.ipv6? ? 128 : 32)
    "\"#{value}\""
  else
    "\"#{value}/#{value.prefix}\""
  end
end

この変更によって各アドレスの扱いが次のように変わります:

  • IPv4(既存動作を維持): 192.168.1.0/32"192.168.1.0"192.168.1.0/24"192.168.1.0/24"
  • IPv6(バグ修正): ::/32"::/32"fe80::/10"fe80::/10"::/0"::/0"
  • IPv6フルマスク(対称性の向上): ::1/128"::1"/128 を省略)

::1/128 のケースは動作変更ですが、IPAddr.new("::1") を経由しても同一アドレスに復元されるため、ラウンドトリップの正確性は保たれます。また、OID::InetOID::Cidr を継承しており type_cast_for_schema をオーバーライドしていないため、この修正は inet カラムにも自動的に適用されます。

テスト面では activerecord/test/cases/adapters/postgresql/cidr_test.rb に2つのテストケースが追加されています。IPv4の既存動作確認とIPv6の修正後の動作確認を網羅しており、既存の network_test.rb はIPv4デフォルト値のスキーマダンプアサーションを含むためIPv4リグレッションのチェックとしても機能します。

設計判断

value.ipv6? ? 128 : 32 という三項演算子による分岐が採用されました。IPv4とIPv6のフルマスク長をハードコードした最小限の変更であり、既存のコードパスに影響を与えません。IPAddripv6? メソッドを活用することで、アドレスファミリーの判定をRuby標準ライブラリに委ねており、新たな依存を導入していない点も特徴的です。

IPv6フルマスク(/128)を省略する変更はバグ修正の副産物として含まれていますが、PR本文では「対称性のための化粧的変更(cosmetic change)」と明示されています。ラウンドトリップの正確性は担保されているため問題はありませんが、既存の schema.rb を持つプロジェクトで ::1/128 形式のデフォルト値がある場合、次回のスキーマダンプで ::1 に書き換わることになります。

まとめ

10年間見過ごされてきたIPv4中心の仮定を1行の条件式で修正し、IPv6サブネットプレフィックスのサイレントな破壊を防ぎます。変更行数は最小限ですが、cidrinet の両型に同時に適用され、スキーマダンプの信頼性をIPv6環境でも保証する実質的な修正です。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
478675f7

この記事は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リンク記法の正確性

ファイル名付きシンタックスハイライト、コミットIDやPR番号のリンク記法がガイドラインに沿って正しく使用されています。

対象読者への適合性 ✓ PASS

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

専門用語が適切に使用され、前提知識が過度に説明されていないため、専門知識を持つエンジニアという対象読者に適合しています。

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

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

各セクション・各パラグラフが「総論→各論」の構造で書かれ、トピックセンテンスが明確です。1段落1トピックの原則も守られており、可読性が高いです。

Diff内容との照合 ✓ PASS

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

記事内で引用されているコード変更前後の内容は、提供されたDiff情報と正確に一致しています。ファイルパスの指定も正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`cidr`, `inet`, `OID::Cidr`, `type_cast_for_schema`などの技術用語が文脈に沿って正確に使用されています。

説明の技術的正確性 ✓ PASS

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

IPv6プレフィックスが失われるメカニズムや、修正による影響範囲(`inet`型にも適用される点など)について、技術的に正確かつ論理的に説明されています。

事実の突合 ✓ PASS

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

「10年来のバグ」「対称性のための化粧的変更」といった記述は、すべてPR Descriptionやコミット履歴で裏付けられており、ハルシネーション(創作)は見られません。

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

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

PR番号(#57438)、コミットID(4321cd09a5)、プレフィックス長(/32, /128)などの数値や固有名詞はすべて正確です。

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

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

記事のタイトルはPR「Preserve IPv6 prefix when dumping PostgreSQL `cidr`/`inet` defaults to schema.rb」の内容を日本語で的確に要約しており、主題の乖離はありません。

外部知識の正確性 ✓ PASS

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

PR情報に記載のない外部知識(LTS、リリース予定など)は含まれておらず、すべての記述が提供された情報源に基づいています。

時間表現の正確性 ✓ PASS

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

「2014年の導入以来」「10年来」といった時間表現は、PRに記載されたコミット日に基づいており、正確です。