Commit 5747b0d3 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Make deleting with optimistic locking respect NULL

For now deleting with optimistic locking is broken when
lock_version is still NULL, because Rails would try to
delete with `lock_version = 0` while in the database
the column is still `NULL`.

The monkey patches would force Rails just pass whatever
in the column, and stop Rails from casting `NULL` into `0`
when the value is read from database.

Closes #24766
parent bbcce5f7
...@@ -52,6 +52,24 @@ module ActiveRecord ...@@ -52,6 +52,24 @@ module ActiveRecord
raise raise
end end
end end
# This is patched because we need it to query `lock_version IS NULL`
# rather than `lock_version = 0` whenever lock_version is NULL.
def relation_for_destroy
return super unless locking_enabled?
column_name = self.class.locking_column
table_name = self.class.quoted_table_name
super.where("#{table_name}.#{column_name}" => self[column_name])
end
end
# This is patched because we want `lock_version` default to `NULL`
# rather than `0`
class LockingType < SimpleDelegator
def type_cast_from_database(value)
super
end
end end
end end
end end
...@@ -7,15 +7,21 @@ describe Projects::DestroyService, services: true do ...@@ -7,15 +7,21 @@ describe Projects::DestroyService, services: true do
let!(:remove_path) { path.sub(/\.git\Z/, "+#{project.id}+deleted.git") } let!(:remove_path) { path.sub(/\.git\Z/, "+#{project.id}+deleted.git") }
let!(:async) { false } # execute or async_execute let!(:async) { false } # execute or async_execute
shared_examples 'deleting the project' do
it 'deletes the project' do
expect(Project.all).not_to include(project)
expect(Dir.exist?(path)).to be_falsey
expect(Dir.exist?(remove_path)).to be_falsey
end
end
context 'Sidekiq inline' do context 'Sidekiq inline' do
before do before do
# Run sidekiq immediatly to check that renamed repository will be removed # Run sidekiq immediatly to check that renamed repository will be removed
Sidekiq::Testing.inline! { destroy_project(project, user, {}) } Sidekiq::Testing.inline! { destroy_project(project, user, {}) }
end end
it { expect(Project.all).not_to include(project) } it_behaves_like 'deleting the project'
it { expect(Dir.exist?(path)).to be_falsey }
it { expect(Dir.exist?(remove_path)).to be_falsey }
end end
context 'Sidekiq fake' do context 'Sidekiq fake' do
...@@ -38,11 +44,21 @@ describe Projects::DestroyService, services: true do ...@@ -38,11 +44,21 @@ describe Projects::DestroyService, services: true do
Sidekiq::Testing.inline! { destroy_project(project, user, {}) } Sidekiq::Testing.inline! { destroy_project(project, user, {}) }
end end
it 'deletes the project' do it_behaves_like 'deleting the project'
expect(Project.all).not_to include(project) end
expect(Dir.exist?(path)).to be_falsey
expect(Dir.exist?(remove_path)).to be_falsey context 'delete with pipeline' do # which has optimistic locking
let!(:pipeline) { create(:ci_pipeline, project: project) }
before do
expect(project).to receive(:destroy!).and_call_original
perform_enqueued_jobs do
destroy_project(project, user, {})
end
end end
it_behaves_like 'deleting the project'
end end
context 'container registry' do context 'container registry' do
......
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