Commit 4afdfe76 authored by Yorick Peterse's avatar Yorick Peterse

Merge branch 'blackst0ne-rails5-fix-optimistic-lock-values' into 'master'

[Rails5] Fix optimistic lock value

Closes #47961

See merge request gitlab-org/gitlab-ce!19878
parents 66bff49b 145832d6
---
title: "[Rails5] Fix optimistic lock value"
merge_request: 19878
author: "@blackst0ne"
type: fixed
# rubocop:disable Lint/RescueException # rubocop:disable Lint/RescueException
# Remove this entire initializer when we are at rails 5.0. # Remove this monkey-patch when all lock_version values are converted from NULLs to zeros.
# This file fixes the bug (see below) which has been fixed in the upstream. # See https://gitlab.com/gitlab-org/gitlab-ce/issues/25228
unless Gitlab.rails5? module ActiveRecord
# This patch fixes https://github.com/rails/rails/issues/26024 module Locking
# TODO: Remove it when it's no longer necessary module Optimistic
# We overwrite this method because we don't want to have default value
module ActiveRecord # for newly created records
module Locking def _create_record(attribute_names = self.attribute_names, *) # :nodoc:
module Optimistic super
# We overwrite this method because we don't want to have default value end
# for newly created records
def _create_record(attribute_names = self.attribute_names, *) # :nodoc:
super
end
def _update_record(attribute_names = self.attribute_names) #:nodoc: def _update_record(attribute_names = self.attribute_names) #:nodoc:
return super unless locking_enabled? return super unless locking_enabled?
return 0 if attribute_names.empty? return 0 if attribute_names.empty?
lock_col = self.class.locking_column lock_col = self.class.locking_column
previous_lock_value = send(lock_col).to_i # rubocop:disable GitlabSecurity/PublicSend previous_lock_value = send(lock_col).to_i # rubocop:disable GitlabSecurity/PublicSend
# This line is added as a patch # This line is added as a patch
previous_lock_value = nil if previous_lock_value == '0' || previous_lock_value == 0 previous_lock_value = nil if previous_lock_value == '0' || previous_lock_value == 0
increment_lock increment_lock
attribute_names += [lock_col] attribute_names += [lock_col]
attribute_names.uniq! attribute_names.uniq!
begin begin
relation = self.class.unscoped relation = self.class.unscoped
affected_rows = relation.where( affected_rows = relation.where(
self.class.primary_key => id, self.class.primary_key => id,
lock_col => previous_lock_value lock_col => previous_lock_value
).update_all( ).update_all(
attributes_for_update(attribute_names).map do |name| attributes_for_update(attribute_names).map do |name|
[name, _read_attribute(name)] [name, _read_attribute(name)]
end.to_h end.to_h
) )
unless affected_rows == 1 unless affected_rows == 1
raise ActiveRecord::StaleObjectError.new(self, "update") raise ActiveRecord::StaleObjectError.new(self, "update")
end end
affected_rows affected_rows
# If something went wrong, revert the version. # If something went wrong, revert the version.
rescue Exception rescue Exception
send(lock_col + '=', previous_lock_value) # rubocop:disable GitlabSecurity/PublicSend send(lock_col + '=', previous_lock_value) # rubocop:disable GitlabSecurity/PublicSend
raise raise
end
end end
end
# This is patched because we need it to query `lock_version IS NULL` # This is patched because we need it to query `lock_version IS NULL`
# rather than `lock_version = 0` whenever lock_version is NULL. # rather than `lock_version = 0` whenever lock_version is NULL.
def relation_for_destroy def relation_for_destroy
return super unless locking_enabled? return super unless locking_enabled?
column_name = self.class.locking_column column_name = self.class.locking_column
super.where(self.class.arel_table[column_name].eq(self[column_name])) super.where(self.class.arel_table[column_name].eq(self[column_name]))
end
end end
end
# This is patched because we want `lock_version` default to `NULL`
# rather than `0`
if Gitlab.rails5?
class LockingType
def deserialize(value)
super
end
# This is patched because we want `lock_version` default to `NULL` def serialize(value)
# rather than `0` super
end
end
else
class LockingType < SimpleDelegator class LockingType < SimpleDelegator
def type_cast_from_database(value) def type_cast_from_database(value)
super super
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment