Skip to content

fix(planner): match a reused relationship variable as the same edge - #83

Merged
prrao87 merged 3 commits into
mainfrom
fix/shared-relationship-variable
Oct 28, 2026
Merged

prrao87 merged 3 commits into
mainfrom
fix/shared-relationship-variable

Conversation

@prrao87

@prrao87 prrao87 commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

Why

A relationship variable written a second time in a query should match that same relationship. Instead, the second occurrence was mishandled:

Query (edges A->B, B->C) Before After
MATCH (a)-[r:KNOWS]->(b), (c)-[r]->(d) RETURN a.name 8 rows A, B
... RETURN c.name, d.name error: no field d__name A-B, B-C
MATCH (a)-[r:KNOWS]->(b), (c)-[r:KNOWS]->(d) ... error: duplicate field r__src 2 rows

An untyped [r] was planned as a no-op, so its endpoints were never bound. A typed one scanned the relationship again under the same name. The issue named a cross join as the cause, but the cross join only connects the second part's start node, as for any pattern that starts from an unbound node.

Fixes #58. Builds on #82 (#59), which has merged; this PR now targets main and its diff shows only this change.

What changed

  • When a hop's relationship variable is already bound in the input rows, the planner no longer scans the relationship. It ties the hop's endpoint nodes to the bound relationship's source and target columns instead: a join for an unbound endpoint, a filter for a bound one. Each direction is handled; an undirected reuse matches either orientation.
  • Because the check looks at the input rows, the same code covers reuse in one MATCH, in a later MATCH, and after WITH r.
  • A reuse written without a type takes the type the variable was first written with, so its endpoints get labels as usual. A different type matches nothing, and inline property filters such as [r {w: 10}] apply to the bound relationship.
  • Reusing a relationship variable in a variable-length segment returned wrong rows. Within one MATCH it now raises an error.
  • The Cypher guide documents the rule and its limits.

Known limits, marked as a shortcut: in the code and in the guide:

  • An undirected binding holds its edge in the direction it was walked, so a later directed reuse follows that direction rather than the stored one.
  • Types are remembered by variable name for the whole query, which only matters if a query drops a relationship variable and reuses its name, untyped, for a new one.
  • Variable-length reuse across clauses is still unsupported, without an error.

Validation

  • cargo test --workspace: all pass, including a new integration test where both the type and the endpoint labels come from the first binding.
  • cargo clippy --workspace --all-targets -- -D warnings and cargo fmt --all: clean.
  • make test in python/: 1619 passed. New cases cover each direction, a reuse in a later MATCH, a reversed bound reuse, inline properties, relationship properties and the variable-length error, with use_csr off and on.
  • make lint, zensical build --strict and typos: clean.

Benchmarks

The benchmark harness from CONTRIBUTING.md was run against this PR's head (9dae1c8, which includes #82) and main, in both suites and both modes. Every run passed every expected-answer check. None of the 44 benchmark queries reuses a relationship variable, so this PR's code path isn't reached; the type pass runs on every query, but it only walks the pattern list. The results, every query and the cost of #82's filter on IC8 and IC14 are in #82's description.

Suite Mode Branch / main (geometric mean)
Custom (30 queries) memory 1.008
Custom (30 queries) lazy 0.999
Interactive complex (IC1–IC14) memory 1.021
Interactive complex (IC1–IC14) lazy 0.972

🤖 Generated with Claude Code

Two single-hop relationships of the same type in one MATCH could bind
the same edge. Add a filter that their endpoints differ, naming
anonymous endpoints so they can be compared.

Fixes #59
Writing a bound relationship variable again, as in
(a)-[r:KNOWS]->(b), (c)-[r]->(d), left the second part unplanned or
failed on duplicate columns. Plan it as a join or filter on r's
endpoint columns, taking an omitted type from the first binding.

Fixes #58
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Deploying lance-csr with  Cloudflare Pages  Cloudflare Pages

Latest commit: 6ad762f
Status: ✅  Deploy successful!
Preview URL: https://3b96f91f.lance-csr.pages.dev
Branch Preview URL: https://fix-shared-relationship-vari.lance-csr.pages.dev

View logs

Base automatically changed from fix/relationship-uniqueness to main October 28, 2026 21:22
@prrao87
prrao87 marked this pull request as ready for review October 28, 2026 23:17
@prrao87
prrao87 merged commit 92c5353 into main Oct 28, 2026
8 checks passed
@prrao87
prrao87 deleted the fix/shared-relationship-variable branch October 28, 2026 23:18
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.

Shared relationship variable across pattern parts falls back to a cross join

1 participant