The Issue
In the internal implementation of the MySQL adapter, a block is passed to execute_and_free, which works for the implementation of calling :all and :primary but for calling the replica there is no super call and thus no yield that would wrap the result by calling the build_result method.
This causes a MySQL::Result to be returned instead of an ActiveRecord::Result and this causes some breaking things. In my case the issues was triggered by using the protected_attributes_continued gem.
https://github.com/rails/rails/blob/40a0294faf6c8aefdbad16d88d3b94fa3050f6dd/activerecord/lib/active_record/connection_adapters/mysql2/database_statements.rb#L21C9-L39C12
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
if without_prepared_statement?(binds)
execute_and_free(sql, name, async: async) do |result|
if result
build_result(columns: result.fields, rows: result.to_a)
else
build_result(columns: [], rows: [])
end
end
else
exec_stmt_and_free(sql, name, binds, cache_stmt: prepare, async: async) do |_, result|
if result
build_result(columns: result.fields, rows: result.to_a)
else
build_result(columns: [], rows: [])
end
end
end
end
How to fix it
The fix is as simple as adding a yield in front of send_to_replica. I am happy to implement this as well, just let me know how to contribute as I miss a CONTRIBUTING file in this repo.
Note: this change should be made also for execute and execute_raw
def execute_and_free(sql, name = nil, async: false)
case where_to_send?(sql)
when :all
send_to_replica(sql, connection: :all, method: :execute)
super(sql, name, async:)
when :replica
yield send_to_replica(sql, connection: :replica, method: :execute)
else
Janus::Context.stick_to_primary if write_query?(sql)
Janus::Context.used_connection(:primary)
super(sql, name, async:)
end
end
The Issue
In the internal implementation of the MySQL adapter, a block is passed to
execute_and_free, which works for the implementation of calling:alland:primarybut for calling the replica there is no super call and thus noyieldthat would wrap the result by calling thebuild_resultmethod.This causes a MySQL::Result to be returned instead of an ActiveRecord::Result and this causes some breaking things. In my case the issues was triggered by using the protected_attributes_continued gem.
https://github.com/rails/rails/blob/40a0294faf6c8aefdbad16d88d3b94fa3050f6dd/activerecord/lib/active_record/connection_adapters/mysql2/database_statements.rb#L21C9-L39C12
How to fix it
The fix is as simple as adding a
yieldin front ofsend_to_replica. I am happy to implement this as well, just let me know how to contribute as I miss a CONTRIBUTING file in this repo.Note: this change should be made also for
executeandexecute_raw