Skip to content

Commit

Permalink
Fixed #28107 -- Added DatabaseFeatures.allows_group_by_selected_pks_o…
Browse files Browse the repository at this point in the history
…n_model() to allow enabling optimization for unmanaged models.
  • Loading branch information
Tasssadar authored and felixxm committed Sep 9, 2019
1 parent 10d5e43 commit b1d37fe
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 1 deletion.
5 changes: 5 additions & 0 deletions django/db/backends/base/features.py
Expand Up @@ -313,3 +313,8 @@ def supports_transactions(self):
count, = cursor.fetchone()
cursor.execute('DROP TABLE ROLLBACK_TEST')
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
6 changes: 5 additions & 1 deletion django/db/models/sql/compiler.py
Expand Up @@ -171,7 +171,11 @@ def collapse_group_by(self, expressions, having):
# database views on which the optimization might not be allowed.
pks = {
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}
expressions = [
Expand Down
11 changes: 11 additions & 0 deletions docs/releases/3.0.txt
Expand Up @@ -355,6 +355,17 @@ Models

* :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
~~~~~~~~~~~~~~~~~~~~~~

Expand Down
27 changes: 27 additions & 0 deletions tests/aggregation_regress/tests.py
Expand Up @@ -1333,6 +1333,33 @@ def assertQuerysetResults(queryset):
self.assertIn(field.name, grouping[index + 1][0])
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):
qs = Author.objects.annotate(Count('book_contact_set__contact'))
self.assertIn(' JOIN ', str(qs.query))
Expand Down

0 comments on commit b1d37fe

Please sign in to comment.