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).
Summary (lowering routing; misleading error)
MATCH (a)-[e]->(b) RETURN b.id, count(a) ...fails on all engines withGFQLValidationError: [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
Only
count(<the OTHER endpoint alias>)trips it. This is graph-benchmark q1 ("top-3 most-followed":count(follower)).Root cause (verified)
graphistry/compute/gfql/cypher/lowering.py:1716(dupes at 2188, 2201)._validate_row_expr_scope(lowering.py:1714-1722) via_validate_aggregate_expr_scope(2236-2258), called from aggregate-projection lowering (4468-4476).b.id→ active=b(_active_match_alias_for_stage,2126, used at4019). Thencount(a)references aliasa; at1714,active='b',root='a' != 'b', anda ∉ allowed_roots(single-source path leavesallowed_match_aliasesempty) → raises. The query was routed to the single-source row table (materializes onlyb) rather than the multi-alias bindings table.Fix (effort: S–M)
Lowering routing change, no new execution code (bindings table +
group_byboth already exist, engine-agnostic, and are used by q8/q9):rows(binding_ops=serialize_binding_ops(pattern)), factoryast.py:1576) and populatescope.allowed_match_aliaseswith all pattern aliases so_validate_row_expr_scopeacceptsa.group_by(keys=[b-key], aggregations=[('c','count','a')])._lower_aggregate_projection_stage(lowering.py:~4300-4480) and source/alias selection (~4019-4074)._reject_unsound_relationship_multiplicity_aggregates_commonis already skipped on the bindings path (4324) — correct.count(<other simple node alias>)→count(*)when semantically identical (each row has exactly onea), but the bindings route is the general/correct fix (also needed forcount(DISTINCT a)and multi-column projections ofa).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).