1
0
mirror of https://github.com/django/django.git synced 2025-03-28 10:10:45 +00:00

Refs #26780 -- Made prefetch_related() don't use window expressions fo sliced queries if not supported.

This commit is contained in:
Mariusz Felisiak 2022-09-06 05:54:35 +02:00 committed by GitHub
parent 19e0587ee5
commit 69fa2e8eb2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 35 additions and 7 deletions

View File

@ -64,7 +64,13 @@ and two directions (forward and reverse) for a total of six combinations.
""" """
from django.core.exceptions import FieldError from django.core.exceptions import FieldError
from django.db import DEFAULT_DB_ALIAS, connections, router, transaction from django.db import (
DEFAULT_DB_ALIAS,
NotSupportedError,
connections,
router,
transaction,
)
from django.db.models import Q, Window, signals from django.db.models import Q, Window, signals
from django.db.models.functions import RowNumber from django.db.models.functions import RowNumber
from django.db.models.lookups import GreaterThan, LessThanOrEqual from django.db.models.lookups import GreaterThan, LessThanOrEqual
@ -85,13 +91,16 @@ class ForeignKeyDeferredAttribute(DeferredAttribute):
def _filter_prefetch_queryset(queryset, field_name, instances): def _filter_prefetch_queryset(queryset, field_name, instances):
predicate = Q(**{f"{field_name}__in": instances}) predicate = Q(**{f"{field_name}__in": instances})
db = queryset._db or DEFAULT_DB_ALIAS
if queryset.query.is_sliced: if queryset.query.is_sliced:
if not connections[db].features.supports_over_clause:
raise NotSupportedError(
"Prefetching from a limited queryset is only supported on backends "
"that support window functions."
)
low_mark, high_mark = queryset.query.low_mark, queryset.query.high_mark low_mark, high_mark = queryset.query.low_mark, queryset.query.high_mark
order_by = [ order_by = [
expr expr for expr, _ in queryset.query.get_compiler(using=db).get_order_by()
for expr, _ in queryset.query.get_compiler(
using=queryset._db or DEFAULT_DB_ALIAS
).get_order_by()
] ]
window = Window(RowNumber(), partition_by=field_name, order_by=order_by) window = Window(RowNumber(), partition_by=field_name, order_by=order_by)
predicate &= GreaterThan(window, low_mark) predicate &= GreaterThan(window, low_mark)

View File

@ -2,11 +2,16 @@ from unittest import mock
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.db import connection from django.db import NotSupportedError, connection
from django.db.models import Prefetch, QuerySet, prefetch_related_objects from django.db.models import Prefetch, QuerySet, prefetch_related_objects
from django.db.models.query import get_prefetcher from django.db.models.query import get_prefetcher
from django.db.models.sql import Query from django.db.models.sql import Query
from django.test import TestCase, override_settings from django.test import (
TestCase,
override_settings,
skipIfDBFeature,
skipUnlessDBFeature,
)
from django.test.utils import CaptureQueriesContext, ignore_warnings from django.test.utils import CaptureQueriesContext, ignore_warnings
from django.utils.deprecation import RemovedInDjango50Warning from django.utils.deprecation import RemovedInDjango50Warning
@ -1911,6 +1916,7 @@ class NestedPrefetchTests(TestCase):
class PrefetchLimitTests(TestDataMixin, TestCase): class PrefetchLimitTests(TestDataMixin, TestCase):
@skipUnlessDBFeature("supports_over_clause")
def test_m2m_forward(self): def test_m2m_forward(self):
authors = Author.objects.all() # Meta.ordering authors = Author.objects.all() # Meta.ordering
with self.assertNumQueries(3): with self.assertNumQueries(3):
@ -1924,6 +1930,7 @@ class PrefetchLimitTests(TestDataMixin, TestCase):
with self.subTest(book=book): with self.subTest(book=book):
self.assertEqual(book.authors_sliced, list(book.authors.all())[1:]) self.assertEqual(book.authors_sliced, list(book.authors.all())[1:])
@skipUnlessDBFeature("supports_over_clause")
def test_m2m_reverse(self): def test_m2m_reverse(self):
books = Book.objects.order_by("title") books = Book.objects.order_by("title")
with self.assertNumQueries(3): with self.assertNumQueries(3):
@ -1937,6 +1944,7 @@ class PrefetchLimitTests(TestDataMixin, TestCase):
with self.subTest(author=author): with self.subTest(author=author):
self.assertEqual(author.books_sliced, list(author.books.all())[1:2]) self.assertEqual(author.books_sliced, list(author.books.all())[1:2])
@skipUnlessDBFeature("supports_over_clause")
def test_foreignkey_reverse(self): def test_foreignkey_reverse(self):
authors = Author.objects.order_by("-name") authors = Author.objects.order_by("-name")
with self.assertNumQueries(3): with self.assertNumQueries(3):
@ -1960,6 +1968,7 @@ class PrefetchLimitTests(TestDataMixin, TestCase):
list(book.first_time_authors.all())[1:], list(book.first_time_authors.all())[1:],
) )
@skipUnlessDBFeature("supports_over_clause")
def test_reverse_ordering(self): def test_reverse_ordering(self):
authors = Author.objects.reverse() # Reverse Meta.ordering authors = Author.objects.reverse() # Reverse Meta.ordering
with self.assertNumQueries(3): with self.assertNumQueries(3):
@ -1972,3 +1981,13 @@ class PrefetchLimitTests(TestDataMixin, TestCase):
for book in books: for book in books:
with self.subTest(book=book): with self.subTest(book=book):
self.assertEqual(book.authors_sliced, list(book.authors.all())[1:]) self.assertEqual(book.authors_sliced, list(book.authors.all())[1:])
@skipIfDBFeature("supports_over_clause")
def test_window_not_supported(self):
authors = Author.objects.all()
msg = (
"Prefetching from a limited queryset is only supported on backends that "
"support window functions."
)
with self.assertRaisesMessage(NotSupportedError, msg):
list(Book.objects.prefetch_related(Prefetch("authors", authors[1:])))