Skip to content

Commit d36828c

Browse files
committed
Add unit test
1 parent ae8b727 commit d36828c

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
@@ -567,3 +567,333 @@ def test_or_with_mixed_pushable_and_non_pushable_fields(self):
567567
{"$match": {"$or": [{"queries__reader.name": "Alice"}, {"name": "Central"}]}},
568568
],
569569
)
570+
571+
def test_double_negation_pushdown(self):
572+
a1 = Author.objects.create(name="Alice")
573+
a2 = Author.objects.create(name="Bob")
574+
b1 = Book.objects.create(title="Book1", author=a1, isbn="111")
575+
Book.objects.create(title="Book2", author=a2, isbn="222")
576+
b3 = Book.objects.create(title="Book3", author=a1, isbn="333")
577+
expected = [b1, b3]
578+
with self.assertNumQueries(1) as ctx:
579+
self.assertSequenceEqual(
580+
Book.objects.filter(~(~models.Q(author__name="Alice") | models.Q(title="Book4"))),
581+
expected,
582+
)
583+
self.assertAggregateQuery(
584+
ctx.captured_queries[0]["sql"],
585+
"queries__book",
586+
[
587+
{
588+
"$lookup": {
589+
"from": "queries__author",
590+
"let": {"parent__field__0": "$author_id"},
591+
"pipeline": [
592+
{
593+
"$match": {
594+
"$and": [
595+
{
596+
"$expr": {
597+
"$and": [{"$eq": ["$$parent__field__0", "$_id"]}]
598+
}
599+
},
600+
{"name": "Alice"},
601+
]
602+
}
603+
}
604+
],
605+
"as": "queries__author",
606+
}
607+
},
608+
{"$unwind": "$queries__author"},
609+
{
610+
"$match": {
611+
"$nor": [
612+
{
613+
"$or": [
614+
{"$nor": [{"queries__author.name": "Alice"}]},
615+
{"title": "Book4"},
616+
]
617+
}
618+
]
619+
}
620+
},
621+
],
622+
)
623+
624+
def test_partial_or_pushdown(self):
625+
a1 = Author.objects.create(name="Alice")
626+
a2 = Author.objects.create(name="Bob")
627+
a3 = Author.objects.create(name="Charlie")
628+
b1 = Book.objects.create(title="B1", author=a1, isbn="111")
629+
b2 = Book.objects.create(title="B2", author=a2, isbn="111")
630+
Book.objects.create(title="B3", author=a3, isbn="222")
631+
condition = models.Q(author__name="Alice") | (
632+
models.Q(author__name="Bob") & models.Q(isbn="111")
633+
)
634+
expected = [b1, b2]
635+
with self.assertNumQueries(1) as ctx:
636+
self.assertSequenceEqual(list(Book.objects.filter(condition)), expected)
637+
self.assertAggregateQuery(
638+
ctx.captured_queries[0]["sql"],
639+
"queries__book",
640+
[
641+
{
642+
"$lookup": {
643+
"as": "queries__author",
644+
"from": "queries__author",
645+
"let": {"parent__field__0": "$author_id"},
646+
"pipeline": [
647+
{
648+
"$match": {
649+
"$and": [
650+
{
651+
"$expr": {
652+
"$and": [{"$eq": ["$$parent__field__0", "$_id"]}]
653+
}
654+
},
655+
{"$or": [{"name": "Alice"}, {"name": "Bob"}]},
656+
]
657+
}
658+
}
659+
],
660+
}
661+
},
662+
{"$unwind": "$queries__author"},
663+
{
664+
"$match": {
665+
"$or": [
666+
{"queries__author.name": "Alice"},
667+
{"$and": [{"queries__author.name": "Bob"}, {"isbn": "111"}]},
668+
]
669+
}
670+
},
671+
],
672+
)
673+
674+
def test_multiple_ors_with_partial_pushdown(self):
675+
a1 = Author.objects.create(name="Alice")
676+
a2 = Author.objects.create(name="Bob")
677+
a3 = Author.objects.create(name="Charlie")
678+
a4 = Author.objects.create(name="David")
679+
b1 = Book.objects.create(title="B1", author=a1, isbn="111")
680+
b2 = Book.objects.create(title="B2", author=a1, isbn="222")
681+
b3 = Book.objects.create(title="B3", author=a2, isbn="333")
682+
b4 = Book.objects.create(title="B4", author=a3, isbn="333")
683+
Book.objects.create(title="B5", author=a4, isbn="444")
684+
685+
left = models.Q(author__name="Alice") & (models.Q(isbn="111") | models.Q(isbn="222"))
686+
right = (models.Q(author__name="Bob") | models.Q(author__name="Charlie")) & models.Q(
687+
isbn="333"
688+
)
689+
condition = left | right
690+
691+
expected = [b1, b2, b3, b4]
692+
with self.assertNumQueries(1) as ctx:
693+
self.assertSequenceEqual(list(Book.objects.filter(condition)), expected)
694+
695+
self.assertAggregateQuery(
696+
ctx.captured_queries[0]["sql"],
697+
"queries__book",
698+
[
699+
{
700+
"$lookup": {
701+
"as": "queries__author",
702+
"from": "queries__author",
703+
"let": {"parent__field__0": "$author_id"},
704+
"pipeline": [
705+
{
706+
"$match": {
707+
"$and": [
708+
{
709+
"$expr": {
710+
"$and": [{"$eq": ["$$parent__field__0", "$_id"]}]
711+
}
712+
},
713+
{
714+
"$or": [
715+
{"name": "Alice"},
716+
{"$or": [{"name": "Bob"}, {"name": "Charlie"}]},
717+
]
718+
},
719+
]
720+
}
721+
}
722+
],
723+
}
724+
},
725+
{"$unwind": "$queries__author"},
726+
{
727+
"$match": {
728+
"$or": [
729+
{
730+
"$and": [
731+
{"queries__author.name": "Alice"},
732+
{"$or": [{"isbn": "111"}, {"isbn": "222"}]},
733+
]
734+
},
735+
{
736+
"$and": [
737+
{
738+
"$or": [
739+
{"queries__author.name": "Bob"},
740+
{"queries__author.name": "Charlie"},
741+
]
742+
},
743+
{"isbn": "333"},
744+
]
745+
},
746+
]
747+
}
748+
},
749+
],
750+
)
751+
752+
def test_self_join_tag_three_levels_none_pushable(self):
753+
t1 = Tag.objects.create(name="T1")
754+
t2 = Tag.objects.create(name="T2", parent=t1)
755+
t3 = Tag.objects.create(name="T3", parent=t2)
756+
Tag.objects.create(name="T4", parent=t3)
757+
Tag.objects.create(name="T5", parent=t1)
758+
t6 = Tag.objects.create(name="T6", parent=t2)
759+
cond = (
760+
models.Q(name="T1") | models.Q(parent__name="T2") | models.Q(parent__parent__name="T3")
761+
)
762+
expected = [t1, t3, t6]
763+
with self.assertNumQueries(1) as ctx:
764+
self.assertSequenceEqual(list(Tag.objects.filter(cond)), expected)
765+
self.assertAggregateQuery(
766+
ctx.captured_queries[0]["sql"],
767+
"queries__tag",
768+
# Django translate this kind of queries into left outer join
769+
[
770+
{
771+
"$lookup": {
772+
"as": "T2",
773+
"from": "queries__tag",
774+
"let": {"parent__field__0": "$parent_id"},
775+
"pipeline": [
776+
{
777+
"$match": {
778+
"$expr": {"$and": [{"$eq": ["$$parent__field__0", "$_id"]}]}
779+
}
780+
}
781+
],
782+
}
783+
},
784+
{
785+
"$set": {
786+
"T2": {
787+
"$cond": {
788+
"else": "$T2",
789+
"if": {
790+
"$or": [
791+
{"$eq": [{"$type": "$T2"}, "missing"]},
792+
{"$eq": [{"$size": "$T2"}, 0]},
793+
]
794+
},
795+
"then": [{}],
796+
}
797+
}
798+
}
799+
},
800+
{"$unwind": "$T2"},
801+
{
802+
"$lookup": {
803+
"as": "T3",
804+
"from": "queries__tag",
805+
"let": {"parent__field__0": "$T2.parent_id"},
806+
"pipeline": [
807+
{
808+
"$match": {
809+
"$expr": {"$and": [{"$eq": ["$$parent__field__0", "$_id"]}]}
810+
}
811+
}
812+
],
813+
}
814+
},
815+
{
816+
"$set": {
817+
"T3": {
818+
"$cond": {
819+
"else": "$T3",
820+
"if": {
821+
"$or": [
822+
{"$eq": [{"$type": "$T3"}, "missing"]},
823+
{"$eq": [{"$size": "$T3"}, 0]},
824+
]
825+
},
826+
"then": [{}],
827+
}
828+
}
829+
}
830+
},
831+
{"$unwind": "$T3"},
832+
{"$match": {"$or": [{"name": "T1"}, {"T2.name": "T2"}, {"T3.name": "T3"}]}},
833+
],
834+
)
835+
836+
def test_self_join_tag_three_levels_pushable(self):
837+
t1 = Tag.objects.create(name="T1")
838+
t2 = Tag.objects.create(name="T2", parent=t1)
839+
t3 = Tag.objects.create(name="T3", parent=t2)
840+
Tag.objects.create(name="T4", parent=t3)
841+
Tag.objects.create(name="T5", parent=t1)
842+
Tag.objects.create(name="T6", parent=t2)
843+
with self.assertNumQueries(1) as ctx:
844+
self.assertSequenceEqual(
845+
list(Tag.objects.filter(name="T1", parent__name="T2", parent__parent__name="T3")),
846+
[],
847+
)
848+
849+
self.assertAggregateQuery(
850+
ctx.captured_queries[0]["sql"],
851+
"queries__tag",
852+
[
853+
{
854+
"$lookup": {
855+
"as": "T2",
856+
"from": "queries__tag",
857+
"let": {"parent__field__0": "$parent_id"},
858+
"pipeline": [
859+
{
860+
"$match": {
861+
"$and": [
862+
{
863+
"$expr": {
864+
"$and": [{"$eq": ["$$parent__field__0", "$_id"]}]
865+
}
866+
},
867+
{"name": "T2"},
868+
]
869+
}
870+
}
871+
],
872+
}
873+
},
874+
{"$unwind": "$T2"},
875+
{
876+
"$lookup": {
877+
"as": "T3",
878+
"from": "queries__tag",
879+
"let": {"parent__field__0": "$T2.parent_id"},
880+
"pipeline": [
881+
{
882+
"$match": {
883+
"$and": [
884+
{
885+
"$expr": {
886+
"$and": [{"$eq": ["$$parent__field__0", "$_id"]}]
887+
}
888+
},
889+
{"name": "T3"},
890+
]
891+
}
892+
}
893+
],
894+
}
895+
},
896+
{"$unwind": "$T3"},
897+
{"$match": {"$and": [{"name": "T1"}, {"T2.name": "T2"}, {"T3.name": "T3"}]}},
898+
],
899+
)

0 commit comments

Comments
 (0)