`reset_column_sequences!` が quoted スキーマのテーブルでクラッシュする不具合を修正

rails/rails

reset_column_sequences! がスキーマ名を引用符で囲んだテーブルに対して呼び出された際、NoMethodError が発生していました。本稿では原因と修正内容、設計上の判断を解説します。

背景

reset_column_sequences! は PostgreSQL アダプタでシリアル/UUID 列のシーケンスをテーブル内容に合わせてリセットする内部ロジックです。
Quoted schema(例: "App".widgets)のテーブルを対象にすると、内部で生成された @tables マップのキーと実際に取得したテーブル名が一致せず、nil.column = … が実行されて NoMethodError がスローされました。

根本原因は SequenceResetregclass::text から得たスキーマ修飾名を delete_prefix('"').delete_suffix('"') で単純にアンコートしていた点です。この手法は "App".widgetsApp".widgets に変換し、マップキー App.widgets と食い違っていました。既存テストは小文字スキーマしか扱わなかったため、問題は顕在化していませんでした。

技術的な変更

SequenceReset の初期化 でテーブル名を格納する際、Utils.extract_schema_qualified_name を用いて正規化した文字列をキーにしました。

@tables = tables.to_h { |table, data| [Utils.extract_schema_qualified_name(table.to_s).to_s, Data.new(*data)] }

シーケンスのバックフィル 部分でも同様に正規化を適用し、delete_prefix 系の処理を排除しました。

primary_key_column_and_sequences.rows.each do |table, column, namespace, sequence|
  table = Utils.extract_schema_qualified_name(table).to_s
  @tables[table].column = column
  @tables[table].sequence = PostgreSQL::Name.new(namespace, sequence) if sequence
end

UUID ループでも同様の変更を行い、両方のパスで一貫した名前処理を実現しています。これにより quoted スキーマでも @tables のキーが正しくマッチし、reset_column_sequences! が期待通りにシーケンスをリセットします。

テスト追加 により修正を検証しました。新しいテスト test_reset_column_sequences_with_quoted_schema は、"Test_CamelSchema".widgets に対してシーケンスが 100 以上に進むことを確認します。

def test_reset_column_sequences_with_quoted_schema
  @connection.execute('CREATE SCHEMA "Test_CamelSchema"')
  @connection.execute('CREATE TABLE "Test_CamelSchema".widgets (id serial primary key)')
  @connection.execute('INSERT INTO "Test_CamelSchema".widgets (id) VALUES (100)')
  @connection.reset_column_sequences!([['"Test_CamelSchema".widgets']])
  next_id = @connection.select_value(%Q{SELECT nextval(pg_get_serial_sequence('"Test_CamelSchema".widgets', 'id'))})
  assert_operator next_id.to_i, :>, 100
ensure
  @connection.execute('DROP SCHEMA IF EXISTS "Test_CamelSchema" CASCADE')
end

設計判断

修正では 既存の Utils.unquote_identifier を置き換えるのではなく、汎用的に利用可能な Utils.extract_schema_qualified_name を統一的に適用する方針を採りました。これにより、現在のコードベースで他の箇所が期待通りに動作しているかを追加調査する必要がなく、最小限のリスクで問題を解消できます。
同じ根本原因が別の箇所(foreign_keysto_table 取得)でも顕在化しており、そちらは別 PR #57561 で対処されています。今回の変更はシーケンスリセットロジックに限定し、他の機能への副作用を回避する点が設計上の重要な判断です。

まとめ

reset_column_sequences! が quoted スキーマのテーブルでクラッシュしていた原因は不適切な名前アンコートにあり、Utils.extract_schema_qualified_name を用いた正規化で根本的に解決しました。テストの追加により安全性が検証され、既存の非 quoted ケースへの影響はありません。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
64790d85

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

品質レビュー結果

Review Status:
リトライ後承認
Review Count:
2回 (改善を経て承認)
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文、背景、技術的変更、設計判断、まとめが明確に配置されており、総論→各論→結論の構成が遵守されています。

カスタムMarkdown構文 ✓ PASS

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

コードブロックは `言語:ファイルパス` の形式で正しく記述され、PRリンクは `[#57563](URL)` の形式で正しくリンク化されています。

対象読者への適合性 ✓ PASS

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

内容は Rails 開発者向けの高度な技術情報で、初心者向けの過剰な説明はありません。

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

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

各セクションは総論・各論・結論の段落構成があり、段落はトピックセンテンスで始まり、1段落1トピックで長さも適切です。空行で区切られています。

Diff内容との照合 ✓ PASS

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

記事中のコードブロックは提供された Diff と完全に一致しており、変更内容が正確に反映されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

使用されている技術用語は PR と一致し、誤用は見られません。

説明の技術的正確性 ✓ PASS

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

根本原因や修正内容の説明が PR の記述と合致しており、技術的に正確です。

事実の突合 ✓ PASS

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

記事のすべての主張は PR タイトル・説明・Diff で裏付けられており、ハルシネーションはありません。

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

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

PR 番号やテストで使用した数値(100 など)は Diff と一致しています。

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

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

記事タイトルは PR の趣旨を正確に日本語化しており、内容と一致しています。

外部知識の正確性 ✓ PASS

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

記事は PR 以外の外部知識(LTS、リリース日程等)を含んでいません。

時間表現の正確性 ✓ PASS

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

時間表現は使用されておらず、PR と矛盾する箇所はありません。