1+ from bson import ObjectId
2+ from django .db import models
13from django .test import TestCase
24
3- from .models import Author , Book
5+ from .models import Author , Book , Order , Tag
46
57
68class 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