diff --git a/django_mongodb/compiler.py b/django_mongodb/compiler.py index a21ce9b86..9f1478d75 100644 --- a/django_mongodb/compiler.py +++ b/django_mongodb/compiler.py @@ -245,7 +245,9 @@ def execute_sql( try: query = self.build_query( # Avoid $project (columns=None) if unneeded. - columns if self.query.annotations or not self.query.default_cols else None + columns + if self.query.annotations or not self.query.default_cols or self.query.distinct + else None ) except EmptyResultSet: return iter([]) if result_type == MULTI else None @@ -331,13 +333,7 @@ def cursor_iter(self, cursor, chunk_size, columns): def check_query(self): """Check if the current query is supported by the database.""" - if self.query.distinct or getattr( - # In the case of Query.distinct().count(), the distinct attribute - # will be set on the inner_query. - getattr(self.query, "inner_query", None), - "distinct", - None, - ): + if self.query.distinct: # This is a heuristic to detect QuerySet.datetimes() and dates(). # "datetimefield" and "datefield" are the names of the annotations # the methods use. A user could annotate with the same names which @@ -346,7 +342,6 @@ def check_query(self): raise NotSupportedError("QuerySet.datetimes() is not supported on MongoDB.") if "datefield" in self.query.annotations: raise NotSupportedError("QuerySet.dates() is not supported on MongoDB.") - raise NotSupportedError("QuerySet.distinct() is not supported on MongoDB.") if self.query.extra: if any(key.startswith("_prefetch_related_") for key in self.query.extra): raise NotSupportedError("QuerySet.prefetch_related() is not supported on MongoDB.") @@ -365,7 +360,19 @@ def build_query(self, columns=None): ) query.combinator_pipeline = self.get_combinator_queries() else: - query.project_fields = self.get_project_fields(columns, ordering_fields) + if self.query.distinct: + # If query is distinct, build a $group stage for distinct + # fields, then set project fields based on the grouped _id. + distinct_fields = self.get_project_fields( + columns, ordering_fields, force_expression=True + ) + if not query.aggregation_pipeline: + query.aggregation_pipeline = [] + query.aggregation_pipeline.append({"$group": {"_id": distinct_fields}}) + query.project_fields = {key: f"$_id.{key}" for key in distinct_fields} + else: + # Otherwise, project fields without grouping. + query.project_fields = self.get_project_fields(columns, ordering_fields) # If columns is None, then get_project_fields() won't add # ordering_fields to $project. Use $addFields (extra_fields) instead. if columns is None: @@ -750,7 +757,7 @@ def build_query(self, columns=None): # Avoid $project (columns=None) if unneeded. columns = ( compiler.get_columns() - if compiler.query.annotations or not compiler.query.default_cols + if self.query.annotations or not self.query.default_cols or self.query.distinct else None ) subquery = compiler.build_query(columns) diff --git a/django_mongodb/features.py b/django_mongodb/features.py index 8278004b6..842e89c48 100644 --- a/django_mongodb/features.py +++ b/django_mongodb/features.py @@ -231,39 +231,13 @@ def django_test_expected_failures(self): "datetimes.tests.DateTimesTests.test_related_model_traverse", "model_inheritance_regress.tests.ModelInheritanceTest.test_issue_7105", "queries.tests.Queries1Tests.test_ticket7155", + "queries.tests.Queries1Tests.test_ticket7791", + "queries.tests.Queries1Tests.test_tickets_6180_6203", "queries.tests.Queries1Tests.test_tickets_7087_12242", "timezones.tests.LegacyDatabaseTests.test_query_datetimes", "timezones.tests.NewDatabaseTests.test_query_datetimes", "timezones.tests.NewDatabaseTests.test_query_datetimes_in_other_timezone", }, - "QuerySet.distinct() is not supported.": { - "aggregation.tests.AggregateTestCase.test_sum_distinct_aggregate", - "aggregation_regress.tests.AggregationTests.test_annotate_distinct_aggregate", - "aggregation_regress.tests.AggregationTests.test_conditional_aggregate_on_complex_condition", - "aggregation_regress.tests.AggregationTests.test_distinct_conditional_aggregate", - "lookup.tests.LookupTests.test_lookup_collision_distinct", - "many_to_many.tests.ManyToManyTests.test_reverse_selects", - "many_to_many.tests.ManyToManyTests.test_selects", - "many_to_one.tests.ManyToOneTests.test_reverse_selects", - "ordering.tests.OrderingTests.test_orders_nulls_first_on_filtered_subquery", - "queries.tests.ExcludeTest17600.test_exclude_plain_distinct", - "queries.tests.ExcludeTest17600.test_exclude_with_q_is_equal_to_plain_exclude", - "queries.tests.ExcludeTest17600.test_exclude_with_q_is_equal_to_plain_exclude_variation", - "queries.tests.ExcludeTest17600.test_exclude_with_q_object_distinct", - "queries.tests.ExcludeTests.test_exclude_m2m_through", - "queries.tests.ExistsSql.test_distinct_exists", - "queries.tests.ExistsSql.test_sliced_distinct_exists", - "queries.tests.ExistsSql.test_ticket_18414", - "queries.tests.Queries1Tests.test_ticket4464", - "queries.tests.Queries1Tests.test_ticket7096", - "queries.tests.Queries1Tests.test_ticket7791", - "queries.tests.Queries1Tests.test_tickets_1878_2939", - "queries.tests.Queries1Tests.test_tickets_5321_7070", - "queries.tests.Queries1Tests.test_tickets_5324_6704", - "queries.tests.Queries1Tests.test_tickets_6180_6203", - "queries.tests.Queries6Tests.test_distinct_ordered_sliced_subquery_aggregation", - "update.tests.AdvancedTests.test_update_all", - }, "QuerySet.extra() is not supported.": { "aggregation.tests.AggregateTestCase.test_exists_extra_where_with_aggregate", "annotations.tests.NonAggregateAnnotationTestCase.test_column_field_ordering", @@ -281,6 +255,7 @@ def django_test_expected_failures(self): "queries.test_qs_combinators.QuerySetSetOperationTests.test_union_multiple_models_with_values_list_and_order_by_extra_select", "queries.test_qs_combinators.QuerySetSetOperationTests.test_union_with_extra_and_values_list", "queries.tests.EscapingTests.test_ticket_7302", + "queries.tests.Queries1Tests.test_tickets_1878_2939", "queries.tests.Queries5Tests.test_extra_select_literal_percent_s", "queries.tests.Queries5Tests.test_ticket7256", "queries.tests.ValuesQuerysetTests.test_extra_multiple_select_params_values_order_by",