diff --git a/changelogs/unreleased/fix-gb-add-pipeline-builds-foreign-key.yml b/changelogs/unreleased/fix-gb-add-pipeline-builds-foreign-key.yml new file mode 100644 index 00000000000..bded7bb7cc4 --- /dev/null +++ b/changelogs/unreleased/fix-gb-add-pipeline-builds-foreign-key.yml @@ -0,0 +1,5 @@ +--- +title: Add database foreign key constraint between pipelines and build +merge_request: 18822 +author: +type: fixed diff --git a/db/migrate/20180420010016_add_pipeline_build_foreign_key.rb b/db/migrate/20180420010016_add_pipeline_build_foreign_key.rb new file mode 100644 index 00000000000..6fabe07bc9c --- /dev/null +++ b/db/migrate/20180420010016_add_pipeline_build_foreign_key.rb @@ -0,0 +1,27 @@ +class AddPipelineBuildForeignKey < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + execute <<~SQL + DELETE FROM ci_builds WHERE project_id IS NULL OR commit_id IS NULL + SQL + + execute <<~SQL + DELETE FROM ci_builds WHERE NOT EXISTS + (SELECT true FROM ci_pipelines WHERE ci_pipelines.id = ci_builds.commit_id) + AND stage_id IS NULL + SQL + + add_concurrent_foreign_key(:ci_builds, :ci_pipelines, column: :commit_id) + end + + def down + return unless foreign_key_exists?(:ci_builds, :ci_pipelines, column: :commit_id) + + remove_foreign_key(:ci_builds, column: :commit_id) + end +end diff --git a/spec/migrations/add_pipeline_build_foreign_key_spec.rb b/spec/migrations/add_pipeline_build_foreign_key_spec.rb new file mode 100644 index 00000000000..e9413f52f19 --- /dev/null +++ b/spec/migrations/add_pipeline_build_foreign_key_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20180420010016_add_pipeline_build_foreign_key.rb') + +describe AddPipelineBuildForeignKey, :migration do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:pipelines) { table(:ci_pipelines) } + let(:builds) { table(:ci_builds) } + + before do + namespaces.create(id: 10, name: 'gitlab-org', path: 'gitlab-org') + projects.create!(id: 11, namespace_id: 10, name: 'gitlab', path: 'gitlab') + pipelines.create!(id: 12, project_id: 11, ref: 'master', sha: 'adf43c3a') + + builds.create!(id: 101, commit_id: 12, project_id: 11) + builds.create!(id: 102, commit_id: 222, project_id: 11) + builds.create!(id: 103, commit_id: 333, project_id: 11) + builds.create!(id: 104, commit_id: 12, project_id: 11) + builds.create!(id: 106, commit_id: nil, project_id: 11) + builds.create!(id: 107, commit_id: 12, project_id: nil) + end + + it 'adds foreign key after removing orphans' do + expect(builds.all.count).to eq 6 + expect(foreign_key_exists?(:ci_builds, :ci_pipelines, column: :commit_id)).to be_falsey + + migrate! + + expect(builds.all.pluck(:id)).to eq [101, 104] + expect(foreign_key_exists?(:ci_builds, :ci_pipelines, column: :commit_id)).to be_truthy + end +end diff --git a/spec/support/helpers/migrations_helpers.rb b/spec/support/helpers/migrations_helpers.rb index 5d6f662e8fe..84abec75c26 100644 --- a/spec/support/helpers/migrations_helpers.rb +++ b/spec/support/helpers/migrations_helpers.rb @@ -24,6 +24,16 @@ module MigrationsHelpers end end + def foreign_key_exists?(source, target = nil, column: nil) + ActiveRecord::Base.connection.foreign_keys(source).any? do |key| + if column + key.options[:column].to_s == column.to_s + else + key.to_table.to_s == target.to_s + end + end + end + def reset_column_in_all_models clear_schema_cache!