2019-05-02 18:17:27 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2013-02-25 11:42:20 -05:00
|
|
|
class ActiveRecord::Base
|
2013-02-05 14:16:51 -05:00
|
|
|
|
2018-08-20 20:59:18 -04:00
|
|
|
# Handle PG::UniqueViolation as well due to concurrency
|
|
|
|
# find_or_create does find_by(hash) || create!(hash)
|
|
|
|
# in some cases find will not find and multiple creates will be called
|
2018-08-20 21:08:58 -04:00
|
|
|
#
|
|
|
|
# note: Rails 6 has: https://github.com/rails/rails/blob/c83e30da27eafde79164ecb376e8a28ccc8d841f/activerecord/lib/active_record/relation.rb#L171-L201
|
|
|
|
# This means that in Rails 6 we would either use:
|
|
|
|
#
|
|
|
|
# create_or_find_by! (if we are generally creating)
|
|
|
|
#
|
|
|
|
# OR
|
|
|
|
#
|
|
|
|
# find_by(hash) || create_or_find_by(hash) (if we are generally finding)
|
2018-08-20 20:59:18 -04:00
|
|
|
def self.find_or_create_by_safe!(hash)
|
|
|
|
begin
|
|
|
|
find_or_create_by!(hash)
|
2018-09-11 21:03:12 -04:00
|
|
|
rescue PG::UniqueViolation, ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid
|
2018-08-20 20:59:18 -04:00
|
|
|
# try again cause another transaction could have passed by now
|
|
|
|
find_or_create_by!(hash)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-02-05 14:16:51 -05:00
|
|
|
# Execute SQL manually
|
|
|
|
def self.exec_sql(*args)
|
2018-06-20 03:50:11 -04:00
|
|
|
|
2021-11-12 09:52:59 -05:00
|
|
|
Discourse.deprecate("exec_sql should not be used anymore, please use DB.exec or DB.query instead!", drop_from: '2.9.0')
|
2018-06-20 03:50:11 -04:00
|
|
|
|
2013-02-05 14:16:51 -05:00
|
|
|
conn = ActiveRecord::Base.connection
|
2019-05-06 21:27:05 -04:00
|
|
|
sql = ActiveRecord::Base.public_send(:sanitize_sql_array, args)
|
2018-05-03 01:50:30 -04:00
|
|
|
conn.raw_connection.async_exec(sql)
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def exec_sql(*args)
|
|
|
|
ActiveRecord::Base.exec_sql(*args)
|
|
|
|
end
|
|
|
|
|
2013-02-11 15:47:28 -05:00
|
|
|
# Executes the given block +retries+ times (or forever, if explicitly given nil),
|
|
|
|
# catching and retrying SQL Deadlock errors.
|
|
|
|
#
|
|
|
|
# Thanks to: http://stackoverflow.com/a/7427186/165668
|
|
|
|
#
|
|
|
|
def self.retry_lock_error(retries = 5, &block)
|
|
|
|
begin
|
|
|
|
yield
|
|
|
|
rescue ActiveRecord::StatementInvalid => e
|
2013-03-04 19:42:44 -05:00
|
|
|
if e.message =~ /deadlock detected/ && (retries.nil? || retries > 0)
|
2013-02-11 15:47:28 -05:00
|
|
|
retry_lock_error(retries ? retries - 1 : nil, &block)
|
|
|
|
else
|
|
|
|
raise e
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-02-25 11:42:20 -05:00
|
|
|
end
|