Partial update#886
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughDatabase::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. ChangesDocument update sanitization and adapter metadata updates
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Greptile SummaryThis PR converts
Confidence Score: 3/5The adapter can generate syntactically invalid SQL when a partial document carries no tracked columns, which would throw a PDO exception on any such call. The removal of the unconditional The empty-SET-clause defect lives in Important Files Changed
Reviews (12): Last reviewed commit: "Pstgres" | Re-trigger Greptile |
CI failure on run 27276370273All adapter test jobs failed (MongoDB, Postgres, MariaDB, SharedTables/*, Pool, Mirror, Memory, …) with the same family of errors triggered by the new partial-update path in
Root causeThe previous implementation did
Fix (commit)
|
|
Claude could not apply patches from: healing (merge conflicts with current branch tip). These tasks need manual attention. |
CI healing — SQLite/Postgres partial-update propagationWhat failed
…meaning a partial update with Root cause $attributes['_createdAt'] = $document->getCreatedAt(); // null when not provided
$attributes['_updatedAt'] = $document->getUpdatedAt();
$attributes['_permissions'] = json_encode($document->getPermissions()); // "[]"
…
SET {$columns}, _uid = :_newUid
$stmt->bindValue(':_newUid', $document->getId()); // empty string for partial updatesSo a partial Additionally, What I fixed (in commit
Scope note |
CI fix — partial
|
…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>
|
Claude pushed fixes from: healing |
|
Claude could not apply patches from: healing (merge conflicts with current branch tip). These tasks need manual attention. |
|
CI run 27277729469 failed at the Set up Docker Buildx step with a Docker Hub network timeout: This is a flaky infrastructure failure — the runner couldn't reach |
Healing run on CI failure 27277229719What failed: the partial-update refactor was incomplete. Two regressions caused the bulk of the cross-adapter errors (12 errors on MongoDB, 13 on MariaDB/MySQL, 26 on Postgres, plus shared-tables variants):
What I changed:
Verified: Out of scope for this healing pass:
Commit: |
| $sql = " | ||
| UPDATE {$this->getSQLTable($name)} | ||
| SET {$columns} _uid = :_newUid | ||
| SET " . \rtrim($columns, ',') . " | ||
| WHERE _id=:_sequence | ||
| {$this->getTenantQuery($collection)} | ||
| "; |
There was a problem hiding this comment.
Empty SET clause produces invalid SQL for no-op partial updates
Before this PR the SET clause always had at least _uid = :_newUid as a guaranteed trailing column. That fallback is now removed. When the partial document carries no regular attributes (e.g. $permissions-only) and $shouldUpdate is false (so $updatedAt is not injected), $attributes is empty, $columns stays '', and \rtrim('', ',') returns ''. The resulting SQL — UPDATE … SET WHERE _id=:_sequence — is a syntax error that causes a PDO exception.
A concrete failing path: updateDocument($col, $id, new Document(['$permissions' => $exactSamePerms])). No attribute changes → $shouldUpdate = false → no $updatedAt attribute set. No $id in the document → no _uid in attributes. Permissions unchanged → $skipPermissionsUpdate = true. Adapter called with empty $attributes → $columns = '' → invalid SQL.
The same gap exists in Postgres.php and SQLite.php.
|
Claude could not apply patches from: healing (merge conflicts with current branch tip). These tasks need manual attention. |
There was a problem hiding this comment.
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 winRoot cause: Conditional internal metadata mapping can leave SET clause empty.
Both adapters now conditionally add
_updatedAt,_uid,_createdAt, and_permissionsonly 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
📒 Files selected for processing (4)
src/Database/Adapter/MariaDB.phpsrc/Database/Adapter/Postgres.phpsrc/Database/Adapter/SQLite.phpsrc/Database/Database.php
🚧 Files skipped from review as they are similar to previous changes (1)
- src/Database/Database.php
|
Claude could not apply patches from: healing (merge conflicts with current branch tip). These tasks need manual attention. |
| $sql = " | ||
| UPDATE {$this->getSQLTable($name)} | ||
| SET {$columns} _uid = :_newUid | ||
| SET " . \rtrim($columns, ',') . " | ||
| WHERE _id=:_sequence | ||
| {$this->getTenantQuery($collection)} | ||
| "; |
There was a problem hiding this comment.
Empty SET clause on no-op partial updates
Before this PR, SET {$columns} _uid = :_newUid guaranteed a non-empty SET clause because _uid was always appended. That unconditional trailing column is now removed. When the adapter is called with a document that carries no tracked attributes — e.g., a caller passes new Document([]) or a document whose only keys are internal attributes not handled by the adapter's offsetExists checks — $attributes is empty, $columns stays '', \rtrim('', ',') returns '', and the resulting SQL is UPDATE … SET WHERE _id=:_sequence, which is a PDO syntax error.
One concrete path: updateDocument is always called regardless of $shouldUpdate; when $shouldUpdate is false the adapter receives no _updatedAt, and if the partial document also has no user attributes, no $id, no $createdAt, and no $permissions, $attributes is empty. The same gap exists in Postgres.php and SQLite.php.
A guard before the prepare call that skips the UPDATE (or emits a trivial SET _id=_id) when $columns is empty would prevent this.
Summary by CodeRabbit
Bug Fixes
Improvements