Skip to content

Commit 86aed5e

Browse files
committed
Support simple expressions in indexes.
1 parent e7c3c48 commit 86aed5e

File tree

6 files changed

+67
-36
lines changed

6 files changed

+67
-36
lines changed

django_mongodb_backend/expressions/builtins.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
Value,
2424
When,
2525
)
26+
from django.db.models.indexes import IndexExpression
2627
from django.db.models.sql import Query
2728

2829
from ..query_utils import process_lhs
@@ -95,12 +96,25 @@ def expression_wrapper(self, compiler, connection):
9596
return self.expression.as_mql(compiler, connection)
9697

9798

99+
def index_expression(self, compiler, connection):
100+
result = []
101+
for expr in self.get_source_expressions():
102+
if expr is None:
103+
continue
104+
for sub_expr in expr.get_source_expressions():
105+
try:
106+
result.append(sub_expr.as_mql(compiler, connection, as_path=True))
107+
except FullResultSet:
108+
result.append(Value(True).as_mql(compiler, connection))
109+
return result
110+
111+
98112
def negated_expression(self, compiler, connection):
99113
return {"$not": expression_wrapper(self, compiler, connection)}
100114

101115

102-
def order_by(self, compiler, connection):
103-
return self.expression.as_mql(compiler, connection)
116+
def order_by(self, compiler, connection, **extra_args):
117+
return self.expression.as_mql(compiler, connection, **extra_args)
104118

105119

106120
def query(self, compiler, connection, get_wrapping_pipeline=None):
@@ -217,6 +231,7 @@ def register_expressions():
217231
Exists.as_mql = exists
218232
ExpressionList.as_mql = process_lhs
219233
ExpressionWrapper.as_mql = expression_wrapper
234+
IndexExpression.as_mql = index_expression
220235
NegatedExpression.as_mql = negated_expression
221236
OrderBy.as_mql = order_by
222237
Query.as_mql = query

django_mongodb_backend/features.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class DatabaseFeatures(GISFeatures, BaseDatabaseFeatures):
2929
supports_deferrable_unique_constraints = False
3030
supports_explaining_query_execution = True
3131
supports_expression_defaults = False
32-
supports_expression_indexes = False
32+
supports_expression_indexes = True
3333
supports_foreign_keys = False
3434
supports_ignore_conflicts = False
3535
supports_json_field_contains = False

django_mongodb_backend/fields/embedded_model.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -155,16 +155,6 @@ def formfield(self, **kwargs):
155155
}
156156
)
157157

158-
def contribute_to_class(self, cls, name):
159-
super().contribute_to_class(cls, name)
160-
for field in self.embedded_model._meta.local_fields:
161-
embedded_field_name = f"{name}.{field.name}"
162-
field = field.clone()
163-
field.unique = False
164-
field.db_index = False
165-
field.name = embedded_field_name
166-
models.Field.contribute_to_class(field, cls, embedded_field_name, private_only=False)
167-
168158

169159
class KeyTransform(Transform):
170160
def __init__(self, key_name, ref_field, *args, **kwargs):
@@ -213,6 +203,9 @@ def as_mql(self, compiler, connection, as_path=False):
213203
def output_field(self):
214204
return self.ref_field
215205

206+
def db_type(self, connection):
207+
return self.output_field.db_type(connection)
208+
216209

217210
class KeyTransformFactory:
218211
def __init__(self, key_name, ref_field):

django_mongodb_backend/indexes.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from django.core.checks import Error, Warning
55
from django.db import NotSupportedError
66
from django.db.models import FloatField, Index, IntegerField
7+
from django.db.models.expressions import F, OrderBy
78
from django.db.models.lookups import BuiltinLookup
89
from django.db.models.sql.query import Query
910
from django.db.models.sql.where import AND, XOR, WhereNode
@@ -46,10 +47,31 @@ def builtin_lookup_idx(self, compiler, connection):
4647

4748
def get_pymongo_index_model(self, model, schema_editor, field=None, unique=False, column_prefix=""):
4849
"""Return a pymongo IndexModel for this Django Index."""
50+
filter_expression = defaultdict(dict)
51+
expressions_fields = []
4952
if self.contains_expressions:
50-
return None
53+
for expression in self.expressions:
54+
if not isinstance(expression, F | OrderBy):
55+
continue
56+
query = Query(model=model, alias_cols=False)
57+
field_ = expression.resolve_expression(query)
58+
compiler = query.get_compiler(connection=schema_editor.connection)
59+
column = field_.as_mql(compiler, schema_editor.connection, as_path=True)
60+
db_type = (
61+
field_.expression.db_type(schema_editor.connection)
62+
if isinstance(field_, OrderBy)
63+
else field_.db_type(schema_editor.connection)
64+
)
65+
if unique:
66+
filter_expression[column].update({"$type": db_type})
67+
order = (
68+
DESCENDING
69+
if isinstance(expression, OrderBy) and expression.descending
70+
else ASCENDING
71+
)
72+
expressions_fields.append((column, order))
73+
5174
kwargs = {}
52-
filter_expression = defaultdict(dict)
5375
if self.condition:
5476
filter_expression.update(self._get_condition_mql(model, schema_editor))
5577
if unique:
@@ -80,7 +102,7 @@ def get_pymongo_index_model(self, model, schema_editor, field=None, unique=False
80102
for field_name, order in self.fields_orders
81103
]
82104
)
83-
return IndexModel(index_orders, name=self.name, **kwargs)
105+
return IndexModel(expressions_fields + index_orders, name=self.name, **kwargs)
84106

85107

86108
def where_node_idx(self, compiler, connection):

django_mongodb_backend/schema.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ def add_constraint(self, model, constraint, field=None, column_prefix="", parent
355355
nulls_distinct=constraint.nulls_distinct,
356356
):
357357
idx = Index(
358+
*constraint.expressions,
358359
fields=constraint.fields,
359360
name=constraint.name,
360361
condition=constraint.condition,

tests/schema_/test_embedded_model.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import itertools
22

33
from django.db import connection, models
4+
from django.db.models.expressions import F
45
from django.test import TransactionTestCase, skipUnlessDBFeature
56
from django.test.utils import isolate_apps
67

@@ -544,11 +545,18 @@ class Book(models.Model):
544545

545546
class Meta:
546547
app_label = "schema_"
547-
unique_together = [
548-
("author.unique_together_three", "author.unique_together_four"),
548+
constraints = [
549+
models.UniqueConstraint(
550+
F("author__unique_together_three").asc(),
551+
F("author__unique_together_four").desc(),
552+
name="unique_together_34",
553+
),
549554
(
550-
"author.address.unique_together_one",
551-
"author.address.unique_together_two",
555+
models.UniqueConstraint(
556+
F("author__address__unique_together_one"),
557+
F("author__address__unique_together_two").asc(),
558+
name="unique_together_12",
559+
)
552560
),
553561
]
554562

@@ -560,18 +568,14 @@ class Meta:
560568
self.get_constraints_for_columns(
561569
Book, ["author.unique_together_three", "author.unique_together_four"]
562570
),
563-
[
564-
"schema__book_author.unique_together_three_author.unique_together_four_09a570b8_uniq"
565-
],
571+
["unique_together_34"],
566572
)
567573
self.assertEqual(
568574
self.get_constraints_for_columns(
569575
Book,
570576
["author.address.unique_together_one", "author.address.unique_together_two"],
571577
),
572-
[
573-
"schema__book_author.address.unique_together_one_author.address.unique_together_two_2c2d1477_uniq"
574-
],
578+
["unique_together_12"],
575579
)
576580
editor.delete_model(Book)
577581
self.assertTableNotExists(Book)
@@ -599,8 +603,8 @@ class Book(models.Model):
599603
class Meta:
600604
app_label = "schema_"
601605
indexes = [
602-
models.Index(fields=["author.indexed_two"]),
603-
models.Index(fields=["author.address.indexed_one"]),
606+
models.Index(F("author__indexed_two").asc(), name="indexed_two"),
607+
models.Index(F("author__address__indexed_one").asc(), name="indexed_one"),
604608
]
605609

606610
new_field = EmbeddedModelField(Author)
@@ -613,14 +617,14 @@ class Meta:
613617
# Embedded indexes are created.
614618
self.assertEqual(
615619
self.get_constraints_for_columns(Book, ["author.indexed_two"]),
616-
["schema__boo_author._333c90_idx"],
620+
["indexed_two"],
617621
)
618622
self.assertEqual(
619623
self.get_constraints_for_columns(
620624
Book,
621625
["author.address.indexed_one"],
622626
),
623-
["schema__boo_author._f54386_idx"],
627+
["indexed_one"],
624628
)
625629
editor.delete_model(Book)
626630
self.assertTableNotExists(Book)
@@ -648,13 +652,9 @@ class Book(models.Model):
648652
class Meta:
649653
app_label = "schema_"
650654
constraints = [
655+
models.UniqueConstraint(F("author__unique_constraint_two"), name="unique_two"),
651656
models.UniqueConstraint(
652-
fields=["author.unique_constraint_two"],
653-
name="unique_two",
654-
),
655-
models.UniqueConstraint(
656-
fields=["author.address.unique_constraint_one"],
657-
name="unique_one",
657+
F("author__address__unique_constraint_one"), name="unique_one"
658658
),
659659
]
660660

0 commit comments

Comments
 (0)