Skip to content

perf: optimize updateDocument() calls to use sparse documents#11465

Merged
ChiragAgg5k merged 2 commits into1.8.xfrom
optimize-update-document-calls
Mar 6, 2026
Merged

perf: optimize updateDocument() calls to use sparse documents#11465
ChiragAgg5k merged 2 commits into1.8.xfrom
optimize-update-document-calls

Conversation

@ChiragAgg5k
Copy link
Copy Markdown
Member

Summary

This PR optimizes updateDocument() calls across the codebase to pass only changed attributes as sparse Document objects rather than full documents. This is more efficient because updateDocument() internally performs array_merge($old, $new), so passing only changed fields reduces overhead.

Changes

  • 58 files updated to use sparse Document objects
  • AGENTS.md updated with Performance Patterns section documenting the optimization pattern
  • Applied pattern consistently across:
    • Workers (Certificates, Webhooks, Messaging, Migrations)
    • Functions module (HTTP endpoints and Workers/Builds.php)
    • Sites module (Deployments, Variables)
    • Teams module (Memberships, Teams)
    • VCS module (GitHub integrations)
    • Databases Workers
    • app/controllers/api (account.php, users.php, messaging.php)
    • app infrastructure (realtime.php, general.php, init/resources.php, shared/api.php)

Pattern Applied

Before (inefficient):

$user->setAttribute('name', $name);
$user->setAttribute('email', $email);
$dbForProject->updateDocument('users', $user->getId(), $user);

After (optimized):

$user = $dbForProject->updateDocument('users', $user->getId(), new Document([
    'name' => $name,
    'email' => $email,
]));

Exceptions Maintained

Per the optimization guidelines, the following were intentionally excluded:

  • Migration files (need full document updates by design)
  • Cases already using array_merge() with getArrayCopy()
  • Updates with 6+ attributes changing simultaneously (marginal benefit)
  • Complex nested relationship logic where full document state is required

Testing

  • Run full test suite: docker compose exec appwrite php vendor/bin/phpunit
  • Verify no regressions in key flows (auth, messaging, deployments, projects)
  • Run composer format to ensure code style compliance

Related

  • Updates AGENTS.md with new Performance Patterns section

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 6, 2026

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

The PR replaces numerous updateDocument() calls that previously passed whole entity objects with new Utopia\Database\Document instances containing only the changed fields (sparse/partial updates). Changes touch controllers, platform modules, workers, initialization code, and documentation (added AGENTS.md section and CLAUDE.md). Control flow, error handling, and public interfaces are unchanged; several files add imports for Utopia\Database\Document.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.70% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'perf: optimize updateDocument() calls to use sparse documents' clearly and concisely describes the main change in the PR—optimizing updateDocument() calls across the codebase to use sparse Document objects containing only changed attributes.
Description check ✅ Passed The description is comprehensive and directly related to the changeset. It explains the optimization pattern, lists the files affected, provides before/after code examples, mentions exceptions, and includes testing guidance—all relevant to the sparse Document optimization changes.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch optimize-update-document-calls

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.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 6, 2026

Security Scan Results for PR

Docker Image Scan Results

Package Version Vulnerability Severity
imagemagick 7.1.2.13-r0 CVE-2026-25897 CRITICAL
imagemagick 7.1.2.13-r0 CVE-2026-25898 CRITICAL
imagemagick 7.1.2.13-r0 CVE-2026-25968 CRITICAL
imagemagick 7.1.2.13-r0 CVE-2026-25971 CRITICAL
imagemagick 7.1.2.13-r0 CVE-2026-25983 CRITICAL
imagemagick 7.1.2.13-r0 CVE-2026-25986 CRITICAL
imagemagick 7.1.2.13-r0 CVE-2026-25987 CRITICAL
imagemagick 7.1.2.13-r0 CVE-2026-26284 CRITICAL
imagemagick 7.1.2.13-r0 CVE-2026-24481 HIGH
imagemagick 7.1.2.13-r0 CVE-2026-24485 HIGH
imagemagick 7.1.2.13-r0 CVE-2026-25794 HIGH
imagemagick 7.1.2.13-r0 CVE-2026-25795 HIGH
imagemagick 7.1.2.13-r0 CVE-2026-25796 HIGH
imagemagick 7.1.2.13-r0 CVE-2026-25798 HIGH
imagemagick 7.1.2.13-r0 CVE-2026-25799 HIGH
imagemagick 7.1.2.13-r0 CVE-2026-25965 HIGH
imagemagick 7.1.2.13-r0 CVE-2026-25966 HIGH
imagemagick 7.1.2.13-r0 CVE-2026-25967 HIGH
imagemagick 7.1.2.13-r0 CVE-2026-25969 HIGH
imagemagick 7.1.2.13-r0 CVE-2026-25970 HIGH
imagemagick 7.1.2.13-r0 CVE-2026-25985 HIGH
imagemagick 7.1.2.13-r0 CVE-2026-25988 HIGH
imagemagick 7.1.2.13-r0 CVE-2026-25989 HIGH
imagemagick 7.1.2.13-r0 CVE-2026-26066 HIGH
imagemagick 7.1.2.13-r0 CVE-2026-26283 HIGH
imagemagick 7.1.2.13-r0 CVE-2026-27798 HIGH
imagemagick-c++ 7.1.2.13-r0 CVE-2026-25897 CRITICAL
imagemagick-c++ 7.1.2.13-r0 CVE-2026-25898 CRITICAL
imagemagick-c++ 7.1.2.13-r0 CVE-2026-25968 CRITICAL
imagemagick-c++ 7.1.2.13-r0 CVE-2026-25971 CRITICAL
imagemagick-c++ 7.1.2.13-r0 CVE-2026-25983 CRITICAL
imagemagick-c++ 7.1.2.13-r0 CVE-2026-25986 CRITICAL
imagemagick-c++ 7.1.2.13-r0 CVE-2026-25987 CRITICAL
imagemagick-c++ 7.1.2.13-r0 CVE-2026-26284 CRITICAL
imagemagick-c++ 7.1.2.13-r0 CVE-2026-24481 HIGH
imagemagick-c++ 7.1.2.13-r0 CVE-2026-24485 HIGH
imagemagick-c++ 7.1.2.13-r0 CVE-2026-25794 HIGH
imagemagick-c++ 7.1.2.13-r0 CVE-2026-25795 HIGH
imagemagick-c++ 7.1.2.13-r0 CVE-2026-25796 HIGH
imagemagick-c++ 7.1.2.13-r0 CVE-2026-25798 HIGH
imagemagick-c++ 7.1.2.13-r0 CVE-2026-25799 HIGH
imagemagick-c++ 7.1.2.13-r0 CVE-2026-25965 HIGH
imagemagick-c++ 7.1.2.13-r0 CVE-2026-25966 HIGH
imagemagick-c++ 7.1.2.13-r0 CVE-2026-25967 HIGH
imagemagick-c++ 7.1.2.13-r0 CVE-2026-25969 HIGH
imagemagick-c++ 7.1.2.13-r0 CVE-2026-25970 HIGH
imagemagick-c++ 7.1.2.13-r0 CVE-2026-25985 HIGH
imagemagick-c++ 7.1.2.13-r0 CVE-2026-25988 HIGH
imagemagick-c++ 7.1.2.13-r0 CVE-2026-25989 HIGH
imagemagick-c++ 7.1.2.13-r0 CVE-2026-26066 HIGH
imagemagick-c++ 7.1.2.13-r0 CVE-2026-26283 HIGH
imagemagick-c++ 7.1.2.13-r0 CVE-2026-27798 HIGH
imagemagick-dev 7.1.2.13-r0 CVE-2026-25897 CRITICAL
imagemagick-dev 7.1.2.13-r0 CVE-2026-25898 CRITICAL
imagemagick-dev 7.1.2.13-r0 CVE-2026-25968 CRITICAL
imagemagick-dev 7.1.2.13-r0 CVE-2026-25971 CRITICAL
imagemagick-dev 7.1.2.13-r0 CVE-2026-25983 CRITICAL
imagemagick-dev 7.1.2.13-r0 CVE-2026-25986 CRITICAL
imagemagick-dev 7.1.2.13-r0 CVE-2026-25987 CRITICAL
imagemagick-dev 7.1.2.13-r0 CVE-2026-26284 CRITICAL
imagemagick-dev 7.1.2.13-r0 CVE-2026-24481 HIGH
imagemagick-dev 7.1.2.13-r0 CVE-2026-24485 HIGH
imagemagick-dev 7.1.2.13-r0 CVE-2026-25794 HIGH
imagemagick-dev 7.1.2.13-r0 CVE-2026-25795 HIGH
imagemagick-dev 7.1.2.13-r0 CVE-2026-25796 HIGH
imagemagick-dev 7.1.2.13-r0 CVE-2026-25798 HIGH
imagemagick-dev 7.1.2.13-r0 CVE-2026-25799 HIGH
imagemagick-dev 7.1.2.13-r0 CVE-2026-25965 HIGH
imagemagick-dev 7.1.2.13-r0 CVE-2026-25966 HIGH
imagemagick-dev 7.1.2.13-r0 CVE-2026-25967 HIGH
imagemagick-dev 7.1.2.13-r0 CVE-2026-25969 HIGH
imagemagick-dev 7.1.2.13-r0 CVE-2026-25970 HIGH
imagemagick-dev 7.1.2.13-r0 CVE-2026-25985 HIGH
imagemagick-dev 7.1.2.13-r0 CVE-2026-25988 HIGH
imagemagick-dev 7.1.2.13-r0 CVE-2026-25989 HIGH
imagemagick-dev 7.1.2.13-r0 CVE-2026-26066 HIGH
imagemagick-dev 7.1.2.13-r0 CVE-2026-26283 HIGH
imagemagick-dev 7.1.2.13-r0 CVE-2026-27798 HIGH
imagemagick-heic 7.1.2.13-r0 CVE-2026-25897 CRITICAL
imagemagick-heic 7.1.2.13-r0 CVE-2026-25898 CRITICAL
imagemagick-heic 7.1.2.13-r0 CVE-2026-25968 CRITICAL
imagemagick-heic 7.1.2.13-r0 CVE-2026-25971 CRITICAL
imagemagick-heic 7.1.2.13-r0 CVE-2026-25983 CRITICAL
imagemagick-heic 7.1.2.13-r0 CVE-2026-25986 CRITICAL
imagemagick-heic 7.1.2.13-r0 CVE-2026-25987 CRITICAL
imagemagick-heic 7.1.2.13-r0 CVE-2026-26284 CRITICAL
imagemagick-heic 7.1.2.13-r0 CVE-2026-24481 HIGH
imagemagick-heic 7.1.2.13-r0 CVE-2026-24485 HIGH
imagemagick-heic 7.1.2.13-r0 CVE-2026-25794 HIGH
imagemagick-heic 7.1.2.13-r0 CVE-2026-25795 HIGH
imagemagick-heic 7.1.2.13-r0 CVE-2026-25796 HIGH
imagemagick-heic 7.1.2.13-r0 CVE-2026-25798 HIGH
imagemagick-heic 7.1.2.13-r0 CVE-2026-25799 HIGH
imagemagick-heic 7.1.2.13-r0 CVE-2026-25965 HIGH
imagemagick-heic 7.1.2.13-r0 CVE-2026-25966 HIGH
imagemagick-heic 7.1.2.13-r0 CVE-2026-25967 HIGH
imagemagick-heic 7.1.2.13-r0 CVE-2026-25969 HIGH
imagemagick-heic 7.1.2.13-r0 CVE-2026-25970 HIGH
imagemagick-heic 7.1.2.13-r0 CVE-2026-25985 HIGH
imagemagick-heic 7.1.2.13-r0 CVE-2026-25988 HIGH
imagemagick-heic 7.1.2.13-r0 CVE-2026-25989 HIGH
imagemagick-heic 7.1.2.13-r0 CVE-2026-26066 HIGH
imagemagick-heic 7.1.2.13-r0 CVE-2026-26283 HIGH
imagemagick-heic 7.1.2.13-r0 CVE-2026-27798 HIGH
imagemagick-jpeg 7.1.2.13-r0 CVE-2026-25897 CRITICAL
imagemagick-jpeg 7.1.2.13-r0 CVE-2026-25898 CRITICAL
imagemagick-jpeg 7.1.2.13-r0 CVE-2026-25968 CRITICAL
imagemagick-jpeg 7.1.2.13-r0 CVE-2026-25971 CRITICAL
imagemagick-jpeg 7.1.2.13-r0 CVE-2026-25983 CRITICAL
imagemagick-jpeg 7.1.2.13-r0 CVE-2026-25986 CRITICAL
imagemagick-jpeg 7.1.2.13-r0 CVE-2026-25987 CRITICAL
imagemagick-jpeg 7.1.2.13-r0 CVE-2026-26284 CRITICAL
imagemagick-jpeg 7.1.2.13-r0 CVE-2026-24481 HIGH
imagemagick-jpeg 7.1.2.13-r0 CVE-2026-24485 HIGH
imagemagick-jpeg 7.1.2.13-r0 CVE-2026-25794 HIGH
imagemagick-jpeg 7.1.2.13-r0 CVE-2026-25795 HIGH
imagemagick-jpeg 7.1.2.13-r0 CVE-2026-25796 HIGH
imagemagick-jpeg 7.1.2.13-r0 CVE-2026-25798 HIGH
imagemagick-jpeg 7.1.2.13-r0 CVE-2026-25799 HIGH
imagemagick-jpeg 7.1.2.13-r0 CVE-2026-25965 HIGH
imagemagick-jpeg 7.1.2.13-r0 CVE-2026-25966 HIGH
imagemagick-jpeg 7.1.2.13-r0 CVE-2026-25967 HIGH
imagemagick-jpeg 7.1.2.13-r0 CVE-2026-25969 HIGH
imagemagick-jpeg 7.1.2.13-r0 CVE-2026-25970 HIGH
imagemagick-jpeg 7.1.2.13-r0 CVE-2026-25985 HIGH
imagemagick-jpeg 7.1.2.13-r0 CVE-2026-25988 HIGH
imagemagick-jpeg 7.1.2.13-r0 CVE-2026-25989 HIGH
imagemagick-jpeg 7.1.2.13-r0 CVE-2026-26066 HIGH
imagemagick-jpeg 7.1.2.13-r0 CVE-2026-26283 HIGH
imagemagick-jpeg 7.1.2.13-r0 CVE-2026-27798 HIGH
imagemagick-jxl 7.1.2.13-r0 CVE-2026-25897 CRITICAL
imagemagick-jxl 7.1.2.13-r0 CVE-2026-25898 CRITICAL
imagemagick-jxl 7.1.2.13-r0 CVE-2026-25968 CRITICAL
imagemagick-jxl 7.1.2.13-r0 CVE-2026-25971 CRITICAL
imagemagick-jxl 7.1.2.13-r0 CVE-2026-25983 CRITICAL
imagemagick-jxl 7.1.2.13-r0 CVE-2026-25986 CRITICAL
imagemagick-jxl 7.1.2.13-r0 CVE-2026-25987 CRITICAL
imagemagick-jxl 7.1.2.13-r0 CVE-2026-26284 CRITICAL
imagemagick-jxl 7.1.2.13-r0 CVE-2026-24481 HIGH
imagemagick-jxl 7.1.2.13-r0 CVE-2026-24485 HIGH
imagemagick-jxl 7.1.2.13-r0 CVE-2026-25794 HIGH
imagemagick-jxl 7.1.2.13-r0 CVE-2026-25795 HIGH
imagemagick-jxl 7.1.2.13-r0 CVE-2026-25796 HIGH
imagemagick-jxl 7.1.2.13-r0 CVE-2026-25798 HIGH
imagemagick-jxl 7.1.2.13-r0 CVE-2026-25799 HIGH
imagemagick-jxl 7.1.2.13-r0 CVE-2026-25965 HIGH
imagemagick-jxl 7.1.2.13-r0 CVE-2026-25966 HIGH
imagemagick-jxl 7.1.2.13-r0 CVE-2026-25967 HIGH
imagemagick-jxl 7.1.2.13-r0 CVE-2026-25969 HIGH
imagemagick-jxl 7.1.2.13-r0 CVE-2026-25970 HIGH
imagemagick-jxl 7.1.2.13-r0 CVE-2026-25985 HIGH
imagemagick-jxl 7.1.2.13-r0 CVE-2026-25988 HIGH
imagemagick-jxl 7.1.2.13-r0 CVE-2026-25989 HIGH
imagemagick-jxl 7.1.2.13-r0 CVE-2026-26066 HIGH
imagemagick-jxl 7.1.2.13-r0 CVE-2026-26283 HIGH
imagemagick-jxl 7.1.2.13-r0 CVE-2026-27798 HIGH
imagemagick-libs 7.1.2.13-r0 CVE-2026-25897 CRITICAL
imagemagick-libs 7.1.2.13-r0 CVE-2026-25898 CRITICAL
imagemagick-libs 7.1.2.13-r0 CVE-2026-25968 CRITICAL
imagemagick-libs 7.1.2.13-r0 CVE-2026-25971 CRITICAL
imagemagick-libs 7.1.2.13-r0 CVE-2026-25983 CRITICAL
imagemagick-libs 7.1.2.13-r0 CVE-2026-25986 CRITICAL
imagemagick-libs 7.1.2.13-r0 CVE-2026-25987 CRITICAL
imagemagick-libs 7.1.2.13-r0 CVE-2026-26284 CRITICAL
imagemagick-libs 7.1.2.13-r0 CVE-2026-24481 HIGH
imagemagick-libs 7.1.2.13-r0 CVE-2026-24485 HIGH
imagemagick-libs 7.1.2.13-r0 CVE-2026-25794 HIGH
imagemagick-libs 7.1.2.13-r0 CVE-2026-25795 HIGH
imagemagick-libs 7.1.2.13-r0 CVE-2026-25796 HIGH
imagemagick-libs 7.1.2.13-r0 CVE-2026-25798 HIGH
imagemagick-libs 7.1.2.13-r0 CVE-2026-25799 HIGH
imagemagick-libs 7.1.2.13-r0 CVE-2026-25965 HIGH
imagemagick-libs 7.1.2.13-r0 CVE-2026-25966 HIGH
imagemagick-libs 7.1.2.13-r0 CVE-2026-25967 HIGH
imagemagick-libs 7.1.2.13-r0 CVE-2026-25969 HIGH
imagemagick-libs 7.1.2.13-r0 CVE-2026-25970 HIGH
imagemagick-libs 7.1.2.13-r0 CVE-2026-25985 HIGH
imagemagick-libs 7.1.2.13-r0 CVE-2026-25988 HIGH
imagemagick-libs 7.1.2.13-r0 CVE-2026-25989 HIGH
imagemagick-libs 7.1.2.13-r0 CVE-2026-26066 HIGH
imagemagick-libs 7.1.2.13-r0 CVE-2026-26283 HIGH
imagemagick-libs 7.1.2.13-r0 CVE-2026-27798 HIGH
imagemagick-tiff 7.1.2.13-r0 CVE-2026-25897 CRITICAL
imagemagick-tiff 7.1.2.13-r0 CVE-2026-25898 CRITICAL
imagemagick-tiff 7.1.2.13-r0 CVE-2026-25968 CRITICAL
imagemagick-tiff 7.1.2.13-r0 CVE-2026-25971 CRITICAL
imagemagick-tiff 7.1.2.13-r0 CVE-2026-25983 CRITICAL
imagemagick-tiff 7.1.2.13-r0 CVE-2026-25986 CRITICAL
imagemagick-tiff 7.1.2.13-r0 CVE-2026-25987 CRITICAL
imagemagick-tiff 7.1.2.13-r0 CVE-2026-26284 CRITICAL
imagemagick-tiff 7.1.2.13-r0 CVE-2026-24481 HIGH
imagemagick-tiff 7.1.2.13-r0 CVE-2026-24485 HIGH
imagemagick-tiff 7.1.2.13-r0 CVE-2026-25794 HIGH
imagemagick-tiff 7.1.2.13-r0 CVE-2026-25795 HIGH
imagemagick-tiff 7.1.2.13-r0 CVE-2026-25796 HIGH
imagemagick-tiff 7.1.2.13-r0 CVE-2026-25798 HIGH
imagemagick-tiff 7.1.2.13-r0 CVE-2026-25799 HIGH
imagemagick-tiff 7.1.2.13-r0 CVE-2026-25965 HIGH
imagemagick-tiff 7.1.2.13-r0 CVE-2026-25966 HIGH
imagemagick-tiff 7.1.2.13-r0 CVE-2026-25967 HIGH
imagemagick-tiff 7.1.2.13-r0 CVE-2026-25969 HIGH
imagemagick-tiff 7.1.2.13-r0 CVE-2026-25970 HIGH
imagemagick-tiff 7.1.2.13-r0 CVE-2026-25985 HIGH
imagemagick-tiff 7.1.2.13-r0 CVE-2026-25988 HIGH
imagemagick-tiff 7.1.2.13-r0 CVE-2026-25989 HIGH
imagemagick-tiff 7.1.2.13-r0 CVE-2026-26066 HIGH
imagemagick-tiff 7.1.2.13-r0 CVE-2026-26283 HIGH
imagemagick-tiff 7.1.2.13-r0 CVE-2026-27798 HIGH
imagemagick-webp 7.1.2.13-r0 CVE-2026-25897 CRITICAL
imagemagick-webp 7.1.2.13-r0 CVE-2026-25898 CRITICAL
imagemagick-webp 7.1.2.13-r0 CVE-2026-25968 CRITICAL
imagemagick-webp 7.1.2.13-r0 CVE-2026-25971 CRITICAL
imagemagick-webp 7.1.2.13-r0 CVE-2026-25983 CRITICAL
imagemagick-webp 7.1.2.13-r0 CVE-2026-25986 CRITICAL
imagemagick-webp 7.1.2.13-r0 CVE-2026-25987 CRITICAL
imagemagick-webp 7.1.2.13-r0 CVE-2026-26284 CRITICAL
imagemagick-webp 7.1.2.13-r0 CVE-2026-24481 HIGH
imagemagick-webp 7.1.2.13-r0 CVE-2026-24485 HIGH
imagemagick-webp 7.1.2.13-r0 CVE-2026-25794 HIGH
imagemagick-webp 7.1.2.13-r0 CVE-2026-25795 HIGH
imagemagick-webp 7.1.2.13-r0 CVE-2026-25796 HIGH
imagemagick-webp 7.1.2.13-r0 CVE-2026-25798 HIGH
imagemagick-webp 7.1.2.13-r0 CVE-2026-25799 HIGH
imagemagick-webp 7.1.2.13-r0 CVE-2026-25965 HIGH
imagemagick-webp 7.1.2.13-r0 CVE-2026-25966 HIGH
imagemagick-webp 7.1.2.13-r0 CVE-2026-25967 HIGH
imagemagick-webp 7.1.2.13-r0 CVE-2026-25969 HIGH
imagemagick-webp 7.1.2.13-r0 CVE-2026-25970 HIGH
imagemagick-webp 7.1.2.13-r0 CVE-2026-25985 HIGH
imagemagick-webp 7.1.2.13-r0 CVE-2026-25988 HIGH
imagemagick-webp 7.1.2.13-r0 CVE-2026-25989 HIGH
imagemagick-webp 7.1.2.13-r0 CVE-2026-26066 HIGH
imagemagick-webp 7.1.2.13-r0 CVE-2026-26283 HIGH
imagemagick-webp 7.1.2.13-r0 CVE-2026-27798 HIGH
libecpg 18.1-r0 CVE-2026-2004 HIGH
libecpg 18.1-r0 CVE-2026-2005 HIGH
libecpg 18.1-r0 CVE-2026-2006 HIGH
libecpg 18.1-r0 CVE-2026-2007 HIGH
libecpg-dev 18.1-r0 CVE-2026-2004 HIGH
libecpg-dev 18.1-r0 CVE-2026-2005 HIGH
libecpg-dev 18.1-r0 CVE-2026-2006 HIGH
libecpg-dev 18.1-r0 CVE-2026-2007 HIGH
libheif 1.20.2-r1 CVE-2025-68431 HIGH
libpng 1.6.54-r0 CVE-2026-25646 HIGH
libpng-dev 1.6.54-r0 CVE-2026-25646 HIGH
libpq 18.1-r0 CVE-2026-2004 HIGH
libpq 18.1-r0 CVE-2026-2005 HIGH
libpq 18.1-r0 CVE-2026-2006 HIGH
libpq 18.1-r0 CVE-2026-2007 HIGH
libpq-dev 18.1-r0 CVE-2026-2004 HIGH
libpq-dev 18.1-r0 CVE-2026-2005 HIGH
libpq-dev 18.1-r0 CVE-2026-2006 HIGH
libpq-dev 18.1-r0 CVE-2026-2007 HIGH
postgresql18-dev 18.1-r0 CVE-2026-2004 HIGH
postgresql18-dev 18.1-r0 CVE-2026-2005 HIGH
postgresql18-dev 18.1-r0 CVE-2026-2006 HIGH
postgresql18-dev 18.1-r0 CVE-2026-2007 HIGH

Source Code Scan Results

🎉 No vulnerabilities found!

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 6, 2026

🔄 PHP-Retry Summary

Flaky tests detected across commits:

Commit 082a875 - 5 flaky tests
Test Retries Total Time Details
UsageTest::testDatabaseStatsCollectionsAPI 1 10.28s Logs
LegacyConsoleClientTest::testNotBetween 1 240.34s Logs
TablesDBConsoleClientTest::testCreateIndexes 1 242.55s Logs
TablesDBCustomClientTest::testCreateAttributes 1 240.87s Logs
TablesDBTransactionsCustomServerTest::testDeleteDocumentDuringTransaction 1 240.45s Logs
Commit f2ec1b1 - 3 flaky tests
Test Retries Total Time Details
UsageTest::testDatabaseStatsCollectionsAPI 1 10.24s Logs
TablesDBConsoleClientTest::testInvalidDocumentStructure 1 240.84s Logs
TablesDBTransactionsConsoleClientTest::testOperationsOnNonExistentDocuments 1 240.39s Logs
Commit 99dda0e - 1 flaky test
Test Retries Total Time Details
UsageTest::testDatabaseStatsCollectionsAPI 1 10.24s Logs
Commit 8b026d3 - 8 flaky tests
Test Retries Total Time Details
UsageTest::testDatabaseStatsCollectionsAPI 1 10.29s Logs
LegacyCustomClientTest::testInvalidDocumentStructure 1 241.49s Logs
TablesDBConsoleClientTest::testAttributeResponseModels 1 242.13s Logs
TablesDBCustomClientTest::testUpdateAttributeEnum 1 240.38s Logs
TablesDBCustomClientTest::testNotStartsWith 1 240.66s Logs
TablesDBCustomClientTest::testUpdatedBefore 1 240.88s Logs
TablesDBTransactionsCustomClientTest::testMixedSingleOperations 1 240.84s Logs
TablesDBTransactionsCustomClientTest::testBulkDeleteMatchingUpdatedDocuments 1 240.83s Logs
Commit f282618 - 2 flaky tests
Test Retries Total Time Details
UsageTest::testDatabaseStatsCollectionsAPI 1 10.22s Logs
TablesDBConsoleClientTest::testBulkOperators 1 240.55s Logs

Note: Flaky test results are tracked for the last 5 commits

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 6, 2026

✨ Benchmark results

  • Requests per second: 1,759
  • Requests with 200 status code: 316,721
  • P99 latency: 0.093729714

⚡ Benchmark Comparison

Metric This PR Latest version
RPS 1,759 1,128
200 316,721 203,170
P99 0.093729714 0.216119226

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR applies a performance optimization across 58+ files to replace full Document objects with sparse Document objects in updateDocument() calls, passing only the changed attributes. The updateDocument() method internally performs array_merge($old, $new), so sending fewer fields reduces overhead. Additionally, AGENTS.md is updated to document this pattern, and a CLAUDE.md file is added to reference AGENTS.md.

Changes:

  • Replaced full-document updateDocument() calls with sparse Documents across workers, HTTP modules, and controller files
  • Added "Performance Patterns" section to AGENTS.md documenting the optimization
  • Added CLAUDE.md referencing AGENTS.md for AI agent context

Reviewed changes

Copilot reviewed 59 out of 59 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
src/Appwrite/Platform/Workers/Builds.php Sparse document updates for deployment state; critical data loss bug introduced
src/Appwrite/Platform/Workers/Webhooks.php Sparse updates for logs/enabled and attempts reset
src/Appwrite/Platform/Workers/Migrations.php Replaced SET_TYPE_APPEND with manual array append + sparse update
src/Appwrite/Platform/Workers/Messaging.php Sparse update for message delivery fields
src/Appwrite/Platform/Workers/Certificates.php Sparse update for rule status/certificateId/logs
src/Appwrite/Platform/Tasks/Interval.php Sparse update for stale execution status
src/Appwrite/Platform/Modules/VCS/** Sparse updates for VCS repository/installation fields
src/Appwrite/Platform/Modules/Teams/** Sparse updates for membership/team fields
src/Appwrite/Platform/Modules/Sites/** Sparse updates for site/deployment/variable fields
src/Appwrite/Platform/Modules/Projects/** Sparse updates for project/team/label fields
src/Appwrite/Platform/Modules/Functions/** Sparse updates across deployments, variables, schedules
src/Appwrite/Platform/Modules/Databases/** Sparse updates for attribute/index status fields
src/Appwrite/Platform/Modules/Compute/Base.php Sparse updates for function/site latestDeployment fields
src/Appwrite/Platform/Modules/Account/** Sparse updates for MFA, session, and user fields
app/controllers/api/account.php Sparse updates across account endpoints
app/controllers/api/users.php Sparse updates for user CRUD operations
app/controllers/api/messaging.php Sparse update for topic fields
app/controllers/shared/api.php Sparse updates for accessedAt/cache fields
app/controllers/general.php Sparse update for project ping fields
app/init/resources.php Sparse updates for devKey/resourceToken accessedAt
app/realtime.php Sparse update for realtime stats document
AGENTS.md Added "Performance Patterns" documentation section
CLAUDE.md New file referencing AGENTS.md

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@ChiragAgg5k ChiragAgg5k force-pushed the optimize-update-document-calls branch from b4a2d52 to 082a875 Compare March 6, 2026 10:21
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (4)
src/Appwrite/Platform/Modules/Teams/Http/Teams/Name/Update.php (1)

67-71: Redundant setAttribute calls can be removed.

Lines 67-69 modify $team, but those mutations are never used—line 71 creates a fresh sparse Document with the same values and reassigns the result back to $team. The setAttribute calls are now dead code.

♻️ Proposed fix to remove redundant code
-        $team
-            ->setAttribute('name', $name)
-            ->setAttribute('search', implode(' ', [$teamId, $name]));
-
         $team = $dbForProject->updateDocument('teams', $team->getId(), new Document(['name' => $name, 'search' => implode(' ', [$teamId, $name])]));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Appwrite/Platform/Modules/Teams/Http/Teams/Name/Update.php` around lines
67 - 71, Remove the redundant mutations: delete the $team->setAttribute('name',
$name) and $team->setAttribute('search', ...) calls since they are not used
before $team is reassigned by dbForProject->updateDocument; keep the update call
that creates the new sparse Document (new Document(['name' => $name, 'search' =>
implode(' ', [$teamId, $name])])) and assign its result back to $team, ensuring
only the updateDocument invocation and Document construction remain.
src/Appwrite/Platform/Modules/Functions/Http/Variables/Update.php (1)

103-108: Build each sparse patch once and reuse it.

These blocks duplicate the updated field list in two places: first via setAttribute(...), then again in new Document([...]). That makes future edits easy to miss in one path and can desync the in-memory response object from the persisted payload. A small $variablePatch / $schedulePatch array reused for both steps would keep them aligned.

♻️ Example refactor
-        $variable
-            ->setAttribute('key', $key)
-            ->setAttribute('value', $value ?? $variable->getAttribute('value'))
-            ->setAttribute('secret', $secret ?? $variable->getAttribute('secret'))
-            ->setAttribute('search', implode(' ', [$variableId, $function->getId(), $key, 'function']));
+        $variablePatch = [
+            'key' => $key,
+            'value' => $value ?? $variable->getAttribute('value'),
+            'secret' => $secret ?? $variable->getAttribute('secret'),
+            'search' => implode(' ', [$variableId, $function->getId(), $key, 'function']),
+        ];
+
+        foreach ($variablePatch as $attribute => $patchValue) {
+            $variable->setAttribute($attribute, $patchValue);
+        }

         try {
-            $dbForProject->updateDocument('variables', $variable->getId(), new Document([
-                'key' => $key,
-                'value' => $value ?? $variable->getAttribute('value'),
-                'secret' => $secret ?? $variable->getAttribute('secret'),
-                'search' => implode(' ', [$variableId, $function->getId(), $key, 'function']),
-            ]));
+            $dbForProject->updateDocument('variables', $variable->getId(), new Document($variablePatch));
         } catch (DuplicateException $th) {
             throw new Exception(Exception::VARIABLE_ALREADY_EXISTS);
         }

Apply the same pattern to the schedule update block.

Also applies to: 122-126

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Appwrite/Platform/Modules/Functions/Http/Variables/Update.php` around
lines 103 - 108, The variable update duplicates the fields in setAttribute(...)
and in the new Document([...]) passed to $dbForProject->updateDocument; create a
single associative array (e.g. $variablePatch) containing 'key', 'value',
'secret', and 'search' (constructed the same way as currently done with
$variableId, $function->getId(), $key, 'function'), then pass that array to new
Document($variablePatch) for the DB update and call $variable->setAttribute(...)
with the same $variablePatch values so both in-memory and persisted
representations come from the same source; apply the same pattern for the
schedule update block (use $schedulePatch) to avoid duplication and keep them in
sync.
src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.php (1)

150-154: Minor: DateTime::now() called twice may produce slightly different timestamps.

Line 152 sets mfaUpdatedAt to DateTime::now(), but line 154 creates another new Document with a fresh DateTime::now() call. While typically negligible (same second), consider reusing the value for consistency:

♻️ Suggested improvement
+        $mfaUpdatedAt = DateTime::now();
         $session
             ->setAttribute('factors', $factors)
-            ->setAttribute('mfaUpdatedAt', DateTime::now());
+            ->setAttribute('mfaUpdatedAt', $mfaUpdatedAt);

-        $dbForProject->updateDocument('sessions', $session->getId(), new Document(['factors' => $factors, 'mfaUpdatedAt' => DateTime::now()]));
+        $dbForProject->updateDocument('sessions', $session->getId(), new Document(['factors' => $factors, 'mfaUpdatedAt' => $mfaUpdatedAt]));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.php`
around lines 150 - 154, The code calls DateTime::now() twice causing potential
minor timestamp drift; capture the current timestamp once into a variable (e.g.
$now) and reuse it for both $session->setAttribute('mfaUpdatedAt', ...) and the
new Document(['mfaUpdatedAt' => ...]) used in $dbForProject->updateDocument to
ensure the session attribute and stored document share the identical timestamp;
update references to DateTime::now() in the Account MFA update block accordingly
(affecting $session, setAttribute, and the Document passed to updateDocument).
AGENTS.md (1)

54-78: Add an exception for atomic/read-modify-write updates.

Sparse documents are a good perf optimization, but they do not make counters or append-style array updates concurrency-safe. Without that caveat, this guidance is likely to be copied into places where concurrent requests can still drop writes.

📝 Suggested doc tweak
 **Exceptions:**
+- Read/modify/write updates on counters or append-style arrays. Prefer atomic helpers
+  (for example `increaseDocumentAttribute()`) or another concurrency-safe pattern.
 - Migration files (need full document updates by design)
 - Cases already using `array_merge()` with `getArrayCopy()`
 - Updates with 6+ attributes changing simultaneously (marginal benefit)
 - Complex nested relationship logic where full document state is required
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AGENTS.md` around lines 54 - 78, Add an exception to the guidance that sparse
Document updates are not appropriate for atomic/read-modify-write operations
(e.g., counters or append-style array updates) where concurrency-safety is
required; reference updateDocument(), Document, array_merge(), and
getArrayCopy() and state that in these cases callers should use DB-provided
atomic operations or a full-document update/locking strategy instead of a sparse
Document to ensure correctness under concurrent requests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/controllers/api/account.php`:
- Around line 291-294: The update currently always writes both
'emailVerification' and 'phoneVerification' into $dbForProject->updateDocument
which can set phoneVerification to null; instead, construct the Document payload
to include only the verification flags that are present/changed on the $user
(check via $user->hasAttribute(...) or by testing getAttribute(...) !==
null/using whatever "dirty" API exists), e.g. add 'emailVerification' only if
present/changed and add 'phoneVerification' only if present/changed, then call
$dbForProject->updateDocument('users', $user->getId(), new Document($payload))
only when $payload is non-empty so you don't overwrite sparse fields
unintentionally.
- Around line 4521-4525: The update is always writing the 'expired' attribute
from $target back to the DB which can persist null/incorrect values because
createPushTarget() doesn't initialize it; change the updateDocument call (and
the code that prepares its payload) to only include 'expired' when the incoming
request actually supplies or changes that value — e.g., build a $data array with
'identifier' and 'name', then if $target->getAttribute('expired') is set/ !==
null or differs from the stored value add 'expired' to $data before calling
$dbForProject->updateDocument('targets', $target->getId(), new Document($data));
ensure updateDocument and createPushTarget() behavior remain compatible.

In
`@src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php`:
- Around line 122-127: The code in Create.php uses new Document(...) inside the
updateDocument call (in the Duplicate/Create deployment flow) but lacks the
import for Utopia\Database\Document; add a use statement for
Utopia\Database\Document near the other imports (so the instantiation in the
updateDocument call succeeds) and ensure there are no naming conflicts with any
existing Document classes in that file.

In `@src/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.php`:
- Around line 122-125: The code calls new Document([...]) inside the
$authorization->skip closure when calling
$dbForPlatform->updateDocument('schedules', $schedule->getId(), ...) but the
Utopia\Database\Document class is not imported; add the import for
Utopia\Database\Document alongside the other Utopia\Database imports at the top
of the file (or replace new Document(...) with the fully-qualified
\Utopia\Database\Document) so the instantiation in the Delete.php schedule
update succeeds without a fatal "class not found" error.

In `@src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php`:
- Around line 286-291: The update call reassigns $function to a sparse Document
with only the four passed fields, losing other attributes like installationId;
fix by passing a merged array of the current full document and the sparse
updates to updateDocument — e.g., use array_merge($function->getArrayCopy(), [
'scheduleId' => ..., 'scheduleInternalId' => ..., 'repositoryId' => ...,
'repositoryInternalId' => ... ]) when creating the new Document so $function
retains its full state after updateDocument and subsequent calls to
$function->getAttribute(...) work correctly.

In `@src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php`:
- Around line 93-96: Add the missing import for the Utopia Database Document
class so new Document([...]) resolves; update the top of Delete.php to import
Utopia\Database\Document. Locate the block where $authorization->skip calls
$dbForPlatform->updateDocument('schedules', $schedule->getId(), new
Document([...])) and ensure the Document class is imported so instantiating
Document does not cause a "Class not found" fatal error.

In `@src/Appwrite/Platform/Modules/Sites/Http/Variables/Delete.php`:
- Around line 79-81: The call new Document([...]) in Delete.php (inside the
updateDocument call that sets 'live' => false) is resolving to the current
namespace and causing a fatal error; fix it by importing the correct Document
class with a use statement (e.g., add the correct Document FQCN at the top of
the file) or by fully-qualifying the class in-place (e.g., replace new Document
with new \Fully\Qualified\DocumentClassName) so that the Document referenced is
the database Document type used by updateDocument.

In `@src/Appwrite/Platform/Modules/Sites/Http/Variables/Update.php`:
- Around line 96-101: The updateDocument calls in the Update class are
instantiating Document (new Document(...)) but the Utopia\Database\Document
class is not imported; add a top-level import "use Utopia\Database\Document;" to
the Update class file so PHP can resolve Document, and ensure both places where
updateDocument is called (the occurrences that create new Document with
key/value/secret/search — the call using
$dbForProject->updateDocument('variables', $variable->getId(), new
Document([...])) and the similar second update call) continue to use the
imported Document class.

In `@src/Appwrite/Platform/Workers/Migrations.php`:
- Around line 574-578: The code is directly calling
$this->dbForProject->updateDocument(...) to append the oversize-export error,
bypassing updateMigrationDocument() so Realtime events and the in-memory
$migration are not updated; change the flow to append the new error to
$migration->getAttribute('errors', []) and then call
updateMigrationDocument(...) (the existing method that handles persisting
changes, broadcasting Realtime updates and updating the in-memory migration)
instead of calling dbForProject->updateDocument directly, ensuring the migration
state and clients receive the error update.

---

Nitpick comments:
In `@AGENTS.md`:
- Around line 54-78: Add an exception to the guidance that sparse Document
updates are not appropriate for atomic/read-modify-write operations (e.g.,
counters or append-style array updates) where concurrency-safety is required;
reference updateDocument(), Document, array_merge(), and getArrayCopy() and
state that in these cases callers should use DB-provided atomic operations or a
full-document update/locking strategy instead of a sparse Document to ensure
correctness under concurrent requests.

In
`@src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.php`:
- Around line 150-154: The code calls DateTime::now() twice causing potential
minor timestamp drift; capture the current timestamp once into a variable (e.g.
$now) and reuse it for both $session->setAttribute('mfaUpdatedAt', ...) and the
new Document(['mfaUpdatedAt' => ...]) used in $dbForProject->updateDocument to
ensure the session attribute and stored document share the identical timestamp;
update references to DateTime::now() in the Account MFA update block accordingly
(affecting $session, setAttribute, and the Document passed to updateDocument).

In `@src/Appwrite/Platform/Modules/Functions/Http/Variables/Update.php`:
- Around line 103-108: The variable update duplicates the fields in
setAttribute(...) and in the new Document([...]) passed to
$dbForProject->updateDocument; create a single associative array (e.g.
$variablePatch) containing 'key', 'value', 'secret', and 'search' (constructed
the same way as currently done with $variableId, $function->getId(), $key,
'function'), then pass that array to new Document($variablePatch) for the DB
update and call $variable->setAttribute(...) with the same $variablePatch values
so both in-memory and persisted representations come from the same source; apply
the same pattern for the schedule update block (use $schedulePatch) to avoid
duplication and keep them in sync.

In `@src/Appwrite/Platform/Modules/Teams/Http/Teams/Name/Update.php`:
- Around line 67-71: Remove the redundant mutations: delete the
$team->setAttribute('name', $name) and $team->setAttribute('search', ...) calls
since they are not used before $team is reassigned by
dbForProject->updateDocument; keep the update call that creates the new sparse
Document (new Document(['name' => $name, 'search' => implode(' ', [$teamId,
$name])])) and assign its result back to $team, ensuring only the updateDocument
invocation and Document construction remain.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9ebe00f2-a2bc-4a1a-9f49-597b2258229d

📥 Commits

Reviewing files that changed from the base of the PR and between 2306072 and 082a875.

📒 Files selected for processing (59)
  • AGENTS.md
  • CLAUDE.md
  • app/controllers/api/account.php
  • app/controllers/api/messaging.php
  • app/controllers/api/users.php
  • app/controllers/general.php
  • app/controllers/shared/api.php
  • app/init/resources.php
  • app/realtime.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Authenticators/Update.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/RecoveryCodes/Create.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/RecoveryCodes/Update.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Update.php
  • src/Appwrite/Platform/Modules/Compute/Base.php
  • src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Delete.php
  • src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Delete.php
  • src/Appwrite/Platform/Modules/Databases/Workers/Databases.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Delete.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Status/Update.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.php
  • src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php
  • src/Appwrite/Platform/Modules/Functions/Http/Functions/Deployment/Update.php
  • src/Appwrite/Platform/Modules/Functions/Http/Variables/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Variables/Delete.php
  • src/Appwrite/Platform/Modules/Functions/Http/Variables/Update.php
  • src/Appwrite/Platform/Modules/Functions/Workers/Builds.php
  • src/Appwrite/Platform/Modules/Projects/Http/DevKeys/Update.php
  • src/Appwrite/Platform/Modules/Projects/Http/Projects/Labels/Update.php
  • src/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Delete.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Duplicate/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Status/Update.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Sites/Deployment/Update.php
  • src/Appwrite/Platform/Modules/Sites/Http/Variables/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Variables/Delete.php
  • src/Appwrite/Platform/Modules/Sites/Http/Variables/Update.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Create.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Delete.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Status/Update.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Update.php
  • src/Appwrite/Platform/Modules/Teams/Http/Teams/Name/Update.php
  • src/Appwrite/Platform/Modules/VCS/Http/GitHub/Authorize/External/Update.php
  • src/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.php
  • src/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.php
  • src/Appwrite/Platform/Modules/VCS/Http/GitHub/Events/Create.php
  • src/Appwrite/Platform/Modules/VCS/Http/Installations/Repositories/Create.php
  • src/Appwrite/Platform/Tasks/Interval.php
  • src/Appwrite/Platform/Workers/Certificates.php
  • src/Appwrite/Platform/Workers/Messaging.php
  • src/Appwrite/Platform/Workers/Migrations.php
  • src/Appwrite/Platform/Workers/Webhooks.php

@ChiragAgg5k ChiragAgg5k force-pushed the optimize-update-document-calls branch 2 times, most recently from 4e84a2e to f2ec1b1 Compare March 6, 2026 10:55
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (4)
src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php (1)

93-96: ⚠️ Potential issue | 🔴 Critical

Add the missing Utopia\Database\Document import.

Line 93 instantiates new Document([...]), but this file never imports Utopia\Database\Document. In PHP that resolves to the current namespace, so deleting a function with a schedule will fatal at runtime instead of updating the schedule.

🐛 Proposed fix
 use Appwrite\Utopia\Response;
 use Utopia\Database\Database;
 use Utopia\Database\DateTime;
+use Utopia\Database\Document;
 use Utopia\Database\Validator\Authorization;
 use Utopia\Database\Validator\UID;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php` around
lines 93 - 96, The code instantiates new Document(...) within Delete.php (see
the call to $dbForPlatform->updateDocument and the new Document([...])
expression) but lacks the Utopia\Database\Document import; add a "use
Utopia\Database\Document;" import at the top of the file so new Document
resolves to the correct class and the schedule update won’t fatal at runtime.
src/Appwrite/Platform/Workers/Migrations.php (1)

574-578: ⚠️ Potential issue | 🟠 Major

Use updateMigrationDocument() in the export-limit failure path.

Line 576 writes errors straight to the database, which skips the Realtime update and leaves the in-memory $migration stale. Clients can miss this failure after already seeing the earlier completed event.

🔧 Proposed fix
                 $errors = $migration->getAttribute('errors', []);
                 $errors[] = json_encode(['code' => 0, 'message' => $message]);
-                $this->dbForProject->updateDocument('migrations', $migration->getId(), new Document([
-                    'errors' => $errors,
-                ]));
+                $migration->setAttribute('errors', $errors);
+                $this->updateMigrationDocument($migration, $project, $queueForRealtime);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Appwrite/Platform/Workers/Migrations.php` around lines 574 - 578, The
migration failure branch currently writes errors directly with
$this->dbForProject->updateDocument('migrations', $migration->getId(), ...)
which bypasses the centralized update path and leaves the in-memory $migration
and Realtime listeners stale; replace that direct update with a call to the
existing updateMigrationDocument(...) helper (passing the migration id and the
updated 'errors' payload or merging via getAttribute('errors', []) then adding
the new JSON error) so the document is updated through the proper flow and
triggers Realtime/in-memory sync.
src/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.php (1)

122-125: ⚠️ Potential issue | 🔴 Critical

Add the missing Document import.

Line 122 instantiates new Document(...), but Utopia\Database\Document is not imported in this file, so this branch will fail at runtime.

🔧 Proposed fix
 use Utopia\Database\Database;
 use Utopia\Database\DateTime;
+use Utopia\Database\Document;
 use Utopia\Database\Query;
#!/bin/bash
set -euo pipefail

file="src/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.php"

echo "=== Imports ==="
sed -n '1,20p' "$file"

echo
echo "=== Document instantiations ==="
rg -n '\bnew Document\s*\(' "$file"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.php` around
lines 122 - 125, The file instantiates new Document in the $authorization->skip
callback passed to $dbForPlatform->updateDocument but never imports
Utopia\Database\Document; add a use statement importing Utopia\Database\Document
at the top of the file so the new Document([...]) call resolves. Locate the
instantiation in the Delete class where $authorization->skip(fn () =>
$dbForPlatform->updateDocument('schedules', $schedule->getId(), new
Document([...]))); and add the corresponding use Utopia\Database\Document;
import alongside the other imports.
src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php (1)

122-127: ⚠️ Potential issue | 🔴 Critical

Missing Document import will cause fatal error.

The code uses new Document([...]) but Utopia\Database\Document is not imported. This will cause a runtime error.

🐛 Proposed fix: Add missing import

Add to the imports section (around line 12):

 use Utopia\Database\Database;
+use Utopia\Database\Document;
 use Utopia\Database\Helpers\ID;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php`
around lines 122 - 127, The file is instantiating Utopia\Database\Document via
new Document(...) but the class isn't imported, causing a fatal error; add a use
statement for Utopia\Database\Document in the imports block at the top of
Create.php so the Document symbol resolves (the code paths using updateDocument
in the Create class and the new Document([...]) call will then work).
🧹 Nitpick comments (6)
src/Appwrite/Platform/Modules/Projects/Http/Projects/Labels/Update.php (1)

81-84: Redundant setAttribute call.

Line 82 modifies $project, but line 84 immediately reassigns $project to the return value of updateDocument(). Since the sparse Document already contains the labels field, the setAttribute() call has no effect on the final outcome and can be removed.

♻️ Proposed fix
         $labels = (array) \array_values(\array_unique($labels));
-        $project->setAttribute('labels', $labels);

         $project = $dbForPlatform->updateDocument('projects', $project->getId(), new Document(['labels' => $labels]));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Appwrite/Platform/Modules/Projects/Http/Projects/Labels/Update.php`
around lines 81 - 84, The call to $project->setAttribute('labels', $labels) is
redundant because $project is immediately overwritten by the result of
$dbForPlatform->updateDocument('projects', $project->getId(), new
Document(['labels' => $labels])); remove the $project->setAttribute('labels',
$labels) line to avoid dead mutation and rely on updateDocument(...) with the
sparse Document(['labels' => $labels]) to persist the change.
src/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.php (2)

89-108: Remove dead setAttribute calls from loops.

Lines 90, 98, and 106 modify local variables that are never used afterward—the sparse Documents passed to updateDocument() already contain the updated values. Remove these dead lines.

♻️ Proposed fix
         $installations = $dbForPlatform->find('installations', [
             Query::equal('projectInternalId', [$project->getSequence()]),
         ]);
         foreach ($installations as $installation) {
-            $installation->setAttribute('$permissions', $permissions);
             $dbForPlatform->updateDocument('installations', $installation->getId(), new Document(['$permissions' => $permissions]));
         }

         $repositories = $dbForPlatform->find('repositories', [
             Query::equal('projectInternalId', [$project->getSequence()]),
         ]);
         foreach ($repositories as $repository) {
-            $repository->setAttribute('$permissions', $permissions);
             $dbForPlatform->updateDocument('repositories', $repository->getId(), new Document(['$permissions' => $permissions]));
         }

         $vcsComments = $dbForPlatform->find('vcsComments', [
             Query::equal('projectInternalId', [$project->getSequence()]),
         ]);
         foreach ($vcsComments as $vcsComment) {
-            $vcsComment->setAttribute('$permissions', $permissions);
             $dbForPlatform->updateDocument('vcsComments', $vcsComment->getId(), new Document(['$permissions' => $permissions]));
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.php` around
lines 89 - 108, Remove the dead local mutations by deleting the calls to
setAttribute(...) inside the three loops that iterate $installations,
$repositories, and $vcsComments; the calls to
updateDocument('installations'|'repositories'|'vcsComments', ..., new
Document(['$permissions' => $permissions])) already persist the change so remove
the unused $installation->setAttribute('$permissions', $permissions),
$repository->setAttribute('$permissions', $permissions), and
$vcsComment->setAttribute('$permissions', $permissions) lines to avoid needless
local updates.

76-84: Remove dead code from old update pattern.

The setAttribute calls on lines 76-79 are now dead code since $project is immediately overwritten by the return value of updateDocument(). These lines should be removed for clarity.

♻️ Proposed fix
         $permissions = $this->getPermissions($teamId, $projectId);

-        $project
-            ->setAttribute('teamId', $teamId)
-            ->setAttribute('teamInternalId', $team->getSequence())
-            ->setAttribute('$permissions', $permissions);
         $project = $dbForPlatform->updateDocument('projects', $project->getId(), new Document([
             'teamId' => $teamId,
             'teamInternalId' => $team->getSequence(),
             '$permissions' => $permissions,
         ]));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.php` around
lines 76 - 84, The three setAttribute calls on $project (setAttribute('teamId',
...), setAttribute('teamInternalId', ...), setAttribute('$permissions', ...))
are dead because $project is immediately reassigned by
$dbForPlatform->updateDocument(...); remove these redundant setAttribute lines
and rely on the updateDocument call that constructs the new Document({ 'teamId'
=> ..., 'teamInternalId' => ..., '$permissions' => ... }) to apply the changes.
src/Appwrite/Platform/Tasks/Interval.php (1)

162-164: Remove the redundant $execution mutations here.

Line 164 already writes the sparse payload, so the setAttribute() calls on Lines 162-163 no longer affect persistence and just keep part of the old full-document work around.

♻️ Proposed cleanup
                     foreach ($staleExecutions as $execution) {
-                        $execution->setAttribute('status', 'failed');
-                        $execution->setAttribute('errors', 'Execution timed out');
-                        $dbForProject->updateDocument('executions', $execution->getId(), new Document(['status' => 'failed', 'errors' => 'Execution timed out']));
+                        $dbForProject->updateDocument(
+                            'executions',
+                            $execution->getId(),
+                            new Document([
+                                'status' => 'failed',
+                                'errors' => 'Execution timed out',
+                            ])
+                        );
                     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Appwrite/Platform/Tasks/Interval.php` around lines 162 - 164, Remove the
redundant in-memory mutations on $execution: delete the two calls to
$execution->setAttribute('status', 'failed') and
$execution->setAttribute('errors', 'Execution timed out') and keep the single
sparse-patch persistence call $dbForProject->updateDocument('executions',
$execution->getId(), new Document(['status' => 'failed', 'errors' => 'Execution
timed out'])); so only the updateDocument persists the failure state.
src/Appwrite/Platform/Modules/VCS/Http/GitHub/Authorize/External/Update.php (1)

100-103: Remove the leftover full-document mutation.

Line 101 is redundant now that Line 103 persists providerPullRequestIds via a sparse Document and returns the updated repository. Dropping the intermediate setAttribute() keeps this path fully aligned with the optimization pattern introduced by the PR.

♻️ Suggested cleanup
 $providerPullRequestIds = \array_unique(\array_merge($repository->getAttribute('providerPullRequestIds', []), [$providerPullRequestId]));
-$repository = $repository->setAttribute('providerPullRequestIds', $providerPullRequestIds);
-
 $repository = $authorization->skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), new Document(['providerPullRequestIds' => $providerPullRequestIds])));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Appwrite/Platform/Modules/VCS/Http/GitHub/Authorize/External/Update.php`
around lines 100 - 103, The intermediate full-document mutation using
$repository->setAttribute('providerPullRequestIds', $providerPullRequestIds) is
redundant because $authorization->skip(fn () =>
$dbForPlatform->updateDocument('repositories', $repository->getId(), new
Document(['providerPullRequestIds' => $providerPullRequestIds]))) already
persists and returns the updated repository; remove the $repository =
$repository->setAttribute(...) line and keep only the array_unique merge into
$providerPullRequestIds and the authorization->skip updateDocument call
(preserving $providerPullRequestIds, $repository->getId(), Document).
src/Appwrite/Platform/Workers/Webhooks.php (1)

190-193: Redundant local modification on line 190.

The $webhook->setAttribute('attempts', 0) call is now unnecessary since the sparse Document already contains the hardcoded value 'attempts' => 0, and the local $webhook object's attempts attribute is never read after this point.

♻️ Suggested cleanup
         } else {
-            $webhook->setAttribute('attempts', 0); // Reset attempts on success
             $dbForPlatform->updateDocument('webhooks', $webhook->getId(), new Document([
                 'attempts' => 0,
             ]));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Appwrite/Platform/Workers/Webhooks.php` around lines 190 - 193, Remove
the redundant local modification: delete the call to
$webhook->setAttribute('attempts', 0) since you already persist attempts => 0
via $dbForPlatform->updateDocument('webhooks', $webhook->getId(), new
Document(['attempts' => 0])), and the $webhook object's attempts attribute is
not used after this point; leave the updateDocument call intact and ensure no
subsequent code expects the local $webhook to reflect the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/controllers/api/messaging.php`:
- Around line 2695-2698: The PATCH currently always writes both 'name' and
'subscribe' by creating a new Document from the existing $topic, risking stale
overwrites; instead build a sparse payload using only request-touched params
(e.g. check the incoming request body or param getters for 'name' and
'subscribe'), add each non-null/isset field to an $updateFields array, and call
$dbForProject->updateDocument('topics', $topicId, new Document($updateFields));
ensure you reference the same $topicId and $dbForProject/updateDocument call but
do not read values from the mutated $topic when constructing the Document.

In `@app/controllers/api/users.php`:
- Around line 1495-1498: The patch updates only the DB so the in-memory embedded
target ($oldTarget inside $user->targets) remains stale; before returning $user,
update the embedded Document or replace it in $user->targets to reflect the new
identifier (e.g., call $oldTarget->set('identifier', $email) or assign a new
Document with the updated identifier into the array) immediately after calling
$dbForProject->updateDocument('targets', $oldTarget->getId(), ...); apply the
same in-memory sync where the other target-update block occurs (the similar
update around the other mentioned location).

---

Duplicate comments:
In
`@src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php`:
- Around line 122-127: The file is instantiating Utopia\Database\Document via
new Document(...) but the class isn't imported, causing a fatal error; add a use
statement for Utopia\Database\Document in the imports block at the top of
Create.php so the Document symbol resolves (the code paths using updateDocument
in the Create class and the new Document([...]) call will then work).

In `@src/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.php`:
- Around line 122-125: The file instantiates new Document in the
$authorization->skip callback passed to $dbForPlatform->updateDocument but never
imports Utopia\Database\Document; add a use statement importing
Utopia\Database\Document at the top of the file so the new Document([...]) call
resolves. Locate the instantiation in the Delete class where
$authorization->skip(fn () => $dbForPlatform->updateDocument('schedules',
$schedule->getId(), new Document([...]))); and add the corresponding use
Utopia\Database\Document; import alongside the other imports.

In `@src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php`:
- Around line 93-96: The code instantiates new Document(...) within Delete.php
(see the call to $dbForPlatform->updateDocument and the new Document([...])
expression) but lacks the Utopia\Database\Document import; add a "use
Utopia\Database\Document;" import at the top of the file so new Document
resolves to the correct class and the schedule update won’t fatal at runtime.

In `@src/Appwrite/Platform/Workers/Migrations.php`:
- Around line 574-578: The migration failure branch currently writes errors
directly with $this->dbForProject->updateDocument('migrations',
$migration->getId(), ...) which bypasses the centralized update path and leaves
the in-memory $migration and Realtime listeners stale; replace that direct
update with a call to the existing updateMigrationDocument(...) helper (passing
the migration id and the updated 'errors' payload or merging via
getAttribute('errors', []) then adding the new JSON error) so the document is
updated through the proper flow and triggers Realtime/in-memory sync.

---

Nitpick comments:
In `@src/Appwrite/Platform/Modules/Projects/Http/Projects/Labels/Update.php`:
- Around line 81-84: The call to $project->setAttribute('labels', $labels) is
redundant because $project is immediately overwritten by the result of
$dbForPlatform->updateDocument('projects', $project->getId(), new
Document(['labels' => $labels])); remove the $project->setAttribute('labels',
$labels) line to avoid dead mutation and rely on updateDocument(...) with the
sparse Document(['labels' => $labels]) to persist the change.

In `@src/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.php`:
- Around line 89-108: Remove the dead local mutations by deleting the calls to
setAttribute(...) inside the three loops that iterate $installations,
$repositories, and $vcsComments; the calls to
updateDocument('installations'|'repositories'|'vcsComments', ..., new
Document(['$permissions' => $permissions])) already persist the change so remove
the unused $installation->setAttribute('$permissions', $permissions),
$repository->setAttribute('$permissions', $permissions), and
$vcsComment->setAttribute('$permissions', $permissions) lines to avoid needless
local updates.
- Around line 76-84: The three setAttribute calls on $project
(setAttribute('teamId', ...), setAttribute('teamInternalId', ...),
setAttribute('$permissions', ...)) are dead because $project is immediately
reassigned by $dbForPlatform->updateDocument(...); remove these redundant
setAttribute lines and rely on the updateDocument call that constructs the new
Document({ 'teamId' => ..., 'teamInternalId' => ..., '$permissions' => ... }) to
apply the changes.

In `@src/Appwrite/Platform/Modules/VCS/Http/GitHub/Authorize/External/Update.php`:
- Around line 100-103: The intermediate full-document mutation using
$repository->setAttribute('providerPullRequestIds', $providerPullRequestIds) is
redundant because $authorization->skip(fn () =>
$dbForPlatform->updateDocument('repositories', $repository->getId(), new
Document(['providerPullRequestIds' => $providerPullRequestIds]))) already
persists and returns the updated repository; remove the $repository =
$repository->setAttribute(...) line and keep only the array_unique merge into
$providerPullRequestIds and the authorization->skip updateDocument call
(preserving $providerPullRequestIds, $repository->getId(), Document).

In `@src/Appwrite/Platform/Tasks/Interval.php`:
- Around line 162-164: Remove the redundant in-memory mutations on $execution:
delete the two calls to $execution->setAttribute('status', 'failed') and
$execution->setAttribute('errors', 'Execution timed out') and keep the single
sparse-patch persistence call $dbForProject->updateDocument('executions',
$execution->getId(), new Document(['status' => 'failed', 'errors' => 'Execution
timed out'])); so only the updateDocument persists the failure state.

In `@src/Appwrite/Platform/Workers/Webhooks.php`:
- Around line 190-193: Remove the redundant local modification: delete the call
to $webhook->setAttribute('attempts', 0) since you already persist attempts => 0
via $dbForPlatform->updateDocument('webhooks', $webhook->getId(), new
Document(['attempts' => 0])), and the $webhook object's attempts attribute is
not used after this point; leave the updateDocument call intact and ensure no
subsequent code expects the local $webhook to reflect the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0c692097-42c8-460b-8391-336704001820

📥 Commits

Reviewing files that changed from the base of the PR and between 082a875 and 4e84a2e.

📒 Files selected for processing (59)
  • AGENTS.md
  • CLAUDE.md
  • app/controllers/api/account.php
  • app/controllers/api/messaging.php
  • app/controllers/api/users.php
  • app/controllers/general.php
  • app/controllers/shared/api.php
  • app/init/resources.php
  • app/realtime.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Authenticators/Update.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/RecoveryCodes/Create.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/RecoveryCodes/Update.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Update.php
  • src/Appwrite/Platform/Modules/Compute/Base.php
  • src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Delete.php
  • src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Delete.php
  • src/Appwrite/Platform/Modules/Databases/Workers/Databases.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Delete.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Status/Update.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.php
  • src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php
  • src/Appwrite/Platform/Modules/Functions/Http/Functions/Deployment/Update.php
  • src/Appwrite/Platform/Modules/Functions/Http/Variables/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Variables/Delete.php
  • src/Appwrite/Platform/Modules/Functions/Http/Variables/Update.php
  • src/Appwrite/Platform/Modules/Functions/Workers/Builds.php
  • src/Appwrite/Platform/Modules/Projects/Http/DevKeys/Update.php
  • src/Appwrite/Platform/Modules/Projects/Http/Projects/Labels/Update.php
  • src/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Delete.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Duplicate/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Status/Update.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Sites/Deployment/Update.php
  • src/Appwrite/Platform/Modules/Sites/Http/Variables/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Variables/Delete.php
  • src/Appwrite/Platform/Modules/Sites/Http/Variables/Update.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Create.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Delete.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Status/Update.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Update.php
  • src/Appwrite/Platform/Modules/Teams/Http/Teams/Name/Update.php
  • src/Appwrite/Platform/Modules/VCS/Http/GitHub/Authorize/External/Update.php
  • src/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.php
  • src/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.php
  • src/Appwrite/Platform/Modules/VCS/Http/GitHub/Events/Create.php
  • src/Appwrite/Platform/Modules/VCS/Http/Installations/Repositories/Create.php
  • src/Appwrite/Platform/Tasks/Interval.php
  • src/Appwrite/Platform/Workers/Certificates.php
  • src/Appwrite/Platform/Workers/Messaging.php
  • src/Appwrite/Platform/Workers/Migrations.php
  • src/Appwrite/Platform/Workers/Webhooks.php
🚧 Files skipped from review as they are similar to previous changes (22)
  • src/Appwrite/Platform/Modules/VCS/Http/Installations/Repositories/Create.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Update.php
  • AGENTS.md
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Authenticators/Update.php
  • app/controllers/general.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php
  • src/Appwrite/Platform/Modules/VCS/Http/GitHub/Events/Create.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Create.php
  • src/Appwrite/Platform/Workers/Messaging.php
  • src/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Status/Update.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Delete.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Duplicate/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Variables/Delete.php
  • app/realtime.php
  • CLAUDE.md
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.php
  • src/Appwrite/Platform/Modules/Sites/Http/Variables/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.php
  • src/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Status/Update.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/RecoveryCodes/Update.php

@ChiragAgg5k ChiragAgg5k force-pushed the optimize-update-document-calls branch from f2ec1b1 to 99dda0e Compare March 6, 2026 11:15
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (4)
src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php (1)

93-96: ⚠️ Potential issue | 🔴 Critical

Import Utopia\Database\Document before using new Document(...).

Line 93 introduces new Document([...]), but this file does not import Utopia\Database\Document. In this namespace, PHP will resolve Document to Appwrite\Platform\Modules\Functions\Http\Functions\Document, which will fail at runtime.

🐛 Proposed fix
 use Utopia\Database\Database;
 use Utopia\Database\DateTime;
+use Utopia\Database\Document;
 use Utopia\Database\Validator\Authorization;
 use Utopia\Database\Validator\UID;
#!/bin/bash
set -euo pipefail

file="src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php"

echo "== Imports =="
sed -n '1,20p' "$file"

echo
echo "== Document instantiation =="
rg -n 'new Document\(' "$file"

echo
echo "== Document import =="
rg -n '^use Utopia\\Database\\Document;' "$file" || true
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php` around
lines 93 - 96, The code instantiates new Document(...) without importing it,
causing PHP to resolve it to the current namespace; fix by importing
Utopia\Database\Document at the top of the file (add use
Utopia\Database\Document;) or alternatively fully-qualify the instantiation as
new \Utopia\Database\Document([...]) where $authorization->skip(...) calls
$dbForPlatform->updateDocument('schedules', $schedule->getId(), new
Document([...])) so the correct class is used.
src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php (1)

122-127: ⚠️ Potential issue | 🔴 Critical

Import Utopia\Database\Document before this sparse update.

Line 122 instantiates new Document(...), but this file does not import the database Document class. In this namespace, that resolves to a non-existent class and will fatal the duplicate-deployment flow.

🐛 Proposed fix
 use Appwrite\SDK\Response as SDKResponse;
 use Appwrite\Utopia\Response;
 use Utopia\Database\Database;
+use Utopia\Database\Document;
 use Utopia\Database\Helpers\ID;
 use Utopia\Database\Validator\UID;
#!/bin/bash
set -euo pipefail

file="src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php"

echo "=== Imports ==="
sed -n '1,18p' "$file"

echo
echo "=== Document instantiations ==="
rg -n 'new Document' "$file"

Expected result: new Document(...) is present, but use Utopia\Database\Document; is missing from the imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php`
around lines 122 - 127, The code instantiates new Document(...) in the Duplicate
deployment flow (see the new Document(...) call in the Create class), but the
Utopia\Database\Document class is not imported; add the import statement "use
Utopia\Database\Document;" to the top of the file's imports so the new
Document(...) resolves to the correct database Document class and the
duplicate-deployment flow no longer fatals.
app/controllers/api/account.php (2)

4521-4525: ⚠️ Potential issue | 🟠 Major

Don't write expired back unless this request changes it.

createPushTarget() does not initialize expired, so this sparse update can still persist null when the request only refreshes the identifier/name.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/controllers/api/account.php` around lines 4521 - 4525, The update is
overwriting the target.expired field even when the request didn't supply it
(createPushTarget leaves expired unset), so change the code around
updateDocument('targets', ...) to build the Document payload conditionally:
include 'expired' only if the incoming request explicitly provides/changes the
expired value (e.g. check the request for an 'expired' parameter or compare the
requested expired value to $target->getAttribute('expired') before adding it),
otherwise omit 'expired' from the Document so null is not persisted
unintentionally.

291-294: ⚠️ Potential issue | 🟠 Major

Only persist the verification flag that actually changed.

This still writes both verification fields back. In flows where the other attribute was never initialized, the sparse update can write null and either fail validation or alter the stored shape unexpectedly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/controllers/api/account.php` around lines 291 - 294, Only send the
verification field(s) that actually changed to updateDocument: before calling
$dbForProject->updateDocument('users', $user->getId(), new Document(...)) build
an $updates array and conditionally add 'emailVerification' and/or
'phoneVerification' only when their value changed on $user (compare current
value to the original/previous value or use the model's dirty/changed API), then
call updateDocument with new Document($updates) so you never write an unmodified
field back as null or into the stored shape.
🧹 Nitpick comments (3)
src/Appwrite/Platform/Tasks/Interval.php (1)

161-164: Remove the redundant setAttribute() calls.

updateDocument() now receives a sparse Document, so Lines 162-163 mutate the full $execution without affecting the write payload. Dropping those mutations keeps this path aligned with the optimization this PR is introducing.

♻️ Proposed cleanup
                     foreach ($staleExecutions as $execution) {
-                        $execution->setAttribute('status', 'failed');
-                        $execution->setAttribute('errors', 'Execution timed out');
-                        $dbForProject->updateDocument('executions', $execution->getId(), new Document(['status' => 'failed', 'errors' => 'Execution timed out']));
+                        $dbForProject->updateDocument(
+                            'executions',
+                            $execution->getId(),
+                            new Document([
+                                'status' => 'failed',
+                                'errors' => 'Execution timed out',
+                            ])
+                        );
                     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Appwrite/Platform/Tasks/Interval.php` around lines 161 - 164, Remove the
redundant mutations to the in-memory execution before persisting: in the foreach
over $staleExecutions in Interval.php, delete the two calls
$execution->setAttribute('status', 'failed') and
$execution->setAttribute('errors', 'Execution timed out') and rely solely on the
sparse Document passed to $dbForProject->updateDocument('executions',
$execution->getId(), new Document(['status' => 'failed', 'errors' => 'Execution
timed out'])); this keeps the optimization consistent and avoids mutating the
full $execution object.
src/Appwrite/Platform/Modules/Functions/Workers/Builds.php (1)

280-289: Redundant setAttribute calls before sparse document update.

Lines 280-281 set attributes on $deployment, but the variable is immediately reassigned to the updateDocument result (line 282). These setAttribute calls have no effect since the sparse Document already contains the same values. Consider removing them for clarity:

-        $deployment->setAttribute('buildStartedAt', $startTime);
-        $deployment->setAttribute('status', 'processing');
         $deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), new Document([
             'buildStartedAt' => $startTime,
             'status' => 'processing',
         ]));

This pattern repeats throughout the file (lines 368-376, 485-496, 540-548, 562-565, 881-898 with 929-936, 948-955, 1193-1203). The sparse document approach is correct, but the preceding setAttribute calls are now dead code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Appwrite/Platform/Modules/Functions/Workers/Builds.php` around lines 280
- 289, Remove the redundant in-memory attribute assignments before sparse
updates: any calls to $deployment->setAttribute(...) (e.g., the two calls before
$dbForProject->updateDocument('deployments', $deployment->getId(), new
Document([...])) ) should be deleted because updateDocument returns a new
Document and the setAttribute has no effect; keep the sparse Document payload
passed to updateDocument (new Document([...])) and similarly remove preceding
setAttribute calls in the other locations mentioned (the blocks around lines
with $deployment updates and the related $resource->updateDocument calls) so
only the sparse update is performed and the returned Document is used.
app/controllers/api/users.php (1)

1791-1797: Consider building the sparse document with only actually changed fields.

The current implementation always includes all five fields (identifier, expired, providerId, providerInternalId, name) in the update, even when only a subset changed. Since the PR aims to optimize updates by passing only changed attributes, consider conditionally building the sparse document:

♻️ Suggested refactor
-        $target = $dbForProject->updateDocument('targets', $target->getId(), new Document([
-            'identifier' => $target->getAttribute('identifier'),
-            'expired' => $target->getAttribute('expired'),
-            'providerId' => $target->getAttribute('providerId'),
-            'providerInternalId' => $target->getAttribute('providerInternalId'),
-            'name' => $target->getAttribute('name'),
-        ]));
+        $updatePayload = [];
+        if ($identifier) {
+            $updatePayload['identifier'] = $target->getAttribute('identifier');
+            $updatePayload['expired'] = $target->getAttribute('expired');
+        }
+        if ($providerId) {
+            $updatePayload['providerId'] = $target->getAttribute('providerId');
+            $updatePayload['providerInternalId'] = $target->getAttribute('providerInternalId');
+        }
+        if ($name) {
+            $updatePayload['name'] = $target->getAttribute('name');
+        }
+        $target = $dbForProject->updateDocument('targets', $target->getId(), new Document($updatePayload));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/controllers/api/users.php` around lines 1791 - 1797, The update currently
always passes all five fields to updateDocument('targets', $target->getId(), new
Document(...)) even when some didn't change; change this to build a sparse
$payload array containing only attributes that actually changed (e.g. compare
$target->getAttribute('identifier') etc. against the original values via a
getOriginalAttribute/getOriginal* helper or a $target->getChangedAttributes()
list), only add changed keys to $payload, and if $payload is non-empty call
$dbForProject->updateDocument('targets', $target->getId(), new
Document($payload)); if there are no changes skip the updateDocument call.
Ensure you reference the existing symbols updateDocument, 'targets',
$target->getId(), and getAttribute/getOriginalAttribute (or
getChangedAttributes) when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.php`:
- Line 154: The update call is writing DateTime::now() into the sessions
document which can differ from the timestamp already placed on the $session
object earlier; replace DateTime::now() with the existing timestamp value from
$session (e.g. $session->getAttribute('mfaUpdatedAt') or the property used
earlier) when calling $dbForProject->updateDocument('sessions',
$session->getId(), new Document(['factors' => $factors, 'mfaUpdatedAt' =>
<reuse-session-timestamp>])); to ensure the persisted mfaUpdatedAt exactly
matches the session returned to the client.

In `@src/Appwrite/Platform/Modules/Databases/Workers/Databases.php`:
- Around line 322-329: The DB update writes status=>'stuck' but the in-memory
$attribute object isn't updated, so realtime notifications sent later use the
old state; after calling $dbForProject->updateDocument('attributes',
$attribute->getId(), ...) update the local $attribute instance (e.g.,
setAttribute('status','stuck') or equivalent method on the Attribute class) and
also ensure its 'error' field is updated to match the persisted values before
the finally block triggers realtime so listeners receive the aligned stuck/error
payload.

In `@src/Appwrite/Platform/Modules/Functions/Workers/Builds.php`:
- Around line 929-936: The update currently always writes 'adapter' and
'fallbackFile' into the sparse Document (in the call to
$dbForProject->updateDocument and the new Document([...])) even though those
attributes are only set for site resources; change the update to build the
payload array first and only add 'adapter' and 'fallbackFile' when
$resource->getCollection() === 'sites' (i.e. conditionally push those keys into
the array before calling updateDocument with new Document($payload)), so that
function deployments don't receive unset adapter/fallbackFile fields.

In `@src/Appwrite/Platform/Modules/Projects/Http/DevKeys/Update.php`:
- Line 77: The call to $dbForPlatform->updateDocument('devKeys', $key->getId(),
new Document(['name' => $name, 'expire' => $expire])) discards the DB-returned
Document (which contains DB-managed fields like updatedAt); assign the result
back to $key (e.g. $key = $dbForPlatform->updateDocument(...)) and then
serialize/use $key for the response so timestamps are current — mirror the
pattern used in Projects/Update.php.

In `@src/Appwrite/Platform/Workers/Webhooks.php`:
- Around line 176-179: The update is always writing the 'enabled' field which
can incorrectly flip admin changes; change the updateDocument call so it only
includes 'enabled' in the Document payload when this failure branch actually
pauses the webhook (i.e., only add the 'enabled' key when you intend to set it
to false), keep 'logs' always, and build the payload dynamically before calling
$dbForPlatform->updateDocument('webhooks', $webhook->getId(), new Document(...))
so that when not pausing the 'enabled' attribute is omitted entirely.

---

Duplicate comments:
In `@app/controllers/api/account.php`:
- Around line 4521-4525: The update is overwriting the target.expired field even
when the request didn't supply it (createPushTarget leaves expired unset), so
change the code around updateDocument('targets', ...) to build the Document
payload conditionally: include 'expired' only if the incoming request explicitly
provides/changes the expired value (e.g. check the request for an 'expired'
parameter or compare the requested expired value to
$target->getAttribute('expired') before adding it), otherwise omit 'expired'
from the Document so null is not persisted unintentionally.
- Around line 291-294: Only send the verification field(s) that actually changed
to updateDocument: before calling $dbForProject->updateDocument('users',
$user->getId(), new Document(...)) build an $updates array and conditionally add
'emailVerification' and/or 'phoneVerification' only when their value changed on
$user (compare current value to the original/previous value or use the model's
dirty/changed API), then call updateDocument with new Document($updates) so you
never write an unmodified field back as null or into the stored shape.

In
`@src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php`:
- Around line 122-127: The code instantiates new Document(...) in the Duplicate
deployment flow (see the new Document(...) call in the Create class), but the
Utopia\Database\Document class is not imported; add the import statement "use
Utopia\Database\Document;" to the top of the file's imports so the new
Document(...) resolves to the correct database Document class and the
duplicate-deployment flow no longer fatals.

In `@src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php`:
- Around line 93-96: The code instantiates new Document(...) without importing
it, causing PHP to resolve it to the current namespace; fix by importing
Utopia\Database\Document at the top of the file (add use
Utopia\Database\Document;) or alternatively fully-qualify the instantiation as
new \Utopia\Database\Document([...]) where $authorization->skip(...) calls
$dbForPlatform->updateDocument('schedules', $schedule->getId(), new
Document([...])) so the correct class is used.

---

Nitpick comments:
In `@app/controllers/api/users.php`:
- Around line 1791-1797: The update currently always passes all five fields to
updateDocument('targets', $target->getId(), new Document(...)) even when some
didn't change; change this to build a sparse $payload array containing only
attributes that actually changed (e.g. compare
$target->getAttribute('identifier') etc. against the original values via a
getOriginalAttribute/getOriginal* helper or a $target->getChangedAttributes()
list), only add changed keys to $payload, and if $payload is non-empty call
$dbForProject->updateDocument('targets', $target->getId(), new
Document($payload)); if there are no changes skip the updateDocument call.
Ensure you reference the existing symbols updateDocument, 'targets',
$target->getId(), and getAttribute/getOriginalAttribute (or
getChangedAttributes) when making the change.

In `@src/Appwrite/Platform/Modules/Functions/Workers/Builds.php`:
- Around line 280-289: Remove the redundant in-memory attribute assignments
before sparse updates: any calls to $deployment->setAttribute(...) (e.g., the
two calls before $dbForProject->updateDocument('deployments',
$deployment->getId(), new Document([...])) ) should be deleted because
updateDocument returns a new Document and the setAttribute has no effect; keep
the sparse Document payload passed to updateDocument (new Document([...])) and
similarly remove preceding setAttribute calls in the other locations mentioned
(the blocks around lines with $deployment updates and the related
$resource->updateDocument calls) so only the sparse update is performed and the
returned Document is used.

In `@src/Appwrite/Platform/Tasks/Interval.php`:
- Around line 161-164: Remove the redundant mutations to the in-memory execution
before persisting: in the foreach over $staleExecutions in Interval.php, delete
the two calls $execution->setAttribute('status', 'failed') and
$execution->setAttribute('errors', 'Execution timed out') and rely solely on the
sparse Document passed to $dbForProject->updateDocument('executions',
$execution->getId(), new Document(['status' => 'failed', 'errors' => 'Execution
timed out'])); this keeps the optimization consistent and avoids mutating the
full $execution object.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ef6296b5-9617-4bf1-aa5c-035ae4a33d91

📥 Commits

Reviewing files that changed from the base of the PR and between 4e84a2e and f2ec1b1.

📒 Files selected for processing (59)
  • AGENTS.md
  • CLAUDE.md
  • app/controllers/api/account.php
  • app/controllers/api/messaging.php
  • app/controllers/api/users.php
  • app/controllers/general.php
  • app/controllers/shared/api.php
  • app/init/resources.php
  • app/realtime.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Authenticators/Update.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/RecoveryCodes/Create.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/RecoveryCodes/Update.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Update.php
  • src/Appwrite/Platform/Modules/Compute/Base.php
  • src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Delete.php
  • src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Delete.php
  • src/Appwrite/Platform/Modules/Databases/Workers/Databases.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Delete.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Status/Update.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.php
  • src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php
  • src/Appwrite/Platform/Modules/Functions/Http/Functions/Deployment/Update.php
  • src/Appwrite/Platform/Modules/Functions/Http/Variables/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Variables/Delete.php
  • src/Appwrite/Platform/Modules/Functions/Http/Variables/Update.php
  • src/Appwrite/Platform/Modules/Functions/Workers/Builds.php
  • src/Appwrite/Platform/Modules/Projects/Http/DevKeys/Update.php
  • src/Appwrite/Platform/Modules/Projects/Http/Projects/Labels/Update.php
  • src/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Delete.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Duplicate/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Status/Update.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Sites/Deployment/Update.php
  • src/Appwrite/Platform/Modules/Sites/Http/Variables/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Variables/Delete.php
  • src/Appwrite/Platform/Modules/Sites/Http/Variables/Update.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Create.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Delete.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Status/Update.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Update.php
  • src/Appwrite/Platform/Modules/Teams/Http/Teams/Name/Update.php
  • src/Appwrite/Platform/Modules/VCS/Http/GitHub/Authorize/External/Update.php
  • src/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.php
  • src/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.php
  • src/Appwrite/Platform/Modules/VCS/Http/GitHub/Events/Create.php
  • src/Appwrite/Platform/Modules/VCS/Http/Installations/Repositories/Create.php
  • src/Appwrite/Platform/Tasks/Interval.php
  • src/Appwrite/Platform/Workers/Certificates.php
  • src/Appwrite/Platform/Workers/Messaging.php
  • src/Appwrite/Platform/Workers/Migrations.php
  • src/Appwrite/Platform/Workers/Webhooks.php
🚧 Files skipped from review as they are similar to previous changes (27)
  • src/Appwrite/Platform/Modules/VCS/Http/GitHub/Events/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Variables/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Sites/Deployment/Update.php
  • app/realtime.php
  • app/controllers/general.php
  • src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Delete.php
  • app/controllers/api/messaging.php
  • AGENTS.md
  • src/Appwrite/Platform/Modules/VCS/Http/Installations/Repositories/Create.php
  • app/controllers/shared/api.php
  • src/Appwrite/Platform/Workers/Certificates.php
  • src/Appwrite/Platform/Modules/Functions/Http/Variables/Update.php
  • src/Appwrite/Platform/Modules/Projects/Http/Projects/Labels/Update.php
  • src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Delete.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.php
  • src/Appwrite/Platform/Modules/Compute/Base.php
  • src/Appwrite/Platform/Workers/Messaging.php
  • src/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.php
  • src/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.php
  • src/Appwrite/Platform/Modules/Functions/Http/Variables/Delete.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Delete.php
  • src/Appwrite/Platform/Workers/Migrations.php
  • CLAUDE.md
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/RecoveryCodes/Update.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Status/Update.php
  • src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php

@ChiragAgg5k ChiragAgg5k force-pushed the optimize-update-document-calls branch from 99dda0e to 1fefbaa Compare March 6, 2026 11:26
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
app/controllers/api/users.php (1)

1342-1353: ⚠️ Potential issue | 🟠 Major

Stop the empty-password branch from falling through.

After Line 1352 sends the response, execution still continues into the normal hashing path, so the cleared password is immediately replaced with an Argon2 hash of ''.

Proposed fix
         if (\strlen($password) === 0) {
             $user
                 ->setAttribute('password', '')
                 ->setAttribute('passwordUpdate', DateTime::now());

             $user = $dbForProject->updateDocument('users', $user->getId(), new Document([
                 'password' => $user->getAttribute('password'),
                 'passwordUpdate' => $user->getAttribute('passwordUpdate'),
             ]));
             $queueForEvents->setParam('userId', $user->getId());
             $response->dynamic($user, Response::MODEL_USER);
+            return;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/controllers/api/users.php` around lines 1342 - 1353, The empty-password
branch (where strlen($password) === 0) sends a response but does not stop
execution, so after updating the user and calling $response->dynamic($user,
Response::MODEL_USER) the code continues into the normal hashing path and
re-hashes the empty password; fix by exiting the handler immediately after
sending the response—e.g., add an explicit return (or otherwise short-circuit)
right after $response->dynamic($user, Response::MODEL_USER) so the subsequent
password hashing/update logic does not run for the empty-password case.
♻️ Duplicate comments (5)
src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php (1)

286-291: ⚠️ Potential issue | 🔴 Critical

Don't overwrite $function with the sparse update result.

updateDocument() returns a Document, and the adapter contract plus the MariaDB implementation show it returns the document passed in rather than a reloaded merged row. Reassigning here truncates $function to just these four fields, so later reads like Line 314 and the final payload at Line 429 can lose attributes unexpectedly. (raw.githubusercontent.com)

Suggested fix
-        $function = $dbForProject->updateDocument('functions', $function->getId(), new Document([
+        $dbForProject->updateDocument('functions', $function->getId(), new Document([
             'scheduleId' => $function->getAttribute('scheduleId'),
             'scheduleInternalId' => $function->getAttribute('scheduleInternalId'),
             'repositoryId' => $function->getAttribute('repositoryId'),
             'repositoryInternalId' => $function->getAttribute('repositoryInternalId'),
         ]));
In utopia-php/database, does Adapter::updateDocument return the passed Document, and does the MariaDB adapter return that sparse input document instead of refetching the merged row?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php` around
lines 286 - 291, The current code reassigns $function with the sparse Document
returned by $dbForProject->updateDocument(...), which truncates attributes;
instead do not overwrite $function—store the update result in a new variable
(e.g. $updatedDocument) or, better, after calling updateDocument, reload the
full row using the DB read method (e.g. getDocument/readDocument by passing
$function->getId()) and use that fresh full Document for later reads; ensure
updateDocument is only used for the write and subsequent code (lines referencing
$function) uses the original $function or the freshly fetched full Document.
src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php (1)

93-96: ⚠️ Potential issue | 🔴 Critical

Import Utopia\Database\Document before instantiating it.

Line 93 uses new Document([...]), but this file never imports Utopia\Database\Document, so PHP will resolve Document to the current namespace and this delete path will fatal at runtime.

🐛 Proposed fix
 use Utopia\Database\Database;
 use Utopia\Database\DateTime;
+use Utopia\Database\Document;
 use Utopia\Database\Validator\Authorization;

Run this read-only check to confirm the missing import. Expect the new Document( match to exist and the use match to be absent:

#!/bin/bash
file="src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php"
sed -n '1,25p' "$file"
rg -n "new Document\\(" "$file"
rg -n "^use Utopia\\\\Database\\\\Document;" "$file"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php` around
lines 93 - 96, The code instantiates new Document(...) in the Delete flow
($authorization->skip → $dbForPlatform->updateDocument) but doesn't import
Utopia\Database\Document, causing a fatal at runtime; fix by adding the missing
use statement for Utopia\Database\Document at the top of the file (so the new
Document([...]) in the Delete.php function resolves to the correct class).
app/controllers/api/account.php (2)

4521-4525: ⚠️ Potential issue | 🟠 Major

Don't write expired unless this request sets it.

createPushTarget() in this file doesn't initialize expired, so this sparse update can still send expired => null whenever the $identifier branch is skipped.

💡 Proposed fix
-        $target = $dbForProject->updateDocument('targets', $target->getId(), new Document([
-            'identifier' => $target->getAttribute('identifier'),
-            'expired' => $target->getAttribute('expired'),
-            'name' => $target->getAttribute('name'),
-        ]));
+        $updates = new Document([
+            'identifier' => $target->getAttribute('identifier'),
+            'name' => $target->getAttribute('name'),
+        ]);
+
+        if ($identifier) {
+            $updates->setAttribute('expired', false);
+        }
+
+        $target = $dbForProject->updateDocument('targets', $target->getId(), $updates);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/controllers/api/account.php` around lines 4521 - 4525, The update
currently always writes 'expired' (possibly null) in the sparse update called
via $dbForProject->updateDocument('targets', ...), which can overwrite an
existing value when createPushTarget() doesn't set it; change the payload
construction so 'expired' is only included when the incoming request actually
sets it (e.g., check the request param or verify
$target->hasAttribute('expired') / $target->getAttribute('expired') !== null
before adding 'expired' to the Document), leaving the field out of the Document
entirely when not provided.

291-294: ⚠️ Potential issue | 🟠 Major

Only persist the verification flag that actually changed.

Lines 291-294 always emit both verification fields. Several user-creation paths in this file never initialize phoneVerification, so this sparse update can write phoneVerification => null for email/magic-url logins.

💡 Proposed fix
     try {
-        $dbForProject->updateDocument('users', $user->getId(), new Document([
-            'emailVerification' => $user->getAttribute('emailVerification'),
-            'phoneVerification' => $user->getAttribute('phoneVerification'),
-        ]));
+        $updates = new Document([]);
+
+        if (\in_array($verifiedToken->getAttribute('type'), [TOKEN_TYPE_MAGIC_URL, TOKEN_TYPE_EMAIL], true)) {
+            $updates->setAttribute('emailVerification', true);
+        }
+
+        if ($verifiedToken->getAttribute('type') === TOKEN_TYPE_PHONE) {
+            $updates->setAttribute('phoneVerification', true);
+        }
+
+        if (!$updates->isEmpty()) {
+            $dbForProject->updateDocument('users', $user->getId(), $updates);
+        }
     } catch (\Throwable $th) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/controllers/api/account.php` around lines 291 - 294, The update currently
always writes both 'emailVerification' and 'phoneVerification' which can
overwrite phoneVerification with null; change the $dbForProject->updateDocument
call to build a sparse $data array only including keys that were actually
set/changed (e.g. check $user->hasAttribute or use !== null on
$user->getAttribute('phoneVerification') and same for email), then call
$dbForProject->updateDocument('users', $user->getId(), new Document($data)) only
when $data is not empty so you never persist an unset phoneVerification;
reference the existing updateDocument call, the 'users' collection, and the
$user->getAttribute('phoneVerification') / 'emailVerification' attributes when
implementing this conditional assembly.
app/controllers/api/users.php (1)

1495-1500: ⚠️ Potential issue | 🟠 Major

Keep the embedded target list in sync before returning $user.

These sparse target updates only change storage. $oldTarget inside $user->targets still carries the previous identifier, and the delete branches never remove it, so the response can return stale email/phone targets even when the patch succeeded.

Proposed fix
             if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
                 if (\strlen($email) !== 0) {
+                    $oldTarget->setAttribute('identifier', $email);
                     $dbForProject->updateDocument('targets', $oldTarget->getId(), new Document(['identifier' => $email]));
                 } else {
                     $dbForProject->deleteDocument('targets', $oldTarget->getId());
+                    $user->setAttribute(
+                        'targets',
+                        array_values(array_filter(
+                            $user->getAttribute('targets', []),
+                            fn (Document $target) => $target->getId() !== $oldTarget->getId()
+                        ))
+                    );
                 }
             }

Mirror the same fix in the phone branch.

Also applies to: 1587-1592

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/controllers/api/users.php` around lines 1495 - 1500, The response can
return stale targets because after calling $dbForProject->updateDocument(...) or
->deleteDocument(...) you do not update the in-memory $user->targets collection;
locate the branches handling $oldTarget (the one that checks $oldTarget
instanceof Document and strlen($email) !== 0) and mirror the storage changes
into $user->targets: when updating, find the matching target object in
$user->targets (by id from $oldTarget->getId()) and update its identifier to
$email; when deleting, remove that target object from $user->targets. Apply the
same sync logic to the phone branch that uses the phone variable so both
identifier updates and deletions are reflected in $user->targets before
returning $user.
🧹 Nitpick comments (4)
src/Appwrite/Platform/Tasks/Interval.php (1)

161-164: Remove the redundant full-document mutations.

Lines 162-163 still mutate $execution, but Line 164 persists a separate sparse Document. That leaves extra work in this loop and duplicates the source of truth for the same update.

♻️ Proposed cleanup
                     foreach ($staleExecutions as $execution) {
-                        $execution->setAttribute('status', 'failed');
-                        $execution->setAttribute('errors', 'Execution timed out');
-                        $dbForProject->updateDocument('executions', $execution->getId(), new Document(['status' => 'failed', 'errors' => 'Execution timed out']));
+                        $dbForProject->updateDocument(
+                            'executions',
+                            $execution->getId(),
+                            new Document([
+                                'status' => 'failed',
+                                'errors' => 'Execution timed out',
+                            ])
+                        );
                     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Appwrite/Platform/Tasks/Interval.php` around lines 161 - 164, The loop
currently updates the in-memory $execution with setAttribute('status','failed')
and setAttribute('errors',...) and then separately calls
$dbForProject->updateDocument('executions', $execution->getId(), new
Document([...])) which duplicates work; remove the redundant in-memory mutations
and rely on the single persistence call instead: delete the two lines calling
$execution->setAttribute(...) and keep the
$dbForProject->updateDocument('executions', $execution->getId(), new
Document(['status' => 'failed', 'errors' => 'Execution timed out'])) so the
status/errors update is applied only once.
src/Appwrite/Platform/Modules/Projects/Http/Projects/Labels/Update.php (1)

81-84: Drop the redundant $project->setAttribute() call.

Line 82 no longer participates in the write path: Line 84 persists a fresh sparse Document and immediately overwrites $project with the database result. Removing the in-memory mutation makes this sparse-update pattern easier to follow.

♻️ Proposed cleanup
 $labels = (array) \array_values(\array_unique($labels));
-$project->setAttribute('labels', $labels);
-
 $project = $dbForPlatform->updateDocument('projects', $project->getId(), new Document(['labels' => $labels]));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Appwrite/Platform/Modules/Projects/Http/Projects/Labels/Update.php`
around lines 81 - 84, Remove the redundant in-memory mutation: delete the call
to $project->setAttribute('labels', $labels) since the actual write uses
$dbForPlatform->updateDocument('projects', $project->getId(), new
Document(['labels' => $labels])) which returns the persisted $project; keep the
deduping of $labels and the sparse Document update, then rely on the returned
$project from updateDocument.
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Delete.php (1)

123-124: Remove redundant setAttribute call.

Line 123 modifies $relatedAttribute, but the return value from updateDocument() on line 124 is not captured, and $relatedAttribute is never used after this block. The setAttribute call has no effect.

♻️ Proposed cleanup
                if ($relatedAttribute->getAttribute('status') === 'available') {
-                    $relatedAttribute->setAttribute('status', 'deleting');
                    $dbForProject->updateDocument('attributes', $relatedAttribute->getId(), new Document(['status' => 'deleting']));
                }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Delete.php`
around lines 123 - 124, The call to $relatedAttribute->setAttribute('status',
'deleting') is redundant because updateDocument('attributes',
$relatedAttribute->getId(), new Document(['status' => 'deleting'])) performs the
DB update and $relatedAttribute is not used afterwards; remove the
$relatedAttribute->setAttribute(...) line OR, if you intended to update the
in-memory object, replace it by assigning the result of
dbForProject->updateDocument(...) back to $relatedAttribute (e.g.,
$relatedAttribute = $dbForProject->updateDocument(...)); update the code in the
block that references $relatedAttribute, setAttribute, and updateDocument
accordingly.
src/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.php (1)

149-161: Build the sparse payload directly from the local values.

This still mutates the full $installation object before the write, then immediately re-reads those fields into a sparse Document. Passing $owner, $personal, $refreshToken, $accessToken, and $accessTokenExpiry directly keeps the hot path leaner and avoids leaving the in-memory document ahead of storage if updateDocument() throws.

♻️ Suggested cleanup
-                $installation = $installation
-                    ->setAttribute('organization', $owner)
-                    ->setAttribute('personal', $personal)
-                    ->setAttribute('personalRefreshToken', $refreshToken)
-                    ->setAttribute('personalAccessToken', $accessToken)
-                    ->setAttribute('personalAccessTokenExpiry', $accessTokenExpiry);
                 $installation = $dbForPlatform->updateDocument('installations', $installation->getId(), new Document([
-                    'organization' => $installation->getAttribute('organization'),
-                    'personal' => $installation->getAttribute('personal'),
-                    'personalRefreshToken' => $installation->getAttribute('personalRefreshToken'),
-                    'personalAccessToken' => $installation->getAttribute('personalAccessToken'),
-                    'personalAccessTokenExpiry' => $installation->getAttribute('personalAccessTokenExpiry'),
+                    'organization' => $owner,
+                    'personal' => $personal,
+                    'personalRefreshToken' => $refreshToken,
+                    'personalAccessToken' => $accessToken,
+                    'personalAccessTokenExpiry' => $accessTokenExpiry,
                 ]));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.php` around lines
149 - 161, The code mutates $installation with setAttribute calls before calling
$dbForPlatform->updateDocument, which can leave the in-memory document changed
if updateDocument() throws; instead build the sparse update payload directly
from the local variables ($owner, $personal, $refreshToken, $accessToken,
$accessTokenExpiry) and pass that to
$dbForPlatform->updateDocument('installations', $installation->getId(), new
Document([...])) without calling $installation->setAttribute beforehand, then
only update the in-memory $installation after a successful update if needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/controllers/api/users.php`:
- Around line 1290-1292: The update currently sets attributes and writes a
sparse Document (e.g. in the name handler: $user->setAttribute('name', $name);
$dbForProject->updateDocument('users', $user->getId(), new Document(['name' =>
$user->getAttribute('name')]))) but it does not recompute the stored 'search'
field, so list queries still match old values; modify the name, email, and phone
update handlers to recompute the user's search token (same logic used when
building the full user Document) and include 'search' in the Document passed to
updateDocument('users', ...) so updates to name/email/phone also update the
stored search index (apply the same change in the other affected handlers
referenced around lines 1470-1489 and 1562-1581).

In `@src/Appwrite/Platform/Modules/Teams/Http/Memberships/Create.php`:
- Around line 273-283: When auto-confirming the pending membership in the branch
that calls $dbForProject->updateDocument('memberships', $membership->getId(),
...) (the else branch that currently sets 'secret' and 'invited' and should also
set 'confirm' => true and 'joined'), also increment the teams.total counter just
like the privileged/app path does at the other branch (replicate the teams
update logic used there) so that confirmed-member counts remain accurate; locate
the membership update call using $membership and add a subsequent update to the
'teams' document to increment its 'total' field (or perform the same teams
update operation used in the other branch) when changing confirm from false to
true.

In `@src/Appwrite/Platform/Modules/VCS/Http/GitHub/Authorize/External/Update.php`:
- Around line 100-103: The current read-check-append-write around
$providerPullRequestIds on $repository is not atomic and can lose concurrent PR
IDs; replace the manual merge and updateDocument call with an atomic DB update
that appends the PR id only if not present (or uses a set-union operator) so two
concurrent authorizations cannot clobber each other. Concretely, change the
logic around $providerPullRequestIds/$repository and the
$dbForPlatform->updateDocument('repositories', $repository->getId(), ...) call
to perform an atomic append (e.g., use the DB's array union/addToSet operator or
an atomic patch/update operation) or perform the update inside a DB
transaction/conditional update that checks absence of the PR id and retries on
conflict, so the append is done server-side and is safe for concurrent requests.

---

Outside diff comments:
In `@app/controllers/api/users.php`:
- Around line 1342-1353: The empty-password branch (where strlen($password) ===
0) sends a response but does not stop execution, so after updating the user and
calling $response->dynamic($user, Response::MODEL_USER) the code continues into
the normal hashing path and re-hashes the empty password; fix by exiting the
handler immediately after sending the response—e.g., add an explicit return (or
otherwise short-circuit) right after $response->dynamic($user,
Response::MODEL_USER) so the subsequent password hashing/update logic does not
run for the empty-password case.

---

Duplicate comments:
In `@app/controllers/api/account.php`:
- Around line 4521-4525: The update currently always writes 'expired' (possibly
null) in the sparse update called via $dbForProject->updateDocument('targets',
...), which can overwrite an existing value when createPushTarget() doesn't set
it; change the payload construction so 'expired' is only included when the
incoming request actually sets it (e.g., check the request param or verify
$target->hasAttribute('expired') / $target->getAttribute('expired') !== null
before adding 'expired' to the Document), leaving the field out of the Document
entirely when not provided.
- Around line 291-294: The update currently always writes both
'emailVerification' and 'phoneVerification' which can overwrite
phoneVerification with null; change the $dbForProject->updateDocument call to
build a sparse $data array only including keys that were actually set/changed
(e.g. check $user->hasAttribute or use !== null on
$user->getAttribute('phoneVerification') and same for email), then call
$dbForProject->updateDocument('users', $user->getId(), new Document($data)) only
when $data is not empty so you never persist an unset phoneVerification;
reference the existing updateDocument call, the 'users' collection, and the
$user->getAttribute('phoneVerification') / 'emailVerification' attributes when
implementing this conditional assembly.

In `@app/controllers/api/users.php`:
- Around line 1495-1500: The response can return stale targets because after
calling $dbForProject->updateDocument(...) or ->deleteDocument(...) you do not
update the in-memory $user->targets collection; locate the branches handling
$oldTarget (the one that checks $oldTarget instanceof Document and
strlen($email) !== 0) and mirror the storage changes into $user->targets: when
updating, find the matching target object in $user->targets (by id from
$oldTarget->getId()) and update its identifier to $email; when deleting, remove
that target object from $user->targets. Apply the same sync logic to the phone
branch that uses the phone variable so both identifier updates and deletions are
reflected in $user->targets before returning $user.

In `@src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php`:
- Around line 286-291: The current code reassigns $function with the sparse
Document returned by $dbForProject->updateDocument(...), which truncates
attributes; instead do not overwrite $function—store the update result in a new
variable (e.g. $updatedDocument) or, better, after calling updateDocument,
reload the full row using the DB read method (e.g. getDocument/readDocument by
passing $function->getId()) and use that fresh full Document for later reads;
ensure updateDocument is only used for the write and subsequent code (lines
referencing $function) uses the original $function or the freshly fetched full
Document.

In `@src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php`:
- Around line 93-96: The code instantiates new Document(...) in the Delete flow
($authorization->skip → $dbForPlatform->updateDocument) but doesn't import
Utopia\Database\Document, causing a fatal at runtime; fix by adding the missing
use statement for Utopia\Database\Document at the top of the file (so the new
Document([...]) in the Delete.php function resolves to the correct class).

---

Nitpick comments:
In
`@src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Delete.php`:
- Around line 123-124: The call to $relatedAttribute->setAttribute('status',
'deleting') is redundant because updateDocument('attributes',
$relatedAttribute->getId(), new Document(['status' => 'deleting'])) performs the
DB update and $relatedAttribute is not used afterwards; remove the
$relatedAttribute->setAttribute(...) line OR, if you intended to update the
in-memory object, replace it by assigning the result of
dbForProject->updateDocument(...) back to $relatedAttribute (e.g.,
$relatedAttribute = $dbForProject->updateDocument(...)); update the code in the
block that references $relatedAttribute, setAttribute, and updateDocument
accordingly.

In `@src/Appwrite/Platform/Modules/Projects/Http/Projects/Labels/Update.php`:
- Around line 81-84: Remove the redundant in-memory mutation: delete the call to
$project->setAttribute('labels', $labels) since the actual write uses
$dbForPlatform->updateDocument('projects', $project->getId(), new
Document(['labels' => $labels])) which returns the persisted $project; keep the
deduping of $labels and the sparse Document update, then rely on the returned
$project from updateDocument.

In `@src/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.php`:
- Around line 149-161: The code mutates $installation with setAttribute calls
before calling $dbForPlatform->updateDocument, which can leave the in-memory
document changed if updateDocument() throws; instead build the sparse update
payload directly from the local variables ($owner, $personal, $refreshToken,
$accessToken, $accessTokenExpiry) and pass that to
$dbForPlatform->updateDocument('installations', $installation->getId(), new
Document([...])) without calling $installation->setAttribute beforehand, then
only update the in-memory $installation after a successful update if needed.

In `@src/Appwrite/Platform/Tasks/Interval.php`:
- Around line 161-164: The loop currently updates the in-memory $execution with
setAttribute('status','failed') and setAttribute('errors',...) and then
separately calls $dbForProject->updateDocument('executions',
$execution->getId(), new Document([...])) which duplicates work; remove the
redundant in-memory mutations and rely on the single persistence call instead:
delete the two lines calling $execution->setAttribute(...) and keep the
$dbForProject->updateDocument('executions', $execution->getId(), new
Document(['status' => 'failed', 'errors' => 'Execution timed out'])) so the
status/errors update is applied only once.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6cb04582-bd41-405d-84c9-5fbd0b00d6bf

📥 Commits

Reviewing files that changed from the base of the PR and between f2ec1b1 and 99dda0e.

📒 Files selected for processing (59)
  • AGENTS.md
  • CLAUDE.md
  • app/controllers/api/account.php
  • app/controllers/api/messaging.php
  • app/controllers/api/users.php
  • app/controllers/general.php
  • app/controllers/shared/api.php
  • app/init/resources.php
  • app/realtime.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Authenticators/Update.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/RecoveryCodes/Create.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/RecoveryCodes/Update.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Update.php
  • src/Appwrite/Platform/Modules/Compute/Base.php
  • src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Delete.php
  • src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Delete.php
  • src/Appwrite/Platform/Modules/Databases/Workers/Databases.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Delete.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Status/Update.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.php
  • src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php
  • src/Appwrite/Platform/Modules/Functions/Http/Functions/Deployment/Update.php
  • src/Appwrite/Platform/Modules/Functions/Http/Variables/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Variables/Delete.php
  • src/Appwrite/Platform/Modules/Functions/Http/Variables/Update.php
  • src/Appwrite/Platform/Modules/Functions/Workers/Builds.php
  • src/Appwrite/Platform/Modules/Projects/Http/DevKeys/Update.php
  • src/Appwrite/Platform/Modules/Projects/Http/Projects/Labels/Update.php
  • src/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Delete.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Duplicate/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Status/Update.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Sites/Deployment/Update.php
  • src/Appwrite/Platform/Modules/Sites/Http/Variables/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Variables/Delete.php
  • src/Appwrite/Platform/Modules/Sites/Http/Variables/Update.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Create.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Delete.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Status/Update.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Update.php
  • src/Appwrite/Platform/Modules/Teams/Http/Teams/Name/Update.php
  • src/Appwrite/Platform/Modules/VCS/Http/GitHub/Authorize/External/Update.php
  • src/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.php
  • src/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.php
  • src/Appwrite/Platform/Modules/VCS/Http/GitHub/Events/Create.php
  • src/Appwrite/Platform/Modules/VCS/Http/Installations/Repositories/Create.php
  • src/Appwrite/Platform/Tasks/Interval.php
  • src/Appwrite/Platform/Workers/Certificates.php
  • src/Appwrite/Platform/Workers/Messaging.php
  • src/Appwrite/Platform/Workers/Migrations.php
  • src/Appwrite/Platform/Workers/Webhooks.php
🚧 Files skipped from review as they are similar to previous changes (30)
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Update.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Status/Update.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Delete.php
  • src/Appwrite/Platform/Modules/VCS/Http/Installations/Repositories/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Duplicate/Create.php
  • src/Appwrite/Platform/Workers/Webhooks.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Delete.php
  • src/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.php
  • src/Appwrite/Platform/Modules/Projects/Http/DevKeys/Update.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Update.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php
  • src/Appwrite/Platform/Workers/Messaging.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.php
  • app/init/resources.php
  • src/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Delete.php
  • src/Appwrite/Platform/Modules/VCS/Http/GitHub/Events/Create.php
  • src/Appwrite/Platform/Modules/Databases/Workers/Databases.php
  • src/Appwrite/Platform/Workers/Migrations.php
  • app/controllers/api/messaging.php
  • src/Appwrite/Platform/Modules/Compute/Base.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php
  • src/Appwrite/Platform/Workers/Certificates.php
  • src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Delete.php
  • src/Appwrite/Platform/Modules/Teams/Http/Teams/Name/Update.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.php
  • CLAUDE.md
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.php

@ChiragAgg5k ChiragAgg5k force-pushed the optimize-update-document-calls branch from 1fefbaa to da4d83e Compare March 6, 2026 11:30
Optimize updateDocument() calls across the codebase to pass only changed
attributes as sparse Document objects rather than full documents. This is
more efficient because updateDocument() internally performs array_merge().

Changes:
- Updated 58 files to use sparse Document objects
- Added Performance Patterns section to AGENTS.md with optimization guidelines
- Applied pattern to Workers, Functions, Sites, Teams, VCS modules
- Updated app/controllers/api files (account, users, messaging)
- Updated app infrastructure files (realtime, general, init/resources, shared/api)

Exceptions maintained:
- Migration files (need full document updates by design)
- Cases with 6+ attributes (marginal benefit)
- Complex nested relationship logic
@ChiragAgg5k ChiragAgg5k force-pushed the optimize-update-document-calls branch from da4d83e to 8b026d3 Compare March 6, 2026 11:35
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (5)
app/controllers/api/users.php (3)

1579-1582: ⚠️ Potential issue | 🟠 Major

Missing search field update breaks user discovery.

The phone field is part of the user's search index. After this sparse update, users won't be findable by their new phone via /v1/users?search=.

Proposed fix
 $user
     ->setAttribute('phone', $number)
-    ->setAttribute('phoneVerification', false)
+    ->setAttribute('phoneVerification', false)
+    ->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('email'), $number, $user->getAttribute('name')]))
 ;

 // ... later in the try block:
 $user = $dbForProject->updateDocument('users', $user->getId(), new Document([
     'phone' => $user->getAttribute('phone'),
     'phoneVerification' => $user->getAttribute('phoneVerification'),
+    'search' => $user->getAttribute('search'),
 ]));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/controllers/api/users.php` around lines 1579 - 1582, The update call to
$dbForProject->updateDocument('users', $user->getId(), new Document([...]))
omits the user's `search` field so the new phone won't be indexed; modify that
Document to also set the `search` field (e.g. include the updated phone value
from $user->getAttribute('phone') in the `search` payload) when updating the
'users' document so searches by phone continue to work.

1481-1489: ⚠️ Potential issue | 🟠 Major

Missing search field update breaks user discovery.

The email field is part of the user's search index. After this sparse update, users won't be findable by their new email via /v1/users?search=.

Proposed fix
 $user
     ->setAttribute('email', $email)
     ->setAttribute('emailVerification', false)
     ->setAttribute('emailCanonical', $emailCanonical?->getCanonical())
     ->setAttribute('emailIsCanonical', $emailCanonical?->isCanonicalSupported())
     ->setAttribute('emailIsCorporate', $emailCanonical?->isCorporate())
     ->setAttribute('emailIsDisposable', $emailCanonical?->isDisposable())
-    ->setAttribute('emailIsFree', $emailCanonical?->isFree())
+    ->setAttribute('emailIsFree', $emailCanonical?->isFree())
+    ->setAttribute('search', implode(' ', [$user->getId(), $email, $user->getAttribute('phone'), $user->getAttribute('name')]))
 ;

 try {
     $user = $dbForProject->updateDocument('users', $user->getId(), new Document([
         'email' => $user->getAttribute('email'),
         'emailVerification' => $user->getAttribute('emailVerification'),
         'emailCanonical' => $user->getAttribute('emailCanonical'),
         'emailIsCanonical' => $user->getAttribute('emailIsCanonical'),
         'emailIsCorporate' => $user->getAttribute('emailIsCorporate'),
         'emailIsDisposable' => $user->getAttribute('emailIsDisposable'),
         'emailIsFree' => $user->getAttribute('emailIsFree'),
+        'search' => $user->getAttribute('search'),
     ]));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/controllers/api/users.php` around lines 1481 - 1489, The sparse update to
the users document omits the user's searchable "search" field so searches by new
email will fail; inside the call to $dbForProject->updateDocument('users',
$user->getId(), new Document([...])) include an updated 'search' value (e.g.
rebuild or merge $user->getAttribute('search') and the new email string) so the
user's email is present in the search index after the update; update the
Document payload in this block to set 'search' appropriately using the $user
variable and existing search attributes.

1290-1292: ⚠️ Potential issue | 🟠 Major

Missing search field update breaks user discovery.

The name field is part of the user's search index (see line 161: 'search' => implode(' ', [$userId, $email, $phone, $name])). After this sparse update, users won't be findable by their new name via /v1/users?search=.

Proposed fix
 $user->setAttribute('name', $name);
+$user->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('email'), $user->getAttribute('phone'), $name]));

-$user = $dbForProject->updateDocument('users', $user->getId(), new Document(['name' => $user->getAttribute('name')]));
+$user = $dbForProject->updateDocument('users', $user->getId(), new Document([
+    'name' => $user->getAttribute('name'),
+    'search' => $user->getAttribute('search'),
+]));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/controllers/api/users.php` around lines 1290 - 1292, The update currently
only writes the 'name' field which leaves the user's 'search' index stale (the
code earlier builds 'search' from userId, email, phone, name). When updating the
user in updateDocument('users', ...), also set the 'search' field to the
concatenation used elsewhere: build the new search string from the user's id and
current email, phone and the new name (use $user->getId(),
$user->getAttribute('email'), $user->getAttribute('phone'), and the updated
name), and pass both 'name' and 'search' in the Document passed to
updateDocument so searches reflect the new name.
app/controllers/api/account.php (2)

4521-4525: ⚠️ Potential issue | 🟠 Major

Only write expired when this request resets it.

createPushTarget() does not initialize expired, so Line 4523 can still write back null here. Build the sparse payload incrementally and add expired only in the branch that sets it.

💡 Suggested fix
-        $target = $dbForProject->updateDocument('targets', $target->getId(), new Document([
-            'identifier' => $target->getAttribute('identifier'),
-            'expired' => $target->getAttribute('expired'),
-            'name' => $target->getAttribute('name'),
-        ]));
+        $updates = [
+            'name' => $target->getAttribute('name'),
+        ];
+
+        if ($identifier) {
+            $updates['identifier'] = $target->getAttribute('identifier');
+            $updates['expired'] = false;
+        }
+
+        $target = $dbForProject->updateDocument('targets', $target->getId(), new Document($updates));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/controllers/api/account.php` around lines 4521 - 4525, The updateDocument
call currently writes 'expired' unconditionally, which may persist null from
createPushTarget(); modify the payload construction before calling
$dbForProject->updateDocument('targets', $target->getId(), ...) to build a
sparse associative array/document incrementally (e.g. $patch =
['identifier'=>..., 'name'=>...]) and only add 'expired' to $patch when the
request explicitly resets/sets it (the branch in createPushTarget()/request
handling that intends to change expired). Then pass that sparse $patch/document
to updateDocument so 'expired' is not written when unset.

291-294: ⚠️ Potential issue | 🟠 Major

Only persist the verification flag that actually changed.

This still writes both flags back. For email and magic-link flows, phoneVerification is not guaranteed to exist on the loaded user, so this sparse update can overwrite it with null.

💡 Suggested fix
-        $dbForProject->updateDocument('users', $user->getId(), new Document([
-            'emailVerification' => $user->getAttribute('emailVerification'),
-            'phoneVerification' => $user->getAttribute('phoneVerification'),
-        ]));
+        $updates = [];
+
+        if (\in_array($verifiedToken->getAttribute('type'), [TOKEN_TYPE_MAGIC_URL, TOKEN_TYPE_EMAIL], true)) {
+            $updates['emailVerification'] = true;
+        }
+
+        if ($verifiedToken->getAttribute('type') === TOKEN_TYPE_PHONE) {
+            $updates['phoneVerification'] = true;
+        }
+
+        if ($updates !== []) {
+            $dbForProject->updateDocument('users', $user->getId(), new Document($updates));
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/controllers/api/account.php` around lines 291 - 294, The update currently
always writes both 'emailVerification' and 'phoneVerification' which can
overwrite missing fields with null; instead build an updates array that only
includes keys that actually changed or are present on the loaded user and pass
that to $dbForProject->updateDocument('users', $user->getId(), new
Document(...)). Use $user->getAttribute('emailVerification') and
$user->getAttribute('phoneVerification') (or an existence/changed check) to
conditionally add each field so updateDocument and Document are only passed the
verification flag(s) that should be persisted.
🧹 Nitpick comments (6)
src/Appwrite/Platform/Modules/Teams/Http/Teams/Name/Update.php (1)

67-71: Dead code: setAttribute calls on lines 67-69 are now unused.

These lines modify $team, but that object is never used afterward—line 71 creates a new sparse Document with the same values and reassigns $team to the result of updateDocument(). The original $team modifications are discarded.

Remove the redundant setAttribute calls to avoid confusion:

♻️ Proposed fix
         if ($team->isEmpty()) {
             throw new Exception(Exception::TEAM_NOT_FOUND);
         }

-        $team
-            ->setAttribute('name', $name)
-            ->setAttribute('search', implode(' ', [$teamId, $name]));
-
-        $team = $dbForProject->updateDocument('teams', $team->getId(), new Document(['name' => $name, 'search' => implode(' ', [$teamId, $name])]));
+        $team = $dbForProject->updateDocument('teams', $team->getId(), new Document([
+            'name' => $name,
+            'search' => implode(' ', [$teamId, $name])
+        ]));

         $queueForEvents->setParam('teamId', $team->getId());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Appwrite/Platform/Modules/Teams/Http/Teams/Name/Update.php` around lines
67 - 71, The three redundant mutating calls to $team->setAttribute('name',
$name) and ->setAttribute('search', ...) are dead code because $team is
immediately overwritten by the result of $dbForProject->updateDocument(...);
remove those setAttribute lines and keep the existing call to
$dbForProject->updateDocument('teams', $team->getId(), new Document(['name' =>
$name, 'search' => implode(' ', [$teamId, $name])])) so the update is performed
only once; if the original intent was to update the in-memory $team before
persisting, instead pass the modified $team to updateDocument or merge its
attributes into the Document, otherwise simply delete the setAttribute calls.
src/Appwrite/Platform/Modules/VCS/Http/Installations/Repositories/Create.php (1)

112-121: Same redundant attribute-setting pattern.

Similar to the callback handler, you can pass values directly to the sparse Document instead of setting them on $installation first:

♻️ Suggested simplification
-                $installation = $installation
-                    ->setAttribute('personalAccessToken', $accessToken)
-                    ->setAttribute('personalRefreshToken', $refreshToken)
-                    ->setAttribute('personalAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry('')));
-
-                $dbForPlatform->updateDocument('installations', $installation->getId(), new Document([
-                    'personalAccessToken' => $installation->getAttribute('personalAccessToken'),
-                    'personalRefreshToken' => $installation->getAttribute('personalRefreshToken'),
-                    'personalAccessTokenExpiry' => $installation->getAttribute('personalAccessTokenExpiry'),
-                ]));
+                $accessTokenExpiry = DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry(''));
+                
+                $installation = $dbForPlatform->updateDocument('installations', $installation->getId(), new Document([
+                    'personalAccessToken' => $accessToken,
+                    'personalRefreshToken' => $refreshToken,
+                    'personalAccessTokenExpiry' => $accessTokenExpiry,
+                ]));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Appwrite/Platform/Modules/VCS/Http/Installations/Repositories/Create.php`
around lines 112 - 121, The code redundantly calls setAttribute on $installation
before constructing the sparse Document; instead, remove the intermediate
setAttribute calls and pass the values directly into the Document passed to
dbForPlatform->updateDocument. Locate the block using
$installation->setAttribute('personalAccessToken', ...),
setAttribute('personalRefreshToken', ...),
setAttribute('personalAccessTokenExpiry', ...) and replace it by building the
new Document with 'personalAccessToken' => $accessToken, 'personalRefreshToken'
=> $refreshToken, and 'personalAccessTokenExpiry' => DateTime::addSeconds(new
\DateTime(), (int)$oauth2->getAccessTokenExpiry('')), then call
$dbForPlatform->updateDocument('installations', $installation->getId(), new
Document([...])) without mutating $installation via setAttribute.
src/Appwrite/Platform/Modules/Sites/Http/Deployments/Duplicate/Create.php (1)

140-150: Redundant attribute setting before sparse update.

The values are available directly from $deployment:

♻️ Suggested simplification
-        $site = $site
-            ->setAttribute('latestDeploymentId', $deployment->getId())
-            ->setAttribute('latestDeploymentInternalId', $deployment->getSequence())
-            ->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt())
-            ->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
-        $dbForProject->updateDocument('sites', $site->getId(), new Document([
-            'latestDeploymentId' => $site->getAttribute('latestDeploymentId'),
-            'latestDeploymentInternalId' => $site->getAttribute('latestDeploymentInternalId'),
-            'latestDeploymentCreatedAt' => $site->getAttribute('latestDeploymentCreatedAt'),
-            'latestDeploymentStatus' => $site->getAttribute('latestDeploymentStatus'),
-        ]));
+        $dbForProject->updateDocument('sites', $site->getId(), new Document([
+            'latestDeploymentId' => $deployment->getId(),
+            'latestDeploymentInternalId' => $deployment->getSequence(),
+            'latestDeploymentCreatedAt' => $deployment->getCreatedAt(),
+            'latestDeploymentStatus' => $deployment->getAttribute('status', ''),
+        ]));

Note: If $site is used later with the updated attributes, keep the setAttribute calls but consider removing the redundant getAttribute reads in the Document constructor.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Appwrite/Platform/Modules/Sites/Http/Deployments/Duplicate/Create.php`
around lines 140 - 150, The code redundantly sets attributes on $site and then
reads them back via $site->getAttribute when constructing the Document for
dbForProject->updateDocument; instead either remove the intermediate
setAttribute calls and pass values directly from $deployment (use
$deployment->getId(), ->getSequence(), ->getCreatedAt(),
->getAttribute('status')) into the Document constructor, or if $site is needed
later keep the setAttribute calls but change the Document constructor to use
$site->getAttribute only once (or better yet use the $deployment values
directly) to avoid redundant reads; update the block around setAttribute,
updateDocument, and Document to reflect this choice.
src/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.php (1)

324-334: Redundant attribute setting before sparse update.

Values are available directly from $deployment:

♻️ Suggested simplification
-                $resource = $resource
-                    ->setAttribute('latestDeploymentId', $deployment->getId())
-                    ->setAttribute('latestDeploymentInternalId', $deployment->getSequence())
-                    ->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt())
-                    ->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
-                $authorization->skip(fn () => $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), new Document([
-                    'latestDeploymentId' => $resource->getAttribute('latestDeploymentId'),
-                    'latestDeploymentInternalId' => $resource->getAttribute('latestDeploymentInternalId'),
-                    'latestDeploymentCreatedAt' => $resource->getAttribute('latestDeploymentCreatedAt'),
-                    'latestDeploymentStatus' => $resource->getAttribute('latestDeploymentStatus'),
-                ])));
+                $authorization->skip(fn () => $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), new Document([
+                    'latestDeploymentId' => $deployment->getId(),
+                    'latestDeploymentInternalId' => $deployment->getSequence(),
+                    'latestDeploymentCreatedAt' => $deployment->getCreatedAt(),
+                    'latestDeploymentStatus' => $deployment->getAttribute('status', ''),
+                ])));

If $resource attributes are needed later in the method (e.g., for the VCS comment/preview logic), retain the setAttribute calls.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.php` around lines
324 - 334, The block redundantly writes deployment values into $resource before
doing a sparse update; either remove the setAttribute(...) chain and pass values
directly from $deployment to the authorization->skip(...
$dbForProject->updateDocument(...)) call, or if later code relies on $resource
having those attributes, keep the setAttribute calls—so update the sparse update
to use $deployment->getId(), $deployment->getSequence(),
$deployment->getCreatedAt(), and $deployment->getAttribute('status','') directly
(in the call inside authorization->skip) or leave the setAttribute chain intact
if $resource attributes are used later in this method (e.g., for VCS
comment/preview logic).
src/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.php (1)

149-161: Redundant attribute setting before sparse update.

Lines 149-154 set attributes on $installation, then lines 156-160 read them back via getAttribute(). Since you're using a sparse update anyway, pass the values directly to avoid the intermediate mutation:

♻️ Suggested simplification
-            $installation = $installation
-                    ->setAttribute('organization', $owner)
-                    ->setAttribute('personal', $personal)
-                    ->setAttribute('personalRefreshToken', $refreshToken)
-                    ->setAttribute('personalAccessToken', $accessToken)
-                    ->setAttribute('personalAccessTokenExpiry', $accessTokenExpiry);
-                $installation = $dbForPlatform->updateDocument('installations', $installation->getId(), new Document([
-                    'organization' => $installation->getAttribute('organization'),
-                    'personal' => $installation->getAttribute('personal'),
-                    'personalRefreshToken' => $installation->getAttribute('personalRefreshToken'),
-                    'personalAccessToken' => $installation->getAttribute('personalAccessToken'),
-                    'personalAccessTokenExpiry' => $installation->getAttribute('personalAccessTokenExpiry'),
-                ]));
+                $installation = $dbForPlatform->updateDocument('installations', $installation->getId(), new Document([
+                    'organization' => $owner,
+                    'personal' => $personal,
+                    'personalRefreshToken' => $refreshToken,
+                    'personalAccessToken' => $accessToken,
+                    'personalAccessTokenExpiry' => $accessTokenExpiry,
+                ]));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.php` around lines
149 - 161, The code redundantly calls setAttribute on $installation then
immediately reads them back for a sparse update; remove the intermediate
mutations and directly pass the intended values ($owner, $personal,
$refreshToken, $accessToken, $accessTokenExpiry) into the
dbForPlatform->updateDocument call (using $installation->getId() to identify the
record), eliminating the setAttribute(...) lines and replacing the new
Document([...]) payload to use those variables directly.
src/Appwrite/Platform/Modules/Functions/Workers/Builds.php (1)

282-285: Consider extracting a helper for deployment patch + realtime emit.

This worker now repeats the same updateDocument(... new Document([...])) and queueForRealtime->setPayload(...)->trigger() sequence many times. A small helper would reduce noise and make future status/log changes easier to keep consistent.

Also applies to: 372-376, 490-496, 544-548, 563-565, 841-843, 929-936, 952-955, 1198-1203, 1510-1512, 1531-1533

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Appwrite/Platform/Modules/Functions/Workers/Builds.php` around lines 282
- 285, Extract a small helper (e.g., patchDeploymentAndEmit or
updateDeploymentWithRealtime) that accepts the db client ($dbForProject), the
Deployment Document ($deployment or $deployment->getId()), an associative array
of fields to set (or specific params like status, timestamps, logs), and the
queueForRealtime instance; inside the helper call
$dbForProject->updateDocument('deployments', $deployment->getId(), new
Document([...])) and immediately call
$queueForRealtime->setPayload(...)->trigger() with the same payload, then
replace all repeated sequences (the calls around updateDocument and
queueForRealtime->setPayload->trigger at the locations referencing
updateDocument, $deployment, and queueForRealtime) with calls to this new helper
to keep behavior identical while centralizing payload construction and realtime
emission.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@app/controllers/api/account.php`:
- Around line 4521-4525: The updateDocument call currently writes 'expired'
unconditionally, which may persist null from createPushTarget(); modify the
payload construction before calling $dbForProject->updateDocument('targets',
$target->getId(), ...) to build a sparse associative array/document
incrementally (e.g. $patch = ['identifier'=>..., 'name'=>...]) and only add
'expired' to $patch when the request explicitly resets/sets it (the branch in
createPushTarget()/request handling that intends to change expired). Then pass
that sparse $patch/document to updateDocument so 'expired' is not written when
unset.
- Around line 291-294: The update currently always writes both
'emailVerification' and 'phoneVerification' which can overwrite missing fields
with null; instead build an updates array that only includes keys that actually
changed or are present on the loaded user and pass that to
$dbForProject->updateDocument('users', $user->getId(), new Document(...)). Use
$user->getAttribute('emailVerification') and
$user->getAttribute('phoneVerification') (or an existence/changed check) to
conditionally add each field so updateDocument and Document are only passed the
verification flag(s) that should be persisted.

In `@app/controllers/api/users.php`:
- Around line 1579-1582: The update call to
$dbForProject->updateDocument('users', $user->getId(), new Document([...]))
omits the user's `search` field so the new phone won't be indexed; modify that
Document to also set the `search` field (e.g. include the updated phone value
from $user->getAttribute('phone') in the `search` payload) when updating the
'users' document so searches by phone continue to work.
- Around line 1481-1489: The sparse update to the users document omits the
user's searchable "search" field so searches by new email will fail; inside the
call to $dbForProject->updateDocument('users', $user->getId(), new
Document([...])) include an updated 'search' value (e.g. rebuild or merge
$user->getAttribute('search') and the new email string) so the user's email is
present in the search index after the update; update the Document payload in
this block to set 'search' appropriately using the $user variable and existing
search attributes.
- Around line 1290-1292: The update currently only writes the 'name' field which
leaves the user's 'search' index stale (the code earlier builds 'search' from
userId, email, phone, name). When updating the user in updateDocument('users',
...), also set the 'search' field to the concatenation used elsewhere: build the
new search string from the user's id and current email, phone and the new name
(use $user->getId(), $user->getAttribute('email'), $user->getAttribute('phone'),
and the updated name), and pass both 'name' and 'search' in the Document passed
to updateDocument so searches reflect the new name.

---

Nitpick comments:
In `@src/Appwrite/Platform/Modules/Functions/Workers/Builds.php`:
- Around line 282-285: Extract a small helper (e.g., patchDeploymentAndEmit or
updateDeploymentWithRealtime) that accepts the db client ($dbForProject), the
Deployment Document ($deployment or $deployment->getId()), an associative array
of fields to set (or specific params like status, timestamps, logs), and the
queueForRealtime instance; inside the helper call
$dbForProject->updateDocument('deployments', $deployment->getId(), new
Document([...])) and immediately call
$queueForRealtime->setPayload(...)->trigger() with the same payload, then
replace all repeated sequences (the calls around updateDocument and
queueForRealtime->setPayload->trigger at the locations referencing
updateDocument, $deployment, and queueForRealtime) with calls to this new helper
to keep behavior identical while centralizing payload construction and realtime
emission.

In `@src/Appwrite/Platform/Modules/Sites/Http/Deployments/Duplicate/Create.php`:
- Around line 140-150: The code redundantly sets attributes on $site and then
reads them back via $site->getAttribute when constructing the Document for
dbForProject->updateDocument; instead either remove the intermediate
setAttribute calls and pass values directly from $deployment (use
$deployment->getId(), ->getSequence(), ->getCreatedAt(),
->getAttribute('status')) into the Document constructor, or if $site is needed
later keep the setAttribute calls but change the Document constructor to use
$site->getAttribute only once (or better yet use the $deployment values
directly) to avoid redundant reads; update the block around setAttribute,
updateDocument, and Document to reflect this choice.

In `@src/Appwrite/Platform/Modules/Teams/Http/Teams/Name/Update.php`:
- Around line 67-71: The three redundant mutating calls to
$team->setAttribute('name', $name) and ->setAttribute('search', ...) are dead
code because $team is immediately overwritten by the result of
$dbForProject->updateDocument(...); remove those setAttribute lines and keep the
existing call to $dbForProject->updateDocument('teams', $team->getId(), new
Document(['name' => $name, 'search' => implode(' ', [$teamId, $name])])) so the
update is performed only once; if the original intent was to update the
in-memory $team before persisting, instead pass the modified $team to
updateDocument or merge its attributes into the Document, otherwise simply
delete the setAttribute calls.

In `@src/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.php`:
- Around line 149-161: The code redundantly calls setAttribute on $installation
then immediately reads them back for a sparse update; remove the intermediate
mutations and directly pass the intended values ($owner, $personal,
$refreshToken, $accessToken, $accessTokenExpiry) into the
dbForPlatform->updateDocument call (using $installation->getId() to identify the
record), eliminating the setAttribute(...) lines and replacing the new
Document([...]) payload to use those variables directly.

In `@src/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.php`:
- Around line 324-334: The block redundantly writes deployment values into
$resource before doing a sparse update; either remove the setAttribute(...)
chain and pass values directly from $deployment to the authorization->skip(...
$dbForProject->updateDocument(...)) call, or if later code relies on $resource
having those attributes, keep the setAttribute calls—so update the sparse update
to use $deployment->getId(), $deployment->getSequence(),
$deployment->getCreatedAt(), and $deployment->getAttribute('status','') directly
(in the call inside authorization->skip) or leave the setAttribute chain intact
if $resource attributes are used later in this method (e.g., for VCS
comment/preview logic).

In
`@src/Appwrite/Platform/Modules/VCS/Http/Installations/Repositories/Create.php`:
- Around line 112-121: The code redundantly calls setAttribute on $installation
before constructing the sparse Document; instead, remove the intermediate
setAttribute calls and pass the values directly into the Document passed to
dbForPlatform->updateDocument. Locate the block using
$installation->setAttribute('personalAccessToken', ...),
setAttribute('personalRefreshToken', ...),
setAttribute('personalAccessTokenExpiry', ...) and replace it by building the
new Document with 'personalAccessToken' => $accessToken, 'personalRefreshToken'
=> $refreshToken, and 'personalAccessTokenExpiry' => DateTime::addSeconds(new
\DateTime(), (int)$oauth2->getAccessTokenExpiry('')), then call
$dbForPlatform->updateDocument('installations', $installation->getId(), new
Document([...])) without mutating $installation via setAttribute.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 67c99bb8-307d-424b-a537-43657f1a2e72

📥 Commits

Reviewing files that changed from the base of the PR and between 99dda0e and da4d83e.

📒 Files selected for processing (59)
  • AGENTS.md
  • CLAUDE.md
  • app/controllers/api/account.php
  • app/controllers/api/messaging.php
  • app/controllers/api/users.php
  • app/controllers/general.php
  • app/controllers/shared/api.php
  • app/init/resources.php
  • app/realtime.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Authenticators/Update.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/RecoveryCodes/Create.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/RecoveryCodes/Update.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Update.php
  • src/Appwrite/Platform/Modules/Compute/Base.php
  • src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Delete.php
  • src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Delete.php
  • src/Appwrite/Platform/Modules/Databases/Workers/Databases.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Delete.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Status/Update.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.php
  • src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Functions/Delete.php
  • src/Appwrite/Platform/Modules/Functions/Http/Functions/Deployment/Update.php
  • src/Appwrite/Platform/Modules/Functions/Http/Variables/Create.php
  • src/Appwrite/Platform/Modules/Functions/Http/Variables/Delete.php
  • src/Appwrite/Platform/Modules/Functions/Http/Variables/Update.php
  • src/Appwrite/Platform/Modules/Functions/Workers/Builds.php
  • src/Appwrite/Platform/Modules/Projects/Http/DevKeys/Update.php
  • src/Appwrite/Platform/Modules/Projects/Http/Projects/Labels/Update.php
  • src/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Delete.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Duplicate/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Status/Update.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Sites/Deployment/Update.php
  • src/Appwrite/Platform/Modules/Sites/Http/Variables/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Variables/Delete.php
  • src/Appwrite/Platform/Modules/Sites/Http/Variables/Update.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Create.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Delete.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Status/Update.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Update.php
  • src/Appwrite/Platform/Modules/Teams/Http/Teams/Name/Update.php
  • src/Appwrite/Platform/Modules/VCS/Http/GitHub/Authorize/External/Update.php
  • src/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.php
  • src/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.php
  • src/Appwrite/Platform/Modules/VCS/Http/GitHub/Events/Create.php
  • src/Appwrite/Platform/Modules/VCS/Http/Installations/Repositories/Create.php
  • src/Appwrite/Platform/Tasks/Interval.php
  • src/Appwrite/Platform/Workers/Certificates.php
  • src/Appwrite/Platform/Workers/Messaging.php
  • src/Appwrite/Platform/Workers/Migrations.php
  • src/Appwrite/Platform/Workers/Webhooks.php
🚧 Files skipped from review as they are similar to previous changes (25)
  • CLAUDE.md
  • app/controllers/api/messaging.php
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Status/Update.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Update.php
  • src/Appwrite/Platform/Modules/Functions/Http/Functions/Deployment/Update.php
  • src/Appwrite/Platform/Workers/Certificates.php
  • src/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Status/Update.php
  • src/Appwrite/Platform/Modules/Functions/Http/Deployments/Delete.php
  • src/Appwrite/Platform/Modules/Functions/Http/Variables/Update.php
  • src/Appwrite/Platform/Modules/Functions/Http/Variables/Delete.php
  • src/Appwrite/Platform/Modules/Teams/Http/Memberships/Update.php
  • AGENTS.md
  • src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php
  • src/Appwrite/Platform/Workers/Messaging.php
  • src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php
  • src/Appwrite/Platform/Modules/Databases/Workers/Databases.php
  • src/Appwrite/Platform/Modules/Projects/Http/Projects/Labels/Update.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/RecoveryCodes/Create.php
  • src/Appwrite/Platform/Modules/Sites/Http/Variables/Update.php
  • src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Attributes/Delete.php
  • src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Indexes/Delete.php
  • app/realtime.php
  • src/Appwrite/Platform/Modules/Sites/Http/Variables/Delete.php
  • src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Authenticators/Update.php

@ChiragAgg5k ChiragAgg5k merged commit 0d3d14d into 1.8.x Mar 6, 2026
82 checks passed
@ChiragAgg5k ChiragAgg5k deleted the optimize-update-document-calls branch March 6, 2026 15:47
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.

3 participants