PostgreSQL の `RESET` コマンドを読み取り専用コンテキストで実行可能に

rails/rails

PostgreSQL アダプターの READ_QUERY 正規表現に :reset を追加することで、prevent_writes: true の読み取り専用コンテキスト内でも RESET コマンドを実行できるようになりました。

背景

prevent_writes: true の読み取り専用コンテキストでは、ActiveRecord は書き込みクエリの検出に READ_QUERY 正規表現を使用し、マッチしないクエリを ActiveRecord::ReadOnlyError として拒否します。RESET コマンドはこの正規表現に含まれていなかったため、読み取り専用コンテキスト内で実行すると常にエラーが発生していました。

PostgreSQL の RESETSET configuration_parameter TO DEFAULT の構文的ショートカットであり、セッション内で一時的に変更したパラメータを元の値に戻す操作です。たとえば SET statement_timeout = '7s' で設定したタイムアウトを RESET statement_timeout で解除するといった用途が典型例です。このようなセッション管理操作は実際にはデータを変更せず、既存の :set と同様に読み取り安全なクエリとして扱われるべきでした。

その結果、読み取り専用ロールに接続しつつセッション変数を管理するというユースケースが、RESET の実行時に ActiveRecord::ReadOnlyError によってブロックされていました。

技術的な変更

変更は activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb の1行のみです。READ_QUERY 定数のキーワードリストに :reset を追加しています。

変更前:

READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
  :close, :declare, :fetch, :move, :set, :show
) # :nodoc:

変更後:

READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
  :close, :declare, :fetch, :move, :set, :show, :reset
) # :nodoc:

これにより、以下のパターンが読み取り専用コンテキストでエラーなく動作するようになります。

ActiveRecord::Base.connected_to(role: :reading, prevent_writes: true) do
  ActiveRecord::Base.with_connection do |c|
    c.execute("SET statement_timeout = '7s'")
    # some queries
    c.execute("RESET statement_timeout")
    # => no longer raises ActiveRecord::ReadOnlyError
  end
end

テストは activerecord/test/cases/adapters/postgresql/postgresql_adapter_prevent_writes_test.rb に追加されており、既存の test_doesnt_error_when_a_set_query_is_called_while_preventing_writes に倣った構造になっています。また、既存のテストに ensure ブロックで @connection.close を追加し、接続の後処理を保証する修正も含まれています。

設計判断

READ_QUERY キーワードリストへの追記という最小限の修正が採用されています。PostgreSQL アダプター固有のリストに追加されているのは、RESET が PostgreSQL 固有の構文であるためです。他のデータベースアダプターの READ_QUERY は変更されていません。

:set が既にリストに含まれていたという先例が、:reset を同じ扱いにする根拠を明確にしています。SET ... TO DEFAULT と等価なコマンドを異なる扱いにする理由がなく、セッション設定の変更はデータの永続的な書き込みとは異なるという分類がここでも一貫して適用されています。

まとめ

1行の追加により、PostgreSQL のセッション管理操作(SET と対をなす RESET)が読み取り専用コンテキストで一貫して使えるようになりました。ステートメントタイムアウトなどのセッション変数を管理しながらリードレプリカに接続するユースケースで、不要なエラーを回避できます。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
bb4c2e7f

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

ファイル名付きシンタックスハイライトやGitHubへのリンク記法が正しく使用されています。

対象読者への適合性 ✓ PASS

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

ActiveRecordの内部実装に関する内容であり、専門知識を持つエンジニアという対象読者に適した技術レベルと表現です。

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

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

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

Diff内容との照合 ✓ PASS

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

記事内のコードブロック、ファイル名、テストに関する言及は、提供されたDiff情報と完全に一致しており、正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

PR情報で使われている`READ_QUERY`や`ActiveRecord::ReadOnlyError`などの技術用語を正確に使用しています。

説明の技術的正確性 ✓ PASS

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

変更の理由(`RESET`が`READ_QUERY`に含まれていなかった)と結果(実行可能になった)の説明は、技術的に正確で論理的です。

事実の突合 ✓ PASS

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

記事内のすべての主張は、PRのDescriptionやDiff内のコードによって裏付けられており、ハルシネーション(創作)は見られません。

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

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

PR番号(#56945)やファイルパスなどの固有名詞はすべて正確に記載されています。

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

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

記事のタイトルはPRの主題「Postgresの`RESET`を読み取り専用クエリでサポートする」を的確に要約しています。

外部知識の正確性 ✓ PASS

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

PR情報に含まれないバージョン情報やリリース予定などの外部知識の追加はなく、事実に忠実です。

時間表現の正確性 ✓ PASS

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

「〜でした」「〜できるようになりました」といった時間表現が、変更の前後関係を正しく反映しています。