|
1 |
| -from functools import wraps |
| 1 | +from functools import reduce, wraps |
| 2 | +from operator import add as add_operator |
2 | 3 |
|
3 | 4 | from django.core.exceptions import EmptyResultSet, FullResultSet
|
4 | 5 | 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 |
6 | 9 | from django.db.models.sql.constants import INNER
|
7 | 10 | 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 |
9 | 12 | from pymongo import ASCENDING, DESCENDING
|
10 | 13 | from pymongo.errors import DuplicateKeyError, PyMongoError
|
11 | 14 |
|
@@ -219,8 +222,21 @@ def where_node(self, compiler, connection):
|
219 | 222 | if self.connector == AND:
|
220 | 223 | operator = "$and"
|
221 | 224 | 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) |
224 | 240 | else:
|
225 | 241 | operator = "$or"
|
226 | 242 |
|
|
0 commit comments