Skip to content

fix(drizzle): use loose equality for null check in traverseFields to catch undefined#15977

Open
xxarupakaxx wants to merge 3 commits into
payloadcms:mainfrom
xxarupakaxx:fix/traverse-fields-null-check-undefined
Open

fix(drizzle): use loose equality for null check in traverseFields to catch undefined#15977
xxarupakaxx wants to merge 3 commits into
payloadcms:mainfrom
xxarupakaxx:fix/traverse-fields-null-check-undefined

Conversation

@xxarupakaxx

@xxarupakaxx xxarupakaxx commented Mar 17, 2026

Copy link
Copy Markdown

What?

Fixed a bug where restoring a published version does not clear upload/relationship fields when the original version had no value set for those fields.

Why?

In traverseFields.ts, the null check uses strict equality (=== null), which does not catch undefined. When restoring a version where an upload/relationship field was never set, transform/read does not set the property on the result object (leaving it as undefined, not null). The write transform then fails to add the field to relationshipsToDelete, so stale relationship rows persist in the database.

Reproduction steps:

  1. Create a versioned collection with an upload field
  2. Publish a document without setting the image
  3. Edit → set an image → save as Draft
  4. Restore the published version (which has no image)
  5. Expected: image is cleared / Actual: draft image persists

How?

Changed === null to == null (loose equality) in two locations in packages/drizzle/src/transform/write/traverseFields.ts:

  • Line 551 (localized fields): localeData === nulllocaleData == null
  • Line 572 (non-localized fields): fieldData === nullfieldData == null

This ensures both null and undefined trigger deletion of relationship rows.

Tests

Added two regression tests in test/versions/int.spec.ts:

  • should clear upload field when restoring a version where the field was never set
  • should clear relationship field when restoring a version where the field was never set

Also added a media (upload) field to the DraftPosts test collection to support the upload test.

Fixes #15976

…catch undefined

When restoring a version where upload/relationship fields were never set,
the field value is `undefined` (not `null`). The strict equality check
(`=== null`) missed this case, causing stale relationship rows to persist
in the database instead of being deleted.

Changed `=== null` to `== null` (loose equality) in two locations:
- Line 551: localized relationship/upload fields
- Line 572: non-localized relationship/upload fields

Fixes payloadcms#15976
@PatrikKozak PatrikKozak requested a review from r1tsuu March 18, 2026 13:16
Comment thread test/versions/int.spec.ts Outdated
Comment on lines +884 to +889
// This bug only affects drizzle-based adapters (postgres, sqlite) where relationships
// are stored in separate tables and require explicit deletion via relationshipsToDelete.
// MongoDB stores relationships inline in the document, so this specific code path does not apply.
if (payload.db.name === 'mongoose') {
return
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks strange to me, since you don't use any DB-specific APIs in those 2 tests - the output should be the same. Do we need to fix this in MongoDB as well?

@r1tsuu r1tsuu Mar 19, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I ran this test with MongoDB and it failed, so I think we need to remove this condition and fix the issue in MongoDB as well, I think you just need to do the same logic in payload/packages/db-mongodb/src/utilities/transform.ts.
image

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks for catching this! You were right — the test fails on MongoDB as well.

However, fixing it in db-mongodb/src/utilities/transform.ts alone caused unique constraint violations on indexed-fields tests, because setting all falsy relationship/upload fields to null during every write triggers duplicate null errors on unique: true fields.

Instead, I fixed it at the restoreVersion level in the Payload core — missing relationship/upload fields are explicitly set to null in the version data early in the pipeline (right after extracting versionToRestoreWithLocales), so the null values flow through the entire hook chain and reach updateOne/updateGlobal for all adapters.

Removed the mongoose skip condition from both tests.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@r1tsuu
Friendly ping — any thoughts on the MongoDB unique constraint issue I mentioned?

When restoring a version where relationship/upload fields were never
set, the restored data does not include those fields. This causes
database adapters (especially MongoDB, which uses $set semantics) to
preserve stale values from the current document instead of clearing
them.

Set missing relationship/upload fields to null in the version data
early in the restoreVersion pipeline, so the null values flow through
the entire hook chain (afterRead → beforeValidate → beforeChange →
updateOne/updateGlobal).
…t upload/relationship fields

- Add 'media' (upload) field to DraftPosts test collection
- Add test: should clear upload field when restoring a version where
  the field was never set
- Add test: should clear relationship field when restoring a version
  where the field was never set
@xxarupakaxx xxarupakaxx force-pushed the fix/traverse-fields-null-check-undefined branch from 58b83bb to 42482d5 Compare March 25, 2026 08:03
@denolfe denolfe added the v3 label Apr 23, 2026
@github-actions github-actions Bot added the stale label May 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(db-postgres): restoring version does not clear upload/relationship fields when value is undefined

3 participants