Skip to content

Commit 97769ba

Browse files
WaVEVtimgraham
authored andcommitted
add expression support to QuerySet.update()
1 parent dc32581 commit 97769ba

File tree

2 files changed

+35
-87
lines changed

2 files changed

+35
-87
lines changed

django_mongodb/compiler.py

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
from collections import defaultdict
44

55
from bson import SON
6-
from django.core.exceptions import EmptyResultSet, FullResultSet
7-
from django.db import DatabaseError, IntegrityError, NotSupportedError
8-
from django.db.models import Count, Expression
6+
from django.core.exceptions import EmptyResultSet, FieldError, FullResultSet
7+
from django.db import IntegrityError, NotSupportedError
8+
from django.db.models import Count
99
from django.db.models.aggregates import Aggregate, Variance
1010
from django.db.models.expressions import Case, Col, Ref, Value, When
1111
from django.db.models.functions.comparison import Coalesce
@@ -576,9 +576,21 @@ def execute_sql(self, result_type):
576576
related queries are not available.
577577
"""
578578
self.pre_sql_setup()
579-
values = []
579+
values = {}
580580
for field, _, value in self.query.values:
581-
if hasattr(value, "prepare_database_save"):
581+
if hasattr(value, "resolve_expression"):
582+
value = value.resolve_expression(self.query, allow_joins=False, for_save=True)
583+
if value.contains_aggregate:
584+
raise FieldError(
585+
"Aggregate functions are not allowed in this query "
586+
f"({field.name}={value})."
587+
)
588+
if value.contains_over_clause:
589+
raise FieldError(
590+
"Window expressions are not allowed in this query "
591+
f"({field.name}={value})."
592+
)
593+
elif hasattr(value, "prepare_database_save"):
582594
if field.remote_field:
583595
value = value.prepare_database_save(field)
584596
else:
@@ -588,34 +600,25 @@ def execute_sql(self, result_type):
588600
f"{field.__class__.__name__}."
589601
)
590602
prepared = field.get_db_prep_save(value, connection=self.connection)
591-
values.append((field, prepared))
603+
if hasattr(value, "as_mql"):
604+
prepared = prepared.as_mql(self, self.connection)
605+
values[field.column] = prepared
606+
try:
607+
criteria = self.build_query().mongo_query
608+
except EmptyResultSet:
609+
return 0
592610
is_empty = not bool(values)
593-
rows = 0 if is_empty else self.update(values)
611+
rows = 0 if is_empty else self.update(criteria, [{"$set": values}])
594612
for query in self.query.get_related_updates():
595613
aux_rows = query.get_compiler(self.using).execute_sql(result_type)
596614
if is_empty and aux_rows:
597615
rows = aux_rows
598616
is_empty = False
599617
return rows
600618

601-
def update(self, values):
602-
spec = {}
603-
for field, value in values:
604-
if field.primary_key:
605-
raise DatabaseError("Cannot modify _id.")
606-
if isinstance(value, Expression):
607-
raise NotSupportedError("QuerySet.update() with expression not supported.")
608-
# .update(foo=123) --> {'$set': {'foo': 123}}
609-
spec.setdefault("$set", {})[field.column] = value
610-
return self.execute_update(spec)
611-
612619
@wrap_database_errors
613-
def execute_update(self, update_spec):
614-
try:
615-
criteria = self.build_query().mongo_query
616-
except EmptyResultSet:
617-
return 0
618-
return self.collection.update_many(criteria, update_spec).matched_count
620+
def update(self, criteria, pipeline):
621+
return self.collection.update_many(criteria, pipeline).matched_count
619622

620623
def check_query(self):
621624
super().check_query()

django_mongodb/features.py

Lines changed: 8 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ class DatabaseFeatures(BaseDatabaseFeatures):
7272
"many_to_one.tests.ManyToOneTests.test_selects",
7373
# Incorrect JOIN with GenericRelation gives incorrect results.
7474
"aggregation_regress.tests.AggregationTests.test_aggregation_with_generic_reverse_relation",
75+
# QuerySet.update() with UUIDField fails:
76+
# "cannot encode native uuid.UUID with UuidRepresentation.UNSPECIFIED."
77+
"expressions.tests.ValueTests.test_update_UUIDField_using_Value",
78+
"expressions_case.tests.CaseExpressionTests.test_update_uuid",
7579
}
7680
# $bitAnd, #bitOr, and $bitXor are new in MongoDB 6.3.
7781
_django_test_expected_failures_bitwise = {
@@ -125,69 +129,6 @@ def django_test_expected_failures(self):
125129
"many_to_many.tests.ManyToManyTests.test_set_after_prefetch",
126130
"model_forms.tests.OtherModelFormTests.test_prefetch_related_queryset",
127131
},
128-
"QuerySet.update() with expression not supported.": {
129-
"annotations.tests.AliasTests.test_update_with_alias",
130-
"annotations.tests.NonAggregateAnnotationTestCase.test_update_with_annotation",
131-
"db_functions.comparison.test_least.LeastTests.test_update",
132-
"db_functions.comparison.test_greatest.GreatestTests.test_update",
133-
"db_functions.text.test_left.LeftTests.test_basic",
134-
"db_functions.text.test_lower.LowerTests.test_basic",
135-
"db_functions.text.test_replace.ReplaceTests.test_update",
136-
"db_functions.text.test_substr.SubstrTests.test_basic",
137-
"db_functions.text.test_upper.UpperTests.test_basic",
138-
"expressions.tests.BasicExpressionsTests.test_arithmetic",
139-
"expressions.tests.BasicExpressionsTests.test_filter_with_join",
140-
"expressions.tests.BasicExpressionsTests.test_object_update",
141-
"expressions.tests.BasicExpressionsTests.test_order_of_operations",
142-
"expressions.tests.BasicExpressionsTests.test_parenthesis_priority",
143-
"expressions.tests.BasicExpressionsTests.test_update",
144-
"expressions.tests.BasicExpressionsTests.test_update_with_fk",
145-
"expressions.tests.BasicExpressionsTests.test_update_with_none",
146-
"expressions.tests.ExpressionsNumericTests.test_decimal_expression",
147-
"expressions.tests.ExpressionsNumericTests.test_increment_value",
148-
"expressions.tests.FTimeDeltaTests.test_delta_update",
149-
"expressions.tests.FTimeDeltaTests.test_negative_timedelta_update",
150-
"expressions.tests.ValueTests.test_update_TimeField_using_Value",
151-
"expressions.tests.ValueTests.test_update_UUIDField_using_Value",
152-
"expressions_case.tests.CaseDocumentationExamples.test_conditional_update_example",
153-
"expressions_case.tests.CaseExpressionTests.test_update",
154-
"expressions_case.tests.CaseExpressionTests.test_update_big_integer",
155-
"expressions_case.tests.CaseExpressionTests.test_update_binary",
156-
"expressions_case.tests.CaseExpressionTests.test_update_boolean",
157-
"expressions_case.tests.CaseExpressionTests.test_update_date",
158-
"expressions_case.tests.CaseExpressionTests.test_update_date_time",
159-
"expressions_case.tests.CaseExpressionTests.test_update_decimal",
160-
"expressions_case.tests.CaseExpressionTests.test_update_duration",
161-
"expressions_case.tests.CaseExpressionTests.test_update_email",
162-
"expressions_case.tests.CaseExpressionTests.test_update_file",
163-
"expressions_case.tests.CaseExpressionTests.test_update_file_path",
164-
"expressions_case.tests.CaseExpressionTests.test_update_fk",
165-
"expressions_case.tests.CaseExpressionTests.test_update_float",
166-
"expressions_case.tests.CaseExpressionTests.test_update_generic_ip_address",
167-
"expressions_case.tests.CaseExpressionTests.test_update_image",
168-
"expressions_case.tests.CaseExpressionTests.test_update_null_boolean",
169-
"expressions_case.tests.CaseExpressionTests.test_update_positive_big_integer",
170-
"expressions_case.tests.CaseExpressionTests.test_update_positive_integer",
171-
"expressions_case.tests.CaseExpressionTests.test_update_positive_small_integer",
172-
"expressions_case.tests.CaseExpressionTests.test_update_slug",
173-
"expressions_case.tests.CaseExpressionTests.test_update_small_integer",
174-
"expressions_case.tests.CaseExpressionTests.test_update_string",
175-
"expressions_case.tests.CaseExpressionTests.test_update_text",
176-
"expressions_case.tests.CaseExpressionTests.test_update_time",
177-
"expressions_case.tests.CaseExpressionTests.test_update_url",
178-
"expressions_case.tests.CaseExpressionTests.test_update_uuid",
179-
"expressions_case.tests.CaseExpressionTests.test_update_with_expression_as_condition",
180-
"expressions_case.tests.CaseExpressionTests.test_update_with_expression_as_value",
181-
"expressions_case.tests.CaseExpressionTests.test_update_without_default",
182-
"model_fields.test_integerfield.PositiveIntegerFieldTests.test_negative_values",
183-
"queries.test_bulk_update.BulkUpdateNoteTests",
184-
"queries.test_bulk_update.BulkUpdateTests",
185-
"timezones.tests.NewDatabaseTests.test_update_with_timedelta",
186-
"update.tests.AdvancedTests.test_update_annotated_queryset",
187-
"update.tests.AdvancedTests.test_update_negated_f",
188-
"update.tests.AdvancedTests.test_update_negated_f_conditional_annotation",
189-
"update.tests.AdvancedTests.test_update_transformed_field",
190-
},
191132
"AutoField not supported.": {
192133
"bulk_create.tests.BulkCreateTests.test_bulk_insert_nullable_fields",
193134
"lookup.tests.LookupTests.test_filter_by_reverse_related_field_transform",
@@ -216,6 +157,9 @@ def django_test_expected_failures(self):
216157
"one_to_one.tests.OneToOneTests.test_multiple_o2o",
217158
"queries.test_bulk_update.BulkUpdateTests.test_database_routing_batch_atomicity",
218159
},
160+
"MongoDB does not enforce PositiveIntegerField constraint.": {
161+
"model_fields.test_integerfield.PositiveIntegerFieldTests.test_negative_values",
162+
},
219163
"Test assumes integer primary key.": {
220164
"db_functions.comparison.test_cast.CastTests.test_cast_to_integer_foreign_key",
221165
"model_fields.test_foreignkey.ForeignKeyTests.test_to_python",
@@ -386,6 +330,7 @@ def django_test_expected_failures(self):
386330
"one_to_one.tests.OneToOneTests.test_o2o_primary_key_delete",
387331
},
388332
"Cannot use QuerySet.update() when querying across multiple collections on MongoDB.": {
333+
"expressions.tests.BasicExpressionsTests.test_filter_with_join",
389334
"queries.tests.Queries4Tests.test_ticket7095",
390335
"queries.tests.Queries5Tests.test_ticket9848",
391336
"update.tests.AdvancedTests.test_update_annotated_multi_table_queryset",

0 commit comments

Comments
 (0)