Skip to content

Commit f43849c

Browse files
committed
Implementation of KeyTransformIsNull, KeyTransformIn.
1 parent 42104e1 commit f43849c

File tree

3 files changed

+40
-33
lines changed

3 files changed

+40
-33
lines changed

django_mongodb/features.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
77
supports_ignore_conflicts = False
88
# Not implemented: https://github.com/mongodb-labs/django-mongodb/issues/8
99
supports_json_field = True
10+
supports_json_field_contains = False
1011
has_native_json_field = True
1112
# BSON Date type doesn't support microsecond precision.
1213
supports_microsecond_precision = False
@@ -336,4 +337,9 @@ class DatabaseFeatures(BaseDatabaseFeatures):
336337
"model_fields.test_jsonfield.JSONFieldTests.test_invalid_value",
337338
"model_fields.test_jsonfield.JSONFieldTests.test_db_check_constraints",
338339
},
340+
"Mongodb's Null behaviour is different from sql's": {
341+
"model_fields.test_jsonfield.TestQuerying.test_none_key_exclude",
342+
"model_fields.test_jsonfield.TestQuerying.test_isnull_key",
343+
},
344+
"Pipeline filtering": {"model_fields.test_jsonfield.TestQuerying.test_icontains"},
339345
}

django_mongodb/fields.py

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from bson import ObjectId, errors
22
from django.core import exceptions
3+
from django.db import NotSupportedError
34
from django.db.models import JSONField
45
from django.db.models.fields import AutoField, Field
56
from django.db.models.fields.json import (
@@ -11,6 +12,8 @@
1112
HasKeys,
1213
JSONExact,
1314
KeyTransform,
15+
KeyTransformIn,
16+
KeyTransformIsNull,
1417
)
1518
from django.utils.translation import gettext_lazy as _
1619

@@ -59,8 +62,14 @@ def from_db_value(self, value, expression, connection):
5962
)
6063

6164

62-
def process_rhs(node, compiler, connection):
65+
def json_process_rhs(node, compiler, connection):
6366
_, value = node.process_rhs(compiler, connection)
67+
68+
# Django's framework transform the [None] into a [null],
69+
# we have to revertit.
70+
if value == ["null"]:
71+
value = [None]
72+
6473
lookup_name = node.lookup_name
6574
if lookup_name not in ("in", "range"):
6675
value = value[0] if len(value) > 0 else []
@@ -78,41 +87,33 @@ def key_transform(self, compiler, connection):
7887
return ".".join([lhs_mql, *key_transforms])
7988

8089

81-
def data_contains(self, compiler, connection):
82-
lhs_mql = process_lhs(self, compiler, connection)
83-
value = process_rhs(self, compiler, connection)
84-
if not value:
85-
return {lhs_mql: {"$type": "array"}}
86-
if isinstance(value, list):
87-
return {"$and": [{lhs_mql: v} for v in value]}
88-
if isinstance(value, dict):
89-
return {"$and": [{f"{lhs_mql}.{k}": v} for k, v in value.items()]}
90-
return {lhs_mql: value}
90+
def data_contains(self, compiler, connection): # noqa: ARG001
91+
raise NotSupportedError("contains lookup is not supported on this database backend.")
92+
93+
94+
def contained_by(self, compiler, connection): # noqa: ARG001
95+
raise NotSupportedError("contained_by lookup is not supported on this database backend.")
9196

9297

9398
def json_exact(self, compiler, connection):
99+
rhs_mql = json_process_rhs(self, compiler, connection)
100+
lhs_mql = process_lhs(self, compiler, connection)
101+
return {lhs_mql: {"$eq": rhs_mql, "$exists": True}}
102+
103+
104+
def key_transform_isnull(self, compiler, connection):
94105
lhs_mql = process_lhs(self, compiler, connection)
95-
rhs_mql = process_rhs(self, compiler, connection)
96-
return {lhs_mql: {"$eq": rhs_mql}}
106+
rhs_mql = json_process_rhs(self, compiler, connection)
107+
if rhs_mql is False:
108+
return {lhs_mql: {"$neq": None}}
109+
return {"$or": [{lhs_mql: {"$eq": None}}, {lhs_mql: {"$exists": False}}]}
97110

98111

99-
def contained_by(self, compiler, connection):
112+
def key_transform_in(self, compiler, connection):
100113
lhs_mql = process_lhs(self, compiler, connection)
101-
value = process_rhs(self, compiler, connection)
102-
if isinstance(value, list):
103-
if not value:
104-
return {lhs_mql: []}
105-
return {"$and": [{"$or": [{lhs_mql: v}, {lhs_mql: {"$exists": False}}]} for v in value]}
106-
if isinstance(value, dict):
107-
if not value:
108-
return {lhs_mql: {}}
109-
return {
110-
"$and": [
111-
{"$or": [{f"{lhs_mql}.{k}": v}, {f"{lhs_mql}.{k}": {"$exists": False}}]}
112-
for k, v in value.items()
113-
]
114-
}
115-
return {lhs_mql: value}
114+
value = json_process_rhs(self, compiler, connection)
115+
rhs_mql = connection.operators[self.lookup_name](value)
116+
return {lhs_mql: rhs_mql}
116117

117118

118119
def has_key_lookup(self, compiler, connection):
@@ -124,9 +125,7 @@ def has_key_lookup(self, compiler, connection):
124125
for key in rhs:
125126
if isinstance(key, KeyTransform):
126127
*_, rhs_key_transforms = key.preprocess_lhs(compiler, connection)
127-
rhs_key_transforms = ".".join(
128-
rhs_key_transforms
129-
) # process_lhs(key, compiler, connection)
128+
rhs_key_transforms = ".".join(rhs_key_transforms)
130129
else:
131130
rhs_key_transforms = str(key)
132131
rhs_json_path = f"{lhs}.{rhs_key_transforms}"
@@ -151,3 +150,5 @@ def load_fields():
151150
HasAnyKeys.mongo_operator = "$or"
152151
HasKey.mongo_operator = None
153152
HasKeys.mongo_operator = "$and"
153+
KeyTransformIsNull.as_mql = key_transform_isnull
154+
KeyTransformIn.as_mql = key_transform_in

django_mongodb/query.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ def where_node(self, compiler, connection):
156156

157157
if self.negated and mql:
158158
lhs, rhs = next(iter(mql.items()))
159-
mql = {lhs: {"$not": rhs}}
159+
mql = {"$nor": rhs} if lhs == "$or" else {lhs: {"$not": rhs}}
160160

161161
return mql
162162

0 commit comments

Comments
 (0)