- Database library: createCollection() accepts metadata parameter
for arbitrary key-value pairs on the _metadata document
- Appwrite: passes externalId (user-facing collection ID) when
creating collections via createCollection(metadata: ['externalId' => $collectionId])
- Metadata decorator: reads $collection->getAttribute('externalId')
directly from the collection metadata — zero queries, zero caches,
zero overhead
- getDatabasesDB: removed dbForProject dependency and all mapping logic
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The UserEvents hook copied event state at resource-resolution time
(before the init hook set the user), causing webhooks to receive null
user IDs. Now stores the source queueForEvents as a reference and
copies at fire-time when the user Document has been populated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Endpoints pass the collection document they already have to
getDatabasesDB. The factory registers the single collection mapping
on the Metadata decorator — no bulk find queries, no static cache,
no dbForProject dependency.
The decorator is now purely stateless with zero database overhead.
Collection ID resolution uses only the pre-registered mapping from
the endpoint's request parameters.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Query::contains() is deprecated for array attributes in the new query
library. The deprecation warnings were spamming worker logs and
potentially slowing attribute processing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The decorator no longer queries dbForProject directly. Instead,
getDatabasesDB pre-populates the mapping via setCollectionId() and
uses a static cache (Metadata::getCachedMap) so the query runs at most
ONCE per database per Swoole worker process. The decorator is now
stateless — no database dependencies, just a pre-set map.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The mapping query was running once per getDatabasesDB call (once per
request). With static cache keyed by database sequence, the query runs
once per database per Swoole worker process. Subsequent requests reuse
the cached mapping. setCollectionId updates both instance and static
caches for newly created collections.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove processDocument() and all its calls — the decorator approach is
the intended design. The Metadata decorator lazily loads the collection
ID mapping from dbForProject on first use, wrapped in silent() to
prevent lifecycle hooks. Maps both relative (collection_N) and full
(database_M_collection_N) keys to user-facing IDs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Metadata decorator required a collection ID mapping query that added
overhead and caused Redis OOM in CI. Instead, stamp $databaseId and
$collectionId/$tableId directly in endpoint actions where user-facing
IDs are available from request parameters — matching the 1.9.x approach.
- Remove Metadata decorator from getDatabasesDB hooks
- Restore processDocument() on Documents/Action base class
- Add processDocument() calls in Get, Create, XList, Update, Upsert
- Simplify Metadata.php to stateless map-only decorator (no queries)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move the collection ID mapping query from the Metadata decorator to
getDatabasesDB where it runs once during resource init. The mapping
includes both relative keys (collection_N) and full keys
(database_M_collection_N) for dedicated mode compatibility.
The Metadata decorator is now purely stateless — no database queries,
no dbForProject dependency. It only uses the pre-set map from
setCollectionId().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The find() query on dbForProject triggers lifecycle hooks (Usage,
UserEvents, FunctionCache) that generate Redis messages, causing OOM
under CI parallel load. Wrapping in silent() prevents these hooks from
firing during the mapping query.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Console::warning() from utopia-php/cli is not available in HTTP/Swoole
context, causing a fatal error inside the catch block and breaking the
entire decorator flow.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Temporary debug logging to diagnose why collection ID mapping fails
in dedicated mode CI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a no-op protected method that Cloud can override to enforce
billing/block checks before processing git deployments. The hook
is called inside the foreach loop after project validation, so any
exception it throws is caught and logged as an error.
https://claude.ai/code/session_01HP1N9hHbqMzxm5QmaoGhyZ
The decorator receives full internal keys like 'database_2_collection_15'
but the mapping uses relative keys like 'collection_15'. Strip the
database prefix before looking up the mapping.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The silent() and skipValidation() wrappers may have prevented the find
query from executing correctly. Simplified to just authorization->skip()
which is sufficient to bypass permission checks.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move collection ID mapping query from getDatabasesDB init to lazy
loading inside the Metadata decorator. The query is wrapped in
silent() and skipValidation() to prevent triggering lifecycle hooks
(Usage, Events) that were contributing to Redis OOM in CI.
The mapping is loaded once on first need, not during resource init,
reducing overhead for requests that don't access document endpoints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move the collection mapping query from inside the Metadata decorator to
the getDatabasesDB resource factory. This avoids decorator-level
database queries that can fail in various contexts and cause cascading
issues.
The mapping is loaded once when getDatabasesDB creates the database
instance, and the results are passed to the Metadata decorator via
setCollectionId(). The decorator itself is now stateless with respect
to database queries.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace findOne per decoration with a single find query that loads all
collection sequence→ID mappings on first access. Caches the result for
the lifetime of the decorator instance. Silently falls back to internal
names on failure.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The decorator receives _metadata documents where $id is the internal
collection name (e.g. 'collection_5'), not the user-facing collection
ID. The old processDocument() got the user-facing ID from the endpoint.
Fix: extract the sequence number from the internal name, look up the
Appwrite collection document by $sequence to get the user-facing $id,
and cache the mapping.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use Utopia\Query\Schema\ForeignKeyAction instead of the non-existent
Utopia\Database\ForeignKeyAction.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The updateRelationship() method expects ForeignKeyAction enum but
Action.php was passing the raw string from options. Use
ForeignKeyAction::from() to convert.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Metadata decorator: look for relatedCollection inside options array
since new Attribute format nests it there
- Skip decorators during silenced operations to prevent $databaseId etc.
from leaking into internal write operations
- Fix aggregate test queries to use Query::count('*', 'total') instead
of Query::count('total') which treats 'total' as a column name
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- UserEvents hook: set project on events before triggering to fix null
project in function worker queue messages. Also use $this->project
directly for console check instead of $this->events->getProject()
which was null.
- Metadata decorator: stop removing $collection from documents since
the Response model layer already handles this in filter(). Removing it
in the decorator broke updateDocument and cursor pagination.
- Update utopia-php/migration to feat-query-lib branch with ColumnType
enum, Attribute objects, and removed Database::VAR_* constants.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Float attributes must use ColumnType::Double (value 'double') to match
the existing AttributeFloat response model condition and Range validator.
ColumnType::Float (value 'float') caused "Missing model" errors and
Range validation failures.
Also reverts Collection/Table response model rule keys from $databaseId
back to databaseId since the Metadata decorator skips _metadata
documents — collection responses use the non-prefixed key.
Updates utopia-php/database with Attribute validator Float case fix.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
castingAfter must run unconditionally in createDocument to convert
adapter-specific types (MongoDB UTCDateTime) to PHP types.
Also removes hardcoded project ID check in Executions worker that was
preventing execution upserts for a specific project.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Metadata decorator now skips _metadata collection documents to prevent
injected $databaseId/$collectionId attributes from leaking into SQL
UPDATE statements via getAttributes()
- Add Tenancy hook to dbForProject in shared mode (was missing, causing
401 on session verification)
- Update Collection/Table response models to use $databaseId with $
prefix matching the decorator
- Update utopia-php/database with MongoDB tenant null filter fix
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>