Commit 242cb46a authored by Andreas Brandl's avatar Andreas Brandl Committed by Tiger

Disallow add_column_with_default with NOT NULL

See https://gitlab.com/gitlab-org/gitlab/issues/38060
parent 610aa44c
......@@ -9,7 +9,7 @@ class AddPrivilegedToRunner < ActiveRecord::Migration[4.2]
disable_ddl_transaction!
def up
add_column_with_default :clusters_applications_runners, :privileged, :boolean, default: true, allow_null: false
add_column_with_default :clusters_applications_runners, :privileged, :boolean, default: true, allow_null: false # rubocop:disable Migration/AddColumnWithDefault
end
def down
......
......@@ -5,7 +5,7 @@ class AddPagesAccessLevelToProjectFeature < ActiveRecord::Migration[4.2]
DOWNTIME = false
def up
add_column_with_default(:project_features, :pages_access_level, :integer, default: ProjectFeature::PUBLIC, allow_null: false)
add_column_with_default(:project_features, :pages_access_level, :integer, default: ProjectFeature::PUBLIC, allow_null: false) # rubocop:disable Migration/AddColumnWithDefault
change_column_default(:project_features, :pages_access_level, ProjectFeature::ENABLED)
end
......
......@@ -10,7 +10,7 @@ class AddSquashToMergeRequests < ActiveRecord::Migration[4.2]
def up
unless column_exists?(:merge_requests, :squash)
# rubocop:disable Migration/UpdateLargeTable
add_column_with_default :merge_requests, :squash, :boolean, default: false, allow_null: false
add_column_with_default :merge_requests, :squash, :boolean, default: false, allow_null: false # rubocop:disable Migration/AddColumnWithDefault
end
end
......
......@@ -11,7 +11,7 @@ class EnsureRemoteMirrorColumns < ActiveRecord::Migration[4.2]
add_column :remote_mirrors, :remote_name, :string unless column_exists?(:remote_mirrors, :remote_name) # rubocop:disable Migration/AddLimitToStringColumns
unless column_exists?(:remote_mirrors, :only_protected_branches)
add_column_with_default(:remote_mirrors,
add_column_with_default(:remote_mirrors, # rubocop:disable Migration/AddColumnWithDefault
:only_protected_branches,
:boolean,
default: false,
......
......@@ -10,7 +10,7 @@ class AddDeployStrategyToProjectAutoDevops < ActiveRecord::Migration[4.2]
disable_ddl_transaction!
def up
add_column_with_default :project_auto_devops, :deploy_strategy, :integer, default: 0, allow_null: false
add_column_with_default :project_auto_devops, :deploy_strategy, :integer, default: 0, allow_null: false # rubocop:disable Migration/AddColumnWithDefault
end
def down
......
......@@ -15,7 +15,7 @@ class AddStatusToDeployments < ActiveRecord::Migration[4.2]
# However, we have to use the default value for avoiding `NOT NULL` violation during the transition period.
# The default value should be removed in the future release.
def up
add_column_with_default(:deployments,
add_column_with_default(:deployments, # rubocop:disable Migration/AddColumnWithDefault
:status,
:integer,
limit: 2,
......
......@@ -12,7 +12,7 @@ class AddMaskedToCiVariables < ActiveRecord::Migration[5.0]
disable_ddl_transaction!
def up
add_column_with_default :ci_variables, :masked, :boolean, default: false, allow_null: false
add_column_with_default :ci_variables, :masked, :boolean, default: false, allow_null: false # rubocop:disable Migration/AddColumnWithDefault
end
def down
......
......@@ -12,7 +12,7 @@ class AddMaskedToCiGroupVariables < ActiveRecord::Migration[5.0]
disable_ddl_transaction!
def up
add_column_with_default :ci_group_variables, :masked, :boolean, default: false, allow_null: false
add_column_with_default :ci_group_variables, :masked, :boolean, default: false, allow_null: false # rubocop:disable Migration/AddColumnWithDefault
end
def down
......
......@@ -8,9 +8,11 @@ class AddMultiLineAttributesToSuggestion < ActiveRecord::Migration[5.0]
disable_ddl_transaction!
def up
# rubocop:disable Migration/AddColumnWithDefault
add_column_with_default :suggestions, :lines_above, :integer, default: 0, allow_null: false
add_column_with_default :suggestions, :lines_below, :integer, default: 0, allow_null: false
add_column_with_default :suggestions, :outdated, :boolean, default: false, allow_null: false
# rubocop:enable Migration/AddColumnWithDefault
end
def down
......
......@@ -8,7 +8,7 @@ class AddMergeTrainEnabledToCiCdSettings < ActiveRecord::Migration[5.1]
disable_ddl_transaction!
def up
add_column_with_default :project_ci_cd_settings, :merge_trains_enabled, :boolean, default: false, allow_null: false
add_column_with_default :project_ci_cd_settings, :merge_trains_enabled, :boolean, default: false, allow_null: false # rubocop:disable Migration/AddColumnWithDefault
end
def down
......
......@@ -8,7 +8,7 @@ class AddDeploymentEventsToServices < ActiveRecord::Migration[5.0]
disable_ddl_transaction!
def up
add_column_with_default(:services, :deployment_events, :boolean, default: false, allow_null: false)
add_column_with_default(:services, :deployment_events, :boolean, default: false, allow_null: false) # rubocop:disable Migration/AddColumnWithDefault
end
def down
......
......@@ -8,7 +8,7 @@ class AddRuleTypeToApprovalProjectRules < ActiveRecord::Migration[5.1]
disable_ddl_transaction!
def up
add_column_with_default :approval_project_rules, :rule_type, :integer, limit: 2, default: 0, allow_null: false
add_column_with_default :approval_project_rules, :rule_type, :integer, limit: 2, default: 0, allow_null: false # rubocop:disable Migration/AddColumnWithDefault
end
def down
......
......@@ -11,7 +11,7 @@ class AddShowWhitespaceInDiffsToUserPreferences < ActiveRecord::Migration[5.2]
disable_ddl_transaction!
def up
add_column_with_default :user_preferences, :show_whitespace_in_diffs, :boolean, default: true, allow_null: false
add_column_with_default :user_preferences, :show_whitespace_in_diffs, :boolean, default: true, allow_null: false # rubocop:disable Migration/AddColumnWithDefault
end
def down
......
......@@ -9,7 +9,7 @@ class AddCleanupStatusToCluster < ActiveRecord::Migration[5.2]
disable_ddl_transaction!
def up
add_column_with_default(:clusters, :cleanup_status,
add_column_with_default(:clusters, :cleanup_status, # rubocop:disable Migration/AddColumnWithDefault
:smallint,
default: 1,
allow_null: false)
......
......@@ -8,7 +8,7 @@ class AddEnabledToGrafanaIntegrations < ActiveRecord::Migration[5.2]
disable_ddl_transaction!
def up
add_column_with_default(
add_column_with_default( # rubocop:disable Migration/AddColumnWithDefault
:grafana_integrations,
:enabled,
:boolean,
......
......@@ -8,7 +8,7 @@ class AddArtifactsToCiBuildNeed < ActiveRecord::Migration[5.2]
disable_ddl_transaction!
def up
add_column_with_default(:ci_build_needs, :artifacts,
add_column_with_default(:ci_build_needs, :artifacts, # rubocop:disable Migration/AddColumnWithDefault
:boolean,
default: true,
allow_null: false)
......
......@@ -34,6 +34,9 @@ blocking access to the table being modified. See ["Adding Columns With Default
Values"](migration_style_guide.md#adding-columns-with-default-values) for more
information on how to use this method.
Note that usage of `add_column_with_default` with `allow_null: false` to also add
a `NOT NULL` constraint is [discouraged](https://gitlab.com/gitlab-org/gitlab/issues/38060).
## Dropping Columns
Removing columns is tricky because running GitLab processes may still be using
......
# frozen_string_literal: true
require_relative '../../migration_helpers'
module RuboCop
module Cop
module Migration
# Cop that checks if columns are added in a way that doesn't require
# downtime.
class AddColumnWithDefault < RuboCop::Cop::Cop
include MigrationHelpers
WHITELISTED_TABLES = [:application_settings].freeze
MSG = '`add_column_with_default` with `allow_null: false` may cause prolonged lock situations and downtime, ' \
'see https://gitlab.com/gitlab-org/gitlab/issues/38060'.freeze
def on_send(node)
return unless in_migration?(node)
name = node.children[1]
return unless name == :add_column_with_default
# Ignore whitelisted tables.
return if table_whitelisted?(node.children[2])
opts = node.children.last
return unless opts && opts.type == :hash
opts.each_node(:pair) do |pair|
if disallows_null_values?(pair)
add_offense(node, location: :selector)
end
end
end
def table_whitelisted?(symbol)
symbol && symbol.type == :sym &&
WHITELISTED_TABLES.include?(symbol.children[0])
end
def disallows_null_values?(pair)
options = [hash_key_type(pair), hash_key_name(pair), hash_value(pair)]
options == [:sym, :allow_null, :false] # rubocop:disable Lint/BooleanSymbol
end
def hash_key_type(pair)
pair.children[0].type
end
def hash_key_name(pair)
pair.children[0].children[0]
end
def hash_value(pair)
pair.children[1].type
end
end
end
end
end
......@@ -17,6 +17,7 @@ require_relative 'cop/prefer_class_methods_over_module'
require_relative 'cop/put_project_routes_under_scope'
require_relative 'cop/put_group_routes_under_scope'
require_relative 'cop/migration/add_column'
require_relative 'cop/migration/add_column_with_default'
require_relative 'cop/migration/add_concurrent_foreign_key'
require_relative 'cop/migration/add_concurrent_index'
require_relative 'cop/migration/add_index'
......
# frozen_string_literal: true
require 'spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/migration/add_column_with_default'
describe RuboCop::Cop::Migration::AddColumnWithDefault do
include CopHelper
let(:cop) { described_class.new }
context 'outside of a migration' do
it 'does not register any offenses' do
expect_no_offenses(<<~RUBY)
def up
add_reference(:projects, :users)
end
RUBY
end
end
context 'in a migration' do
before do
allow(cop).to receive(:in_migration?).and_return(true)
end
let(:offense) { '`add_column_with_default` with `allow_null: false` may cause prolonged lock situations and downtime, see https://gitlab.com/gitlab-org/gitlab/issues/38060' }
it 'registers an offense when specifying allow_null: false' do
expect_offense(<<~RUBY)
def up
add_column_with_default(:ci_build_needs, :artifacts, :boolean, default: true, allow_null: false)
^^^^^^^^^^^^^^^^^^^^^^^ #{offense}
end
RUBY
end
it 'registers no offense when specifying allow_null: true' do
expect_no_offenses(<<~RUBY)
def up
add_column_with_default(:ci_build_needs, :artifacts, :boolean, default: true, allow_null: true)
end
RUBY
end
it 'registers no offense when allow_null is not specified' do
expect_no_offenses(<<~RUBY)
def up
add_column_with_default(:ci_build_needs, :artifacts, :boolean, default: true)
end
RUBY
end
it 'registers no offense for application_settings (whitelisted table)' do
expect_no_offenses(<<~RUBY)
def up
add_column_with_default(:application_settings, :another_column, :boolean, default: true, allow_null: false)
end
RUBY
end
end
end
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