diff options
Diffstat (limited to 'spec/lib/gitlab/database/migration_helpers_spec.rb')
-rw-r--r-- | spec/lib/gitlab/database/migration_helpers_spec.rb | 288 |
1 files changed, 49 insertions, 239 deletions
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index bed444ee7c7..48e1c97e97f 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe Gitlab::Database::MigrationHelpers do +RSpec.describe Gitlab::Database::MigrationHelpers do let(:model) do ActiveRecord::Migration.new.extend(described_class) end @@ -178,6 +178,19 @@ describe Gitlab::Database::MigrationHelpers do model.remove_concurrent_index_by_name(:users, "index_x_by_y") end + + it 'removes the index with keyword arguments' do + expect(model).to receive(:remove_index) + .with(:users, { algorithm: :concurrently, name: "index_x_by_y" }) + + model.remove_concurrent_index_by_name(:users, name: "index_x_by_y") + end + + it 'raises an error if the index is blank' do + expect do + model.remove_concurrent_index_by_name(:users, wrong_key: "index_x_by_y") + end.to raise_error 'remove_concurrent_index_by_name must get an index name as the second argument' + end end end end @@ -690,12 +703,28 @@ describe Gitlab::Database::MigrationHelpers do model.rename_column_concurrently(:users, :old, :new) end + context 'with existing records and type casting' do + let(:trigger_name) { model.rename_trigger_name(:users, :id, :new) } + let(:user) { create(:user) } + + it 'copies the value to the new column using the type_cast_function', :aggregate_failures do + expect(model).to receive(:copy_indexes).with(:users, :id, :new) + expect(model).to receive(:add_not_null_constraint).with(:users, :new) + expect(model).to receive(:execute).with("UPDATE \"users\" SET \"new\" = cast_to_jsonb_with_default(\"users\".\"id\") WHERE \"users\".\"id\" >= #{user.id}") + expect(model).to receive(:execute).with("DROP TRIGGER IF EXISTS #{trigger_name}\nON \"users\"\n") + expect(model).to receive(:execute).with("CREATE TRIGGER #{trigger_name}\nBEFORE INSERT OR UPDATE\nON \"users\"\nFOR EACH ROW\nEXECUTE PROCEDURE #{trigger_name}()\n") + expect(model).to receive(:execute).with("CREATE OR REPLACE FUNCTION #{trigger_name}()\nRETURNS trigger AS\n$BODY$\nBEGIN\n NEW.\"new\" := NEW.\"id\";\n RETURN NEW;\nEND;\n$BODY$\nLANGUAGE 'plpgsql'\nVOLATILE\n") + + model.rename_column_concurrently(:users, :id, :new, type_cast_function: 'cast_to_jsonb_with_default') + end + end + it 'passes the batch_column_name' do expect(model).to receive(:column_exists?).with(:users, :other_batch_column).and_return(true) expect(model).to receive(:check_trigger_permissions!).and_return(true) expect(model).to receive(:create_column_from).with( - :users, :old, :new, type: nil, batch_column_name: :other_batch_column + :users, :old, :new, type: nil, batch_column_name: :other_batch_column, type_cast_function: nil ).and_return(true) expect(model).to receive(:install_rename_triggers).and_return(true) @@ -703,6 +732,14 @@ describe Gitlab::Database::MigrationHelpers do model.rename_column_concurrently(:users, :old, :new, batch_column_name: :other_batch_column) end + it 'passes the type_cast_function' do + expect(model).to receive(:create_column_from).with( + :users, :old, :new, type: nil, batch_column_name: :id, type_cast_function: 'JSON' + ).and_return(true) + + model.rename_column_concurrently(:users, :old, :new, type_cast_function: 'JSON') + end + it 'raises an error with invalid batch_column_name' do expect do model.rename_column_concurrently(:users, :old, :new, batch_column_name: :invalid) @@ -866,10 +903,19 @@ describe Gitlab::Database::MigrationHelpers do describe '#change_column_type_concurrently' do it 'changes the column type' do expect(model).to receive(:rename_column_concurrently) - .with('users', 'username', 'username_for_type_change', type: :text) + .with('users', 'username', 'username_for_type_change', type: :text, type_cast_function: nil) model.change_column_type_concurrently('users', 'username', :text) end + + context 'with type cast' do + it 'changes the column type with casting the value to the new type' do + expect(model).to receive(:rename_column_concurrently) + .with('users', 'username', 'username_for_type_change', type: :text, type_cast_function: 'JSON') + + model.change_column_type_concurrently('users', 'username', :text, type_cast_function: 'JSON') + end + end end describe '#cleanup_concurrent_column_type_change' do @@ -1215,166 +1261,6 @@ describe Gitlab::Database::MigrationHelpers do end end - describe '#bulk_queue_background_migration_jobs_by_range' do - context 'when the model has an ID column' do - let!(:id1) { create(:user).id } - let!(:id2) { create(:user).id } - let!(:id3) { create(:user).id } - - before do - User.class_eval do - include EachBatch - end - end - - context 'with enough rows to bulk queue jobs more than once' do - before do - stub_const('Gitlab::Database::MigrationHelpers::BACKGROUND_MIGRATION_JOB_BUFFER_SIZE', 1) - end - - it 'queues jobs correctly' do - Sidekiq::Testing.fake! do - model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2) - - expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]]) - expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]]) - end - end - - it 'queues jobs in groups of buffer size 1' do - expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([['FooJob', [id1, id2]]]) - expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([['FooJob', [id3, id3]]]) - - model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2) - end - end - - context 'with not enough rows to bulk queue jobs more than once' do - it 'queues jobs correctly' do - Sidekiq::Testing.fake! do - model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2) - - expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]]) - expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]]) - end - end - - it 'queues jobs in bulk all at once (big buffer size)' do - expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([['FooJob', [id1, id2]], - ['FooJob', [id3, id3]]]) - - model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob', batch_size: 2) - end - end - - context 'without specifying batch_size' do - it 'queues jobs correctly' do - Sidekiq::Testing.fake! do - model.bulk_queue_background_migration_jobs_by_range(User, 'FooJob') - - expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3]]) - end - end - end - end - - context "when the model doesn't have an ID column" do - it 'raises error (for now)' do - expect do - model.bulk_queue_background_migration_jobs_by_range(ProjectAuthorization, 'FooJob') - end.to raise_error(StandardError, /does not have an ID/) - end - end - end - - describe '#queue_background_migration_jobs_by_range_at_intervals' do - context 'when the model has an ID column' do - let!(:id1) { create(:user).id } - let!(:id2) { create(:user).id } - let!(:id3) { create(:user).id } - - around do |example| - Timecop.freeze { example.run } - end - - before do - User.class_eval do - include EachBatch - end - end - - it 'returns the final expected delay' do - Sidekiq::Testing.fake! do - final_delay = model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, batch_size: 2) - - expect(final_delay.to_f).to eq(20.minutes.to_f) - end - end - - it 'returns zero when nothing gets queued' do - Sidekiq::Testing.fake! do - final_delay = model.queue_background_migration_jobs_by_range_at_intervals(User.none, 'FooJob', 10.minutes) - - expect(final_delay).to eq(0) - end - end - - context 'with batch_size option' do - it 'queues jobs correctly' do - Sidekiq::Testing.fake! do - model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, batch_size: 2) - - expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id2]]) - expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.minutes.from_now.to_f) - expect(BackgroundMigrationWorker.jobs[1]['args']).to eq(['FooJob', [id3, id3]]) - expect(BackgroundMigrationWorker.jobs[1]['at']).to eq(20.minutes.from_now.to_f) - end - end - end - - context 'without batch_size option' do - it 'queues jobs correctly' do - Sidekiq::Testing.fake! do - model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes) - - expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3]]) - expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.minutes.from_now.to_f) - end - end - end - - context 'with other_job_arguments option' do - it 'queues jobs correctly' do - Sidekiq::Testing.fake! do - model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, other_job_arguments: [1, 2]) - - expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3, 1, 2]]) - expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.minutes.from_now.to_f) - end - end - end - - context 'with initial_delay option' do - it 'queues jobs correctly' do - Sidekiq::Testing.fake! do - model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, other_job_arguments: [1, 2], initial_delay: 10.minutes) - - expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3, 1, 2]]) - expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(20.minutes.from_now.to_f) - end - end - end - end - - context "when the model doesn't have an ID column" do - it 'raises error (for now)' do - expect do - model.queue_background_migration_jobs_by_range_at_intervals(ProjectAuthorization, 'FooJob', 10.seconds) - end.to raise_error(StandardError, /does not have an ID/) - end - end - end - describe '#change_column_type_using_background_migration' do let!(:issue) { create(:issue, :closed, closed_at: Time.zone.now) } @@ -1485,26 +1371,6 @@ describe Gitlab::Database::MigrationHelpers do end end - describe '#perform_background_migration_inline?' do - it 'returns true in a test environment' do - stub_rails_env('test') - - expect(model.perform_background_migration_inline?).to eq(true) - end - - it 'returns true in a development environment' do - stub_rails_env('development') - - expect(model.perform_background_migration_inline?).to eq(true) - end - - it 'returns false in a production environment' do - stub_rails_env('production') - - expect(model.perform_background_migration_inline?).to eq(false) - end - end - describe '#index_exists_by_name?' do it 'returns true if an index exists' do ActiveRecord::Base.connection.execute( @@ -1973,62 +1839,6 @@ describe Gitlab::Database::MigrationHelpers do end end - describe '#migrate_async' do - it 'calls BackgroundMigrationWorker.perform_async' do - expect(BackgroundMigrationWorker).to receive(:perform_async).with("Class", "hello", "world") - - model.migrate_async("Class", "hello", "world") - end - - it 'pushes a context with the current class name as caller_id' do - expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s) - - model.migrate_async('Class', 'hello', 'world') - end - end - - describe '#migrate_in' do - it 'calls BackgroundMigrationWorker.perform_in' do - expect(BackgroundMigrationWorker).to receive(:perform_in).with(10.minutes, 'Class', 'Hello', 'World') - - model.migrate_in(10.minutes, 'Class', 'Hello', 'World') - end - - it 'pushes a context with the current class name as caller_id' do - expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s) - - model.migrate_in(10.minutes, 'Class', 'Hello', 'World') - end - end - - describe '#bulk_migrate_async' do - it 'calls BackgroundMigrationWorker.bulk_perform_async' do - expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([%w(Class hello world)]) - - model.bulk_migrate_async([%w(Class hello world)]) - end - - it 'pushes a context with the current class name as caller_id' do - expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s) - - model.bulk_migrate_async([%w(Class hello world)]) - end - end - - describe '#bulk_migrate_in' do - it 'calls BackgroundMigrationWorker.bulk_perform_in_' do - expect(BackgroundMigrationWorker).to receive(:bulk_perform_in).with(10.minutes, [%w(Class hello world)]) - - model.bulk_migrate_in(10.minutes, [%w(Class hello world)]) - end - - it 'pushes a context with the current class name as caller_id' do - expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s) - - model.bulk_migrate_in(10.minutes, [%w(Class hello world)]) - end - end - describe '#check_constraint_name' do it 'returns a valid constraint name' do name = model.check_constraint_name(:this_is_a_very_long_table_name, |