What
The Cypher route declines a variable whose name equals a binding column of the same frame (node-id, source, destination or edge-id), with typed error E108. Example on g.edges(df, "src", "dst"):
MATCH (src)-[e]->(dst) RETURN src.name
openCypher allows this: variables and property keys/columns are separate namespaces, and RETURN src projects an entity, so nothing collides.
Why we decline today
Lowering passes the variable straight through as the chain alias, and a chain alias is materialized as a marker column named after the alias on the frame that owns the binding. That is the same collision an op-list alias has, and there the decline is the right answer (an op-list alias is an output column, so the marker would be a duplicate output name; openCypher rejects duplicate result columns at RETURN the same way). On the Cypher surface the decline fires at the scoping layer, which is the wrong layer.
Contract (decided 2026-09-06 with the owner, thread on #2056)
One rule on both surfaces: scope shadows, projection declines duplicate output names. Binding columns are structure; the binding stays on the original column values everywhere.
Fix
- Lowering maps a binding-named variable to an internal marker (
generate_safe_column_name) for the chain.
- The row pipeline resolves
alias.prop, whole-entity RETURN alias, WITH alias re-entry and aggregates through that map; the endpoint / id column is never touched.
- Remove the Cypher-route E108 for this case; keep it on op lists.
- Pins: pos+neg on both surfaces, all four engines, single-hop / multi-hop / varlen,
RETURN src, RETURN src.src, WITH src ... MATCH, count(src); tck-gfql green; route harness parity.
Follow-up to #2056; not a change to #2056.
What
The Cypher route declines a variable whose name equals a binding column of the same frame (node-id, source, destination or edge-id), with typed error E108. Example on
g.edges(df, "src", "dst"):openCypher allows this: variables and property keys/columns are separate namespaces, and
RETURN srcprojects an entity, so nothing collides.Why we decline today
Lowering passes the variable straight through as the chain alias, and a chain alias is materialized as a marker column named after the alias on the frame that owns the binding. That is the same collision an op-list alias has, and there the decline is the right answer (an op-list alias is an output column, so the marker would be a duplicate output name; openCypher rejects duplicate result columns at
RETURNthe same way). On the Cypher surface the decline fires at the scoping layer, which is the wrong layer.Contract (decided 2026-09-06 with the owner, thread on #2056)
One rule on both surfaces: scope shadows, projection declines duplicate output names. Binding columns are structure; the binding stays on the original column values everywhere.
name=equal to a same-frame binding column stays a typed decline (fix(gfql): chain result contract: binding-column aliases decline, duplicate ids answer once, no internal columns (#2050, #2051, #2067) #2056). Cross-frame aliases already work.RETURNthat yields two output columns with one name is an error.Fix
generate_safe_column_name) for the chain.alias.prop, whole-entityRETURN alias,WITH aliasre-entry and aggregates through that map; the endpoint / id column is never touched.RETURN src,RETURN src.src,WITH src ... MATCH,count(src); tck-gfql green; route harness parity.Follow-up to #2056; not a change to #2056.