Commit 41ef8f7d authored by Andreas Brandl's avatar Andreas Brandl

Merge branch '38385-disallow-add-column-with-default' into 'master'

Consider absence of explicit `allow_null: true` to be an offense too

Closes #38385

See merge request gitlab-org/gitlab!22234
parents dfd44347 2477c6b2
...@@ -8,7 +8,7 @@ class AddCommonToPrometheusMetrics < ActiveRecord::Migration[4.2] ...@@ -8,7 +8,7 @@ class AddCommonToPrometheusMetrics < ActiveRecord::Migration[4.2]
disable_ddl_transaction! disable_ddl_transaction!
def up def up
add_column_with_default(:prometheus_metrics, :common, :boolean, default: false) add_column_with_default(:prometheus_metrics, :common, :boolean, default: false) # rubocop:disable Migration/AddColumnWithDefault
end end
def down def down
......
...@@ -8,7 +8,7 @@ class AddLegacyAbacToClusterProvidersGcp < ActiveRecord::Migration[4.2] ...@@ -8,7 +8,7 @@ class AddLegacyAbacToClusterProvidersGcp < ActiveRecord::Migration[4.2]
disable_ddl_transaction! disable_ddl_transaction!
def up def up
add_column_with_default(:cluster_providers_gcp, :legacy_abac, :boolean, default: true) add_column_with_default(:cluster_providers_gcp, :legacy_abac, :boolean, default: true) # rubocop:disable Migration/AddColumnWithDefault
end end
def down def down
......
...@@ -9,7 +9,7 @@ class AddClusterTypeToClusters < ActiveRecord::Migration[4.2] ...@@ -9,7 +9,7 @@ class AddClusterTypeToClusters < ActiveRecord::Migration[4.2]
disable_ddl_transaction! disable_ddl_transaction!
def up def up
add_column_with_default(:clusters, :cluster_type, :smallint, default: PROJECT_CLUSTER_TYPE) add_column_with_default(:clusters, :cluster_type, :smallint, default: PROJECT_CLUSTER_TYPE) # rubocop:disable Migration/AddColumnWithDefault
end end
def down def down
......
...@@ -8,7 +8,7 @@ class AddEmailHeaderAndFooterEnabledFlagToAppearancesTable < ActiveRecord::Migra ...@@ -8,7 +8,7 @@ class AddEmailHeaderAndFooterEnabledFlagToAppearancesTable < ActiveRecord::Migra
DOWNTIME = false DOWNTIME = false
def up def up
add_column_with_default(:appearances, :email_header_and_footer_enabled, :boolean, default: false) add_column_with_default(:appearances, :email_header_and_footer_enabled, :boolean, default: false) # rubocop:disable Migration/AddColumnWithDefault
end end
def down def down
......
...@@ -8,7 +8,7 @@ class AddAutoSslEnabledToPagesDomain < ActiveRecord::Migration[5.0] ...@@ -8,7 +8,7 @@ class AddAutoSslEnabledToPagesDomain < ActiveRecord::Migration[5.0]
disable_ddl_transaction! disable_ddl_transaction!
def up def up
add_column_with_default :pages_domains, :auto_ssl_enabled, :boolean, default: false add_column_with_default :pages_domains, :auto_ssl_enabled, :boolean, default: false # rubocop:disable Migration/AddColumnWithDefault
end end
def down def down
......
...@@ -8,7 +8,7 @@ class AddManagedToCluster < ActiveRecord::Migration[5.0] ...@@ -8,7 +8,7 @@ class AddManagedToCluster < ActiveRecord::Migration[5.0]
DOWNTIME = false DOWNTIME = false
def up def up
add_column_with_default(:clusters, :managed, :boolean, default: true) add_column_with_default(:clusters, :managed, :boolean, default: true) # rubocop:disable Migration/AddColumnWithDefault
end end
def down def down
......
...@@ -8,7 +8,7 @@ class AddVariableTypeToCiVariables < ActiveRecord::Migration[5.0] ...@@ -8,7 +8,7 @@ class AddVariableTypeToCiVariables < ActiveRecord::Migration[5.0]
ENV_VAR_VARIABLE_TYPE = 1 ENV_VAR_VARIABLE_TYPE = 1
def up def up
add_column_with_default(:ci_variables, :variable_type, :smallint, default: ENV_VAR_VARIABLE_TYPE) add_column_with_default(:ci_variables, :variable_type, :smallint, default: ENV_VAR_VARIABLE_TYPE) # rubocop:disable Migration/AddColumnWithDefault
end end
def down def down
......
...@@ -8,7 +8,7 @@ class AddVariableTypeToCiGroupVariables < ActiveRecord::Migration[5.0] ...@@ -8,7 +8,7 @@ class AddVariableTypeToCiGroupVariables < ActiveRecord::Migration[5.0]
ENV_VAR_VARIABLE_TYPE = 1 ENV_VAR_VARIABLE_TYPE = 1
def up def up
add_column_with_default(:ci_group_variables, :variable_type, :smallint, default: ENV_VAR_VARIABLE_TYPE) add_column_with_default(:ci_group_variables, :variable_type, :smallint, default: ENV_VAR_VARIABLE_TYPE) # rubocop:disable Migration/AddColumnWithDefault
end end
def down def down
......
...@@ -8,7 +8,7 @@ class AddVariableTypeToCiPipelineVariables < ActiveRecord::Migration[5.0] ...@@ -8,7 +8,7 @@ class AddVariableTypeToCiPipelineVariables < ActiveRecord::Migration[5.0]
ENV_VAR_VARIABLE_TYPE = 1 ENV_VAR_VARIABLE_TYPE = 1
def up def up
add_column_with_default(:ci_pipeline_variables, :variable_type, :smallint, default: ENV_VAR_VARIABLE_TYPE) add_column_with_default(:ci_pipeline_variables, :variable_type, :smallint, default: ENV_VAR_VARIABLE_TYPE) # rubocop:disable Migration/AddColumnWithDefault
end end
def down def down
......
...@@ -8,7 +8,7 @@ class AddVariableTypeToCiPipelineScheduleVariables < ActiveRecord::Migration[5.0 ...@@ -8,7 +8,7 @@ class AddVariableTypeToCiPipelineScheduleVariables < ActiveRecord::Migration[5.0
ENV_VAR_VARIABLE_TYPE = 1 ENV_VAR_VARIABLE_TYPE = 1
def up def up
add_column_with_default(:ci_pipeline_schedule_variables, :variable_type, :smallint, default: ENV_VAR_VARIABLE_TYPE) add_column_with_default(:ci_pipeline_schedule_variables, :variable_type, :smallint, default: ENV_VAR_VARIABLE_TYPE) # rubocop:disable Migration/AddColumnWithDefault
end end
def down def down
......
...@@ -12,7 +12,7 @@ class AddRuleTypeToApprovalMergeRequestApprovalRules < ActiveRecord::Migration[5 ...@@ -12,7 +12,7 @@ class AddRuleTypeToApprovalMergeRequestApprovalRules < ActiveRecord::Migration[5
disable_ddl_transaction! disable_ddl_transaction!
def up def up
add_column_with_default(:approval_merge_request_rules, :rule_type, :integer, limit: 2, default: 1) add_column_with_default(:approval_merge_request_rules, :rule_type, :integer, limit: 2, default: 1) # rubocop:disable Migration/AddColumnWithDefault
end end
def down def down
......
...@@ -12,7 +12,7 @@ class AddSourceToPagesDomains < ActiveRecord::Migration[5.1] ...@@ -12,7 +12,7 @@ class AddSourceToPagesDomains < ActiveRecord::Migration[5.1]
disable_ddl_transaction! disable_ddl_transaction!
def up def up
add_column_with_default(:pages_domains, :certificate_source, :smallint, default: 0) add_column_with_default(:pages_domains, :certificate_source, :smallint, default: 0) # rubocop:disable Migration/AddColumnWithDefault
end end
def down def down
......
...@@ -8,7 +8,7 @@ class AddStrategiesToOperationsFeatureFlagScopes < ActiveRecord::Migration[5.1] ...@@ -8,7 +8,7 @@ class AddStrategiesToOperationsFeatureFlagScopes < ActiveRecord::Migration[5.1]
disable_ddl_transaction! disable_ddl_transaction!
def up def up
add_column_with_default :operations_feature_flag_scopes, :strategies, :jsonb, default: [{ name: "default", parameters: {} }] add_column_with_default :operations_feature_flag_scopes, :strategies, :jsonb, default: [{ name: "default", parameters: {} }] # rubocop:disable Migration/AddColumnWithDefault
end end
def down def down
......
...@@ -11,7 +11,7 @@ class AddNamespacePerEnvironmentFlagToClusters < ActiveRecord::Migration[5.1] ...@@ -11,7 +11,7 @@ class AddNamespacePerEnvironmentFlagToClusters < ActiveRecord::Migration[5.1]
disable_ddl_transaction! disable_ddl_transaction!
def up def up
add_column_with_default :clusters, :namespace_per_environment, :boolean, default: false add_column_with_default :clusters, :namespace_per_environment, :boolean, default: false # rubocop:disable Migration/AddColumnWithDefault
end end
def down def down
......
...@@ -12,7 +12,7 @@ class AddObjectStorageFlagToGeoNode < ActiveRecord::Migration[5.2] ...@@ -12,7 +12,7 @@ class AddObjectStorageFlagToGeoNode < ActiveRecord::Migration[5.2]
disable_ddl_transaction! disable_ddl_transaction!
def up def up
add_column_with_default :geo_nodes, :sync_object_storage, :boolean, default: false add_column_with_default :geo_nodes, :sync_object_storage, :boolean, default: false # rubocop:disable Migration/AddColumnWithDefault
end end
def down def down
......
...@@ -9,7 +9,7 @@ class AddMergeRequestsRequireCodeOwnerApprovalToProtectedBranches < ActiveRecord ...@@ -9,7 +9,7 @@ class AddMergeRequestsRequireCodeOwnerApprovalToProtectedBranches < ActiveRecord
disable_ddl_transaction! disable_ddl_transaction!
def up def up
add_column_with_default( add_column_with_default( # rubocop:disable Migration/AddColumnWithDefault
:protected_branches, :protected_branches,
:code_owner_approval_required, :code_owner_approval_required,
:boolean, :boolean,
......
...@@ -8,7 +8,7 @@ class AddActiveJobsLimitToPlans < ActiveRecord::Migration[5.2] ...@@ -8,7 +8,7 @@ class AddActiveJobsLimitToPlans < ActiveRecord::Migration[5.2]
disable_ddl_transaction! disable_ddl_transaction!
def up def up
add_column_with_default :plans, :active_jobs_limit, :integer, default: 0 add_column_with_default :plans, :active_jobs_limit, :integer, default: 0 # rubocop:disable Migration/AddColumnWithDefault
end end
def down def down
......
...@@ -7,7 +7,7 @@ class AddMaxIssueCountToList < ActiveRecord::Migration[4.2] ...@@ -7,7 +7,7 @@ class AddMaxIssueCountToList < ActiveRecord::Migration[4.2]
DOWNTIME = false DOWNTIME = false
def up def up
add_column_with_default :lists, :max_issue_count, :integer, default: 0 add_column_with_default :lists, :max_issue_count, :integer, default: 0 # rubocop:disable Migration/AddColumnWithDefault
end end
def down def down
......
...@@ -8,7 +8,7 @@ class AddCloudRunToClustersProvidersGcp < ActiveRecord::Migration[5.2] ...@@ -8,7 +8,7 @@ class AddCloudRunToClustersProvidersGcp < ActiveRecord::Migration[5.2]
disable_ddl_transaction! disable_ddl_transaction!
def up def up
add_column_with_default(:cluster_providers_gcp, :cloud_run, :boolean, default: false) add_column_with_default(:cluster_providers_gcp, :cloud_run, :boolean, default: false) # rubocop:disable Migration/AddColumnWithDefault
end end
def down def down
......
...@@ -8,7 +8,7 @@ class AddExpireNotificationDeliveredToPersonalAccessTokens < ActiveRecord::Migra ...@@ -8,7 +8,7 @@ class AddExpireNotificationDeliveredToPersonalAccessTokens < ActiveRecord::Migra
disable_ddl_transaction! disable_ddl_transaction!
def up def up
add_column_with_default :personal_access_tokens, :expire_notification_delivered, :boolean, default: false add_column_with_default :personal_access_tokens, :expire_notification_delivered, :boolean, default: false # rubocop:disable Migration/AddColumnWithDefault
end end
def down def down
......
...@@ -8,7 +8,7 @@ class AddCommentActionsToServices < ActiveRecord::Migration[5.2] ...@@ -8,7 +8,7 @@ class AddCommentActionsToServices < ActiveRecord::Migration[5.2]
disable_ddl_transaction! disable_ddl_transaction!
def up def up
add_column_with_default(:services, :comment_on_event_enabled, :boolean, default: true) add_column_with_default(:services, :comment_on_event_enabled, :boolean, default: true) # rubocop:disable Migration/AddColumnWithDefault
end end
def down def down
......
...@@ -8,7 +8,7 @@ class AddMaxIssueWeightToList < ActiveRecord::Migration[5.2] ...@@ -8,7 +8,7 @@ class AddMaxIssueWeightToList < ActiveRecord::Migration[5.2]
DOWNTIME = false DOWNTIME = false
def up def up
add_column_with_default :lists, :max_issue_weight, :integer, default: 0 add_column_with_default :lists, :max_issue_weight, :integer, default: 0 # rubocop:disable Migration/AddColumnWithDefault
end end
def down def down
......
...@@ -9,7 +9,7 @@ class AddSecretToSnippet < ActiveRecord::Migration[5.2] ...@@ -9,7 +9,7 @@ class AddSecretToSnippet < ActiveRecord::Migration[5.2]
def up def up
unless column_exists?(:snippets, :secret) unless column_exists?(:snippets, :secret)
add_column_with_default :snippets, :secret, :boolean, default: false add_column_with_default :snippets, :secret, :boolean, default: false # rubocop:disable Migration/AddColumnWithDefault
end end
add_concurrent_index :snippets, [:visibility_level, :secret] add_concurrent_index :snippets, [:visibility_level, :secret]
......
...@@ -9,7 +9,7 @@ class AddStateToMergeTrains < ActiveRecord::Migration[5.2] ...@@ -9,7 +9,7 @@ class AddStateToMergeTrains < ActiveRecord::Migration[5.2]
disable_ddl_transaction! disable_ddl_transaction!
def up def up
add_column_with_default :merge_trains, :status, :integer, limit: 2, default: MERGE_TRAIN_STATUS_CREATED add_column_with_default :merge_trains, :status, :integer, limit: 2, default: MERGE_TRAIN_STATUS_CREATED # rubocop:disable Migration/AddColumnWithDefault
end end
def down def down
......
...@@ -8,7 +8,7 @@ class AddIssueLinksType < ActiveRecord::Migration[5.1] ...@@ -8,7 +8,7 @@ class AddIssueLinksType < ActiveRecord::Migration[5.1]
disable_ddl_transaction! disable_ddl_transaction!
def up def up
add_column_with_default :issue_links, :link_type, :integer, default: 0, limit: 2 add_column_with_default :issue_links, :link_type, :integer, default: 0, limit: 2 # rubocop:disable Migration/AddColumnWithDefault
end end
def down def down
......
...@@ -9,8 +9,10 @@ class AddWildcardAndDomainTypeToPagesDomains < ActiveRecord::Migration[5.2] ...@@ -9,8 +9,10 @@ class AddWildcardAndDomainTypeToPagesDomains < ActiveRecord::Migration[5.2]
disable_ddl_transaction! disable_ddl_transaction!
def up def up
# rubocop:disable Migration/AddColumnWithDefault
add_column_with_default :pages_domains, :wildcard, :boolean, default: false add_column_with_default :pages_domains, :wildcard, :boolean, default: false
add_column_with_default :pages_domains, :domain_type, :integer, limit: 2, default: PROJECT_TYPE add_column_with_default :pages_domains, :domain_type, :integer, limit: 2, default: PROJECT_TYPE
# rubocop:enable Migration/AddColumnWithDefault
end end
def down def down
......
...@@ -10,7 +10,7 @@ class AddBroadcastTypeToBroadcastMessage < ActiveRecord::Migration[5.2] ...@@ -10,7 +10,7 @@ class AddBroadcastTypeToBroadcastMessage < ActiveRecord::Migration[5.2]
disable_ddl_transaction! disable_ddl_transaction!
def up def up
add_column_with_default(:broadcast_messages, :broadcast_type, :smallint, default: BROADCAST_MESSAGE_BANNER_TYPE) add_column_with_default(:broadcast_messages, :broadcast_type, :smallint, default: BROADCAST_MESSAGE_BANNER_TYPE) # rubocop:disable Migration/AddColumnWithDefault
end end
def down def down
......
...@@ -6,8 +6,10 @@ class AddNeedsResyncToProjectRegistry < ActiveRecord::Migration[4.2] ...@@ -6,8 +6,10 @@ class AddNeedsResyncToProjectRegistry < ActiveRecord::Migration[4.2]
disable_ddl_transaction! disable_ddl_transaction!
def up def up
# rubocop:disable Migration/AddColumnWithDefault
add_column_with_default(:project_registry, :resync_repository, :boolean, default: true) add_column_with_default(:project_registry, :resync_repository, :boolean, default: true)
add_column_with_default(:project_registry, :resync_wiki, :boolean, default: true) add_column_with_default(:project_registry, :resync_wiki, :boolean, default: true)
# rubocop:enable Migration/AddColumnWithDefault
end end
def down def down
......
...@@ -12,52 +12,33 @@ module RuboCop ...@@ -12,52 +12,33 @@ module RuboCop
WHITELISTED_TABLES = [:application_settings].freeze WHITELISTED_TABLES = [:application_settings].freeze
MSG = '`add_column_with_default` with `allow_null: false` may cause prolonged lock situations and downtime, ' \ MSG = '`add_column_with_default` without `allow_null: true` may cause prolonged lock situations and downtime, ' \
'see https://gitlab.com/gitlab-org/gitlab/issues/38060'.freeze 'see https://gitlab.com/gitlab-org/gitlab/issues/38060'.freeze
def_node_matcher :add_column_with_default?, <<~PATTERN
(send _ :add_column_with_default $_ ... (hash $...))
PATTERN
def on_send(node) def on_send(node)
return unless in_migration?(node) return unless in_migration?(node)
name = node.children[1] add_column_with_default?(node) do |table, options|
break if table_whitelisted?(table) || nulls_allowed?(options)
return unless name == :add_column_with_default
# Ignore whitelisted tables. add_offense(node, location: :selector)
return if table_whitelisted?(node.children[2]) end
end
opts = node.children.last
return unless opts && opts.type == :hash private
opts.each_node(:pair) do |pair| def nulls_allowed?(options)
if disallows_null_values?(pair) options.find { |opt| opt.key.value == :allow_null && opt.value.true_type? }
add_offense(node, location: :selector)
end
end
end end
def table_whitelisted?(symbol) def table_whitelisted?(symbol)
symbol && symbol.type == :sym && symbol && symbol.type == :sym &&
WHITELISTED_TABLES.include?(symbol.children[0]) WHITELISTED_TABLES.include?(symbol.children[0])
end 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
end end
......
...@@ -27,7 +27,7 @@ describe RuboCop::Cop::Migration::AddColumnWithDefault do ...@@ -27,7 +27,7 @@ describe RuboCop::Cop::Migration::AddColumnWithDefault do
allow(cop).to receive(:in_migration?).and_return(true) allow(cop).to receive(:in_migration?).and_return(true)
end 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' } let(:offense) { '`add_column_with_default` without `allow_null: true` 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 it 'registers an offense when specifying allow_null: false' do
expect_offense(<<~RUBY) expect_offense(<<~RUBY)
...@@ -46,10 +46,11 @@ describe RuboCop::Cop::Migration::AddColumnWithDefault do ...@@ -46,10 +46,11 @@ describe RuboCop::Cop::Migration::AddColumnWithDefault do
RUBY RUBY
end end
it 'registers no offense when allow_null is not specified' do it 'registers an offense when allow_null is not specified' do
expect_no_offenses(<<~RUBY) expect_offense(<<~RUBY)
def up def up
add_column_with_default(:ci_build_needs, :artifacts, :boolean, default: true) add_column_with_default(:ci_build_needs, :artifacts, :boolean, default: true)
^^^^^^^^^^^^^^^^^^^^^^^ #{offense}
end end
RUBY RUBY
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