Skip to content

Commit cf8678e

Browse files
committed
JSONField: Haskey lookups.
1 parent fec0025 commit cf8678e

File tree

2 files changed

+83
-7
lines changed

2 files changed

+83
-7
lines changed

django_mongodb/features.py

Lines changed: 1 addition & 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+
has_native_json_field = True
1011
# Not implemented: https://github.com/mongodb-labs/django-mongodb/issues/7
1112
supports_transactions = False
1213
uses_savepoints = False

django_mongodb/fields.py

Lines changed: 82 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,20 @@
22
from django.core import exceptions
33
from django.db.models import JSONField
44
from django.db.models.fields import AutoField, Field
5-
from django.db.models.fields.json import DataContains, JSONExact, KeyTransform
5+
from django.db.models.fields.json import (
6+
ContainedBy,
7+
DataContains,
8+
HasAnyKeys,
9+
HasKey,
10+
HasKeyLookup,
11+
HasKeys,
12+
JSONExact,
13+
KeyTransform,
14+
)
615
from django.utils.translation import gettext_lazy as _
716

817
from .base import DatabaseWrapper
9-
from .query_utils import process_lhs, process_rhs
18+
from .query_utils import process_lhs
1019

1120

1221
class MongoAutoField(AutoField):
@@ -50,19 +59,35 @@ def from_db_value(self, value, expression, connection):
5059
)
5160

5261

62+
def process_rhs(node, compiler, connection):
63+
_, value = node.process_rhs(compiler, connection)
64+
lookup_name = node.lookup_name
65+
if lookup_name not in ("in", "range"):
66+
value = value[0] if len(value) > 0 else []
67+
68+
return value
69+
70+
5371
def key_transform(self, compiler, connection):
54-
_, _, key_transforms = self.preprocess_lhs(compiler, connection)
55-
lhs_mql = process_lhs(self, compiler, connection)
72+
key_transforms = [self.key_name]
73+
previous = self.lhs
74+
while isinstance(previous, KeyTransform):
75+
key_transforms.insert(0, previous.key_name)
76+
previous = previous.lhs
77+
lhs_mql = previous.as_mql(compiler, connection)
5678
return ".".join([lhs_mql, *key_transforms])
5779

5880

5981
def data_contains(self, compiler, connection):
6082
lhs_mql = process_lhs(self, compiler, connection)
6183
value = process_rhs(self, compiler, connection)
62-
# rhs_mql = connection.operators[self.lookup_name](value)
6384
if not value:
64-
return {lhs_mql: {"$ne": None}}
65-
return {lhs_mql: {"$all": 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}
6691

6792

6893
def json_exact(self, compiler, connection):
@@ -71,8 +96,58 @@ def json_exact(self, compiler, connection):
7196
return {lhs_mql: {"$eq": rhs_mql}}
7297

7398

99+
def contained_by(self, compiler, connection):
100+
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}
116+
117+
118+
def has_key_lookup(self, compiler, connection):
119+
lhs = process_lhs(self, compiler, connection)
120+
rhs = self.rhs
121+
if not isinstance(rhs, (list | tuple)):
122+
rhs = [rhs]
123+
paths = []
124+
for key in rhs:
125+
if isinstance(key, KeyTransform):
126+
*_, rhs_key_transforms = key.preprocess_lhs(compiler, connection)
127+
rhs_key_transforms = ".".join(
128+
rhs_key_transforms
129+
) # process_lhs(key, compiler, connection)
130+
else:
131+
rhs_key_transforms = str(key)
132+
rhs_json_path = f"{lhs}.{rhs_key_transforms}"
133+
paths.append(rhs_json_path)
134+
135+
keys = []
136+
for path in paths:
137+
keys.append({path: {"$exists": True}})
138+
if self.mongo_operator is None:
139+
assert len(keys) == 1
140+
return keys[0]
141+
return {self.mongo_operator: keys}
142+
143+
74144
def load_fields():
75145
JSONField.from_db_value = from_db_value
76146
DataContains.as_mql = data_contains
77147
KeyTransform.as_mql = key_transform
78148
JSONExact.as_mql = json_exact
149+
ContainedBy.as_mql = contained_by
150+
HasKeyLookup.as_mql = has_key_lookup
151+
HasAnyKeys.mongo_operator = "$or"
152+
HasKey.mongo_operator = None
153+
HasKeys.mongo_operator = "$and"

0 commit comments

Comments
 (0)