Skip to content

Fix duplicate MERGE default projection#520

Merged
adsharma merged 1 commit into
mainfrom
fix/unwind-merge-uuid-default
May 25, 2026
Merged

Fix duplicate MERGE default projection#520
adsharma merged 1 commit into
mainfrom
fix/unwind-merge-uuid-default

Conversation

@adsharma
Copy link
Copy Markdown
Contributor

@adsharma adsharma commented May 25, 2026

Fixes: #170

Summary

This fix separates the responsibilities more cleanly:

Planner / LogicalMerge:

  • Decides whether duplicate-created MERGE rows are user-visible.
  • Computes suppressDuplicateCreatedOutput from the logical child schema, merge keys, inserted pattern expressions, and ON CREATE / ON MATCH state.
  • Suppresses duplicate-created output only when the row has no non-key upstream payload and no ON CREATE side effects.

Mapper / map_merge:

  • No longer classifies payload semantics.
  • Just reads logicalMerge.suppressesDuplicateCreatedOutput() and passes that decision into physical MergeInfo.
  • Still owns the mechanical translation from logical expressions to evaluators, executors, DataPos, and local table layout.

Executor / persistent Merge:

  • Enforces the planner’s decision.
  • The pattern creation table still deduplicates physical creation by merge key.
  • If a later row hits an already-created key, the executor either suppresses the duplicate-created output row or rehydrates the already-created pattern for output, depending on the planner-provided flag.

The important semantic invariant is:

MERGE creates at most one physical pattern per merge key, but duplicate-created output rows are preserved only when they carry user-visible upstream payload.

That gives the desired behavior for both cases:

  • UNWIND [1, 1] AS i MERGE (a {stuff: i}) RETURN a.id returns one row.
  • MATCH ... WITH ..., age MERGE (...) RETURN u.ID, age still returns one row per upstream payload row.

The UUID/default part is fixed separately but consistently: when a duplicate-created row must still be output, we read the already-created node’s projected properties from storage instead of re-evaluating default expressions. That prevents volatile defaults like gen_random_uuid() from producing a fresh value for a row that was not actually inserted.

Root Cause

The unwind dedup changes let duplicate no-match MERGE rows continue through the merge output path. The duplicate-created path skipped the physical insert but still evaluated insert defaults again, so projected defaults such as gen_random_uuid() differed from the row that was actually stored.

Validation

  • cmake --build build/release --target lbug_shell e2e_test --config Release
  • manual release-shell repro for duplicate UUID MERGE
  • E2E_TEST_FILES_DIRECTORY=test/test_files build/release/test/runner/e2e_test --gtest_filter='issue~issue.issue_170'
  • E2E_TEST_FILES_DIRECTORY=test/test_files build/release/test/runner/e2e_test --gtest_filter='issueissue.unwind_merge_rel_after_rel_match_dedup:issueissue.unwind_merge_with_projection_dedup:issue~issue.unwind_merge_with_projection_preserves_match_expansion'

@adsharma adsharma force-pushed the fix/unwind-merge-uuid-default branch 3 times, most recently from 9132b90 to 79aed5c Compare May 25, 2026 18:32
@adsharma adsharma force-pushed the fix/unwind-merge-uuid-default branch from 79aed5c to 9b85d90 Compare May 25, 2026 19:01
@adsharma adsharma force-pushed the fix/unwind-merge-uuid-default branch from 9b85d90 to 3a7f8bb Compare May 25, 2026 19:17
@adsharma adsharma marked this pull request as ready for review May 25, 2026 19:23
@adsharma adsharma merged commit c5a3c73 into main May 25, 2026
4 checks passed
@adsharma adsharma deleted the fix/unwind-merge-uuid-default branch May 26, 2026 02:11
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.

Bug: Merge node with gen_random_uuid() primary key returns wrong primary key on match

1 participant