Skip to content

Commit 184d5e6

Browse files
committed
Add unit test
1 parent b90d50e commit 184d5e6

File tree

1 file changed

+330
-0
lines changed

1 file changed

+330
-0
lines changed

tests/queries_/test_mql.py

Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,3 +749,333 @@ def test_or_with_mixed_pushable_and_non_pushable_fields(self):
749749
{"$match": {"$or": [{"queries__reader.name": "Alice"}, {"name": "Central"}]}},
750750
],
751751
)
752+
753+
def test_double_negation_pushdown(self):
754+
a1 = Author.objects.create(name="Alice")
755+
a2 = Author.objects.create(name="Bob")
756+
b1 = Book.objects.create(title="Book1", author=a1, isbn="111")
757+
Book.objects.create(title="Book2", author=a2, isbn="222")
758+
b3 = Book.objects.create(title="Book3", author=a1, isbn="333")
759+
expected = [b1, b3]
760+
with self.assertNumQueries(1) as ctx:
761+
self.assertSequenceEqual(
762+
Book.objects.filter(~(~models.Q(author__name="Alice") | models.Q(title="Book4"))),
763+
expected,
764+
)
765+
self.assertAggregateQuery(
766+
ctx.captured_queries[0]["sql"],
767+
"queries__book",
768+
[
769+
{
770+
"$lookup": {
771+
"from": "queries__author",
772+
"let": {"parent__field__0": "$author_id"},
773+
"pipeline": [
774+
{
775+
"$match": {
776+
"$and": [
777+
{
778+
"$expr": {
779+
"$and": [{"$eq": ["$$parent__field__0", "$_id"]}]
780+
}
781+
},
782+
{"name": "Alice"},
783+
]
784+
}
785+
}
786+
],
787+
"as": "queries__author",
788+
}
789+
},
790+
{"$unwind": "$queries__author"},
791+
{
792+
"$match": {
793+
"$nor": [
794+
{
795+
"$or": [
796+
{"$nor": [{"queries__author.name": "Alice"}]},
797+
{"title": "Book4"},
798+
]
799+
}
800+
]
801+
}
802+
},
803+
],
804+
)
805+
806+
def test_partial_or_pushdown(self):
807+
a1 = Author.objects.create(name="Alice")
808+
a2 = Author.objects.create(name="Bob")
809+
a3 = Author.objects.create(name="Charlie")
810+
b1 = Book.objects.create(title="B1", author=a1, isbn="111")
811+
b2 = Book.objects.create(title="B2", author=a2, isbn="111")
812+
Book.objects.create(title="B3", author=a3, isbn="222")
813+
condition = models.Q(author__name="Alice") | (
814+
models.Q(author__name="Bob") & models.Q(isbn="111")
815+
)
816+
expected = [b1, b2]
817+
with self.assertNumQueries(1) as ctx:
818+
self.assertSequenceEqual(list(Book.objects.filter(condition)), expected)
819+
self.assertAggregateQuery(
820+
ctx.captured_queries[0]["sql"],
821+
"queries__book",
822+
[
823+
{
824+
"$lookup": {
825+
"as": "queries__author",
826+
"from": "queries__author",
827+
"let": {"parent__field__0": "$author_id"},
828+
"pipeline": [
829+
{
830+
"$match": {
831+
"$and": [
832+
{
833+
"$expr": {
834+
"$and": [{"$eq": ["$$parent__field__0", "$_id"]}]
835+
}
836+
},
837+
{"$or": [{"name": "Alice"}, {"name": "Bob"}]},
838+
]
839+
}
840+
}
841+
],
842+
}
843+
},
844+
{"$unwind": "$queries__author"},
845+
{
846+
"$match": {
847+
"$or": [
848+
{"queries__author.name": "Alice"},
849+
{"$and": [{"queries__author.name": "Bob"}, {"isbn": "111"}]},
850+
]
851+
}
852+
},
853+
],
854+
)
855+
856+
def test_multiple_ors_with_partial_pushdown(self):
857+
a1 = Author.objects.create(name="Alice")
858+
a2 = Author.objects.create(name="Bob")
859+
a3 = Author.objects.create(name="Charlie")
860+
a4 = Author.objects.create(name="David")
861+
b1 = Book.objects.create(title="B1", author=a1, isbn="111")
862+
b2 = Book.objects.create(title="B2", author=a1, isbn="222")
863+
b3 = Book.objects.create(title="B3", author=a2, isbn="333")
864+
b4 = Book.objects.create(title="B4", author=a3, isbn="333")
865+
Book.objects.create(title="B5", author=a4, isbn="444")
866+
867+
left = models.Q(author__name="Alice") & (models.Q(isbn="111") | models.Q(isbn="222"))
868+
right = (models.Q(author__name="Bob") | models.Q(author__name="Charlie")) & models.Q(
869+
isbn="333"
870+
)
871+
condition = left | right
872+
873+
expected = [b1, b2, b3, b4]
874+
with self.assertNumQueries(1) as ctx:
875+
self.assertSequenceEqual(list(Book.objects.filter(condition)), expected)
876+
877+
self.assertAggregateQuery(
878+
ctx.captured_queries[0]["sql"],
879+
"queries__book",
880+
[
881+
{
882+
"$lookup": {
883+
"as": "queries__author",
884+
"from": "queries__author",
885+
"let": {"parent__field__0": "$author_id"},
886+
"pipeline": [
887+
{
888+
"$match": {
889+
"$and": [
890+
{
891+
"$expr": {
892+
"$and": [{"$eq": ["$$parent__field__0", "$_id"]}]
893+
}
894+
},
895+
{
896+
"$or": [
897+
{"name": "Alice"},
898+
{"$or": [{"name": "Bob"}, {"name": "Charlie"}]},
899+
]
900+
},
901+
]
902+
}
903+
}
904+
],
905+
}
906+
},
907+
{"$unwind": "$queries__author"},
908+
{
909+
"$match": {
910+
"$or": [
911+
{
912+
"$and": [
913+
{"queries__author.name": "Alice"},
914+
{"$or": [{"isbn": "111"}, {"isbn": "222"}]},
915+
]
916+
},
917+
{
918+
"$and": [
919+
{
920+
"$or": [
921+
{"queries__author.name": "Bob"},
922+
{"queries__author.name": "Charlie"},
923+
]
924+
},
925+
{"isbn": "333"},
926+
]
927+
},
928+
]
929+
}
930+
},
931+
],
932+
)
933+
934+
def test_self_join_tag_three_levels_none_pushable(self):
935+
t1 = Tag.objects.create(name="T1")
936+
t2 = Tag.objects.create(name="T2", parent=t1)
937+
t3 = Tag.objects.create(name="T3", parent=t2)
938+
Tag.objects.create(name="T4", parent=t3)
939+
Tag.objects.create(name="T5", parent=t1)
940+
t6 = Tag.objects.create(name="T6", parent=t2)
941+
cond = (
942+
models.Q(name="T1") | models.Q(parent__name="T2") | models.Q(parent__parent__name="T3")
943+
)
944+
expected = [t1, t3, t6]
945+
with self.assertNumQueries(1) as ctx:
946+
self.assertSequenceEqual(list(Tag.objects.filter(cond)), expected)
947+
self.assertAggregateQuery(
948+
ctx.captured_queries[0]["sql"],
949+
"queries__tag",
950+
# Django translate this kind of queries into left outer join
951+
[
952+
{
953+
"$lookup": {
954+
"as": "T2",
955+
"from": "queries__tag",
956+
"let": {"parent__field__0": "$parent_id"},
957+
"pipeline": [
958+
{
959+
"$match": {
960+
"$expr": {"$and": [{"$eq": ["$$parent__field__0", "$_id"]}]}
961+
}
962+
}
963+
],
964+
}
965+
},
966+
{
967+
"$set": {
968+
"T2": {
969+
"$cond": {
970+
"else": "$T2",
971+
"if": {
972+
"$or": [
973+
{"$eq": [{"$type": "$T2"}, "missing"]},
974+
{"$eq": [{"$size": "$T2"}, 0]},
975+
]
976+
},
977+
"then": [{}],
978+
}
979+
}
980+
}
981+
},
982+
{"$unwind": "$T2"},
983+
{
984+
"$lookup": {
985+
"as": "T3",
986+
"from": "queries__tag",
987+
"let": {"parent__field__0": "$T2.parent_id"},
988+
"pipeline": [
989+
{
990+
"$match": {
991+
"$expr": {"$and": [{"$eq": ["$$parent__field__0", "$_id"]}]}
992+
}
993+
}
994+
],
995+
}
996+
},
997+
{
998+
"$set": {
999+
"T3": {
1000+
"$cond": {
1001+
"else": "$T3",
1002+
"if": {
1003+
"$or": [
1004+
{"$eq": [{"$type": "$T3"}, "missing"]},
1005+
{"$eq": [{"$size": "$T3"}, 0]},
1006+
]
1007+
},
1008+
"then": [{}],
1009+
}
1010+
}
1011+
}
1012+
},
1013+
{"$unwind": "$T3"},
1014+
{"$match": {"$or": [{"name": "T1"}, {"T2.name": "T2"}, {"T3.name": "T3"}]}},
1015+
],
1016+
)
1017+
1018+
def test_self_join_tag_three_levels_pushable(self):
1019+
t1 = Tag.objects.create(name="T1")
1020+
t2 = Tag.objects.create(name="T2", parent=t1)
1021+
t3 = Tag.objects.create(name="T3", parent=t2)
1022+
Tag.objects.create(name="T4", parent=t3)
1023+
Tag.objects.create(name="T5", parent=t1)
1024+
Tag.objects.create(name="T6", parent=t2)
1025+
with self.assertNumQueries(1) as ctx:
1026+
self.assertSequenceEqual(
1027+
list(Tag.objects.filter(name="T1", parent__name="T2", parent__parent__name="T3")),
1028+
[],
1029+
)
1030+
1031+
self.assertAggregateQuery(
1032+
ctx.captured_queries[0]["sql"],
1033+
"queries__tag",
1034+
[
1035+
{
1036+
"$lookup": {
1037+
"as": "T2",
1038+
"from": "queries__tag",
1039+
"let": {"parent__field__0": "$parent_id"},
1040+
"pipeline": [
1041+
{
1042+
"$match": {
1043+
"$and": [
1044+
{
1045+
"$expr": {
1046+
"$and": [{"$eq": ["$$parent__field__0", "$_id"]}]
1047+
}
1048+
},
1049+
{"name": "T2"},
1050+
]
1051+
}
1052+
}
1053+
],
1054+
}
1055+
},
1056+
{"$unwind": "$T2"},
1057+
{
1058+
"$lookup": {
1059+
"as": "T3",
1060+
"from": "queries__tag",
1061+
"let": {"parent__field__0": "$T2.parent_id"},
1062+
"pipeline": [
1063+
{
1064+
"$match": {
1065+
"$and": [
1066+
{
1067+
"$expr": {
1068+
"$and": [{"$eq": ["$$parent__field__0", "$_id"]}]
1069+
}
1070+
},
1071+
{"name": "T3"},
1072+
]
1073+
}
1074+
}
1075+
],
1076+
}
1077+
},
1078+
{"$unwind": "$T3"},
1079+
{"$match": {"$and": [{"name": "T1"}, {"T2.name": "T2"}, {"T3.name": "T3"}]}},
1080+
],
1081+
)

0 commit comments

Comments
 (0)