Skip to content

Commit 52a2d47

Browse files
committed
Clean up.
1 parent c4f8bbe commit 52a2d47

File tree

7 files changed

+84
-107
lines changed

7 files changed

+84
-107
lines changed

README.md

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,6 @@ Migrations for 'admin':
115115
- `distinct()`
116116
- `extra()`
117117

118-
- `Subquery`, `Exists`, and using a `QuerySet` in `QuerySet.annotate()` aren't
119-
supported.
120-
121-
- Queries with joins aren't supported.
122-
123118
- `DateTimeField` doesn't support microsecond precision, and correspondingly,
124119
`DurationField` stores milliseconds rather than microseconds.
125120

django_mongodb/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@
66

77
check_django_compatability()
88

9-
from .datastructures import register_datastructures # noqa: E402
109
from .expressions import register_expressions # noqa: E402
1110
from .fields import register_fields # noqa: E402
1211
from .functions import register_functions # noqa: E402
1312
from .lookups import register_lookups # noqa: E402
1413
from .query import register_nodes # noqa: E402
1514

16-
register_datastructures()
1715
register_expressions()
1816
register_fields()
1917
register_functions()

django_mongodb/compiler.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from django.db.models.constants import LOOKUP_SEP
66
from django.db.models.sql import compiler
77
from django.db.models.sql.constants import GET_ITERATOR_CHUNK_SIZE, MULTI
8+
from django.utils.functional import cached_property
89

910
from .base import Cursor
1011
from .query import MongoQuery, wrap_database_errors
@@ -79,12 +80,6 @@ def _make_result(self, entity, columns, converters, tuple_expected=False):
7980
The entity is assumed to be a dict using field database column
8081
names as keys.
8182
"""
82-
result = self._apply_converters(entity, columns, converters)
83-
if tuple_expected:
84-
result = tuple(result)
85-
return result
86-
87-
def _apply_converters(self, entity, columns, converters):
8883
result = []
8984
for name, col in columns:
9085
field = col.field
@@ -101,6 +96,8 @@ def _apply_converters(self, entity, columns, converters):
10196
for converter in converters.get(name, ()):
10297
value = converter(value, col, self.connection)
10398
result.append(value)
99+
if tuple_expected:
100+
result = tuple(result)
104101
return result
105102

106103
def check_query(self):
@@ -140,8 +137,9 @@ def get_count(self, check_exists=False):
140137
def build_query(self, columns=None):
141138
"""Check if the query is supported and prepare a MongoQuery."""
142139
self.check_query()
140+
self.setup_query()
143141
query = self.query_class(self, columns)
144-
query.mongo_lookups = self.get_lookup_clauses()
142+
query.lookups_pipeline = self.get_lookup_clauses()
145143
try:
146144
query.mongo_query = {"$expr": self.query.where.as_mql(self, self.connection)}
147145
except FullResultSet:
@@ -220,7 +218,7 @@ def _get_ordering(self):
220218
field_ordering.append((opts.get_field(name), ascending))
221219
return field_ordering
222220

223-
@property
221+
@cached_property
224222
def collection_name(self):
225223
return self.query.get_meta().db_table
226224

django_mongodb/datastructures.py

Lines changed: 0 additions & 75 deletions
This file was deleted.

django_mongodb/features.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
4141
"ordering.tests.OrderingTests.test_order_by_expression_ref",
4242
"ordering.tests.OrderingTests.test_order_by_f_expression",
4343
"ordering.tests.OrderingTests.test_order_by_f_expression_duplicates",
44+
"ordering.tests.OrderingTests.test_ordering_select_related_collision",
4445
"ordering.tests.OrderingTests.test_reverse_ordering_pure",
4546
# 'ManyToOneRel' object has no attribute 'column'
4647
"m2m_through.tests.M2mThroughTests.test_order_by_relational_field_through_model",
@@ -63,8 +64,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
6364
"model_fields.test_jsonfield.TestQuerying.test_order_grouping_custom_decoder",
6465
"model_fields.test_jsonfield.TestQuerying.test_ordering_by_transform",
6566
"model_fields.test_jsonfield.TestQuerying.test_ordering_grouping_by_key_transform",
66-
# Ordering 'OrderBy' is not iterable
67-
"ordering.tests.OrderingTests.test_ordering_select_related_collision",
6867
}
6968
# $bitAnd, #bitOr, and $bitXor are new in MongoDB 6.3.
7069
_django_test_expected_failures_bitwise = {

django_mongodb/operations.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from django.conf import settings
88
from django.db import DataError
99
from django.db.backends.base.operations import BaseDatabaseOperations
10-
from django.db.models.expressions import Col, Combinable
10+
from django.db.models.expressions import Combinable
1111
from django.utils import timezone
1212
from django.utils.regex_helper import _lazy_re_compile
1313

@@ -159,12 +159,6 @@ def execute_sql_flush(self, tables):
159159
if not options.get("capped", False):
160160
collection.drop()
161161

162-
def prepare_join_on_clause(self, lhs_table, lhs_field, rhs_table, rhs_field):
163-
lhs_expr = Col(lhs_table, lhs_field)
164-
rhs_expr = Col(rhs_table, rhs_field)
165-
166-
return lhs_expr, rhs_expr
167-
168162
def prep_lookup_value(self, value, field, lookup):
169163
"""
170164
Perform type-conversion on `value` before using as a filter parameter.

django_mongodb/query.py

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from django.core.exceptions import EmptyResultSet, FullResultSet
44
from django.db import DatabaseError, IntegrityError
55
from django.db.models import Value
6+
from django.db.models.sql.constants import INNER
7+
from django.db.models.sql.datastructures import Join
68
from django.db.models.sql.where import AND, XOR, WhereNode
79
from pymongo import ASCENDING, DESCENDING
810
from pymongo.errors import DuplicateKeyError, PyMongoError
@@ -43,10 +45,7 @@ def __init__(self, compiler, columns):
4345
self.collection_name = self.compiler.collection_name
4446
self.collection = self.compiler.get_collection()
4547
self.mongo_query = getattr(compiler.query, "raw_query", {})
46-
# maybe I have to create a new object or named tuple.
47-
# it will save lookups, some filters (in case of inner) and project to rename field
48-
# don't know if the rename is needed
49-
self.mongo_lookups = None
48+
self.lookups_pipeline = None
5049

5150
def __repr__(self):
5251
return f"<MongoQuery: {self.mongo_query!r} ORDER {self.ordering!r}>"
@@ -108,16 +107,15 @@ def get_cursor(self):
108107
# another column.
109108
fields[name] = 1 if name == column else f"${column}"
110109

111-
# Add the subquery results if fields is defined.
110+
# Add the related results if fields is defined.
112111
if fields:
113112
for alias in self.query.alias_map:
114113
if self.query.alias_refcount[alias] and self.collection_name != alias:
115114
fields[alias] = 1
116115

117116
pipeline = []
118-
if self.mongo_lookups:
119-
lookups = self.mongo_lookups
120-
pipeline.extend(lookups)
117+
if self.lookups_pipeline:
118+
pipeline.extend(self.lookups_pipeline)
121119
if self.mongo_query:
122120
pipeline.append({"$match": self.mongo_query})
123121
if fields:
@@ -131,6 +129,75 @@ def get_cursor(self):
131129
return self.collection.aggregate(pipeline)
132130

133131

132+
def join(self, compiler, connection):
133+
lookups_pipeline = []
134+
join_fields = self.join_fields or self.join_cols
135+
lhs_fields = []
136+
rhs_fields = []
137+
for lhs, rhs in join_fields:
138+
if isinstance(lhs, str):
139+
lhs_mql = lhs
140+
rhs_mql = rhs
141+
else:
142+
lhs, rhs = connection.ops.prepare_join_on_clause(
143+
self.parent_alias, lhs, self.table_name, rhs
144+
)
145+
lhs_mql = lhs.as_mql(compiler, connection)
146+
rhs_mql = rhs.as_mql(compiler, connection)
147+
# replace prefix, in lookup stages the reference
148+
# to this column is without the collection name.
149+
rhs_mql = rhs_mql.replace(f"{self.table_name}.", "", 1)
150+
lhs_fields.append(lhs_mql)
151+
rhs_fields.append(rhs_mql)
152+
153+
parent_template = "parent__field__"
154+
lookups_pipeline = [
155+
{
156+
"$lookup": {
157+
"from": self.table_name,
158+
"let": {
159+
f"{parent_template}{i}": parent_field
160+
for i, parent_field in enumerate(lhs_fields)
161+
},
162+
"pipeline": [
163+
{
164+
"$match": {
165+
"$expr": {
166+
"$and": [
167+
{"$eq": [f"$${parent_template}{i}", field]}
168+
for i, field in enumerate(rhs_fields)
169+
]
170+
}
171+
}
172+
}
173+
],
174+
"as": self.table_alias,
175+
}
176+
},
177+
]
178+
if self.join_type != INNER:
179+
lookups_pipeline.append(
180+
{
181+
"$set": {
182+
self.table_alias: {
183+
"$cond": {
184+
"if": {
185+
"$or": [
186+
{"$eq": [{"$type": f"${self.table_alias}"}, "missing"]},
187+
{"$eq": [{"$size": f"${self.table_alias}"}, 0]},
188+
]
189+
},
190+
"then": [{}],
191+
"else": f"${self.table_alias}",
192+
}
193+
}
194+
}
195+
}
196+
)
197+
lookups_pipeline.append({"$unwind": f"${self.table_alias}"})
198+
return lookups_pipeline
199+
200+
134201
def where_node(self, compiler, connection):
135202
if self.connector == AND:
136203
full_needed, empty_needed = len(self.children), 1
@@ -181,4 +248,5 @@ def where_node(self, compiler, connection):
181248

182249

183250
def register_nodes():
251+
Join.as_mql = join
184252
WhereNode.as_mql = where_node

0 commit comments

Comments
 (0)