Skip to content

Commit b68b13f

Browse files
committed
add xor support for Q objects
1 parent 6b18784 commit b68b13f

File tree

2 files changed

+22
-5
lines changed

2 files changed

+22
-5
lines changed

.github/workflows/test-python.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ jobs:
9999
sessions_tests
100100
timezones
101101
update
102+
xor_lookups
102103
103104
docs:
104105
name: Docs Checks

django_mongodb/query.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
from functools import wraps
1+
from functools import reduce, wraps
2+
from operator import add as add_operator
23

34
from django.core.exceptions import EmptyResultSet, FullResultSet
45
from django.db import DatabaseError, IntegrityError
5-
from django.db.models import Value
6+
from django.db.models.expressions import Case, Value, When
7+
from django.db.models.functions import Mod
8+
from django.db.models.lookups import Exact
69
from django.db.models.sql.constants import INNER
710
from django.db.models.sql.datastructures import Join
8-
from django.db.models.sql.where import AND, XOR, WhereNode
11+
from django.db.models.sql.where import AND, OR, XOR, WhereNode
912
from pymongo import ASCENDING, DESCENDING
1013
from pymongo.errors import DuplicateKeyError, PyMongoError
1114

@@ -219,8 +222,21 @@ def where_node(self, compiler, connection):
219222
if self.connector == AND:
220223
operator = "$and"
221224
elif self.connector == XOR:
222-
# https://github.com/mongodb-labs/django-mongodb/issues/27
223-
raise NotImplementedError("XOR is not yet supported.")
225+
# MongoDB doesn't support $xor, so convert:
226+
# a XOR b XOR c XOR ...
227+
# to:
228+
# (a OR b OR c OR ...) AND MOD(a + b + c + ..., 2) == 1
229+
# The result of an n-ary XOR is true when an odd number of operands
230+
# are true.
231+
lhs = self.__class__(self.children, OR)
232+
rhs_sum = reduce(
233+
add_operator,
234+
(Case(When(c, then=1), default=0) for c in self.children),
235+
)
236+
if len(self.children) > 2:
237+
rhs_sum = Mod(rhs_sum, 2)
238+
rhs = Exact(1, rhs_sum)
239+
return self.__class__([lhs, rhs], AND, self.negated).as_mql(compiler, connection)
224240
else:
225241
operator = "$or"
226242

0 commit comments

Comments
 (0)