Commit 6b32bd5c authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'iterations_add_daterange_constraint-unrevert' into 'master'

Iterations - constraint date ranges do not overlap

See merge request gitlab-org/gitlab!33340
parents ba8399bd b1dfb4a9
---
title: Add btree_gist PGSQL extension and add DB constraints for Iteration date ranges
merge_request: 33340
author:
type: added
# frozen_string_literal: true
class EnableBtreeGistExtension < ActiveRecord::Migration[6.0]
DOWNTIME = false
def up
execute 'CREATE EXTENSION IF NOT EXISTS btree_gist'
end
def down
execute 'DROP EXTENSION IF EXISTS btree_gist'
end
end
# frozen_string_literal: true
class IterationDateRangeConstraint < ActiveRecord::Migration[6.0]
DOWNTIME = false
def up
execute <<~SQL
ALTER TABLE sprints
ADD CONSTRAINT iteration_start_and_due_daterange_project_id_constraint
EXCLUDE USING gist
( project_id WITH =,
daterange(start_date, due_date, '[]') WITH &&
)
WHERE (project_id IS NOT NULL)
SQL
execute <<~SQL
ALTER TABLE sprints
ADD CONSTRAINT iteration_start_and_due_daterange_group_id_constraint
EXCLUDE USING gist
( group_id WITH =,
daterange(start_date, due_date, '[]') WITH &&
)
WHERE (group_id IS NOT NULL)
SQL
end
def down
execute <<~SQL
ALTER TABLE sprints
DROP CONSTRAINT IF EXISTS iteration_start_and_due_daterange_project_id_constraint
SQL
execute <<~SQL
ALTER TABLE sprints
DROP CONSTRAINT IF EXISTS iteration_start_and_due_daterange_group_id_constraint
SQL
end
end
...@@ -8,6 +8,8 @@ CREATE SCHEMA gitlab_partitions_static; ...@@ -8,6 +8,8 @@ CREATE SCHEMA gitlab_partitions_static;
COMMENT ON SCHEMA gitlab_partitions_static IS 'Schema to hold static partitions, e.g. for hash partitioning'; COMMENT ON SCHEMA gitlab_partitions_static IS 'Schema to hold static partitions, e.g. for hash partitioning';
CREATE EXTENSION IF NOT EXISTS btree_gist WITH SCHEMA public;
CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA public; CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA public;
CREATE TABLE public.product_analytics_events_experimental ( CREATE TABLE public.product_analytics_events_experimental (
...@@ -17858,6 +17860,12 @@ ALTER TABLE ONLY public.issue_user_mentions ...@@ -17858,6 +17860,12 @@ ALTER TABLE ONLY public.issue_user_mentions
ALTER TABLE ONLY public.issues ALTER TABLE ONLY public.issues
ADD CONSTRAINT issues_pkey PRIMARY KEY (id); ADD CONSTRAINT issues_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.sprints
ADD CONSTRAINT iteration_start_and_due_daterange_group_id_constraint EXCLUDE USING gist (group_id WITH =, daterange(start_date, due_date, '[]'::text) WITH &&) WHERE ((group_id IS NOT NULL));
ALTER TABLE ONLY public.sprints
ADD CONSTRAINT iteration_start_and_due_daterange_project_id_constraint EXCLUDE USING gist (project_id WITH =, daterange(start_date, due_date, '[]'::text) WITH &&) WHERE ((project_id IS NOT NULL));
ALTER TABLE ONLY public.jira_connect_installations ALTER TABLE ONLY public.jira_connect_installations
ADD CONSTRAINT jira_connect_installations_pkey PRIMARY KEY (id); ADD CONSTRAINT jira_connect_installations_pkey PRIMARY KEY (id);
...@@ -23760,6 +23768,8 @@ COPY "schema_migrations" (version) FROM STDIN; ...@@ -23760,6 +23768,8 @@ COPY "schema_migrations" (version) FROM STDIN;
20200514000009 20200514000009
20200514000132 20200514000132
20200514000340 20200514000340
20200515152649
20200515153633
20200515155620 20200515155620
20200518091745 20200518091745
20200518114540 20200518114540
......
...@@ -54,7 +54,10 @@ RSpec.describe Iteration do ...@@ -54,7 +54,10 @@ RSpec.describe Iteration do
end end
context 'when dates overlap' do context 'when dates overlap' do
context 'same group' do let(:start_date) { 5.days.from_now }
let(:due_date) { 6.days.from_now }
shared_examples_for 'overlapping dates' do
context 'when start_date is in range' do context 'when start_date is in range' do
let(:start_date) { 5.days.from_now } let(:start_date) { 5.days.from_now }
let(:due_date) { 3.weeks.from_now } let(:due_date) { 3.weeks.from_now }
...@@ -63,6 +66,11 @@ RSpec.describe Iteration do ...@@ -63,6 +66,11 @@ RSpec.describe Iteration do
expect(subject).not_to be_valid expect(subject).not_to be_valid
expect(subject.errors[:base]).to include('Dates cannot overlap with other existing Iterations') expect(subject.errors[:base]).to include('Dates cannot overlap with other existing Iterations')
end end
it 'is not valid even if forced' do
subject.validate # to generate iid/etc
expect { subject.save!(validate: false) }.to raise_exception(ActiveRecord::StatementInvalid, /#{constraint_name}/)
end
end end
context 'when end_date is in range' do context 'when end_date is in range' do
...@@ -73,25 +81,84 @@ RSpec.describe Iteration do ...@@ -73,25 +81,84 @@ RSpec.describe Iteration do
expect(subject).not_to be_valid expect(subject).not_to be_valid
expect(subject.errors[:base]).to include('Dates cannot overlap with other existing Iterations') expect(subject.errors[:base]).to include('Dates cannot overlap with other existing Iterations')
end end
it 'is not valid even if forced' do
subject.validate # to generate iid/etc
expect { subject.save!(validate: false) }.to raise_exception(ActiveRecord::StatementInvalid, /#{constraint_name}/)
end
end end
context 'when both overlap' do context 'when both overlap' do
let(:start_date) { 5.days.from_now }
let(:due_date) { 6.days.from_now }
it 'is not valid' do it 'is not valid' do
expect(subject).not_to be_valid expect(subject).not_to be_valid
expect(subject.errors[:base]).to include('Dates cannot overlap with other existing Iterations') expect(subject.errors[:base]).to include('Dates cannot overlap with other existing Iterations')
end end
it 'is not valid even if forced' do
subject.validate # to generate iid/etc
expect { subject.save!(validate: false) }.to raise_exception(ActiveRecord::StatementInvalid, /#{constraint_name}/)
end
end end
end end
context 'different group' do context 'group' do
let(:start_date) { 5.days.from_now } it_behaves_like 'overlapping dates' do
let(:due_date) { 6.days.from_now } let(:constraint_name) { 'iteration_start_and_due_daterange_group_id_constraint' }
let(:group) { create(:group) } end
context 'different group' do
let(:group) { create(:group) }
it { is_expected.to be_valid }
it 'does not trigger exclusion constraints' do
expect { subject.save! }.not_to raise_exception
end
end
context 'in a project' do
let(:project) { create(:project) }
subject { build(:iteration, project: project, start_date: start_date, due_date: due_date) }
it { is_expected.to be_valid }
it { is_expected.to be_valid } it 'does not trigger exclusion constraints' do
expect { subject.save! }.not_to raise_exception
end
end
end
context 'project' do
let_it_be(:existing_iteration) { create(:iteration, project: project, start_date: 4.days.from_now, due_date: 1.week.from_now) }
subject { build(:iteration, project: project, start_date: start_date, due_date: due_date) }
it_behaves_like 'overlapping dates' do
let(:constraint_name) { 'iteration_start_and_due_daterange_project_id_constraint' }
end
context 'different project' do
let(:project) { create(:project) }
it { is_expected.to be_valid }
it 'does not trigger exclusion constraints' do
expect { subject.save! }.not_to raise_exception
end
end
context 'in a group' do
let(:group) { create(:group) }
subject { build(:iteration, group: group, start_date: start_date, due_date: due_date) }
it { is_expected.to be_valid }
it 'does not trigger exclusion constraints' do
expect { subject.save! }.not_to raise_exception
end
end
end end
end 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