Skip to content

Commit f1ce6d4

Browse files
committed
Add unit test
1 parent 7635cbe commit f1ce6d4

File tree

1 file changed

+159
-1
lines changed

1 file changed

+159
-1
lines changed

tests/queries_/test_mql.py

Lines changed: 159 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
from bson import ObjectId
2+
from django.db import models
13
from django.test import TestCase
24

3-
from .models import Author, Book
5+
from .models import Author, Book, Order, Tag
46

57

68
class MQLTests(TestCase):
@@ -25,3 +27,159 @@ def test_join(self):
2527
"{'$unwind': '$queries__author'}, "
2628
"{'$match': {'$expr': {'$eq': ['$queries__author.name', 'Bob']}}}])",
2729
)
30+
31+
32+
class LookupConditionPushdownTests(TestCase):
33+
def test_filter_on_local_and_related_fields(self):
34+
with self.assertNumQueries(1) as ctx:
35+
list(Book.objects.filter(title="Don", author__name="John"))
36+
query = ctx.captured_queries[0]["sql"]
37+
self.assertEqual(
38+
query,
39+
"db.queries__book.aggregate(["
40+
"{'$lookup': {'from': 'queries__author', "
41+
"'let': {'parent__field__0': '$author_id'}, 'pipeline': ["
42+
"{'$match': {'$expr': {'$and': [{'$eq': ['$$parent__field__0', "
43+
"'$_id']}, {'$eq': ['$name', 'John']}]}}}], 'as': "
44+
"'queries__author'}}, {'$unwind': '$queries__author'}, {'$match': "
45+
"{'$expr': {'$and': [{'$eq': ['$queries__author.name', 'John']}, "
46+
"{'$eq': ['$title', 'Don']}]}}}])",
47+
)
48+
49+
def test_or_mixing_local_and_related_fields_is_not_pushable(self):
50+
with self.assertNumQueries(1) as ctx:
51+
list(Book.objects.filter(models.Q(title="Don") | models.Q(author__name="John")))
52+
query = ctx.captured_queries[0]["sql"]
53+
self.assertEqual(
54+
query,
55+
"db.queries__book.aggregate([{'$lookup': {'from': "
56+
"'queries__author', 'let': {'parent__field__0': '$author_id'}, "
57+
"'pipeline': [{'$match': {'$expr': {'$and': [{'$eq': "
58+
"['$$parent__field__0', '$_id']}]}}}], 'as': 'queries__author'}}, "
59+
"{'$unwind': '$queries__author'}, {'$match': {'$expr': {'$or': "
60+
"[{'$eq': ['$title', 'Don']}, {'$eq': ['$queries__author.name', "
61+
"'John']}]}}}])",
62+
)
63+
64+
def test_filter_on_self_join_fields(self):
65+
with self.assertNumQueries(1) as ctx:
66+
list(
67+
Tag.objects.filter(
68+
parent__name="parent", parent__group_id=ObjectId("6891ff7822e475eddc20f159")
69+
)
70+
)
71+
query = ctx.captured_queries[0]["sql"]
72+
self.assertEqual(
73+
query,
74+
"db.queries__tag.aggregate([{'$lookup': {'from': 'queries__tag', 'let': "
75+
"{'parent__field__0': '$parent_id'}, 'pipeline': [{'$match': {'$expr': "
76+
"{'$and': [{'$eq': ['$$parent__field__0', '$_id']}, {'$and': [{'$eq': "
77+
"['$group_id', ObjectId('6891ff7822e475eddc20f159')]}, {'$eq': ['$name', "
78+
"'parent']}]}]}}}], 'as': 'T2'}}, {'$unwind': '$T2'}, {'$match': {'$expr': "
79+
"{'$and': [{'$eq': ['$T2.group_id', ObjectId('6891ff7822e475eddc20f159')]}, "
80+
"{'$eq': ['$T2.name', 'parent']}]}}}])",
81+
)
82+
83+
def test_filter_on_reverse_foreignkey_relation(self):
84+
with self.assertNumQueries(1) as ctx:
85+
list(Order.objects.filter(items__status=ObjectId("6891ff7822e475eddc20f159")))
86+
query = ctx.captured_queries[0]["sql"]
87+
self.assertEqual(
88+
query,
89+
"db.queries__order.aggregate([{'$lookup': {'from': "
90+
"'queries__orderitem', 'let': {'parent__field__0': '$_id'}, "
91+
"'pipeline': [{'$match': {'$expr': {'$and': [{'$eq': "
92+
"['$$parent__field__0', '$order_id']}, {'$eq': ['$status', "
93+
"ObjectId('6891ff7822e475eddc20f159')]}]}}}], 'as': "
94+
"'queries__orderitem'}}, {'$unwind': '$queries__orderitem'}, "
95+
"{'$match': {'$expr': {'$eq': ['$queries__orderitem.status', "
96+
"ObjectId('6891ff7822e475eddc20f159')]}}}, "
97+
"{'$addFields': {'_id': '$_id'}}, {'$sort': SON([('_id', 1)])}])",
98+
)
99+
100+
def test_filter_on_local_and_nested_join_fields(self):
101+
with self.assertNumQueries(1) as ctx:
102+
list(
103+
Order.objects.filter(
104+
name="My Order",
105+
items__order__name="My Order",
106+
items__status=ObjectId("6891ff7822e475eddc20f159"),
107+
)
108+
)
109+
query = ctx.captured_queries[0]["sql"]
110+
self.assertEqual(
111+
query,
112+
"db.queries__order.aggregate([{'$lookup': {'from': "
113+
"'queries__orderitem', 'let': {'parent__field__0': '$_id'}, "
114+
"'pipeline': [{'$match': {'$expr': {'$and': [{'$eq': "
115+
"['$$parent__field__0', '$order_id']}, {'$eq': ['$status', "
116+
"ObjectId('6891ff7822e475eddc20f159')]}]}}}], 'as': "
117+
"'queries__orderitem'}}, {'$unwind': '$queries__orderitem'}, "
118+
"{'$lookup': {'from': 'queries__order', 'let': "
119+
"{'parent__field__0': '$queries__orderitem.order_id'}, "
120+
"'pipeline': [{'$match': {'$expr': {'$and': [{'$eq': "
121+
"['$$parent__field__0', '$_id']}, {'$eq': ['$name', 'My Order']}]}"
122+
"}}], 'as': 'T3'}}, {'$unwind': '$T3'}, {'$match': {'$expr': "
123+
"{'$and': [{'$eq': ['$T3.name', 'My Order']}, {'$eq': "
124+
"['$queries__orderitem.status', "
125+
"ObjectId('6891ff7822e475eddc20f159')]}, {'$eq': ['$name', "
126+
"'My Order']}]}}}, {'$addFields': {'_id': '$_id'}}, "
127+
"{'$sort': SON([('_id', 1)])}])",
128+
)
129+
130+
def test_negated_related_filter_is_not_pushable(self):
131+
with self.assertNumQueries(1) as ctx:
132+
list(Book.objects.filter(~models.Q(author__name="John")))
133+
query = ctx.captured_queries[0]["sql"]
134+
self.assertEqual(
135+
query,
136+
"db.queries__book.aggregate([{'$lookup': {'from': "
137+
"'queries__author', 'let': {'parent__field__0': '$author_id'}, "
138+
"'pipeline': [{'$match': {'$expr': {'$and': [{'$eq': "
139+
"['$$parent__field__0', '$_id']}]}}}], 'as': 'queries__author'}}, "
140+
"{'$unwind': '$queries__author'}, {'$match': {'$expr': "
141+
"{'$not': {'$eq': ['$queries__author.name', 'John']}}}}])",
142+
)
143+
144+
def test_or_on_local_fields_only(self):
145+
with self.assertNumQueries(1) as ctx:
146+
list(Order.objects.filter(models.Q(name="A") | models.Q(name="B")))
147+
query = ctx.captured_queries[0]["sql"]
148+
self.assertEqual(
149+
query,
150+
"db.queries__order.aggregate([{'$match': {'$expr': {'$or': "
151+
"[{'$eq': ['$name', 'A']}, {'$eq': ['$name', 'B']}]}}}, "
152+
"{'$addFields': {'_id': '$_id'}}, {'$sort': SON([('_id', 1)])}])",
153+
)
154+
155+
def test_or_with_mixed_pushable_and_non_pushable_fields(self):
156+
with self.assertNumQueries(1) as ctx:
157+
list(Book.objects.filter(models.Q(author__name="John") | models.Q(title="Don")))
158+
query = ctx.captured_queries[0]["sql"]
159+
self.assertEqual(
160+
query,
161+
"db.queries__book.aggregate([{'$lookup': {'from': "
162+
"'queries__author', 'let': {'parent__field__0': '$author_id'}, "
163+
"'pipeline': [{'$match': {'$expr': {'$and': [{'$eq': "
164+
"['$$parent__field__0', '$_id']}]}}}], 'as': 'queries__author'}}, "
165+
"{'$unwind': '$queries__author'}, {'$match': {'$expr': {'$or': "
166+
"[{'$eq': ['$queries__author.name', 'John']}, {'$eq': ['$title', "
167+
"'Don']}]}}}])",
168+
)
169+
170+
def test_push_equality_between_parent_and_child_fields(self):
171+
with self.assertNumQueries(1) as ctx:
172+
list(Order.objects.filter(items__status=models.F("id")))
173+
query = ctx.captured_queries[0]["sql"]
174+
self.assertEqual(
175+
query,
176+
"db.queries__order.aggregate([{'$lookup': {'from': "
177+
"'queries__orderitem', 'let': {'parent__field__0': '$_id', "
178+
"'parent__field__1': '$queries__orderitem.status'}, 'pipeline': "
179+
"[{'$match': {'$expr': {'$and': [{'$eq': ['$$parent__field__0', "
180+
"'$order_id']}, {'$eq': ['$status', '$$parent__field__1']}]}}}], "
181+
"'as': 'queries__orderitem'}}, {'$unwind': "
182+
"'$queries__orderitem'}, {'$match': {'$expr': {'$eq': "
183+
"['$queries__orderitem.status', '$_id']}}}, {'$addFields': "
184+
"{'_id': '$_id'}}, {'$sort': SON([('_id', 1)])}])",
185+
)

0 commit comments

Comments
 (0)