fix: Cypher -> SET n:Label on same node across row fanout must be idempotent - #4025
Conversation
…mpotent When the same node appears on multiple rows (e.g. cartesian MATCH), repeated SET n:Label crashed: row 1 deleted the old vertex and created a new one with a fresh RID; row 2 still held the deleted vertex and failed with "Record not found" or "Duplicated key". Fix: add a labelReplacements map (Map<RID,Vertex>) to the ResultSet anonymous class in SetStep.syncPull(), alongside the existing writtenDocs map. On each call to applyLabels, look up the incoming vertex's RID. If a prior replacement exists, redirect the row's variable to the already-replaced vertex and let the existing type-equality check return early (idempotent). Otherwise proceed with the normal delete+create flow and record old RID → new vertex for future rows. Fixes #4017 Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 8 |
🟢 Coverage 77.27% diff coverage · -8.63% coverage variation
Metric Results Coverage variation ✅ -8.63% coverage variation Diff coverage ✅ 77.27% diff coverage Coverage variation details
Coverable lines Covered lines Coverage Common ancestor commit (9a5b0e1) 120132 88786 73.91% Head commit (48d049a) 151423 (+31291) 98839 (+10053) 65.27% (-8.63%) Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch:
<coverage of head commit> - <coverage of common ancestor commit>Diff coverage details
Coverable lines Covered lines Diff coverage Pull request (#4025) 22 17 77.27% Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified:
<covered lines added or modified>/<coverable lines added or modified> * 100%
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
Code Review - PR #4025: Cypher SET n:Label idempotency on row fanoutOverviewThis PR fixes a real crash bug (issue #4017) where The approach is sound and consistent with the existing CorrectnessLooks good:
Potential issue - interaction between If a SET clause combines a property assignment and a label assignment on the same variable in that order - e.g.
The PR description only covers pure label-fanout. If the combined case is also broken before this PR, that should be noted; if it was working before, it now regresses. A test or explicit exclusion comment would help clarify this. The safest fix would be to remove Test CoverageGood:
Minor - inaccurate javadoc comment in test: // The cartesian MATCH produces Alice twice and Charlie twice (age > peer)Alice only qualifies once (age 30 > Bob's 25). The cartesian product yields the pairs Missing test - edge preservation under fanout: Code QualityComment cleanup is good: The old comment explaining why label SET was not covered by Consistent with existing style: The new map follows the same anonymous-ResultSet-field pattern as Minor - private void applyLabels(final SetClause.SetItem item, final Result result,
final Map<RID, Vertex> labelReplacements) {The two-line signature is fine given the project's PerformanceThe Summary
Overall this is a solid, targeted fix. Recommend addressing the test comment and considering a note on the 🤖 Generated with Claude Code |
There was a problem hiding this comment.
Code Review
This pull request introduces a labelReplacements map to SetStep to track vertex replacements occurring during label updates, which is necessary because ArcadeDB replaces the vertex (changing its RID) when labels are modified. This ensures that subsequent rows in a fanout operation correctly reference the new vertex instance. The feedback highlights the need to pre-resolve all vertex aliases in a row to prevent property updates from failing on deleted records and recommends a recursive lookup in the replacement map to handle scenarios where a vertex is replaced multiple times.
| if (setClause == null || setClause.isEmpty()) | ||
| return; |
There was a problem hiding this comment.
The labelReplacements map should be used to pre-resolve all vertex aliases in the current row before processing any SetItem. This is critical because other SET operations (like property updates) rely on resolveLatestDoc, which currently only checks writtenDocs. If a node was replaced by a label change in a previous row, its RID has changed, and resolveLatestDoc will fail to find the latest state or attempt to modify a deleted record. By pre-resolving all aliases at the start of the row, we ensure that all subsequent operations (properties, maps, and labels) observe the correct, latest vertex instance.
if (setClause == null || setClause.isEmpty())
return;
if (!labelReplacements.isEmpty()) {
for (final String propName : result.getPropertyNames()) {
final Object propValue = result.getProperty(propName);
if (propValue instanceof Vertex v) {
Vertex replacement = labelReplacements.get(v.getIdentity());
if (replacement != null) {
Vertex next;
while ((next = labelReplacements.get(replacement.getIdentity())) != null)
replacement = next;
((ResultInternal) result).setProperty(propName, replacement);
}
}
}
}There was a problem hiding this comment.
Implemented. Added the pre-resolution block at the start of applySetOperations (before the transaction begin) that walks all result properties, looks up any Vertex value whose RID appears in labelReplacements, and chain-traverses to the current head. This ensures resolveLatestDoc in property/map SET operations always sees the live vertex rather than the deleted original, even when upstream steps re-deliver the old RID on a new row. Commit: 48d049a.
| final Vertex prior = labelReplacements.get(originalRid); | ||
| if (prior != null) { | ||
| propagateUpdateToSameNodeAliases(result, vertex, prior); | ||
| vertex = prior; | ||
| } |
There was a problem hiding this comment.
The lookup in labelReplacements must be recursive to handle cases where a node is replaced multiple times within the same query (e.g., multiple label changes across different rows). Without recursion, if a node was replaced from RID A to B, and then from B to C, a later row seeing RID A would only redirect to the deleted RID B, causing a crash when attempting to load its labels. Additionally, if the pre-resolution logic is added to applySetOperations, this block becomes redundant for the initial variable resolution but still serves as a safety check for multiple label updates in the same row.
| final Vertex prior = labelReplacements.get(originalRid); | |
| if (prior != null) { | |
| propagateUpdateToSameNodeAliases(result, vertex, prior); | |
| vertex = prior; | |
| } | |
| Vertex prior = labelReplacements.get(originalRid); | |
| if (prior != null) { | |
| Vertex next; | |
| while ((next = labelReplacements.get(prior.getIdentity())) != null) | |
| prior = next; | |
| propagateUpdateToSameNodeAliases(result, vertex, prior); | |
| vertex = prior; | |
| } |
There was a problem hiding this comment.
Implemented the chain traversal as suggested. The loop follows prior.getIdentity() through labelReplacements until it finds the head, so a vertex replaced A->B->C across rows is always resolved to C. Same pattern also applied inside the pre-resolution block added to applySetOperations. Commit: 48d049a.
… writtenDocs entry on label replace, add edge-preservation fanout test - Fix inaccurate javadoc in setLabelIdempotentOnSameNodeAcrossRowFanout: Alice qualifies for one pair (Alice>Bob), not twice - Pass writtenDocs into applyLabels so the stale MutableDocument entry for the old RID is removed after vertex replacement, preventing subsequent rows from reading the replaced document's pre-replacement state - Add setLabelPreservesEdgesAcrossRowFanout test: verifies that edges copied during the first label-replacement are still present after the second fanout hit redirects to the already-replaced vertex Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
… in SetStep - At the start of each row in applySetOperations, walk labelReplacements to update any result property that still holds a deleted (old-RID) vertex so that property-SET operations via resolveLatestDoc observe the live vertex - In applyLabels, follow the replacement chain recursively so multi-step replacements (A->B->C) are resolved to the current head regardless of how many label changes have been applied in prior rows Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Code Review -
|
Code Review - PR #4025:
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4025 +/- ##
==========================================
- Coverage 64.91% 64.24% -0.67%
==========================================
Files 1597 1597
Lines 120132 120168 +36
Branches 25580 25587 +7
==========================================
- Hits 77981 77204 -777
- Misses 31377 32296 +919
+ Partials 10774 10668 -106 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
… 2.64.1 to 2.65.0 [skip ci] Bumps [com.google.api.grpc:proto-google-common-protos](https://github.com/googleapis/sdk-platform-java) from 2.64.1 to 2.65.0. Release notes *Sourced from [com.google.api.grpc:proto-google-common-protos's releases](https://github.com/googleapis/sdk-platform-java/releases).* > v2.65.0 > ------- > > [2.65.0](googleapis/sdk-platform-java@v2.64.2...v2.65.0) (2026-01-12) > ------------------------------------------------------------------------------------------------ > > ### Features > > * add org.json:json to third-party-dependencies pom ([ArcadeData#4047](https://redirect.github.com/googleapis/sdk-platform-java/issues/4047)) ([ffa432e](googleapis/sdk-platform-java@ffa432e)), closes [ArcadeData#4046](https://redirect.github.com/googleapis/sdk-platform-java/issues/4046) > * remove dependency management of graal-sdk ([ArcadeData#4033](https://redirect.github.com/googleapis/sdk-platform-java/issues/4033)) ([ad05c34](googleapis/sdk-platform-java@ad05c34)) > > ### Bug Fixes > > * add api\_version breadcrumb to client docs ([ArcadeData#4018](https://redirect.github.com/googleapis/sdk-platform-java/issues/4018)) ([a2b2179](googleapis/sdk-platform-java@a2b2179)) > * Create a single S2AChannelCredentials per application ([ArcadeData#3989](https://redirect.github.com/googleapis/sdk-platform-java/issues/3989)) ([3758b43](googleapis/sdk-platform-java@3758b43)) > * provide API to share the same background executor for channel po… ([ArcadeData#4030](https://redirect.github.com/googleapis/sdk-platform-java/issues/4030)) ([178182c](googleapis/sdk-platform-java@178182c)) > > ### Dependencies > > * update dependencies.txt for grpc-gcp to 1.9.0 ([ArcadeData#4025](https://redirect.github.com/googleapis/sdk-platform-java/issues/4025)) ([b68791d](googleapis/sdk-platform-java@b68791d)) > * update google api dependencies ([ArcadeData#3917](https://redirect.github.com/googleapis/sdk-platform-java/issues/3917)) ([480cf13](googleapis/sdk-platform-java@480cf13)) > * update google.http-client.version to 2.0.3 ([ArcadeData#4054](https://redirect.github.com/googleapis/sdk-platform-java/issues/4054)) ([b9a8c89](googleapis/sdk-platform-java@b9a8c89)) > > ### Documentation > > * Fix retry guide link in javadocs ([ArcadeData#4029](https://redirect.github.com/googleapis/sdk-platform-java/issues/4029)) ([b43f77c](googleapis/sdk-platform-java@b43f77c)) > > v2.65.0-rc1 > ----------- > > [2.65.0-rc1](googleapis/sdk-platform-java@v2.64.2...v2.65.0-rc1) (2025-12-11) > -------------------------------------------------------------------------------------------------------- > > ### Features > > * feat: Upgrade protobuf runtime and gen code to 4.33. ([googleapis/sdk-platform-java#4000](https://redirect.github.com/googleapis/sdk-platform-java/pull/4000)) (<googleapis/sdk-platform-java@ac25015>) > * feat: configure rc releases to be on prerelease mode (<googleapis/sdk-platform-java@e9055b0>) > > ### Bug Fixes > > * fix: add rc versioning scheme (<googleapis/sdk-platform-java@2939320>) > > ### Dependencies > > * deps: update auth to 1.42.0-rc1 and http-client to 2.1.0-rc1 ([googleapis/sdk-platform-java#4014](https://redirect.github.com/googleapis/sdk-platform-java/pull/4014)) (<googleapis/sdk-platform-java@0e01361>) > * deps: Upgrade grpc to 1.76.2 ([googleapis/sdk-platform-java#4006](https://redirect.github.com/googleapis/sdk-platform-java/pull/4006)(https://redirect.github.com/googleapis/sdk-platform-java/commit/a8060a263ba34556b8d4d347a57625849a6859bc)) > * deps: update google api dependencies ([googleapis/sdk-platform-java#3917](https://redirect.github.com/googleapis/sdk-platform-java/pull/3917)(https://redirect.github.com/googleapis/sdk-platform-java/commit/480cf13148687c53c4af3da9d48490aeb5bf4b88)) > > v2.64.2 > ------- > > [2.64.2](googleapis/sdk-platform-java@v2.64.1...v2.64.2) (2025-12-10) > ------------------------------------------------------------------------------------------------ ... (truncated) Changelog *Sourced from [com.google.api.grpc:proto-google-common-protos's changelog](https://github.com/googleapis/sdk-platform-java/blob/main/CHANGELOG.md).* > [2.65.0](googleapis/sdk-platform-java@v2.64.2...v2.65.0) (2026-01-12) > ------------------------------------------------------------------------------------------------ > > ### Features > > * add org.json:json to third-party-dependencies pom ([ArcadeData#4047](https://redirect.github.com/googleapis/sdk-platform-java/issues/4047)) ([ffa432e](googleapis/sdk-platform-java@ffa432e)), closes [ArcadeData#4046](https://redirect.github.com/googleapis/sdk-platform-java/issues/4046) > * remove dependency management of graal-sdk ([ArcadeData#4033](https://redirect.github.com/googleapis/sdk-platform-java/issues/4033)) ([ad05c34](googleapis/sdk-platform-java@ad05c34)) > > ### Bug Fixes > > * add api\_version breadcrumb to client docs ([ArcadeData#4018](https://redirect.github.com/googleapis/sdk-platform-java/issues/4018)) ([a2b2179](googleapis/sdk-platform-java@a2b2179)) > * Create a single S2AChannelCredentials per application ([ArcadeData#3989](https://redirect.github.com/googleapis/sdk-platform-java/issues/3989)) ([3758b43](googleapis/sdk-platform-java@3758b43)) > * provide API to share the same background executor for channel po… ([ArcadeData#4030](https://redirect.github.com/googleapis/sdk-platform-java/issues/4030)) ([178182c](googleapis/sdk-platform-java@178182c)) > > ### Dependencies > > * update dependencies.txt for grpc-gcp to 1.9.0 ([ArcadeData#4025](https://redirect.github.com/googleapis/sdk-platform-java/issues/4025)) ([b68791d](googleapis/sdk-platform-java@b68791d)) > * update google api dependencies ([ArcadeData#3917](https://redirect.github.com/googleapis/sdk-platform-java/issues/3917)) ([480cf13](googleapis/sdk-platform-java@480cf13)) > * update google.http-client.version to 2.0.3 ([ArcadeData#4054](https://redirect.github.com/googleapis/sdk-platform-java/issues/4054)) ([b9a8c89](googleapis/sdk-platform-java@b9a8c89)) > > ### Documentation > > * Fix retry guide link in javadocs ([ArcadeData#4029](https://redirect.github.com/googleapis/sdk-platform-java/issues/4029)) ([b43f77c](googleapis/sdk-platform-java@b43f77c)) > > [2.64.2](googleapis/sdk-platform-java@v2.64.1...v2.64.2) (2025-12-10) > ------------------------------------------------------------------------------------------------ > > ### Bug Fixes > > * add per-service api\_version to gapic\_metadata ([ArcadeData#3984](https://redirect.github.com/googleapis/sdk-platform-java/issues/3984)) ([d589306](googleapis/sdk-platform-java@d589306)) > > ### Dependencies > > * Upgrade auth to 1.41.0. ([ArcadeData#4008](https://redirect.github.com/googleapis/sdk-platform-java/issues/4008)) ([833b3e5](googleapis/sdk-platform-java@833b3e5)) > * upgrade grpc to 1.76.1 ([ArcadeData#3993](https://redirect.github.com/googleapis/sdk-platform-java/issues/3993)) ([958611e](googleapis/sdk-platform-java@958611e)) > * Upgrade grpc to 1.76.2 ([ArcadeData#4006](https://redirect.github.com/googleapis/sdk-platform-java/issues/4006)) ([a8060a2](googleapis/sdk-platform-java@a8060a2)) Commits * [`972f87a`](googleapis/sdk-platform-java@972f87a) chore(main): release 2.65.0 ([ArcadeData#4058](https://redirect.github.com/googleapis/sdk-platform-java/issues/4058)) * [`74a2dd6`](googleapis/sdk-platform-java@74a2dd6) chore: update googleapis commit at Wed Dec 10 02:37:02 UTC 2025 ([ArcadeData#4013](https://redirect.github.com/googleapis/sdk-platform-java/issues/4013)) * [`b9a8c89`](googleapis/sdk-platform-java@b9a8c89) deps: update google.http-client.version to 2.0.3 ([ArcadeData#4054](https://redirect.github.com/googleapis/sdk-platform-java/issues/4054)) * [`93cbb09`](googleapis/sdk-platform-java@93cbb09) chore(deps): update upper bound dependencies file ([ArcadeData#4052](https://redirect.github.com/googleapis/sdk-platform-java/issues/4052)) * [`aa4a7f7`](googleapis/sdk-platform-java@aa4a7f7) build: Update macOS version in nightly workflow ([ArcadeData#4048](https://redirect.github.com/googleapis/sdk-platform-java/issues/4048)) * [`ffa432e`](googleapis/sdk-platform-java@ffa432e) feat: add org.json:json to third-party-dependencies pom ([ArcadeData#4047](https://redirect.github.com/googleapis/sdk-platform-java/issues/4047)) * [`3758b43`](googleapis/sdk-platform-java@3758b43) fix: Create a single S2AChannelCredentials per application ([ArcadeData#3989](https://redirect.github.com/googleapis/sdk-platform-java/issues/3989)) * [`178182c`](googleapis/sdk-platform-java@178182c) fix: provide API to share the same background executor for channel po… ([ArcadeData#4030](https://redirect.github.com/googleapis/sdk-platform-java/issues/4030)) * [`ad05c34`](googleapis/sdk-platform-java@ad05c34) feat: remove dependency management of graal-sdk ([ArcadeData#4033](https://redirect.github.com/googleapis/sdk-platform-java/issues/4033)) * [`6f59f7c`](googleapis/sdk-platform-java@6f59f7c) tests: Use fixed copyright year in tests. ([ArcadeData#4036](https://redirect.github.com/googleapis/sdk-platform-java/issues/4036)) * Additional commits viewable in [compare view](googleapis/sdk-platform-java@v2.64.1...v2.65.0) [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- Dependabot commands and options You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
…o [skip ci] Bumps [marked](https://github.com/markedjs/marked) from 18.0.6 to 18.0.7. Release notes *Sourced from [marked's releases](https://github.com/markedjs/marked/releases).* > v18.0.7 > ------- > > [18.0.7](markedjs/marked@v18.0.6...v18.0.7) (2026-07-21) > ----------------------------------------------------------------------------------- > > ### Bug Fixes > > * Avoid O(n^2) backtracking in HTML block close and tilde interrupt regexes ([#4014](https://redirect.github.com/markedjs/marked/issues/4014)) ([f945fc5](markedjs/marked@f945fc5)), closes [#3991](https://redirect.github.com/markedjs/marked/issues/3991) > * Avoid O(n^2) masked source rebuild in inline tokenizer ([#4017](https://redirect.github.com/markedjs/marked/issues/4017)) ([9154f8f](markedjs/marked@9154f8f)) > * keep empty list after blockquote as a sibling block ([#4004](https://redirect.github.com/markedjs/marked/issues/4004)) ([3f144a0](markedjs/marked@3f144a0)) > * preserve code spans adjacent to tildes ([#4012](https://redirect.github.com/markedjs/marked/issues/4012)) ([0de7188](markedjs/marked@0de7188)) > * Recognize setext headings whose first line starts with # ([#4015](https://redirect.github.com/markedjs/marked/issues/4015)) ([f056437](markedjs/marked@f056437)), closes [#1](https://redirect.github.com/markedjs/marked/issues/1) > * treat a line of only tabs as a blank line between paragraphs ([#4007](https://redirect.github.com/markedjs/marked/issues/4007)) ([bc2f121](markedjs/marked@bc2f121)) Commits * [`a8971a1`](markedjs/marked@a8971a1) chore(release): 18.0.7 [skip ci] * [`d899c2e`](markedjs/marked@d899c2e) chore(deps): bump actions/setup-node from 6 to 7 ([#4025](https://redirect.github.com/markedjs/marked/issues/4025)) * [`7fbf82e`](markedjs/marked@7fbf82e) chore(deps-dev): bump semantic-release from 25.0.7 to 25.0.8 ([#4026](https://redirect.github.com/markedjs/marked/issues/4026)) * [`738edf2`](markedjs/marked@738edf2) chore(deps-dev): bump brace-expansion from 5.0.2 to 5.0.6 ([#4027](https://redirect.github.com/markedjs/marked/issues/4027)) * [`9154f8f`](markedjs/marked@9154f8f) fix: Avoid O(n^2) masked source rebuild in inline tokenizer ([#4017](https://redirect.github.com/markedjs/marked/issues/4017)) * [`f945fc5`](markedjs/marked@f945fc5) fix: Avoid O(n^2) backtracking in HTML block close and tilde interrupt regexe... * [`3f144a0`](markedjs/marked@3f144a0) fix: keep empty list after blockquote as a sibling block ([#4004](https://redirect.github.com/markedjs/marked/issues/4004)) * [`0de7188`](markedjs/marked@0de7188) fix: preserve code spans adjacent to tildes ([#4012](https://redirect.github.com/markedjs/marked/issues/4012)) * [`f056437`](markedjs/marked@f056437) fix: Recognize setext headings whose first line starts with # ([#4015](https://redirect.github.com/markedjs/marked/issues/4015)) * [`12bfa94`](markedjs/marked@12bfa94) chore(deps-dev): bump semantic-release from 25.0.5 to 25.0.7 ([#4020](https://redirect.github.com/markedjs/marked/issues/4020)) * Additional commits viewable in [compare view](markedjs/marked@v18.0.6...v18.0.7) [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- Dependabot commands and options You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Summary
Fixes #4017
SET n:Labelcrashed when the same node appeared on multiple rows (e.g. cartesianMATCH (n:Person) MATCH (m:Person) WHERE n.age > m.age SET n:Employee). Row 1 deleted the old vertex and created a new one with a fresh RID; row 2 still held the deleted vertex reference and failed with "Record not found" or "Duplicated key".labelReplacements: Map<RID, Vertex>field to theResultSetanonymous class inSetStep.syncPull(), alongside the existingwrittenDocsmap. On each call toapplyLabels, if the incoming vertex's RID was already replaced on a prior row, the current row's variable is redirected to the already-replaced vertex and the existing type-equality check returns early (idempotent). Otherwise the normal delete+create flow runs and the replacement is recorded for future rows.Test plan
setLabelIdempotentOnSameNodeAcrossRowFanout- exact reproducer from the issue (single label, cartesian MATCH)setMultipleLabelsIdempotentOnSameNodeAcrossRowFanout- stronger case with two labels (SET n:Employee:Checked)OpenCypher*suite: 5406 tests, 0 failures, 0 regressions🤖 Generated with Claude Code