| 545 | 545 | |
| 546 | 546 | return dict(zip(outer_query.annotation_select, result)) |
| 547 | 547 | |
| 548 | def strip_unused_annotations(self): | |
| 549 | """ | |
| 550 | Remove annotations that are not referenced in filters, ordering, or other annotations. | |
| 551 | This optimizes count() queries by excluding unnecessary annotations. | |
| 552 | """ | |
| 553 | if not self.annotations: | |
| 554 | return | |
| 555 | ||
| 556 | # Find all annotation names that are referenced in the query | |
| 557 | referenced_annotations = set() | |
| 558 | ||
| 559 | # Check filters (WHERE clause) | |
| 560 | if self.where: | |
| 561 | referenced_annotations.update(self._find_referenced_annotations(self.where)) | |
| 562 | ||
| 563 | # Check ordering | |
| 564 | for order_expr in self.order_by: | |
| 565 | referenced_annotations.update(self._find_referenced_annotations(order_expr)) | |
| 566 | ||
| 567 | # Check other annotations (annotations can reference other annotations) | |
| 568 | for alias, annotation in list(self.annotations.items()): | |
| 569 | # Find references in this annotation, but exclude self-references | |
| 570 | referenced_in_annotation = self._find_referenced_annotations_excluding_self(annotation, alias) | |
| 571 | referenced_annotations.update(referenced_in_annotation) | |
| 572 | ||
| 573 | # Keep only annotations that are referenced | |
| 574 | annotations_to_keep = {} | |
| 575 | for alias, annotation in self.annotations.items(): | |
| 576 | if alias in referenced_annotations: | |
| 577 | annotations_to_keep[alias] = annotation | |
| 578 | ||
| 579 | self.annotations = annotations_to_keep | |
| 580 | # Clear cache to ensure annotation_select is recalculated | |
| 581 | self._annotation_select_cache = None | |
| 582 | ||
| 583 | def _find_referenced_annotations(self, expr): | |
| 584 | """ | |
| 585 | Recursively find all annotation names referenced in an expression. | |
| 586 | """ | |
| 587 | referenced = set() | |
| 588 | ||
| 589 | if expr is None: | |
| 590 | return referenced | |
| 591 | ||
| 592 | # Handle string references (used in ordering) | |
| 593 | if isinstance(expr, str): | |
| 594 | if expr in self.annotations: | |
| 595 | referenced.add(expr) | |
| 596 | return referenced | |
| 597 | ||
| 598 | # Check if this expression directly matches any annotation | |
| 599 | for alias, annotation in self.annotations.items(): | |
| 600 | if expr is annotation: | |
| 601 | referenced.add(alias) | |
| 602 | ||
| 603 | # Handle F expressions that reference annotations | |
| 604 | if hasattr(expr, 'name') and isinstance(expr, F): | |
| 605 | # Check if this F expression references an annotation | |
| 606 | if expr.name in self.annotations: | |
| 607 | referenced.add(expr.name) | |
| 608 | ||
| 609 | # Handle Ref expressions (used for annotation references in subqueries) | |
| 610 | if hasattr(expr, 'refs') and isinstance(expr, Ref): | |
| 611 | if expr.refs in self.annotations: | |
| 612 | referenced.add(expr.refs) | |
| 613 | ||
| 614 | # Recursively check source expressions | |
| 615 | if hasattr(expr, 'get_source_expressions'): | |
| 616 | for source_expr in expr.get_source_expressions(): | |
| 617 | referenced.update(self._find_referenced_annotations(source_expr)) | |
| 618 | ||
| 619 | # Handle WhereNode and other tree-like structures | |
| 620 | if hasattr(expr, 'children'): | |
| 621 | for child in expr.children: | |
| 622 | referenced.update(self._find_referenced_annotations(child)) | |
| 623 | ||
| 624 | # Handle expressions with lhs and rhs (like GreaterThan, LessThan, etc.) | |
| 625 | if hasattr(expr, 'lhs') and hasattr(expr, 'rhs'): | |
| 626 | referenced.update(self._find_referenced_annotations(expr.lhs)) | |
| 627 | referenced.update(self._find_referenced_annotations(expr.rhs)) | |
| 628 | ||
| 629 | # Handle expressions with a single child (like Neg, etc.) | |
| 630 | if hasattr(expr, 'child'): | |
| 631 | referenced.update(self._find_referenced_annotations(expr.child)) | |
| 632 | ||
| 633 | return referenced | |
| 634 | ||
| 635 | def _find_referenced_annotations_excluding_self(self, expr, exclude_alias): | |
| 636 | """ | |
| 637 | Find referenced annotations in an expression, excluding self-references. | |
| 638 | """ | |
| 639 | referenced = set() | |
| 640 | ||
| 641 | if expr is None: | |
| 642 | return referenced | |
| 643 | ||
| 644 | # Handle string references (used in ordering) | |
| 645 | if isinstance(expr, str): | |
| 646 | if expr in self.annotations and expr != exclude_alias: | |
| 647 | referenced.add(expr) | |
| 648 | return referenced | |
| 649 | ||
| 650 | # Check if this expression directly matches any annotation (excluding self) | |
| 651 | for alias, annotation in self.annotations.items(): | |
| 652 | if expr is annotation and alias != exclude_alias: | |
| 653 | referenced.add(alias) | |
| 654 | ||
| 655 | # Handle F expressions that reference annotations | |
| 656 | if hasattr(expr, 'name') and isinstance(expr, F): | |
| 657 | # Check if this F expression references an annotation (excluding self) | |
| 658 | if expr.name in self.annotations and expr.name != exclude_alias: | |
| 659 | referenced.add(expr.name) | |
| 660 | ||
| 661 | # Handle Ref expressions (used for annotation references in subqueries) | |
| 662 | if hasattr(expr, 'refs') and isinstance(expr, Ref): | |
| 663 | if expr.refs in self.annotations and expr.refs != exclude_alias: | |
| 664 | referenced.add(expr.refs) | |
| 665 | ||
| 666 | # Recursively check source expressions | |
| 667 | if hasattr(expr, 'get_source_expressions'): | |
| 668 | for source_expr in expr.get_source_expressions(): | |
| 669 | referenced.update(self._find_referenced_annotations_excluding_self(source_expr, exclude_alias)) | |
| 670 | ||
| 671 | # Handle WhereNode and other tree-like structures | |
| 672 | if hasattr(expr, 'children'): | |
| 673 | for child in expr.children: | |
| 674 | referenced.update(self._find_referenced_annotations_excluding_self(child, exclude_alias)) | |
| 675 | ||
| 676 | # Handle expressions with lhs and rhs (like GreaterThan, LessThan, etc.) | |
| 677 | if hasattr(expr, 'lhs') and hasattr(expr, 'rhs'): | |
| 678 | referenced.update(self._find_referenced_annotations_excluding_self(expr.lhs, exclude_alias)) | |
| 679 | referenced.update(self._find_referenced_annotations_excluding_self(expr.rhs, exclude_alias)) | |
| 680 | ||
| 681 | # Handle expressions with a single child (like Neg, etc.) | |
| 682 | if hasattr(expr, 'child'): | |
| 683 | referenced.update(self._find_referenced_annotations_excluding_self(expr.child, exclude_alias)) | |
| 684 | ||
| 685 | return referenced | |
| 686 | ||
| 548 | 687 | def get_count(self, using): |
| 549 | 688 | """ |
| 550 | 689 | Perform a COUNT() query using the current filter constraints. |
| 551 | 690 | """ |
| 552 | 691 | obj = self.clone() |
| 692 | # Strip unused annotations to optimize count queries | |
| 693 | obj.strip_unused_annotations() | |
| 553 | 694 | obj.add_annotation(Count("*"), alias="__count", is_summary=True) |
| 554 | 695 | return obj.get_aggregation(using, ["__count"])["__count"] |
| 555 | 696 |
| Test Name | Status |
|---|---|
test_unreferenced_aggregate_annotation_pruned (aggregation.tests.AggregateAnnotationPruningTests) | Fail |
Subquery annotations must be included in the GROUP BY if they use | Fail |
test_non_aggregate_annotation_pruned (aggregation.tests.AggregateAnnotationPruningTests) | Pass |
test_unused_aliased_aggregate_pruned (aggregation.tests.AggregateAnnotationPruningTests) | Pass |
test_referenced_aggregate_annotation_kept (aggregation.tests.AggregateAnnotationPruningTests) | Pass |
test_add_implementation (aggregation.tests.AggregateTestCase) | Pass |
test_aggregate_alias (aggregation.tests.AggregateTestCase) | Pass |
test_aggregate_annotation (aggregation.tests.AggregateTestCase) | Pass |
test_aggregate_in_order_by (aggregation.tests.AggregateTestCase) | Pass |
test_aggregate_join_transform (aggregation.tests.AggregateTestCase) | Pass |
test_aggregate_multi_join (aggregation.tests.AggregateTestCase) | Pass |
test_aggregate_over_aggregate (aggregation.tests.AggregateTestCase) | Pass |
test_aggregate_over_complex_annotation (aggregation.tests.AggregateTestCase) | Pass |
test_aggregate_transform (aggregation.tests.AggregateTestCase) | Pass |
test_aggregation_default_after_annotation (aggregation.tests.AggregateTestCase) | Pass |
test_aggregation_default_compound_expression (aggregation.tests.AggregateTestCase) | Pass |
test_aggregation_default_expression (aggregation.tests.AggregateTestCase) | Pass |
test_aggregation_default_group_by (aggregation.tests.AggregateTestCase) | Pass |
test_aggregation_default_integer (aggregation.tests.AggregateTestCase) | Pass |
test_aggregation_default_not_in_aggregate (aggregation.tests.AggregateTestCase) | Pass |
test_aggregation_default_passed_another_aggregate (aggregation.tests.AggregateTestCase) | Pass |
test_aggregation_default_unset (aggregation.tests.AggregateTestCase) | Pass |
test_aggregation_default_unsupported_by_count (aggregation.tests.AggregateTestCase) | Pass |
test_aggregation_default_using_date_from_database (aggregation.tests.AggregateTestCase) | Pass |
test_aggregation_default_using_date_from_python (aggregation.tests.AggregateTestCase) | Pass |
test_aggregation_default_using_datetime_from_database (aggregation.tests.AggregateTestCase) | Pass |
test_aggregation_default_using_datetime_from_python (aggregation.tests.AggregateTestCase) | Pass |
test_aggregation_default_using_decimal_from_database (aggregation.tests.AggregateTestCase) | Pass |
test_aggregation_default_using_decimal_from_python (aggregation.tests.AggregateTestCase) | Pass |
test_aggregation_default_using_duration_from_database (aggregation.tests.AggregateTestCase) | Pass |
test_aggregation_default_using_duration_from_python (aggregation.tests.AggregateTestCase) | Pass |
test_aggregation_default_using_time_from_database (aggregation.tests.AggregateTestCase) | Pass |
test_aggregation_default_using_time_from_python (aggregation.tests.AggregateTestCase) | Pass |
test_aggregation_default_zero (aggregation.tests.AggregateTestCase) | Pass |
test_aggregation_exists_annotation (aggregation.tests.AggregateTestCase) | Pass |
test_aggregation_exists_multivalued_outeref (aggregation.tests.AggregateTestCase) | Pass |
test_aggregation_expressions (aggregation.tests.AggregateTestCase) | Pass |
test_aggregation_filter_exists (aggregation.tests.AggregateTestCase) | Pass |
test_aggregation_nested_subquery_outerref (aggregation.tests.AggregateTestCase) | Pass |
test_aggregation_order_by_not_selected_annotation_values (aggregation.tests.AggregateTestCase) | Pass |
Random() is not included in the GROUP BY when used for ordering. | Pass |
Subquery annotations are excluded from the GROUP BY if they are | Pass |
test_aggregation_subquery_annotation_exists (aggregation.tests.AggregateTestCase) | Pass |
test_aggregation_subquery_annotation_related_field (aggregation.tests.AggregateTestCase) | Pass |
Subquery annotations and external aliases are excluded from the GROUP | Pass |
test_aggregation_subquery_annotation_values_collision (aggregation.tests.AggregateTestCase) | Pass |
test_alias_sql_injection (aggregation.tests.AggregateTestCase) | Pass |
test_annotate_basic (aggregation.tests.AggregateTestCase) | Pass |
test_annotate_defer (aggregation.tests.AggregateTestCase) | Pass |
test_annotate_defer_select_related (aggregation.tests.AggregateTestCase) | Pass |
test_annotate_m2m (aggregation.tests.AggregateTestCase) | Pass |
test_annotate_ordering (aggregation.tests.AggregateTestCase) | Pass |
test_annotate_over_annotate (aggregation.tests.AggregateTestCase) | Pass |
test_annotate_values (aggregation.tests.AggregateTestCase) | Pass |
test_annotate_values_aggregate (aggregation.tests.AggregateTestCase) | Pass |
test_annotate_values_list (aggregation.tests.AggregateTestCase) | Pass |
test_annotated_aggregate_over_annotated_aggregate (aggregation.tests.AggregateTestCase) | Pass |
test_annotation (aggregation.tests.AggregateTestCase) | Pass |
test_annotation_expressions (aggregation.tests.AggregateTestCase) | Pass |
test_arguments_must_be_expressions (aggregation.tests.AggregateTestCase) | Pass |
test_avg_decimal_field (aggregation.tests.AggregateTestCase) | Pass |
test_avg_duration_field (aggregation.tests.AggregateTestCase) | Pass |
test_backwards_m2m_annotate (aggregation.tests.AggregateTestCase) | Pass |
test_coalesced_empty_result_set (aggregation.tests.AggregateTestCase) | Pass |
test_combine_different_types (aggregation.tests.AggregateTestCase) | Pass |
test_complex_aggregations_require_kwarg (aggregation.tests.AggregateTestCase) | Pass |
test_complex_values_aggregation (aggregation.tests.AggregateTestCase) | Pass |
test_count (aggregation.tests.AggregateTestCase) | Pass |
test_count_distinct_expression (aggregation.tests.AggregateTestCase) | Pass |
test_count_star (aggregation.tests.AggregateTestCase) | Pass |
.dates() returns a distinct set of dates when applied to a | Pass |
test_decimal_max_digits_has_no_effect (aggregation.tests.AggregateTestCase) | Pass |
test_distinct_on_aggregate (aggregation.tests.AggregateTestCase) | Pass |
test_empty_aggregate (aggregation.tests.AggregateTestCase) | Pass |
test_empty_result_optimization (aggregation.tests.AggregateTestCase) | Pass |
test_even_more_aggregate (aggregation.tests.AggregateTestCase) | Pass |
test_exists_extra_where_with_aggregate (aggregation.tests.AggregateTestCase) | Pass |
test_exists_none_with_aggregate (aggregation.tests.AggregateTestCase) | Pass |
test_expression_on_aggregation (aggregation.tests.AggregateTestCase) | Pass |
test_filter_aggregate (aggregation.tests.AggregateTestCase) | Pass |
Filtering against an aggregate requires the usage of the HAVING clause. | Pass |
test_filtering (aggregation.tests.AggregateTestCase) | Pass |
test_fkey_aggregate (aggregation.tests.AggregateTestCase) | Pass |
Exists annotations are included in the GROUP BY if they are | Pass |
Subquery annotations are included in the GROUP BY if they are | Pass |
An annotation included in values() before an aggregate should be | Pass |
test_more_aggregation (aggregation.tests.AggregateTestCase) | Pass |
test_multi_arg_aggregate (aggregation.tests.AggregateTestCase) | Pass |
test_multiple_aggregates (aggregation.tests.AggregateTestCase) | Pass |
An annotation not included in values() before an aggregate should be | Pass |
test_nonaggregate_aggregation_throws (aggregation.tests.AggregateTestCase) | Pass |
test_nonfield_annotation (aggregation.tests.AggregateTestCase) | Pass |
test_order_of_precedence (aggregation.tests.AggregateTestCase) | Pass |
test_related_aggregate (aggregation.tests.AggregateTestCase) | Pass |
test_reverse_fkey_annotate (aggregation.tests.AggregateTestCase) | Pass |
test_single_aggregate (aggregation.tests.AggregateTestCase) | Pass |
Sum on a distinct() QuerySet should aggregate only the distinct items. | Pass |
test_sum_duration_field (aggregation.tests.AggregateTestCase) | Pass |
Subqueries do not needlessly contain ORDER BY, SELECT FOR UPDATE or | Pass |
Aggregation over sliced queryset works correctly. | Pass |
Doing exclude() on a foreign model after annotate() doesn't crash. | Pass |
test_values_aggregation (aggregation.tests.AggregateTestCase) | Pass |
test_values_annotation_with_expression (aggregation.tests.AggregateTestCase) | Pass |
Loading...
Ridges.AI© 2025 Ridges AI. Building the future of decentralized AI development.
