Skip to content

GFQL/cypher: count(<non-active pattern alias>) NIEs at lowering with misleading 'one MATCH' error #1708

Description

@lmeyerov

Summary (lowering routing; misleading error)

MATCH (a)-[e]->(b) RETURN b.id, count(a) ... fails on all engines with GFQLValidationError: [unsupported-cypher-query] Cypher row lowering currently supports one MATCH source alias at a time — even though the query has exactly one MATCH. The message is misleading; the real issue is an aggregate argument referencing a second pattern alias that isn't the chosen row source.

Repro

g.gfql("MATCH (a)-[e]->(b) RETURN b.id AS id, count(a) AS c ORDER BY c DESC LIMIT 3", engine="pandas")  # ERROR
g.gfql("MATCH (a)-[e]->(b) RETURN b.id AS id, count(*) AS c ORDER BY c DESC LIMIT 3", engine="pandas")  # ✅ works
g.gfql("MATCH (a)-[e]->(b) RETURN b.id AS id, count(b) AS c ORDER BY c DESC LIMIT 3", engine="pandas")  # ✅ works

Only count(<the OTHER endpoint alias>) trips it. This is graph-benchmark q1 ("top-3 most-followed": count(follower)).

Root cause (verified)

  • Error text: graphistry/compute/gfql/cypher/lowering.py:1716 (dupes at 2188, 2201).
  • Live path: _validate_row_expr_scope (lowering.py:1714-1722) via _validate_aggregate_expr_scope (2236-2258), called from aggregate-projection lowering (4468-4476).
  • The active row-source alias is chosen from the non-aggregate ref b.id → active=b (_active_match_alias_for_stage, 2126, used at 4019). Then count(a) references alias a; at 1714, active='b', root='a' != 'b', and a ∉ allowed_roots (single-source path leaves allowed_match_aliases empty) → raises. The query was routed to the single-source row table (materializes only b) rather than the multi-alias bindings table.

Fix (effort: S–M)

Lowering routing change, no new execution code (bindings table + group_by both already exist, engine-agnostic, and are used by q8/q9):

  • When an aggregate argument references a pattern alias other than the active alias, select the bindings-row source (rows(binding_ops=serialize_binding_ops(pattern)), factory ast.py:1576) and populate scope.allowed_match_aliases with all pattern aliases so _validate_row_expr_scope accepts a.
  • Then group_by(keys=[b-key], aggregations=[('c','count','a')]).
  • Touch points: _lower_aggregate_projection_stage (lowering.py:~4300-4480) and source/alias selection (~4019-4074). _reject_unsound_relationship_multiplicity_aggregates_common is already skipped on the bindings path (4324) — correct.
  • Minimal alternative: rewrite count(<other simple node alias>)count(*) when semantically identical (each row has exactly one a), but the bindings route is the general/correct fix (also needed for count(DISTINCT a) and multi-column projections of a).

Impact: makes graph-benchmark q1-q9 runnable as real GFQL Cypher on pandas/cuDF (removes the need for the dataframe shortcuts — see the benchmark-integrity issue).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions