Skip to content

Commit 91e9b3a

Browse files
committed
Clean up.
1 parent ffe9f62 commit 91e9b3a

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):
@@ -150,8 +147,9 @@ def get_count(self, check_exists=False):
150147
def build_query(self, columns=None):
151148
"""Check if the query is supported and prepare a MongoQuery."""
152149
self.check_query()
150+
self.setup_query()
153151
query = self.query_class(self, columns)
154-
query.mongo_lookups = self.get_lookup_clauses()
152+
query.lookups_pipeline = self.get_lookup_clauses()
155153
try:
156154
query.mongo_query = {"$expr": self.query.where.as_mql(self, self.connection)}
157155
except FullResultSet:
@@ -230,7 +228,7 @@ def _get_ordering(self):
230228
field_ordering.append((opts.get_field(name), ascending))
231229
return field_ordering
232230

233-
@property
231+
@cached_property
234232
def collection_name(self):
235233
return self.query.get_meta().db_table
236234

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
@@ -39,6 +39,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
3939
"ordering.tests.OrderingTests.test_order_by_expression_ref",
4040
"ordering.tests.OrderingTests.test_order_by_f_expression",
4141
"ordering.tests.OrderingTests.test_order_by_f_expression_duplicates",
42+
"ordering.tests.OrderingTests.test_ordering_select_related_collision",
4243
"ordering.tests.OrderingTests.test_reverse_ordering_pure",
4344
# 'ManyToOneRel' object has no attribute 'column'
4445
"m2m_through.tests.M2mThroughTests.test_order_by_relational_field_through_model",
@@ -61,8 +62,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
6162
"model_fields.test_jsonfield.TestQuerying.test_order_grouping_custom_decoder",
6263
"model_fields.test_jsonfield.TestQuerying.test_ordering_by_transform",
6364
"model_fields.test_jsonfield.TestQuerying.test_ordering_grouping_by_key_transform",
64-
# Ordering 'OrderBy' is not iterable
65-
"ordering.tests.OrderingTests.test_ordering_select_related_collision",
6665
}
6766
# $bitAnd, #bitOr, and $bitXor are new in MongoDB 6.3.
6867
_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}>"
@@ -112,16 +111,15 @@ def get_cursor(self):
112111
# another column.
113112
fields[name] = 1 if name == column else f"${column}"
114113

115-
# Add the subquery results if fields is defined.
114+
# Add the related results if fields is defined.
116115
if fields:
117116
for alias in self.query.alias_map:
118117
if self.query.alias_refcount[alias] and self.collection_name != alias:
119118
fields[alias] = 1
120119

121120
pipeline = []
122-
if self.mongo_lookups:
123-
lookups = self.mongo_lookups
124-
pipeline.extend(lookups)
121+
if self.lookups_pipeline:
122+
pipeline.extend(self.lookups_pipeline)
125123
if self.mongo_query:
126124
pipeline.append({"$match": self.mongo_query})
127125
if fields:
@@ -135,6 +133,75 @@ def get_cursor(self):
135133
return self.collection.aggregate(pipeline)
136134

137135

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

186253

187254
def register_nodes():
255+
Join.as_mql = join
188256
WhereNode.as_mql = where_node

0 commit comments

Comments
 (0)