ArelのINSERT/UPDATE/DELETEにRETURNING句のサポートを追加

rails/rails

Arelのステートメントクラスおよびマネージャークラスに returning メソッドが追加され、INSERT/UPDATE/DELETE 文で RETURNING 句を構築できるようになりました。これにより、データの変更と取得を1つのクエリで表現する複雑なSQL構造をArelで組み立てられます。

背景

PostgreSQLとSQLiteは INSERT/UPDATE/DELETE 文に対して RETURNING 句をサポートしていますが、ArelにはこれをAST(抽象構文木)として表現する仕組みがありませんでした。これまで RETURNING を使ったクエリを構築するには、生SQLを文字列として埋め込むしかなく、Arelの型安全な構文構築の恩恵を受けられませんでした。

ActiverRecordレベルでの RETURNING サポートについては #39968#42955 で議論されてきた経緯があります。本PRはActiveRecord層への統合は行わず、Arelのクエリビルダー層のみを対象として RETURNING 句の構造を表現できるようにしています。

RETURNING の典型的なユースケースとして、CTE(Common Table Expression)との組み合わせが挙げられます。例えば、カウンターキューテーブルの全レコードを削除しながらその集計値を取得するクエリは以下のように構築できます:

counter_queue = Arel::Table.new(:counter_queue)
flush = Arel::Table.new(:flush)

delete_manager = Arel::DeleteManager.new
  .from(counter_queue)
  .returning([counter_queue[:post_id], counter_queue[:delta]])

select_manager = Arel::SelectManager.new
  .with(Arel::Nodes::TableAlias.new(Arel::Nodes::Grouping.new(delete_manager.ast), flush.name))
  .from(flush)
  .project(flush[:post_id], flush[:delta].sum)
  .group(flush[:post_id])

これにより生成されるSQLは次のようになります:

WITH "flush" AS (
  DELETE FROM "counter_queue"
  RETURNING "counter_queue"."post_id", "counter_queue"."delta"
)
SELECT "flush"."post_id", SUM("flush"."delta")
FROM "flush"
GROUP BY "flush"."post_id"

技術的な変更

変更は、ノードクラス・マネージャークラス・Visitorクラスの3層にわたり、互いに対応した形で加えられています。

ノードクラスへの returning 属性の追加として、DeleteStatementInsertStatementUpdateStatement の3クラスに attr_accessor :returning が追加され、コンストラクタで @returning = [] に初期化されます。また、initialize_copyhasheql? のそれぞれも returning を含むように更新されており、クローンや同一性比較が正確に機能します。

# 変更前
attr_accessor :relation, :wheres, :groups, :havings, :orders, :limit, :offset, :comment, :key

# 変更後
attr_accessor :relation, :wheres, :groups, :havings, :orders, :limit, :offset, :comment, :key, :returning

マネージャークラスへの returning メソッドの追加として、DeleteManagerInsertManagerUpdateManager にそれぞれ以下のようなメソッドが追加されています。メソッドチェーンを維持するため self を返す設計です:

def returning(returning)
  @ast.returning << returning
  self
end

引数には単一の値と配列の両方を渡せます。Arel.star*)や [users[:id], users[:name]] のようなカラム指定が可能で、複数回呼び出すことで RETURNING リストに項目を追加できます。

Visitorクラスの更新として、to_sql.rbvisit_Arel_Nodes_DeleteStatementvisit_Arel_Nodes_UpdateStatementvisit_Arel_Nodes_InsertStatementreturning の出力ロジックが追加されています:

if o.returning.empty?
  collector
else
  collector << " RETURNING "
  visit o.returning, collector
end

returning が空配列の場合は RETURNING 句を出力しないため、既存のクエリへの影響はありません。dot.rb の各 visit_edge にも "returning" が追加され、ASTの可視化にも対応しています。

設計判断

returning をArrayとして保持し、<< で追記する方式が採用されています。これにより returning メソッドを複数回呼び出すことで返却カラムを段階的に追加できます。一方、RETURNING 句はSQL標準ではなくPostgreSQLとSQLite固有の構文であるため、MySQLでは利用できないことに注意が必要です。本PRはArelレベルのみの変更であり、アダプターレベルでのデータベース互換性チェックは行っていません。

ActiveRecordの INSERT/UPDATE/DELETE との統合は今回の変更には含まれていません。これは意図的な判断であり、Arelを使った複雑なクエリを組み立てる用途に絞ることで、ActiveRecord層の変更を伴わずに機能を提供しています。

まとめ

本PRはArelのAST表現を拡張し、RETURNING 句をファーストクラスの構文要素として扱えるようにした変更です。ノード・マネージャー・Visitorの3層に一貫して returning のサポートを追加することで、CTEと組み合わせた高度なデータ操作クエリをArelの構文で安全に構築できる基盤が整いました。

記事メタデータ

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

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

品質レビュー結果

Review Status:
リトライ後承認
Review Count:
2回 (改善を経て承認)
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

「総論→各論→結論」の構成が明確です。リード文、背景、技術的な変更、設計判断、まとめの各要素がすべて含まれており、理想的な構成です。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きシンタックスハイライト、PR番号のリンク記法など、全てのカスタムMarkdown構文が正しく使用されています。

対象読者への適合性 ✓ PASS

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

Arel、AST、CTEといった高度なトピックを扱っており、専門知識を持つエンジニアという対象読者に完全に適合した内容と表現です。

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

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

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

Diff内容との照合 ✓ PASS

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

記事内で引用されているコードブロックは、提供されたDiff情報と完全に一致しており、ファイル名も正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

AST(抽象構文木)、CTE、Visitor、マネージャークラスなど、関連する技術用語が文脈に沿って正確に使用されています。

説明の技術的正確性 ✓ PASS

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

ノード、マネージャー、Visitorの3層にわたる変更という説明は技術的に正確で、Diff内容とも完全に整合性が取れています。

事実の突合 ✓ PASS

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

記事内のすべての主張(関連PRの言及、DBサポート状況など)は、PRのDescriptionで裏付けられており、ハルシネーションは一切見られません。

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

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

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

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

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

記事のタイトルはPRのタイトルを正確に反映しており、記事全体の内容もPRの主旨と完全に一致しています。

外部知識の正確性 ✓ PASS

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

DBのサポート状況や過去のPRに関する言及は、すべてPR Descriptionに含まれている情報であり、外部からの不正確な知識の持ち込みはありません。

時間表現の正確性 ✓ PASS

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

「これまで〜だった」といった過去の状態と、今回の変更内容が適切に表現されており、時間表現に関する誤りはありません。