Skip to content

Restricting a dj.U() aggregation raises a bare AssertionError #1580

Open

Description

@dimitri-yatsenko

Restricting a universal aggregation, dj.U().aggr(...) & <condition>, raises an AssertionError with no message, on any backend. The aggregation itself returns the expected single row; only the restriction fails, including when the restricted aggregate is used to restrict another table.

Versions: v2.3.4 and master at 68fdea5, MySQL 8.0.

Reproduction

@schema
class File(dj.Manual):
    definition = """
    file_id : int32
    ---
    filename : varchar(255)
    """

File.insert([(1, "a_part1.dat"), (2, "a_part2.dat"), (3, "a_meta.json")])
total = dj.U().aggr(File, n="count(*)")

total.to_dicts()                      # [{'n': 3}]
(total & "n = 3").to_dicts()          # AssertionError
(total & {"n": 3}).to_dicts()         # AssertionError
len(File & (total & "n = 3"))         # AssertionError

Expected: (total & "n = 3") returns [{'n': 3}] (and [] when the count differs), and File & (total & "n = 3") keeps every row of File when the restricted aggregate is non-empty and none when it is empty, as restriction by an expression with no common attributes does elsewhere (condition.py, "without common attributes, any non-empty set matches everything").

Cause

Aggregation.make_sql (expression.py:1358):

assert self._grouping_attributes or not self.restriction

A restriction on an aggregation is emitted as HAVING (MySQL) or as an outer WHERE around a subquery (PostgreSQL), but both paths assume grouping attributes exist. The PostgreSQL branch is also gated on them (use_subquery_for_having = ... and self._grouping_attributes).

Proposed fix

  • Remove the assertion.
  • MySQL: emit HAVING without GROUP BY, which MySQL accepts for an ungrouped aggregate and which may reference the select alias (SELECT count(*) AS n FROM t HAVING n = 3).
  • PostgreSQL: take the existing subquery path whenever the aggregation is restricted, with or without grouping attributes.
  • Tests: restrict dj.U().aggr(T, n="count(*)") by a string and by a dict, on both backends, with matching and non-matching counts; and restrict another table by the restricted aggregate.

Workaround

Wrap the aggregate in a subquery before restricting it:

gate = dj.U().aggr(File, n="count(*)").make_subquery() & {"n": 3}
File & gate      # all rows when the count is 3, none otherwise

len(gate) still fails on this form; see the companion issue on len() of an expression with an empty primary key.

Activity

  1. added this to the v2.4 milestone on Oct 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions