Skip to content

Commit 67302c3

Browse files
committed
add support for QuerySet.distinct()
1 parent b3fd2c4 commit 67302c3

File tree

2 files changed

+21
-39
lines changed

2 files changed

+21
-39
lines changed

django_mongodb/compiler.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,9 @@ def execute_sql(
277277
try:
278278
query = self.build_query(
279279
# Avoid $project (columns=None) if unneeded.
280-
columns if self.query.annotations or not self.query.default_cols else None
280+
columns
281+
if self.query.annotations or not self.query.default_cols or self.query.distinct
282+
else None
281283
)
282284
except EmptyResultSet:
283285
return iter([]) if result_type == MULTI else None
@@ -363,13 +365,7 @@ def cursor_iter(self, cursor, chunk_size, columns):
363365

364366
def check_query(self):
365367
"""Check if the current query is supported by the database."""
366-
if self.query.distinct or getattr(
367-
# In the case of Query.distinct().count(), the distinct attribute
368-
# will be set on the inner_query.
369-
getattr(self.query, "inner_query", None),
370-
"distinct",
371-
None,
372-
):
368+
if self.query.distinct:
373369
# This is a heuristic to detect QuerySet.datetimes() and dates().
374370
# "datetimefield" and "datefield" are the names of the annotations
375371
# the methods use. A user could annotate with the same names which
@@ -378,7 +374,6 @@ def check_query(self):
378374
raise NotSupportedError("QuerySet.datetimes() is not supported on MongoDB.")
379375
if "datefield" in self.query.annotations:
380376
raise NotSupportedError("QuerySet.dates() is not supported on MongoDB.")
381-
raise NotSupportedError("QuerySet.distinct() is not supported on MongoDB.")
382377
if self.query.extra:
383378
if any(key.startswith("_prefetch_related_") for key in self.query.extra):
384379
raise NotSupportedError("QuerySet.prefetch_related() is not supported on MongoDB.")
@@ -397,7 +392,19 @@ def build_query(self, columns=None):
397392
)
398393
query.combinator_pipeline = self.get_combinator_queries()
399394
else:
400-
query.project_fields = self.get_project_fields(columns, ordering_fields)
395+
if self.query.distinct:
396+
# If query is distinct, build a $group stage for distinct
397+
# fields, then set project fields based on the grouped _id.
398+
distinct_fields = self.get_project_fields(
399+
columns, ordering_fields, force_expression=True
400+
)
401+
if not query.aggregation_pipeline:
402+
query.aggregation_pipeline = []
403+
query.aggregation_pipeline.append({"$group": {"_id": distinct_fields}})
404+
query.project_fields = {key: f"$_id.{key}" for key in distinct_fields}
405+
else:
406+
# Otherwise, project fields without grouping.
407+
query.project_fields = self.get_project_fields(columns, ordering_fields)
401408
# If columns is None, then get_project_fields() won't add
402409
# ordering_fields to $project. Use $addFields (extra_fields) instead.
403410
if columns is None:
@@ -779,7 +786,7 @@ def build_query(self, columns=None):
779786
# Avoid $project (columns=None) if unneeded.
780787
columns = (
781788
compiler.get_columns()
782-
if compiler.query.annotations or not compiler.query.default_cols
789+
if self.query.annotations or not self.query.default_cols or self.query.distinct
783790
else None
784791
)
785792
subquery = compiler.build_query(columns)

django_mongodb/features.py

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -268,39 +268,13 @@ def django_test_expected_failures(self):
268268
"datetimes.tests.DateTimesTests.test_related_model_traverse",
269269
"model_inheritance_regress.tests.ModelInheritanceTest.test_issue_7105",
270270
"queries.tests.Queries1Tests.test_ticket7155",
271+
"queries.tests.Queries1Tests.test_ticket7791",
272+
"queries.tests.Queries1Tests.test_tickets_6180_6203",
271273
"queries.tests.Queries1Tests.test_tickets_7087_12242",
272274
"timezones.tests.LegacyDatabaseTests.test_query_datetimes",
273275
"timezones.tests.NewDatabaseTests.test_query_datetimes",
274276
"timezones.tests.NewDatabaseTests.test_query_datetimes_in_other_timezone",
275277
},
276-
"QuerySet.distinct() is not supported.": {
277-
"aggregation.tests.AggregateTestCase.test_sum_distinct_aggregate",
278-
"aggregation_regress.tests.AggregationTests.test_annotate_distinct_aggregate",
279-
"aggregation_regress.tests.AggregationTests.test_conditional_aggregate_on_complex_condition",
280-
"aggregation_regress.tests.AggregationTests.test_distinct_conditional_aggregate",
281-
"lookup.tests.LookupTests.test_lookup_collision_distinct",
282-
"many_to_many.tests.ManyToManyTests.test_reverse_selects",
283-
"many_to_many.tests.ManyToManyTests.test_selects",
284-
"many_to_one.tests.ManyToOneTests.test_reverse_selects",
285-
"ordering.tests.OrderingTests.test_orders_nulls_first_on_filtered_subquery",
286-
"queries.tests.ExcludeTest17600.test_exclude_plain_distinct",
287-
"queries.tests.ExcludeTest17600.test_exclude_with_q_is_equal_to_plain_exclude",
288-
"queries.tests.ExcludeTest17600.test_exclude_with_q_is_equal_to_plain_exclude_variation",
289-
"queries.tests.ExcludeTest17600.test_exclude_with_q_object_distinct",
290-
"queries.tests.ExcludeTests.test_exclude_m2m_through",
291-
"queries.tests.ExistsSql.test_distinct_exists",
292-
"queries.tests.ExistsSql.test_sliced_distinct_exists",
293-
"queries.tests.ExistsSql.test_ticket_18414",
294-
"queries.tests.Queries1Tests.test_ticket4464",
295-
"queries.tests.Queries1Tests.test_ticket7096",
296-
"queries.tests.Queries1Tests.test_ticket7791",
297-
"queries.tests.Queries1Tests.test_tickets_1878_2939",
298-
"queries.tests.Queries1Tests.test_tickets_5321_7070",
299-
"queries.tests.Queries1Tests.test_tickets_5324_6704",
300-
"queries.tests.Queries1Tests.test_tickets_6180_6203",
301-
"queries.tests.Queries6Tests.test_distinct_ordered_sliced_subquery_aggregation",
302-
"update.tests.AdvancedTests.test_update_all",
303-
},
304278
"QuerySet.extra() is not supported.": {
305279
"aggregation.tests.AggregateTestCase.test_exists_extra_where_with_aggregate",
306280
"annotations.tests.NonAggregateAnnotationTestCase.test_column_field_ordering",
@@ -318,6 +292,7 @@ def django_test_expected_failures(self):
318292
"queries.test_qs_combinators.QuerySetSetOperationTests.test_union_multiple_models_with_values_list_and_order_by_extra_select",
319293
"queries.test_qs_combinators.QuerySetSetOperationTests.test_union_with_extra_and_values_list",
320294
"queries.tests.EscapingTests.test_ticket_7302",
295+
"queries.tests.Queries1Tests.test_tickets_1878_2939",
321296
"queries.tests.Queries5Tests.test_extra_select_literal_percent_s",
322297
"queries.tests.Queries5Tests.test_ticket7256",
323298
"queries.tests.ValuesQuerysetTests.test_extra_multiple_select_params_values_order_by",

0 commit comments

Comments
 (0)