Skip to content

Elaborate an aggregate's argument: a subquery inside SUM(...) / COUNT(...) never lowers its descriptors #100

Open

Description

@matiastoro

elaborate_expr (src/elaborate/mod.rs) recurses into every expression form that can hold a subquery body — Binop, Call, Case, Record, Coalesce, ListComprehension, and the three subquery variants themselves — but Expr::Agg falls through its trailing other => other arm. So a descriptor written inside an aggregate's argument never has its value_filters lowered into a WHERE, and the query does not compile:

MATCH (p:P) RETURN SUM(VALUE { MATCH (x:P {n: 1}) RETURN x.n }) AS s
-- Type error: Descriptor for Some("x") still carries value_filters at typecheck
--   time (elaboration bug)

MATCH (p:P) RETURN COUNT(EXISTS { MATCH (x:P {n: 99}) }) AS c
-- same

-- control: the identical subquery outside an aggregate elaborates fine
MATCH (p:P) RETURN VALUE { MATCH (x:P {n: 1}) RETURN x.n } AS s
-- [1], [1], [1]

It fails loudly, which is the good news

The typechecker's guard — "still carries value_filters at typecheck time (elaboration bug)" — catches it, so this is a rejected legal query, not a silently wrong answer. The filter is never quietly dropped. That is why this is an enhancement rather than a correctness bug, and why it can wait.

Why it surfaced now

Before 2be5d5a a bare aggregate in RETURN was its own ReturnItem::Aggregate variant, which elaborate_query's RETURN loop skipped entirely — so the gap existed for the same shapes and for a second, unrelated reason. Folding the variant into ReturnItem::Expr (#95 follow-up) removed that second cause and left this one as the only thing standing between an aggregate's argument and elaboration. Behaviour is unchanged either way; the refactor just made the omission legible.

Fix

Add the missing arm to elaborate_expr:

Expr::Agg(agg) => Expr::Agg(Box::new(match *agg {
    Aggregator::CountStar => Aggregator::CountStar,
    Aggregator::GeneralSet { kind, quantifier, expr } => Aggregator::GeneralSet {
        kind,
        quantifier,
        expr: elaborate_expr(expr),
    },
})),

optimizer/existential.rs::fold_in_expr already descends into Expr::Agg this way, so the shape has a precedent in the codebase; elaboration is simply missing its counterpart.

Tests belong in a new case in tests/correlated_subquery_test.rs or alongside the aggregate tests: the two repros above, plus a control asserting the descriptor actually filters (a subquery whose descriptor excludes rows must return fewer than the unfiltered form, so the test fails if the filter is lowered but ignored).

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

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions