migrations: Fix migration 0574 to handle edge-case DirectMessageGroups.

The Django ORM query would not work correctly for pathological
DirectMessageGroups with no Subscription rows. When the Subquery gave
empty results, the UPDATE would set group_size to null - when the point
of the migration was exactly to make sure this column is always set and
allow making the column non-nullable in 0575.

Such DirectMessageGroups can occur as a result doing .delete() on all
UserProfiles that were in the group - or by doing realm deletion via
either .delete() or `manage.py delete_realm`.

The natural choice for group_size of these objects is 0. The simple SQL
query naturally achieves this result, without needing any special
handling for this case.
This commit is contained in:
Mateusz Mandera
2025-03-25 17:08:48 +08:00
committed by Tim Abbott
parent c517e95e6b
commit 60e166bcd0

View File

@@ -1,9 +1,8 @@
# Generated by Django 5.0.6 on 2024-08-16 06:44 # Generated by Django 5.0.6 on 2024-08-16 06:44
from django.db import migrations, transaction from django.db import connection, migrations
from django.db.backends.base.schema import BaseDatabaseSchemaEditor from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from django.db.migrations.state import StateApps from django.db.migrations.state import StateApps
from django.db.models import Count, Model, OuterRef, Subquery
BATCH_SIZE = 1000 BATCH_SIZE = 1000
@@ -11,9 +10,7 @@ BATCH_SIZE = 1000
def backfill_group_size_field_for_direct_message_groups( def backfill_group_size_field_for_direct_message_groups(
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
) -> None: ) -> None:
DirectMessageGroup = apps.get_model("zerver", "DirectMessageGroup")
Recipient = apps.get_model("zerver", "Recipient") Recipient = apps.get_model("zerver", "Recipient")
Subscription = apps.get_model("zerver", "Subscription")
RECIPIENT_DIRECT_MESSAGE_GROUP = 3 RECIPIENT_DIRECT_MESSAGE_GROUP = 3
@@ -35,31 +32,35 @@ def backfill_group_size_field_for_direct_message_groups(
while recipient_id_lower_bound <= max_recipient_id: while recipient_id_lower_bound <= max_recipient_id:
do_backfill_group_size_field_for_direct_message_groups( do_backfill_group_size_field_for_direct_message_groups(
Subscription,
DirectMessageGroup,
recipient_id_lower_bound, recipient_id_lower_bound,
min(recipient_id_lower_bound + BATCH_SIZE, max_recipient_id), min(recipient_id_lower_bound + BATCH_SIZE, max_recipient_id),
) )
recipient_id_lower_bound += BATCH_SIZE + 1 recipient_id_lower_bound += BATCH_SIZE + 1
@transaction.atomic
def do_backfill_group_size_field_for_direct_message_groups( def do_backfill_group_size_field_for_direct_message_groups(
subscription_model: type[Model],
direct_message_group_model: type[Model],
recipient_id_lower_bound: int, recipient_id_lower_bound: int,
recipient_id_upper_bound: int, recipient_id_upper_bound: int,
) -> None: ) -> None:
direct_message_group_sub_size = ( with connection.cursor() as cursor:
subscription_model._default_manager.filter(recipient=OuterRef("recipient")) update_query = """
.values("recipient") UPDATE zerver_huddle
.annotate(count=Count("id")) SET group_size = (
.values("count")[:1] SELECT COUNT(*)
FROM zerver_subscription
WHERE zerver_subscription.recipient_id = zerver_huddle.recipient_id
) )
WHERE zerver_huddle.recipient_id BETWEEN %(lower_bound)s AND %(upper_bound)s
AND zerver_huddle.group_size IS NULL
"""
direct_message_group_model._default_manager.filter( cursor.execute(
recipient__id__range=(recipient_id_lower_bound, recipient_id_upper_bound), group_size=None update_query,
).update(group_size=Subquery(direct_message_group_sub_size)) {
"lower_bound": recipient_id_lower_bound,
"upper_bound": recipient_id_upper_bound,
},
)
class Migration(migrations.Migration): class Migration(migrations.Migration):