Skip to content

Partial update#886

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

Partial update#886
fogelito wants to merge 30 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 sparse/partial document updates by capturing the caller's $inputKeys before the merge step and passing only those keys — plus $sequence and $updatedAt — to the adapters as $adapterDocument, preventing unchanged columns from being overwritten.

  • Database.php captures $inputKeys outside the transaction, constructs a partial $adapterDocument via array_intersect_key after encode and relationship processing, and switches the structure validator to PartialStructure so only user-provided fields are checked.
  • All three SQL adapters now conditionally add internal fields, fix permissions queries to use the $id parameter instead of $document->getId() (preventing null-ID mismatches), and add an empty($attributes) guard that is currently unreachable.
  • Memory.php strips unmapped internal fields from the documentToRow result before the sparse array_merge and back-fills $id before writing permissions.

Confidence Score: 4/5

Safe to merge with one minor inefficiency: every partial-update call unconditionally issues a DB write even for genuine no-ops.

The core partial-update mechanism is well-designed — $inputKeys capture, array_intersect_key scoping, and adapter-level conditional attribute handling all work correctly. The permissions null-ID fix is a real improvement. The one non-blocking concern is that the newly added empty($attributes) guard in all three SQL adapters is dead code: _updatedAt is always injected into $adapterDocument before the guard can fire, so even a completely empty partial update still executes a DB row write. No data is corrupted, but the intended skip-on-empty optimization never triggers.

All three SQL adapters share the dead empty($attributes) guard; Database.php is the natural place to add the no-op early-return if the team wants to avoid redundant writes.

Important Files Changed

Filename Overview
src/Database/Database.php Core partial-update logic: $inputKeys captured before merge, $adapterDocument constructed via array_intersect_key, $shouldUpdate drives permission/timestamp checks. Logic is sound but the dead-code guard and no-op write risk are minor concerns.
src/Database/Adapter/MariaDB.php Conditional internal-attribute handling added; permissions now use $id param instead of $document->getId() fixing the null-ID bug. The empty($attributes) guard is dead code since _updatedAt is always injected before it.
src/Database/Adapter/Postgres.php Mirrors MariaDB changes — same conditional attribute pattern, same $id fix for permissions, same dead empty($attributes) guard.
src/Database/Adapter/SQLite.php Mirrors MariaDB/Postgres changes; correctly calls rtrim($columns, ',') before building SQL; same dead empty($attributes) guard.
src/Database/Adapter/Memory.php Sparse-update logic for in-memory adapter: correctly strips unmapped internal fields from $update before array_merge, and back-fills $id on the document before writePermissions. Logic is clean.
src/Database/Document.php Return-type widened to string
tests/e2e/Adapter/Scopes/DocumentTests.php testPartialUpdateDocument added as entirely commented-out WIP code with undefined variables and a hardcoded-failure assertion; safe because it will not execute.
phpunit.xml stopOnFailure flipped to true; while the known-bad test is commented out, this will abort the suite on the first failure from any other test.

Reviews (21): Last reviewed commit: "fix early return" | 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
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