summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--rubocop/cop/migration/add_column_with_default_to_large_table.rb51
-rw-r--r--rubocop/rubocop.rb1
-rw-r--r--spec/rubocop/cop/migration/add_column_with_default_to_large_table_spec.rb44
3 files changed, 96 insertions, 0 deletions
diff --git a/rubocop/cop/migration/add_column_with_default_to_large_table.rb b/rubocop/cop/migration/add_column_with_default_to_large_table.rb
new file mode 100644
index 00000000000..2372e6b60ea
--- /dev/null
+++ b/rubocop/cop/migration/add_column_with_default_to_large_table.rb
@@ -0,0 +1,51 @@
+require_relative '../../migration_helpers'
+
+module RuboCop
+ module Cop
+ module Migration
+ # This cop checks for `add_column_with_default` on a table that's been
+ # explicitly blacklisted because of its size.
+ #
+ # Even though this helper performs the update in batches to avoid
+ # downtime, using it with tables with millions of rows still causes a
+ # significant delay in the deploy process and is best avoided.
+ #
+ # See https://gitlab.com/gitlab-com/infrastructure/issues/1602 for more
+ # information.
+ class AddColumnWithDefaultToLargeTable < RuboCop::Cop::Cop
+ include MigrationHelpers
+
+ MSG = 'Using `add_column_with_default` on the `%s` table will take a ' \
+ 'long time to complete, and should be avoided unless absolutely ' \
+ 'necessary'.freeze
+
+ LARGE_TABLES = %i[
+ events
+ issues
+ merge_requests
+ namespaces
+ notes
+ projects
+ routes
+ users
+ ].freeze
+
+ def_node_matcher :add_column_with_default?, <<~PATTERN
+ (send nil :add_column_with_default $(sym ...) ...)
+ PATTERN
+
+ def on_send(node)
+ return unless in_migration?(node)
+
+ matched = add_column_with_default?(node)
+ return unless matched
+
+ table = matched.to_a.first
+ return unless LARGE_TABLES.include?(table)
+
+ add_offense(node, :expression, format(MSG, table))
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index cdc0fbfd6a8..4ff204f939e 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -1,6 +1,7 @@
require_relative 'cop/custom_error_class'
require_relative 'cop/gem_fetcher'
require_relative 'cop/migration/add_column'
+require_relative 'cop/migration/add_column_with_default_to_large_table'
require_relative 'cop/migration/add_concurrent_foreign_key'
require_relative 'cop/migration/add_concurrent_index'
require_relative 'cop/migration/add_index'
diff --git a/spec/rubocop/cop/migration/add_column_with_default_to_large_table_spec.rb b/spec/rubocop/cop/migration/add_column_with_default_to_large_table_spec.rb
new file mode 100644
index 00000000000..07cb3fc4a2e
--- /dev/null
+++ b/spec/rubocop/cop/migration/add_column_with_default_to_large_table_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../../rubocop/cop/migration/add_column_with_default_to_large_table'
+
+describe RuboCop::Cop::Migration::AddColumnWithDefaultToLargeTable do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'in migration' do
+ before do
+ allow(cop).to receive(:in_migration?).and_return(true)
+ end
+
+ described_class::LARGE_TABLES.each do |table|
+ it "registers an offense for the #{table} table" do
+ inspect_source(cop, "add_column_with_default :#{table}, :column, default: true")
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ end
+ end
+ end
+
+ it 'registers no offense for non-blacklisted tables' do
+ inspect_source(cop, "add_column_with_default :table, :column, default: true")
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'outside of migration' do
+ it 'registers no offense' do
+ table = described_class::LARGE_TABLES.sample
+ inspect_source(cop, "add_column_with_default :#{table}, :column, default: true")
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+end