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:
parent
19e0587ee5
commit
69fa2e8eb2
@ -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)
|
||||||
|
@ -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:])))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user