Skip to content

Partial update#886

Open
fogelito wants to merge 37 commits into
mainfrom
partial-update
Open

Partial update#886
fogelito wants to merge 37 commits into
mainfrom
partial-update

Conversation

@fogelito

@fogelito fogelito commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • Bug Fixes

    • Prevents accidental overwriting of internal metadata during updates.
    • Honors configuration for preserving or omitting creation timestamps.
    • Reduces inconsistent tenant/version handling to avoid conflicting saves.
  • Improvements

    • Conditional handling of metadata and permissions to avoid unintended changes.
    • Safer merging and stricter validation of persisted vs incoming data for more reliable updates.

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Database::updateDocument sanitizes in-flight updates (removes internals, optional $createdAt, tenant normalization), validates with PartialStructure against persisted document, restores persisted $sequence before adapter call, and merges persisted data with adapter-updated attributes. MariaDB/Postgres/SQLite adapters now conditionally map internal fields, adjust permission id bindings, and trim UPDATE SET/bindings.

Changes

Document update sanitization and adapter metadata updates

Layer / File(s) Summary
Database sanitization, normalization, validation, sequence restore, and merge
src/Database/Database.php
Strip internal fields from incoming documents; optionally remove $createdAt when preserveDates is false; normalize $tenant in shared-tables mode; validate updates with PartialStructure using the persisted $old as currentDocument; restore persisted $sequence onto the in-flight document before calling the adapter; after adapter returns, merge the persisted array with adapter-updated attributes and continue existing casting/cache/post-update logic.
MariaDB conditional metadata copy and permission id binding / UPDATE clause
src/Database/Adapter/MariaDB.php
updateDocument() conditionally copies _updatedAt, _uid, _createdAt, and _permissions only when those offsets exist on the incoming Document; permission read/delete bind :_uid from method $id; inserted permission _uid uses computed newUid (incoming $id if present else method $id); UPDATE SET uses trimmed $columns and the :_newUid binding was removed.
Postgres conditional metadata copy and permission id binding / UPDATE clause
src/Database/Adapter/Postgres.php
updateDocument() conditionally maps _updatedAt, _uid, _createdAt, _permissions only when present on the incoming Document; permission queries/deletes bind :_uid from method $id; inserted permissions compute _uid from incoming $id when present; UPDATE SET no longer appends _uid = :_newUid and :_newUid binding removed.
SQLite conditional metadata copy and permission id binding / UPDATE clause
src/Database/Adapter/SQLite.php
updateDocument() conditionally sets _updatedAt, _uid, _createdAt, _permissions only when the incoming Document exposes them; permission reads/deletes bind :_uid from method $id; inserting permissions computes newUid from incoming $id if present; UPDATE SET stops appending _uid = :_newUid and its binding removed.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • abnegate

Poem

🐰 I nibble keys both old and new,
I drop the dates that shouldn't stew,
Sequence snug where it must stay,
Adapters mind which fields to convey,
The merged doc hops home, all true.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Partial update' is vague and does not clearly convey what specific changes are being made in this pull request. Clarify the title to better describe the main change, such as 'Support conditional metadata updates in document operations' or 'Make metadata updates conditional on document field presence'.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch partial-update

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 and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR implements partial document updates across all adapters, allowing callers to pass a sparse Document containing only the fields they want to change rather than a full replacement. The Database::updateDocument method now strips immutable fields, captures $inputKeys to scope the adapter write, and defers $updatedAt mutation to only when actual field changes are detected; each SQL adapter was updated to conditionally map internal metadata columns only when present in the incoming document.

  • Database.php: Builds $inputKeys before the transaction to constrain which columns reach the adapter; adds an early collection-not-found guard; fixes the previously missing casting() call after populateDocumentsRelationships.
  • SQL adapters (MariaDB / Postgres / SQLite): Replace the unconditional four-field assignment with offsetExists guards and an empty($attributes) short-circuit; $id/$uid is now resolved from the function parameter as a fallback when the partial document omits it.
  • Memory adapter: Strips internally-mapped fields from the update row when the partial document does not contain them, and restores $id on the document before writePermissions.
  • Tests: Adds testPartialUpdateDocument (UID rename preserves all other fields) and testUpdateDocumentChangePermissions (permissions-only update, role access verified after change).

Confidence Score: 3/5

Not safe to merge in its current state — debug output introduced in this diff is still present in the MariaDB adapter and will write the full UPDATE SQL to stdout on every document update in production.

The partial-update logic in Database.php and the adapter changes are structurally correct, but MariaDB.php still contains a var_dump($sql) call added in this PR that leaks query internals to PHP output on every single document update. In addition, stopOnFailure was flipped to true in phpunit.xml, which halts the test suite on the first failure and can mask the true pass/fail status of the full suite. Both issues were raised in prior review rounds and remain unresolved in the current head commit.

src/Database/Adapter/MariaDB.php (var_dump debug statement at line 1090) and phpunit.xml (stopOnFailure configuration) need attention before merging.

Important Files Changed

Filename Overview
src/Database/Database.php Core partial-update logic: strips immutable fields before the transaction, builds $inputKeys to scope the adapter write, conditionally sets $updatedAt only when $shouldUpdate is true, and adds the previously-missing casting() call after the transaction. Logic is sound but complex.
src/Database/Adapter/MariaDB.php Adapter conditionally maps only present internal fields to SQL columns; includes an empty-attributes early-return guard. A var_dump($sql) debug statement was introduced at line 1090 and remains unremoved.
src/Database/Adapter/Postgres.php Same conditional offsetExists pattern as MariaDB; no debug output left in. Change looks correct.
src/Database/Adapter/SQLite.php Mirrors MariaDB/Postgres conditional offsetExists changes; no extra debug output.
src/Database/Adapter/Memory.php Strips internal fields from the update row when absent from the partial document to prevent overwriting existing values, and restores $id on the document before writePermissions. Logic is correct.
src/Database/Document.php Return types of getCreatedAt/getUpdatedAt broadened to include UTCDateTime for MongoDB compatibility; straightforward and non-breaking.
tests/e2e/Adapter/Scopes/DocumentTests.php Adds testPartialUpdateDocument (UID rename) and testUpdateDocumentChangePermissions (permissions-only update) — both are complete and meaningful. An empty placeholder method testPartialUpdateDocument_place() was left in.
phpunit.xml stopOnFailure flipped to true, which halts the suite on the first failure and can hide subsequent test results.

Reviews (27): Last reviewed commit: "formatting" | Re-trigger Greptile

Comment thread src/Database/Database.php Outdated
Comment thread src/Database/Database.php Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Claude could not apply patches from: healing (merge conflicts with current branch tip). These tasks need manual attention.

…pters

The partial-update change in Database.php only sets internal attributes
(`$createdAt`, `$updatedAt`, `$permissions`, `$id`) on the Document when
the user explicitly provided them. MariaDB's updateDocument was updated
to honor that (offsetExists checks before writing `_createdAt`, `_uid`,
`_permissions`), but SQLite and Postgres still unconditionally wrote
those columns — using `$document->getCreatedAt()`/`getPermissions()`
(returning null/`"[]"`) and `_uid = :_newUid` bound to
`$document->getId()` (empty string for partial updates without `$id`).
SQLite especially blew up because the row's `_uid` got rewritten to an
empty string, so subsequent `getDocument` calls returned null and any
operator-driven update appeared to lose its result.

Also makes `_updatedAt` conditional in MariaDB so partial updates with
`shouldUpdate=false` (e.g. relationship-only updates) don't overwrite
the parent row's `_updatedAt` with NULL.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Claude pushed fixes from: healing

e29ee24...f94cc01

@github-actions

Copy link
Copy Markdown
Contributor

Claude could not apply patches from: healing (merge conflicts with current branch tip). These tasks need manual attention.

Comment thread src/Database/Adapter/MariaDB.php
@github-actions

Copy link
Copy Markdown
Contributor

Claude could not apply patches from: healing (merge conflicts with current branch tip). These tasks need manual attention.

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Database/Adapter/MariaDB.php (1)

980-991: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Root cause: Conditional internal metadata mapping can leave SET clause empty.

Both adapters now conditionally add _updatedAt, _uid, _createdAt, and _permissions only when the corresponding offsets exist. When the Database layer performs a permissions-only update ($shouldUpdate=false), none of these offsets may be present, and if no user attributes exist, the SET clause becomes empty.

Consider coordinating the fix across both adapters or addressing this at the Database layer by ensuring at least one column is always present when calling the adapter.

🤖 Prompt for 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.

In `@src/Database/Adapter/MariaDB.php` around lines 980 - 991, The adapter builds
$attributes conditionally from $document->offsetExists checks which can produce
an empty SET clause for permissions-only updates; update
src/Database/Adapter/MariaDB.php so the update builder always supplies at least
one column when composing $attributes — for example, ensure '_updatedAt' is
always added (use $document->getUpdatedAt() if available or a fallback like
current timestamp) or add a dedicated internal no-op field key (e.g.,
'_internal_update_flag') when none of
'_updatedAt','_uid','_createdAt','_permissions' were added; keep the checks for
existing offsets but guarantee $attributes is non-empty before building the SET,
and mirror the same change in the other adapter to keep behavior consistent.
🤖 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.

Outside diff comments:
In `@src/Database/Adapter/MariaDB.php`:
- Around line 980-991: The adapter builds $attributes conditionally from
$document->offsetExists checks which can produce an empty SET clause for
permissions-only updates; update src/Database/Adapter/MariaDB.php so the update
builder always supplies at least one column when composing $attributes — for
example, ensure '_updatedAt' is always added (use $document->getUpdatedAt() if
available or a fallback like current timestamp) or add a dedicated internal
no-op field key (e.g., '_internal_update_flag') when none of
'_updatedAt','_uid','_createdAt','_permissions' were added; keep the checks for
existing offsets but guarantee $attributes is non-empty before building the SET,
and mirror the same change in the other adapter to keep behavior consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0cd5a620-5c8e-445d-bfe2-b0692881fd19

📥 Commits

Reviewing files that changed from the base of the PR and between 6b2a7b5 and f94cc01.

📒 Files selected for processing (4)
  • src/Database/Adapter/MariaDB.php
  • src/Database/Adapter/Postgres.php
  • src/Database/Adapter/SQLite.php
  • src/Database/Database.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Database/Database.php

@github-actions

Copy link
Copy Markdown
Contributor

Claude could not apply patches from: healing (merge conflicts with current branch tip). These tasks need manual attention.

Comment thread src/Database/Database.php Outdated
Comment thread src/Database/Adapter/MariaDB.php
Comment thread src/Database/Adapter/MariaDB.php Outdated
Comment thread tests/e2e/Adapter/Scopes/DocumentTests.php Outdated
Comment thread phpunit.xml
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
stopOnFailure="true"

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.

P1 stopOnFailure was flipped to true, which halts the entire suite on the first failure. Combined with the always-failing assertion in testPartialUpdateDocument, no tests after that point will execute, hiding the real pass/fail status of the rest of the suite. This should be reverted to false (the project's established default) before merging.

Suggested change
stopOnFailure="true"
stopOnFailure="false"

@utopia-php utopia-php deleted a comment from claude Bot Jun 11, 2026
@utopia-php utopia-php deleted a comment from claude Bot Jun 11, 2026
@utopia-php utopia-php deleted a comment from claude Bot Jun 11, 2026
@utopia-php utopia-php deleted a comment from claude Bot Jun 11, 2026
@utopia-php utopia-php deleted a comment from claude Bot Jun 11, 2026
fogelito added 3 commits June 16, 2026 14:11
…date

# Conflicts:
#	src/Database/Adapter/MariaDB.php
#	src/Database/Adapter/Postgres.php
#	src/Database/Adapter/SQLite.php
#	src/Database/Database.php
Comment thread src/Database/Database.php Outdated
Comment thread tests/e2e/Adapter/Scopes/DocumentTests.php Outdated
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.

1 participant