Mysql2Adapter が affected_rows を正しく取得できるようにした変更

rails/rails

Mysql2Adapter が affected_rows の取得に失敗しやすいケースを解消し、結果が nil のときでも行数情報を保持できるようにしました。これにより、テストの不安定さが改善され、SQL 実行結果の型が統一されます。

背景

Mysql2Adapter の perform_queryaffected_rows を取得できないことが散発的に発生していましたmysql2 0.5.6 以降では、#affected_rowsMysql2::Error を投げるケースがあり、特に同一接続で準備済みステートメントが GC された場合に顕在化します。この問題は AR テストスイートで再現し、削除スレッドの返り値が期待とずれる原因となっていました。

技術的な変更

perform_query のローカル変数化と結果ラップ

activerecord/lib/active_record/connection_adapters/mysql2/database_statements.rbperform_queryインスタンス変数 @affected_rows_before_warnings を廃止し、ローカル変数 affected_rows に置き換えました。これによりスレッド間での状態共有がなくなり、安全に行数を保持できます。

@@
-            @affected_rows_before_warnings = result&.size || raw_connection.affected_rows
+            affected_rows = result&.size || raw_connection.affected_rows
@@
-                @affected_rows_before_warnings = stmt.affected_rows
+                affected_rows = stmt.affected_rows
@@
-            intent.notification_payload[:affected_rows] = @affected_rows_before_warnings
+            intent.notification_payload[:affected_rows] = affected_rows

さらに、resultnil の場合は ActiveRecord::Result.empty(affected_rows: affected_rows) を返す ことで、行数情報だけを持つ結果オブジェクトを生成します。result.fields.empty? の際も同様にラップし、未使用の生データは free_raw_result 後に破棄しています。

@@
-            if result.nil?
-              result = ActiveRecord::Result.empty(affected_rows: affected_rows)
-            elsif result.fields.empty?
-              free_raw_result(result)
-              result = ActiveRecord::Result.empty(affected_rows: affected_rows)
-            end
+            if result.nil?
+              result = ActiveRecord::Result.empty(affected_rows: affected_rows)
+            elsif result.fields.empty?
+              free_raw_result(result)
+              result = ActiveRecord::Result.empty(affected_rows: affected_rows)
+            end

mysql2_adapter.rb のクリーンアップ

activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb から不要になった @affected_rows_before_warnings の初期化行を削除し、コードベースをシンプルにしました。

@@
-        @affected_rows_before_warnings = nil

テストの拡張

activerecord/test/cases/adapters/abstract_mysql_adapter/count_deleted_rows_with_lock_test.rb期待行数を 13 件に拡張し、複数スレッドでの削除と作成が正しく同期することを検証しています。これにより、affected_rows が正しく伝播するシナリオがテストで保証されます。

@@
-      expected_count = 13
+      expected_count = 13
@@
-      Bulb.create!(name: "Jimmy", color: "blue")
+      expected_count.times do |i|
+        Bulb.create!(name: "Jimmy #{i}", color: "blue")
+      end
@@
-      assert_equal 1, delete_thread.value
+      assert_equal expected_count, delete_thread.value

設計判断

ActiveRecord::Resultaffected_rows のみを保持するケースでも再利用する方針 が採用されました。元々 SQLite3Adapter が生結果として Result を利用していることから、Mysql2Adapter でも新規クラスを導入せずに同一型で統一することで API の一貫性とコードの重複回避が実現されています。また、インスタンス変数からローカル変数への移行は スレッド安全性の向上 を意図した設計変更です。

まとめ

この PR は、Mysql2Adapter が affected_rows を正しく取得できない曖昧さを解消し、結果が nil の場合でも行数情報を ActiveRecord::Result にラップして提供します。インスタンス変数の除去とローカル変数への置換によりスレッド安全性が向上し、テストも期待通りの行数を検証できるようになりました。結果として、AR の MySQL 対応が堅牢になり、将来的な回帰リスクが低減します。

記事メタデータ

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

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文、背景、技術的変更、設計判断(任意)、まとめの全てが揃っており、総論→各論→結論の流れが明確です。

カスタムMarkdown構文 ⚠ WARNING

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

コードブロックは正しい ````ruby:filepath`` や ````diff:filepath`` の形式で記述されていますが、PRリンクが `[PR #57567](URL)` となっており、仕様の `[ #57567 ](URL)` 形式と完全に一致していません。

対象読者への適合性 ✓ PASS

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

対象はRailsエンジニアであり、専門用語中心の説明で初心者向けの過度な解説はありません。

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

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

各セクションが総論・各論・結論の構成になり、段落はトピックセンテンスで始まり、一段落一トピック・6文以内に収まっています。空行で区切りも適切です。

Diff内容との照合 ⚠ WARNING

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

記事に掲載されたコードは大部分がDiffと一致していますが、`affected_rows = nil` の追加行がDiffに記載されているにもかかわらず記事から省略されています。重要な初期化コードの欠落は情報の完全性に影響します。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

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

説明の技術的正確性 ✓ PASS

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

変更内容の説明はDiffとPRの記述に基づいており、技術的に正確です。

事実の突合 ✓ PASS

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

記事の全ての主張はPRタイトル・説明・Diffで裏付けられており、根拠のない推測や外部情報は含まれていません。

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

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

テストで期待される行数 13 件やPR番号 #57567 など、数値・固有名詞は正確です。

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

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

記事タイトルはPRの主旨(affected_rows の取得改善)を正しく反映しています。

外部知識の正確性 ✓ PASS

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

バージョンサポートやリリース日程等、PRに記載のない外部知識は含まれていません。

時間表現の正確性 ✓ PASS

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

時間表現の歪曲はなく、PRの記述と整合しています。