Skip to content

feat(cypher): multi-alias scalar RETURN and edge alias properties (#981, #982) - #986

Merged
lmeyerov merged 10 commits into
masterfrom
fix/cypher-multi-alias-row-merge
Mar 31, 2026
Merged

lmeyerov merged 10 commits into
masterfrom
fix/cypher-multi-alias-row-merge

Conversation

@lmeyerov

@lmeyerov lmeyerov commented Mar 31, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Support multi-alias scalar RETURN projections and edge alias property access in direct Cypher queries (#981, #982).

Before

MATCH (a:A)-[:R]->(b:B) RETURN a.id AS a_id, b.id AS b_id
→ GFQLValidationError: supports one MATCH source alias at a time

MATCH (a)-[r:KNOWS]->(b) RETURN a.id, r.creationDate, b.firstName
→ unsupported token in row expression: 'r'

After

g.gfql("MATCH (a:A)-[:R]->(b:B) RETURN a.id AS a_id, b.id AS b_id")
# → [{'a_id': 'a', 'b_id': 'b'}]

g.gfql("MATCH (a)-[r:KNOWS]->(b) RETURN a.id, r.creationDate AS cd, b.firstName")
# → [{'a.id': 'a', 'cd': 123, 'b.firstName': 'Bob'}]

Implementation

When multiple MATCH aliases appear in a scalar RETURN, builds a bindings table by joining edges with alias-prefixed node properties via vectorized merge. Each edge row becomes one result row. Pure columnar operations — works on both pandas and cudf.

Guard rails: Only enabled for scalar alias.prop projections from node aliases. Whole-row projections (RETURN n, x), expression-based outputs (RETURN type(r1)), and all-edge-alias patterns are correctly rejected.

Changes

File What
lowering.py _build_alias_endpoints() maps aliases to src/dst; catch multi-alias rejection → bindings path; _ProjectionPlan.all_source_aliases
pipeline.py rows(alias_endpoints=...) builds bindings table via merge
validation.py Allow alias_endpoints param on rows() safelist
ast.py rows() accepts optional alias_endpoints dict
test_lowering.py 11 new tests

Test coverage (11 new tests)

  • Basic multi-alias RETURN (a.id + b.id)
  • Multi-alias with property values (a.val + b.val)
  • Edge alias property access (r.creationDate) — AST row select cannot reference edge alias properties after traversal #982
  • Multiple bindings (2+ edges → 2+ rows)
  • Star graph (1 hub → 3 leaves → 3 rows)
  • Bidirectional edges (correct src/dst swap)
  • Duplicate edges (one binding per edge)
  • Empty match → empty result
  • Aggregate multi-alias still rejected
  • cypher_to_gfql helper works

GPU validation

All tests pass on both pandas and cudf (dgx-spark, graphistry/test-gpu:latest).

Test plan

  • 438 lowering tests pass (11 new, 0 regressions)
  • GPU validation: pandas + cudf on dgx-spark
  • mypy / lint clean
  • TCK conformance passes
  • CI: 97 checks green

🤖 Generated with Claude Code

lmeyerov and others added 10 commits March 30, 2026 20:40
Remove the hard rejection of multi-alias RETURN projections in
_active_match_alias_for_stage() and _build_projection_plan().
Add all_source_aliases tracking to _ProjectionPlan.

The bindings-table merge approach is proven but not yet wired
into execution. Saving progress.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
MATCH (a)-[:R]->(b) RETURN a.id AS a_id, b.id AS b_id now works.

Implementation: when multiple MATCH aliases appear in a scalar RETURN,
build a bindings table by joining edges with node properties for each
alias endpoint (src/dst). The joined table has columns prefixed with
alias names (a.id, b.id, etc.) that the existing row pipeline can
project from directly.

Key changes:
- lowering.py: _build_alias_endpoints() maps node aliases to src/dst
  edge endpoints; catch multi-alias rejection and use bindings path
- pipeline.py: rows(alias_endpoints=...) builds the bindings table
  via vectorized merge (pandas/cudf compatible)
- validation.py: allow alias_endpoints param on rows() safelist
- ast.py: rows() accepts optional alias_endpoints dict
- _ProjectionPlan: track all_source_aliases for multi-alias detection

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…982)

Add 4 end-to-end tests for multi-alias RETURN:
- basic a.id + b.id projection
- multiple bindings (2 edges → 2 rows)
- empty match → empty result
- edge alias property access (r.creationDate) — validates #982 fix

DRY: remove duplicated projection/distinct/order_by/page logic from
try/except fallback path in _lower_projection_chain; both single and
multi-alias now share the same downstream code.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Only allow multi-alias RETURN for scalar projections (a.id, b.name).
Whole-row projections (RETURN n, x) with multiple aliases still raise
the unsupported error, as the bindings table approach doesn't handle
entity-valued outputs.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Only use bindings-table path for pure scalar alias.prop projections.
Reject multi-alias when whole-row outputs or expression-based outputs
(type(), count(), etc.) are present.

Fix mypy: use Optional[GFQLValidationError] for the caught exception.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Tighten multi-alias guard: only use bindings path when at least one
referenced alias is a node (not when all are edge aliases like r1, r2).
Fix both compilation entry point and _lower_projection_chain to use
consistent all_are_edges check.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Add 3 edge-case tests: star graph (1->N bindings), bidirectional edges
(correct src/dst swap), duplicate edges (one binding per edge).

GPU validation: all tests pass on both pandas and cudf (dgx-spark,
graphistry/test-gpu:latest container). The bindings table merge
works correctly with cudf DataFrames.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Document that WITH multi-alias scalar projections are not yet
supported (goes through a different code path than RETURN).
12 total multi-alias tests now.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant