diff --git a/activerecord/lib/active_record/connection_adapters/mysql2/database_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql2/database_statements.rb index d7204e449954a..8f35a4f75cc8f 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2/database_statements.rb @@ -71,6 +71,12 @@ def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notif ActiveSupport::Dependencies.interlock.permit_concurrent_loads do result = stmt.execute(*type_casted_binds) @affected_rows_before_warnings = stmt.affected_rows + + # Ref: https://github.com/brianmario/mysql2/pull/1383 + # by eagerly closing uncached prepared statements, we also reduce the chances of + # that bug happening. It can still happen if `#execute` is used as we have no callback + # to eagerly close the statement. + result.instance_variable_set(:@_ar_stmt_to_close, stmt) if result && !prepare result end rescue ::Mysql2::Error @@ -102,17 +108,34 @@ def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notif end end - def cast_result(result) - if result.nil? || result.fields.empty? + def cast_result(raw_result) + return ActiveRecord::Result.empty if raw_result.nil? + + fields = raw_result.fields + + result = if fields.empty? ActiveRecord::Result.empty else - ActiveRecord::Result.new(result.fields, result.to_a) + ActiveRecord::Result.new(fields, raw_result.to_a) end + + free_raw_result(raw_result) + + result end - def affected_rows(result) + def affected_rows(raw_result) + free_raw_result(raw_result) if raw_result + @affected_rows_before_warnings end + + def free_raw_result(raw_result) + raw_result.free + if stmt = raw_result.instance_variable_get(:@_ar_stmt_to_close) + stmt.close + end + end end end end