Skip to content

Commit bc8f4c0

Browse files
snigdhasandrewshie-sentry
authored andcommitted
fix(aci): Add migration to check group id for exclusion constraint (#88484)
The existing exclusion constraint looks only for overlapping ranges of (date_started, date_ended). We need to enforce this constraint for each group, so that no distinct group has two open periods that overlap. The table itself is expected to have multiple overlapping ranges since we'll have one row per group.
1 parent 85a7066 commit bc8f4c0

File tree

3 files changed

+59
-14
lines changed

3 files changed

+59
-14
lines changed

migrations_lockfile.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ remote_subscriptions: 0003_drop_remote_subscription
1717

1818
replays: 0004_index_together
1919

20-
sentry: 0856_monitors_remove_type_column_state
20+
sentry: 0857_update_group_open_periods_constraint
2121

2222
social_auth: 0002_default_auto_field
2323

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Generated by Django 5.1.7 on 2025-04-01 20:22
2+
3+
from django.db import migrations
4+
5+
from sentry.new_migrations.migrations import CheckedMigration
6+
from sentry.new_migrations.monkey.special import SafeRunSQL
7+
8+
9+
class Migration(CheckedMigration):
10+
# This flag is used to mark that a migration shouldn't be automatically run in production.
11+
# This should only be used for operations where it's safe to run the migration after your
12+
# code has deployed. So this should not be used for most operations that alter the schema
13+
# of a table.
14+
# Here are some things that make sense to mark as post deployment:
15+
# - Large data migrations. Typically we want these to be run manually so that they can be
16+
# monitored and not block the deploy for a long period of time while they run.
17+
# - Adding indexes to large tables. Since this can take a long time, we'd generally prefer to
18+
# run this outside deployments so that we don't block them. Note that while adding an index
19+
# is a schema change, it's completely safe to run the operation after the code has deployed.
20+
# Once deployed, run these manually via: https://develop.sentry.dev/database-migrations/#migration-deployment
21+
22+
is_post_deployment = False
23+
24+
dependencies = [
25+
("sentry", "0856_monitors_remove_type_column_state"),
26+
]
27+
28+
operations = [
29+
migrations.RemoveConstraint(
30+
model_name="groupopenperiod",
31+
name="exclude_open_period_overlap",
32+
),
33+
SafeRunSQL(
34+
"""ALTER TABLE "sentry_groupopenperiod"
35+
ADD CONSTRAINT "exclude_open_period_overlap" EXCLUDE USING GIST (
36+
"group_id" gist_int8_ops WITH =,
37+
(TSTZRANGE("date_started", "date_ended", '[)')) WITH &&
38+
);""",
39+
use_statement_timeout=False,
40+
hints={"tables": ["sentry_groupopenperiod"]},
41+
),
42+
]

src/sentry/models/groupopenperiod.py

+16-13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from django.conf import settings
2-
from django.contrib.postgres.constraints import ExclusionConstraint
3-
from django.contrib.postgres.fields import DateTimeRangeField, RangeBoundary, RangeOperators
2+
from django.contrib.postgres.fields import DateTimeRangeField
43
from django.db import models
54
from django.utils import timezone
65

@@ -44,16 +43,20 @@ class Meta:
4443
# get all open periods since a certain date
4544
models.Index(fields=("group", "date_started")),
4645
)
47-
constraints = (
48-
ExclusionConstraint(
49-
name="exclude_open_period_overlap",
50-
expressions=[
51-
(
52-
TsTzRange("date_started", "date_ended", RangeBoundary()),
53-
RangeOperators.OVERLAPS,
54-
)
55-
],
56-
),
57-
)
46+
47+
# This constraint is applied in the db but can't be represented in the model because Django
48+
# doesn't support specifying the opsclass needed to use the bigint field (group_id) in the constraint.
49+
# constraints = (
50+
# ExclusionConstraint(
51+
# name="exclude_open_period_overlap",
52+
# expressions=[
53+
# (models.F("group"), RangeOperators.EQUAL),
54+
# (
55+
# TsTzRange("date_started", "date_ended", RangeBoundary()),
56+
# RangeOperators.OVERLAPS,
57+
# ),
58+
# ],
59+
# ),
60+
# )
5861

5962
__repr__ = sane_repr("project_id", "group_id", "date_started", "date_ended", "user_id")

0 commit comments

Comments
 (0)