Skip to content

Commit 781e710

Browse files
WaVEVtimgraham
authored andcommitted
INTPYTHON-804 Prevent QuerySet.union() queries from duplicating $project
1 parent 06894c5 commit 781e710

File tree

4 files changed

+78
-2
lines changed

4 files changed

+78
-2
lines changed

django_mongodb_backend/compiler.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,10 @@ def get_combinator_queries(self):
625625
fields[expr.alias] = 1
626626
else:
627627
fields[alias] = f"${ref}" if alias != ref else 1
628-
inner_pipeline.append({"$project": fields})
628+
# Avoid duplicating the same $project stage when reusing subquery
629+
# projections.
630+
if not inner_pipeline or inner_pipeline[-1] != {"$project": fields}:
631+
inner_pipeline.append({"$project": fields})
629632
# Combine query with the current combinator pipeline.
630633
if combinator_pipeline:
631634
combinator_pipeline.append(

docs/releases/5.2.x.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ New features
1515
Bug fixes
1616
---------
1717

18-
- ...
18+
- Prevented ``QuerySet.union()`` queries from duplicating the ``$project``
19+
pipeline.
1920

2021
5.2.2
2122
=====

tests/queries_/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ def __str__(self):
1313
class Book(models.Model):
1414
title = models.CharField(max_length=10)
1515
author = models.ForeignKey(Author, models.CASCADE)
16+
isbn = models.CharField(max_length=13)
1617

1718
def __str__(self):
1819
return self.title
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from django.test import TestCase
2+
3+
from django_mongodb_backend.test import MongoTestCaseMixin
4+
5+
from .models import Book
6+
7+
8+
class UnionTests(MongoTestCaseMixin, TestCase):
9+
def test_union_simple_conditions(self):
10+
with self.assertNumQueries(1) as ctx:
11+
list(Book.objects.filter(title="star wars").union(Book.objects.filter(isbn__in="1234")))
12+
self.assertAggregateQuery(
13+
ctx.captured_queries[0]["sql"],
14+
"queries__book",
15+
[
16+
{"$match": {"title": "star wars"}},
17+
{"$project": {"_id": 1, "author_id": 1, "title": 1, "isbn": 1}},
18+
{
19+
"$unionWith": {
20+
"coll": "queries__book",
21+
"pipeline": [
22+
{"$match": {"isbn": {"$in": ("1", "2", "3", "4")}}},
23+
{"$project": {"_id": 1, "author_id": 1, "title": 1, "isbn": 1}},
24+
],
25+
}
26+
},
27+
{
28+
"$group": {
29+
"_id": {
30+
"_id": "$_id",
31+
"author_id": "$author_id",
32+
"title": "$title",
33+
"isbn": "$isbn",
34+
}
35+
}
36+
},
37+
{
38+
"$addFields": {
39+
"_id": "$_id._id",
40+
"author_id": "$_id.author_id",
41+
"title": "$_id.title",
42+
"isbn": "$_id.isbn",
43+
}
44+
},
45+
],
46+
)
47+
48+
def test_union_all_simple_conditions(self):
49+
with self.assertNumQueries(1) as ctx:
50+
list(
51+
Book.objects.filter(title="star wars").union(
52+
Book.objects.filter(isbn="1234"), all=True
53+
)
54+
)
55+
self.assertAggregateQuery(
56+
ctx.captured_queries[0]["sql"],
57+
"queries__book",
58+
[
59+
{"$match": {"title": "star wars"}},
60+
{"$project": {"_id": 1, "author_id": 1, "title": 1, "isbn": 1}},
61+
{
62+
"$unionWith": {
63+
"coll": "queries__book",
64+
"pipeline": [
65+
{"$match": {"isbn": "1234"}},
66+
{"$project": {"_id": 1, "author_id": 1, "title": 1, "isbn": 1}},
67+
],
68+
}
69+
},
70+
],
71+
)

0 commit comments

Comments
 (0)