1
+ from bson import ObjectId
2
+ from django .db import models
1
3
from django .test import TestCase
2
4
3
- from .models import Author , Book
5
+ from .models import Author , Book , Order , Tag
4
6
5
7
6
8
class MQLTests (TestCase ):
@@ -25,3 +27,159 @@ def test_join(self):
25
27
"{'$unwind': '$queries__author'}, "
26
28
"{'$match': {'$expr': {'$eq': ['$queries__author.name', 'Bob']}}}])" ,
27
29
)
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