diff options
Diffstat (limited to 'spec/lib/gitlab/database/migration_helpers_spec.rb')
-rw-r--r-- | spec/lib/gitlab/database/migration_helpers_spec.rb | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index a763dc08b73..6b709cba5b3 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe Gitlab::Database::MigrationHelpers do + include Database::TableSchemaHelpers + let(:model) do ActiveRecord::Migration.new.extend(described_class) end @@ -96,6 +98,131 @@ RSpec.describe Gitlab::Database::MigrationHelpers do end end + describe '#create_table_with_constraints' do + let(:table_name) { :test_table } + let(:column_attributes) do + [ + { name: 'id', sql_type: 'bigint', null: false, default: nil }, + { name: 'created_at', sql_type: 'timestamp with time zone', null: false, default: nil }, + { name: 'updated_at', sql_type: 'timestamp with time zone', null: false, default: nil }, + { name: 'some_id', sql_type: 'integer', null: false, default: nil }, + { name: 'active', sql_type: 'boolean', null: false, default: 'true' }, + { name: 'name', sql_type: 'text', null: true, default: nil } + ] + end + + before do + allow(model).to receive(:transaction_open?).and_return(true) + end + + context 'when no check constraints are defined' do + it 'creates the table as expected' do + model.create_table_with_constraints table_name do |t| + t.timestamps_with_timezone + t.integer :some_id, null: false + t.boolean :active, null: false, default: true + t.text :name + end + + expect_table_columns_to_match(column_attributes, table_name) + end + end + + context 'when check constraints are defined' do + context 'when the text_limit is explicity named' do + it 'creates the table as expected' do + model.create_table_with_constraints table_name do |t| + t.timestamps_with_timezone + t.integer :some_id, null: false + t.boolean :active, null: false, default: true + t.text :name + + t.text_limit :name, 255, name: 'check_name_length' + t.check_constraint :some_id_is_positive, 'some_id > 0' + end + + expect_table_columns_to_match(column_attributes, table_name) + + expect_check_constraint(table_name, 'check_name_length', 'char_length(name) <= 255') + expect_check_constraint(table_name, 'some_id_is_positive', 'some_id > 0') + end + end + + context 'when the text_limit is not named' do + it 'creates the table as expected, naming the text limit' do + model.create_table_with_constraints table_name do |t| + t.timestamps_with_timezone + t.integer :some_id, null: false + t.boolean :active, null: false, default: true + t.text :name + + t.text_limit :name, 255 + t.check_constraint :some_id_is_positive, 'some_id > 0' + end + + expect_table_columns_to_match(column_attributes, table_name) + + expect_check_constraint(table_name, 'check_cda6f69506', 'char_length(name) <= 255') + expect_check_constraint(table_name, 'some_id_is_positive', 'some_id > 0') + end + end + + it 'runs the change within a with_lock_retries' do + expect(model).to receive(:with_lock_retries).ordered.and_yield + expect(model).to receive(:create_table).ordered.and_call_original + expect(model).to receive(:execute).with(<<~SQL).ordered + ALTER TABLE "#{table_name}"\nADD CONSTRAINT "check_cda6f69506" CHECK (char_length("name") <= 255) + SQL + + model.create_table_with_constraints table_name do |t| + t.text :name + t.text_limit :name, 255 + end + end + + context 'when constraints are given invalid names' do + let(:expected_max_length) { described_class::MAX_IDENTIFIER_NAME_LENGTH } + let(:expected_error_message) { "The maximum allowed constraint name is #{expected_max_length} characters" } + + context 'when the explicit text limit name is not valid' do + it 'raises an error' do + too_long_length = expected_max_length + 1 + + expect do + model.create_table_with_constraints table_name do |t| + t.timestamps_with_timezone + t.integer :some_id, null: false + t.boolean :active, null: false, default: true + t.text :name + + t.text_limit :name, 255, name: ('a' * too_long_length) + t.check_constraint :some_id_is_positive, 'some_id > 0' + end + end.to raise_error(expected_error_message) + end + end + + context 'when a check constraint name is not valid' do + it 'raises an error' do + too_long_length = expected_max_length + 1 + + expect do + model.create_table_with_constraints table_name do |t| + t.timestamps_with_timezone + t.integer :some_id, null: false + t.boolean :active, null: false, default: true + t.text :name + + t.text_limit :name, 255 + t.check_constraint ('a' * too_long_length), 'some_id > 0' + end + end.to raise_error(expected_error_message) + end + end + end + end + end + describe '#add_concurrent_index' do context 'outside a transaction' do before do @@ -1548,6 +1675,69 @@ RSpec.describe Gitlab::Database::MigrationHelpers do end end + describe '#initialize_conversion_of_integer_to_bigint' do + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } + let(:issue) { create(:issue, project: project) } + let!(:event) do + create(:event, :created, project: project, target: issue, author: user) + end + + context 'in a transaction' do + it 'raises RuntimeError' do + allow(model).to receive(:transaction_open?).and_return(true) + + expect { model.initialize_conversion_of_integer_to_bigint(:events, :id) } + .to raise_error(RuntimeError) + end + end + + context 'outside a transaction' do + before do + allow(model).to receive(:transaction_open?).and_return(false) + end + + it 'creates a bigint column and starts backfilling it' do + expect(model) + .to receive(:add_column) + .with( + :events, + 'id_convert_to_bigint', + :bigint, + default: 0, + null: false + ) + + expect(model) + .to receive(:install_rename_triggers) + .with(:events, :id, 'id_convert_to_bigint') + + expect(model).to receive(:queue_background_migration_jobs_by_range_at_intervals).and_call_original + + expect(BackgroundMigrationWorker) + .to receive(:perform_in) + .ordered + .with( + 2.minutes, + 'CopyColumnUsingBackgroundMigrationJob', + [event.id, event.id, :events, :id, :id, 'id_convert_to_bigint', 100] + ) + + expect(Gitlab::BackgroundMigration) + .to receive(:steal) + .ordered + .with('CopyColumnUsingBackgroundMigrationJob') + + model.initialize_conversion_of_integer_to_bigint( + :events, + :id, + batch_size: 300, + sub_batch_size: 100 + ) + end + end + end + describe '#index_exists_by_name?' do it 'returns true if an index exists' do ActiveRecord::Base.connection.execute( |