Skip to content

fix: preserve empty object attributes (DAT-2310) - #935

Merged
abnegate merged 4 commits into
mainfrom
fix/dat-2310-empty-object-fidelity
Aug 12, 2026
Merged

fix: preserve empty object attributes (DAT-2310)#935
abnegate merged 4 commits into
mainfrom
fix/dat-2310-empty-object-fidelity

Conversation

@abnegate

@abnegate abnegate commented Aug 12, 2026

Copy link
Copy Markdown
Member

What changed

  • accept stdClass values for object attributes
  • encode object attributes supplied as arrays or objects
  • preserve empty objects while converting non-empty decoded objects back to associative arrays
  • preserve empty objects in the Mongo adapter conversion path
  • verify top-level, nested, list-contained, and cache-hit fidelity separately from empty arrays

Seen red

On current main:

  • the object validator rejected a top-level empty object
  • PostgreSQL persisted nested empty objects but decoded them as empty arrays
  • MongoDB flattened both top-level and nested empty objects
  • after the storage fixes, released cache 4.0.1 still flattened both cases on the second cached read

Verification

With exact sources from utopia-php/mongo#48 and utopia-php/monorepo#125 mounted, without changing Composer constraints:

  • unit suite: 428 tests, 2310 assertions
  • object fidelity adapter matrix: 32 tests, 134 assertions, 16 adapters explicitly skipped because they do not support object attributes
  • PostgreSQL exact regression: 2 tests, 17 assertions
  • MongoDB exact regression: 2 tests, 17 assertions
  • schemaless MongoDB exact regression: 2 tests, 15 assertions
  • PHPStan level 7 across src and tests: no errors

The current-main locked Pint 1.30.4 reports the same style failures in all five touched files before this patch; that baseline was reproduced from a detached clean ce4e14e worktree.

Blocked release chain

Do not merge or release this in isolation. Its Mongo path requires a stable mongo 1.x release containing utopia-php/mongo#48, and its cache-hit path requires a stable cache 4.0.x release containing utopia-php/monorepo#125. No temporary VCS pin is introduced.

The downstream server-ce public API and Cloud dependency updates remain blocked until those stable packages and this database release exist.

Related: utopia-php/monorepo#123.

Summary by CodeRabbit

  • Bug Fixes
    • Empty objects are preserved correctly as {} (including nested empty objects) rather than being converted to empty arrays.
    • Encoding/decoding and change detection now handle stdClass values correctly, including safe handling of non-string vector inputs.
    • Object validation now accepts empty objects.
  • Tests
    • Added unit and end-to-end coverage for empty and nested empty objects across create, cached reads, and updates (including timestamp and permission behavior).
  • Chores
    • Updated test build arguments for consistent pinned dependencies in CI and Docker builds.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c300fe9a-e1a7-4701-a5da-a6edad31e021

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The change preserves empty stdClass values during validation, encoding, decoding, MongoDB conversion, comparison, and object-attribute operations. Tests cover flat and nested objects. Docker builds now accept pinned repository references.

Changes

Object attribute preservation

Layer / File(s) Summary
Database object pipeline
src/Database/Database.php, src/Database/Validator/ObjectValidator.php, tests/unit/Validator/ObjectTest.php
Object validation accepts stdClass. Encoding and recursive decoding preserve empty objects. Document comparisons use recursive value comparison. Vector decoding accepts non-string values.
Adapter conversion and end-to-end validation
src/Database/Adapter/Mongo.php, tests/e2e/Adapter/Scopes/ObjectAttributeTests.php
MongoDB conversion preserves empty stdClass values. Tests cover nested objects, cached and uncached reads, equivalent updates, and authorization checks.

Pinned build references

Layer / File(s) Summary
Docker reference hydration
.github/workflows/tests.yml, Dockerfile
The workflow passes pinned cache and MongoDB references. The Docker build clones both repositories when references are provided and copies their source into vendor packages.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ObjectAttributeTests
  participant Database
  participant Mongo
  ObjectAttributeTests->>Database: Create and read object attribute
  Database->>Mongo: Encode and persist object value
  Mongo-->>Database: Return object with empty stdClass values
  Database-->>ObjectAttributeTests: Decode cached or uncached attribute
  ObjectAttributeTests->>Database: Submit equivalent or changed update
  Database-->>ObjectAttributeTests: Preserve timestamp or require authorization
Loading

Possibly related PRs

Suggested reviewers: meldiron

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: preserving empty object attributes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/dat-2310-empty-object-fidelity

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR preserves empty-object identity across object validation, serialization, Mongo conversion, change detection, and cached reads.

  • Accepts stdClass values for object attributes and recursively preserves empty objects while decoding.
  • Distinguishes empty objects from empty arrays during update detection.
  • Updates Mongo and cache dependencies to releases containing the required fidelity fixes.
  • Adds adapter-matrix and validator regression coverage for top-level and nested empty objects.

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains.

No blocking failure remains.

Important Files Changed

Filename Overview
composer.lock Updates the cache and Mongo packages to stable releases required by the empty-object preservation paths.
src/Database/Database.php Extends object filtering and recursive equality to preserve empty-object identity through CRUD and caching.
src/Database/Adapter/Mongo.php Keeps empty BSON objects as stdClass while converting non-empty Mongo objects into associative arrays.
src/Database/Validator/ObjectValidator.php Accepts stdClass as a valid object-attribute value.
tests/e2e/Adapter/Scopes/ObjectAttributeTests.php Adds cross-adapter regression coverage for top-level, nested, list-contained, cached, and updated empty objects.
tests/unit/Validator/ObjectTest.php Verifies that the object validator accepts an empty stdClass.

Reviews (4): Last reviewed commit: "chore: depend on released cache and mong..." | Re-trigger Greptile

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/Database/Database.php`:
- Around line 711-741: Update the attribute comparison in updateDocument() to
normalize decoded empty stdClass values to the equivalent empty-array
representation for strict change detection only. Preserve the original decoded
value for persistence and keep non-empty objects and other types unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9526eb6f-1de4-4b7d-9cc3-bb1bf6a98a0b

📥 Commits

Reviewing files that changed from the base of the PR and between ce4e14e and c96d5de.

📒 Files selected for processing (5)
  • src/Database/Adapter/Mongo.php
  • src/Database/Database.php
  • src/Database/Validator/ObjectValidator.php
  • tests/e2e/Adapter/Scopes/ObjectAttributeTests.php
  • tests/unit/Validator/ObjectTest.php

Comment thread src/Database/Database.php
@abnegate

Copy link
Copy Markdown
Member Author

@copilot update to the released cache/mongo versions

Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>

Copilot AI commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

@copilot update to the released cache/mongo versions

Done — bumped utopia-php/mongo in composer.lock from 1.5.1 to 1.5.2 (commit 20f9a64, which contains the empty-object fix from PR #48). utopia-php/cache was already locked at 4.0.1.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Dockerfile`:
- Around line 24-29: Update the Dockerfile source hydration commands to remove
each destination source directory under vendor before copying the pinned cache
and mongo sources, preventing stale files from persisting. After all pinned
sources are copied, run composer dump-autoload --optimize --no-scripts so the
optimized class map reflects them.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 54125718-d497-4158-a7a8-9699eb5d66a0

📥 Commits

Reviewing files that changed from the base of the PR and between c96d5de and d875e11.

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • .github/workflows/tests.yml
  • Dockerfile
  • src/Database/Database.php
  • tests/e2e/Adapter/Scopes/ObjectAttributeTests.php
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/e2e/Adapter/Scopes/ObjectAttributeTests.php
  • src/Database/Database.php

Comment thread Dockerfile Outdated
The Dockerfile cloned utopia-php/monorepo and utopia-php/mongo at fixed
commits and copied their src/ over vendor/, driven by build args pinned in
the workflow:

    UTOPIA_CACHE_REFERENCE=d7c0806f...
    UTOPIA_MONGO_REFERENCE=daea8f82...

That was a vendor patch standing in for releases that did not exist yet.
Both now do, so the lock resolves them normally and the machinery goes.

    utopia-php/cache  4.0.1 -> 4.0.2  source 92e02dab6
    utopia-php/mongo         1.5.2    source 20f9a644a

Both lock references equal the Packagist source references exactly, and
each release contains the pinned commit: d7c0806f is an ancestor of the
cache/4.0.2 tag commit, and 1.5.2 contains daea8f82.

Constraints already admitted these (cache ^4.0.0, mongo 1.*), so only the
lock moved. Removing the build args also stops a Docker build fetching
arbitrary external git refs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@abnegate
abnegate merged commit b2b136a into main Aug 12, 2026
22 checks passed
@abnegate
abnegate deleted the fix/dat-2310-empty-object-fidelity branch August 12, 2026 09:39
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.

2 participants