#37427 assigned Bug

GenericRelation querysets fail as subqueries after implicit database routing

Reported by: Lev Zlobin Owned by: Lev Zlobin
Component: contrib.contenttypes Version: dev
Severity: Normal Keywords: GenericRelation multiple-databases subquery routing replicas
Cc: Triage Stage: Unreviewed
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Generic relation managers eagerly bind the database selected by the read router, even when the caller hasn't selected a database explicitly. When such a queryset is used in an in subquery on another connection, this implicit selection triggers the cross-database ValueError.

Ordinary related managers pass the related instance as a router hint without binding an implicit database choice.

Reproduction

Configure two databases, default and other, with the application's tables and django.contrib.contenttypes migrated on both. The following deterministic router illustrates the issue in a primary/replica configuration:

class ReplicaRouter:
    def db_for_read(self, model, instance=None, **hints):
        if instance is not None:
            return instance._state.db or "other"
        return "other"

    def db_for_write(self, model, **hints):
        return "default"


DATABASE_ROUTERS = [ReplicaRouter()]

Models in an installed application:

from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.db import models


class Review(models.Model):
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey()


class Book(models.Model):
    title = models.CharField(max_length=100)
    reviews = GenericRelation(Review)

After migrating both databases, run:

book = Book.objects.create(title="Example")
Review.objects.create(content_object=book)

inner = book.reviews.all()
outer = Review.objects.filter(pk__in=inner)

# No using() or db_manager() call was made.
assert inner._db == "default"
assert outer._db is None
assert outer.db == "other"

list(outer)

Actual result:

ValueError: Subqueries aren't allowed across different databases. Force the inner query to be evaluated using `list(inner_query)`.

Expected result: the subquery should execute on the outer query's connection (other). Evaluating the inner queryset independently should retain instance-aware routing to default.

Proposed fix

In GenericRelatedObjectManager._apply_rel_filters(), pass the related instance as a queryset routing hint and call using(self._db) only when the manager has an explicitly selected database, following the existing foreign-key and many-to-many related managers.

Explicit using() and db_manager() selections remain respected, including the existing ValueError when an explicitly selected inner database conflicts with the outer connection.

The patch includes a regression test for implicit subquery execution, standalone routing, and explicit database selections, plus documentation explaining database selection for in subqueries.

Verified against the main development branch. The selected SQLite suites completed successfully (1,433 tests, including 20 skips and 5 expected failures). Formatting, lint checks, and the Sphinx HTML documentation build also passed.

Patch: ​https://github.com/django/django/pull/22154

Change History (0)

Note: See TracTickets for help on using tickets.
Back to Top