Skip to content

Fix entity_exists() CID visibility for CREATE + WITH + MERGE#2343

Open
gregfelice wants to merge 1 commit intoapache:masterfrom
gregfelice:fix_1954_merge_entity_exists_visibility
Open

Fix entity_exists() CID visibility for CREATE + WITH + MERGE#2343
gregfelice wants to merge 1 commit intoapache:masterfrom
gregfelice:fix_1954_merge_entity_exists_visibility

Conversation

@gregfelice
Copy link
Contributor

Summary

Fixes #1954.

When a Cypher query chains CREATE ... WITH ... MERGE, vertices created by CREATE become invisible to entity_exists() after a threshold number of input rows, causing MERGE to throw "vertex assigned to variable was deleted".

Root cause: CREATE calls CommandCounterIncrement() which advances the global command ID, but does not update es_snapshot->curcid. The Decrement_Estate_CommandId / Increment_Estate_CommandId macros used by the executors bring curcid back to the same value on each iteration. After enough rows, newly inserted vertices have a Cmin >= curcid and HeapTupleSatisfiesMVCC rejects them (it requires Cmin < curcid).

Example: With 3 input rows, the global CID advances to 3 after three CREATEs, but curcid stays at 2 (due to Decrement/Increment pairs). The third vertex (Cmin=2) is invisible because 2 < 2 is false.

Fix: In entity_exists(), temporarily set es_snapshot->curcid to the current global command ID (via GetCurrentCommandId(false)) for the duration of the heap scan, then restore it. This makes all entities inserted by preceding clauses in the same query visible to the existence check without affecting the broader CID management.

Changes

  • src/backend/executor/cypher_utils.c: Save/restore curcid around the entity_exists() scan
  • regress/sql/cypher_merge.sql: Regression tests for CREATE + WITH + MERGE with 3 rows (1 and 2 MERGEs)
  • regress/expected/cypher_merge.out: Expected output

Test plan

  • Reproduced the bug: 3+ input rows with CREATE + WITH + MERGE triggers "vertex was deleted"
  • Verified the fix: all rows return correct values and edges are created
  • Tested with 2 MERGEs (reporter's full pattern): works correctly
  • All 31 regression tests pass

🤖 Generated with Claude Code

…1954)

When a Cypher query chains CREATE ... WITH ... MERGE, vertices created
by CREATE become invisible to entity_exists() after a threshold number
of input rows. This causes MERGE to throw "vertex assigned to variable
was deleted".

Root cause: CREATE calls CommandCounterIncrement() which advances the
global command ID, but does not update es_snapshot->curcid. The
Decrement/Increment CID macros used by the executors bring curcid back
to the same value on each iteration. After enough rows, newly inserted
vertices have a Cmin >= curcid and HeapTupleSatisfiesMVCC rejects them
(requires Cmin < curcid).

Fix: In entity_exists(), temporarily set es_snapshot->curcid to the
current global command ID (via GetCurrentCommandId) for the duration
of the scan, then restore it. This makes all entities inserted by
preceding clauses in the same query visible to the existence check.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a CID/snapshot visibility bug where entities created earlier in the same Cypher query (CREATE ... WITH ... MERGE) can become invisible to entity_exists(), leading to erroneous “vertex assigned … was deleted” failures (issue #1954).

Changes:

  • Update entity_exists() to temporarily adjust the executor snapshot curcid during the heap scan.
  • Add regression coverage for CREATE + WITH + MERGE over 3 input rows, including a case with two MERGEs.
  • Update expected regression output accordingly.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/backend/executor/cypher_utils.c Adjusts snapshot curcid during entity_exists() scans to make newly-created entities visible.
regress/sql/cypher_merge.sql Adds new regression scenarios reproducing issue #1954 and validating the fix.
regress/expected/cypher_merge.out Captures expected results for the new regression cases.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +232 to +234
saved_curcid = estate->es_snapshot->curcid;
estate->es_snapshot->curcid = GetCurrentCommandId(false);

Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

estate->es_snapshot->curcid can be ahead of the global command ID (e.g., Increment_Estate_CommandId() increments es_snapshot->curcid without changing GetCurrentCommandId()). Unconditionally assigning GetCurrentCommandId(false) here can decrease curcid and make tuples with Cmin == saved_curcid - 1 invisible during the scan, potentially causing false "was deleted" errors. Consider only advancing curcid when the global CID is greater than the current snapshot CID (e.g., use max(saved_curcid, GetCurrentCommandId(false)) or a conditional update), or use a local snapshot copy with an adjusted curcid instead of mutating the shared snapshot.

Copilot uses AI. Check for mistakes.
@jrgemignani
Copy link
Contributor

@gregfelice Please have Opus consider what Copilot mentions above.

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.

Problem with the WITH clause: Vertex assigned to variable <x> was deleted.

3 participants