discourse/lib/mini_sql_multisite_connection.rb
David Taylor 3f6c2f82d0
DEV: Use MiniSql ActiveRecordPostgres adapter (#15767)
This adapter ensures that MiniSql locks the ActiveRecord mutex before using the raw PG connection. This ensures that multiple threads will not attempt to use the same connection simultaneously.

This commit also removes the schema_cache_concurrency freedom-patch, which is no longer required now that cross-thread connection use is controlled by the mutex.

For the original root cause of this issue, see https://github.com/rails/rails/pull/38577
2022-02-03 10:00:28 +00:00

112 lines
2.8 KiB
Ruby

# frozen_string_literal: true
class MiniSqlMultisiteConnection < MiniSql::ActiveRecordPostgres::Connection
class CustomBuilder < MiniSql::Builder
def initialize(connection, sql)
super
end
def secure_category(secure_category_ids, category_alias = 'c')
if secure_category_ids.present?
where("NOT COALESCE(#{category_alias}.read_restricted, false) OR #{category_alias}.id in (:secure_category_ids)", secure_category_ids: secure_category_ids)
else
where("NOT COALESCE(#{category_alias}.read_restricted, false)")
end
self
end
end
class ParamEncoder
def encode(*sql_array)
# use active record to avoid any discrepencies
ActiveRecord::Base.public_send(:sanitize_sql_array, sql_array)
end
end
class AfterCommitWrapper
def initialize(&blk)
raise ArgumentError, "tried to create a Proc without a block in AfterCommitWrapper" if !blk
@callback = blk
end
def committed!(*)
if DB.transaction_open?
# Nested transaction. Pass the callback to the parent
ActiveRecord::Base.connection.add_transaction_record(self)
else
@callback.call
end
end
def before_committed!(*); end
def rolledback!(*); end
def trigger_transactional_callbacks?
true
end
end
def transaction_open?
ActiveRecord::Base.connection.transaction_open?
end
if Rails.env.test?
def test_transaction=(transaction)
@test_transaction = transaction
end
def transaction_open?
ActiveRecord::Base.connection.current_transaction != @test_transaction
end
end
# Allows running arbitrary code after the current transaction has been committed.
# Works with nested ActiveRecord transaction blocks. Useful for scheduling sidekiq jobs.
# If not currently in a transaction, will execute immediately
def after_commit(&blk)
return blk.call if !transaction_open?
ActiveRecord::Base.connection.add_transaction_record(
AfterCommitWrapper.new(&blk)
)
end
def self.instance
new(nil, param_encoder: ParamEncoder.new)
end
# we need a tiny adapter here so we always run against the
# correct multisite connection
def active_record_connection
ActiveRecord::Base.connection
end
# make for a multisite friendly prepared statement cache
def prepared(condition = true)
if condition
conn = raw_connection.instance_variable_get(:@mini_sql_prepared_connection)
if !conn
conn = MiniSql::Postgres::PreparedConnection.new(self)
raw_connection.instance_variable_set(:@mini_sql_prepared_connection, conn)
end
conn
else
self
end
end
def build(sql)
CustomBuilder.new(self, sql)
end
def sql_fragment(query, *args)
if args.length > 0
param_encoder.encode(query, *args)
else
query
end
end
end