Skip to content
代码片段 群组 项目
未验证 提交 f08e8c4b 编辑于 作者: Igor Drozdov's avatar Igor Drozdov 提交者: GitLab
浏览文件

Add ActiveRecord::Enum patch

It removes newly introduced check that doesn't allow defining
an enum without backed column.

A migration job fails because it:

- Performs database migration on a particular version
- The version does not contain email_confirmation_setting column
- Performs another migration that initializes Rails env
- Initializers call Gitlab::CurrentSetting
- ApplicationSetting tries to define email_confirmation_setting enum
- Fails because the enum is not backed by a column
上级 6369cbde
No related branches found
No related tags found
无相关合并请求
# frozen_string_literal: true
# Override ActiveRecord::Enum.load_schema! to remove the check that verifies that
# defined enum attributes are backed by a database column.
#
# The check has been introduced in Rails 7.1: https://github.com/rails/rails/pull/45734
#
# However, there are valid use cases when the enum definition should not raise an error
# when the column does not exist in the column list:
#
# - Perform database migration on a particular version, the one that doesn't contain the column
# - Try performing another operation that loads Rails environment (for example, another migration):
# - Rails environment is being initialized and configured
# - Gitlab::CurrentSetting is being called to customize the configuration
# - ApplicationSetting model tries to define a enum attribute that is not backed by a column on that patricular
# migration version
# - An error is raised
module ActiveRecordAttributesPatch
def attribute(name, *args, **options)
return super unless defined_enums.key?(name)
return unless block_given?
super do |subtype|
subtype = subtype.subtype if ActiveRecord::Enum::EnumType === subtype
ActiveRecord::Enum::EnumType.new(name, defined_enums[name], subtype)
end
end
end
ActiveRecord::Attributes::ClassMethods.prepend(ActiveRecordAttributesPatch)
......@@ -7,6 +7,19 @@ module PgBackendPid
module MigratorPgBackendPid
extend ::Gitlab::Utils::Override
override :with_advisory_lock
def with_advisory_lock
Gitlab::Database::Migrations::PgBackendPid.say(connection)
super
ensure
Gitlab::Database::Migrations::PgBackendPid.say(connection)
end
end
module OldMigratorPgBackendPid
extend ::Gitlab::Utils::Override
override :with_advisory_lock_connection
def with_advisory_lock_connection
super do |conn|
......@@ -20,7 +33,11 @@ def with_advisory_lock_connection
end
def self.patch!
ActiveRecord::Migrator.prepend(MigratorPgBackendPid)
if ::Gitlab.next_rails?
ActiveRecord::Migrator.prepend(MigratorPgBackendPid)
else
ActiveRecord::Migrator.prepend(OldMigratorPgBackendPid)
end
end
def self.say(conn)
......
# frozen_string_literal: true
# Caches loading of additional types from the DB
# https://github.com/rails/rails/blob/v6.0.3.2/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L521-L589
# https://github.com/rails/rails/blob/v7.1.3.4/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L997
# rubocop:disable Gitlab/ModuleWithInstanceVariables
......@@ -32,6 +32,12 @@ def initialize_type_map(map = type_map)
end
def reload_type_map
if ::Gitlab.next_rails? && @type_map.blank?
@type_map = ActiveRecord::Type::HashLookupTypeMap.new
return initialize_type_map
end
TYPE_MAP_CACHE_MONITOR.synchronize do
self.class.type_map_cache[@connection_parameters.hash] = nil
end
......
# frozen_string_literal: true
require 'spec_helper'
# Adds a missing test to provide full coverage for the patch
RSpec.describe 'ActiveRecord::Enum Patch', feature_category: :database do
context 'when enum is not backed by a database column' do
let(:klass) do
Class.new(ActiveRecord::Base) do
self.table_name = :projects
enum values_without_backed_column: [:one, :two]
end
end
it 'does not raise an error' do
expect do
klass.new.values_without_backed_column
end.not_to raise_error
end
end
end
......@@ -4,6 +4,47 @@
RSpec.describe Gitlab::Database::Migrations::PgBackendPid, feature_category: :database do
describe Gitlab::Database::Migrations::PgBackendPid::MigratorPgBackendPid do
let(:block) { -> {} }
let(:klass) do
Class.new do
def initialize(block)
@block = block
end
def with_advisory_lock
@block.call
end
def connection
ApplicationRecord.connection
end
end
end
let(:patched_instance) { klass.prepend(described_class).new(block) }
it 'wraps the method execution with calls to .say' do
expect(Gitlab::Database::Migrations::PgBackendPid).to receive(:say).twice
expect(block).to receive(:call)
patched_instance.with_advisory_lock
end
context 'when an error is raised' do
let(:block) { -> { raise ActiveRecord::ConcurrentMigrationError, 'test' } }
it 'wraps the method execution with calls to .say' do
expect(Gitlab::Database::Migrations::PgBackendPid).to receive(:say).twice
expect do
patched_instance.with_advisory_lock
end.to raise_error ActiveRecord::ConcurrentMigrationError
end
end
end
describe Gitlab::Database::Migrations::PgBackendPid::OldMigratorPgBackendPid do
let(:klass) do
Class.new do
def with_advisory_lock_connection
......@@ -33,16 +74,20 @@ def with_advisory_lock_connection
describe '.patch!' do
it 'patches ActiveRecord::Migrator' do
expect(ActiveRecord::Migrator).to receive(:prepend).with(described_class::MigratorPgBackendPid)
if ::Gitlab.next_rails?
expect(ActiveRecord::Migrator).to receive(:prepend).with(described_class::MigratorPgBackendPid)
else
expect(ActiveRecord::Migrator).to receive(:prepend).with(described_class::OldMigratorPgBackendPid)
end
described_class.patch!
end
end
describe '.say' do
it 'outputs the connection information' do
conn = ActiveRecord::Base.connection
let(:conn) { ActiveRecord::Base.connection }
it 'outputs the connection information' do
expect(conn).to receive(:object_id).and_return(9876)
expect(conn).to receive(:select_value).with('SELECT pg_backend_pid()').and_return(12345)
expect(Gitlab::Database).to receive(:db_config_name).with(conn).and_return('main')
......@@ -53,8 +98,6 @@ def with_advisory_lock_connection
end
it 'outputs nothing if ActiveRecord::Migration.verbose is false' do
conn = ActiveRecord::Base.connection
allow(ActiveRecord::Migration).to receive(:verbose).and_return(false)
expect { described_class.say(conn) }.not_to output.to_stdout
......
......@@ -50,6 +50,8 @@
# Based on https://github.com/rails/rails/blob/v6.1.3.2/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L36-L41
def initialize_connection(config = db_config)
return adapter_class.new(config).connect! if ::Gitlab.next_rails?
conn_params = config.symbolize_keys.compact
conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
......
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册