Fixed #28107 -- Added DatabaseFeatures.allows_group_by_selected_pks_on_model() to allow enabling optimization for unmanaged models.

This commit is contained in:
Vojtech Bocek 2019-07-29 16:40:27 +02:00 committed by Mariusz Felisiak
parent 10d5e439e9
commit b1d37fea8f
4 changed files with 48 additions and 1 deletions

View File

@ -313,3 +313,8 @@ class BaseDatabaseFeatures:
count, = cursor.fetchone() count, = cursor.fetchone()
cursor.execute('DROP TABLE ROLLBACK_TEST') cursor.execute('DROP TABLE ROLLBACK_TEST')
return count == 0 return count == 0
def allows_group_by_selected_pks_on_model(self, model):
if not self.allows_group_by_selected_pks:
return False
return model._meta.managed

View File

@ -171,7 +171,11 @@ class SQLCompiler:
# database views on which the optimization might not be allowed. # database views on which the optimization might not be allowed.
pks = { pks = {
expr for expr in expressions expr for expr in expressions
if hasattr(expr, 'target') and expr.target.primary_key and expr.target.model._meta.managed if (
hasattr(expr, 'target') and
expr.target.primary_key and
self.connection.features.allows_group_by_selected_pks_on_model(expr.target.model)
)
} }
aliases = {expr.alias for expr in pks} aliases = {expr.alias for expr in pks}
expressions = [ expressions = [

View File

@ -355,6 +355,17 @@ Models
* :class:`~django.db.models.CheckConstraint` is now supported on MySQL 8.0.16+. * :class:`~django.db.models.CheckConstraint` is now supported on MySQL 8.0.16+.
* The new ``allows_group_by_selected_pks_on_model()`` method of
``django.db.backends.base.BaseDatabaseFeatures`` allows optimization of
``GROUP BY`` clauses to require only the selected models' primary keys. By
default, it's supported only for managed models on PostgreSQL.
To enable the ``GROUP BY`` primary key-only optimization for unmanaged
models, you have to subclass the PostgreSQL database engine, overriding the
features class ``allows_group_by_selected_pks_on_model()`` method as you
require. See :ref:`Subclassing the built-in database backends
<subclassing-database-backends>` for an example.
Requests and Responses Requests and Responses
~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~

View File

@ -1333,6 +1333,33 @@ class AggregationTests(TestCase):
self.assertIn(field.name, grouping[index + 1][0]) self.assertIn(field.name, grouping[index + 1][0])
assertQuerysetResults(queryset) assertQuerysetResults(queryset)
@skipUnlessDBFeature('allows_group_by_selected_pks')
def test_aggregate_unmanaged_model_as_tables(self):
qs = Book.objects.select_related('contact').annotate(num_authors=Count('authors'))
# Force treating unmanaged models as tables.
with mock.patch(
'django.db.connection.features.allows_group_by_selected_pks_on_model',
return_value=True,
):
with mock.patch.object(Book._meta, 'managed', False), \
mock.patch.object(Author._meta, 'managed', False):
_, _, grouping = qs.query.get_compiler(using='default').pre_sql_setup()
self.assertEqual(len(grouping), 2)
self.assertIn('id', grouping[0][0])
self.assertIn('id', grouping[1][0])
self.assertQuerysetEqual(
qs.order_by('name'),
[
('Artificial Intelligence: A Modern Approach', 2),
('Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp', 1),
('Practical Django Projects', 1),
('Python Web Development with Django', 3),
('Sams Teach Yourself Django in 24 Hours', 1),
('The Definitive Guide to Django: Web Development Done Right', 2),
],
attrgetter('name', 'num_authors'),
)
def test_reverse_join_trimming(self): def test_reverse_join_trimming(self):
qs = Author.objects.annotate(Count('book_contact_set__contact')) qs = Author.objects.annotate(Count('book_contact_set__contact'))
self.assertIn(' JOIN ', str(qs.query)) self.assertIn(' JOIN ', str(qs.query))