Fix entity_exists() CID visibility for CREATE + WITH + MERGE#2343
Fix entity_exists() CID visibility for CREATE + WITH + MERGE#2343gregfelice wants to merge 1 commit intoapache:masterfrom
Conversation
…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>
There was a problem hiding this comment.
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 snapshotcurcidduring the heap scan. - Add regression coverage for
CREATE + WITH + MERGEover 3 input rows, including a case with twoMERGEs. - 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.
| saved_curcid = estate->es_snapshot->curcid; | ||
| estate->es_snapshot->curcid = GetCurrentCommandId(false); | ||
|
|
There was a problem hiding this comment.
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.
|
@gregfelice Please have Opus consider what Copilot mentions above. |
Summary
Fixes #1954.
When a Cypher query chains
CREATE ... WITH ... MERGE, vertices created byCREATEbecome invisible toentity_exists()after a threshold number of input rows, causing MERGE to throw "vertex assigned to variable was deleted".Root cause:
CREATEcallsCommandCounterIncrement()which advances the global command ID, but does not updatees_snapshot->curcid. TheDecrement_Estate_CommandId/Increment_Estate_CommandIdmacros used by the executors bringcurcidback to the same value on each iteration. After enough rows, newly inserted vertices have aCmin >= curcidandHeapTupleSatisfiesMVCCrejects them (it requiresCmin < curcid).Example: With 3 input rows, the global CID advances to 3 after three CREATEs, but
curcidstays at 2 (due to Decrement/Increment pairs). The third vertex (Cmin=2) is invisible because2 < 2is false.Fix: In
entity_exists(), temporarily setes_snapshot->curcidto the current global command ID (viaGetCurrentCommandId(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/restorecurcidaround theentity_exists()scanregress/sql/cypher_merge.sql: Regression tests for CREATE + WITH + MERGE with 3 rows (1 and 2 MERGEs)regress/expected/cypher_merge.out: Expected outputTest plan
🤖 Generated with Claude Code