Skip to content

Missing Yield breaks compatibility with Rails 7.1+ #109

@JustusEilersSC

Description

@JustusEilersSC

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions