Compare commits

...
Author SHA1 Message Date
Jake Barnby 1f9172739e (revert): restore nproc for ParaTest — 3 processes didn't reduce flakiness 2026-04-03 03:21:55 +13:00
Jake BarnbyandClaude Opus 4.6 efa30731e9 (fix): reduce ParaTest processes from nproc to 3 to reduce CI contention
With $(nproc) (typically 4-6), parallel test processes overwhelm Redis
and the attribute worker, causing cascading failures from resource
contention. 3 processes reduces contention while maintaining reasonable
test throughput.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 02:21:49 +13:00
Jake BarnbyandClaude Opus 4.6 8032da148a (fix): update utopia-php/database with externalId in collection schema
The _metadata collection schema now includes externalId as an optional
string attribute, allowing createCollection(metadata: ['externalId' => ...])
to pass Structure validation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 00:43:59 +13:00
Jake BarnbyandClaude Opus 4.6 1b77a78121 (fix): store externalId on collection metadata, eliminate all mapping queries
- 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>
2026-04-02 21:59:25 +13:00
Jake BarnbyandClaude Opus 4.6 1b665fdb6f (fix): bulk-load related collection mappings instead of findOne by $sequence
The findOne by $sequence query fails validation because $sequence is an
internal id-type attribute. Instead, load ALL collections for the
database once on first relationship encounter and cache statically per
database per Swoole worker. No per-attribute queries needed.

Also fixes UUID sequence extraction — the previous explode('_') split
UUIDs incorrectly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 18:39:31 +13:00
Jake BarnbyandClaude Opus 4.6 f66150aa8b (fix): defer event copy in UserEvents to fire-time for correct user context
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>
2026-04-02 13:56:49 +13:00
Jake BarnbyandClaude Opus 4.6 48a01919bc (fix): add static cache for related collection lookups
The findOne query per relationship attribute ran on every getDatabasesDB
call. Static cache keyed by database+internal name caches results across
requests within the same Swoole worker process.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 04:17:23 +13:00
Jake BarnbyandClaude Opus 4.6 4d85ab0dcd (fix): fix related collection lookup by sequence, increase schema timeouts
- Related collection lookup used internal name as document ID, but
  Appwrite collections have user-facing IDs. Extract sequence from
  internal name and use findOne by $sequence instead.
- Increase all schema polling timeouts from 240s to 360s for CI
  dedicated mode parallel load.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 03:40:01 +13:00
Jake BarnbyandClaude Opus 4.6 61a623fcc0 (fix): inject dbForProject into getDatabasesDB for related collection lookups
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 03:14:15 +13:00
Jake BarnbyandClaude Opus 4.6 95552031ef (fix): register related collection ID mappings and update migration lib
- getDatabasesDB now registers mappings for related collections from
  relationship attributes, not just the primary collection. This fixes
  testOneToOneRelationship where nested documents showed internal names.
- Update utopia-php/migration to handle Index objects in createCollection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 03:01:58 +13:00
Jake BarnbyandClaude Opus 4.6 5740cecbe5 (fix): pass collection to getDatabasesDB for zero-query ID mapping
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>
2026-04-02 02:08:19 +13:00
Jake BarnbyandClaude Opus 4.6 080361338c (fix): replace deprecated Query::contains() with Query::containsAny()
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>
2026-04-02 01:23:18 +13:00
Jake Barnby 3f7261167b Merge remote-tracking branch 'origin/1.9.x' into feat-query-lib 2026-04-02 00:56:28 +13:00
Jake BarnbyandClaude Opus 4.6 5a0553a22f (fix): add error body to floatRange assertion for debugging
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 00:10:21 +13:00
Jake BarnbyandClaude Opus 4.6 d7b61e6afc (fix): move collection mapping query from decorator to getDatabasesDB with static cache
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>
2026-04-01 23:24:46 +13:00
Jake BarnbyandClaude Opus 4.6 99394e6dfa (fix): use static cache for collection mapping to avoid per-request queries
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>
2026-04-01 22:20:52 +13:00
Jake BarnbyandClaude Opus 4.6 dc77abf161 (fix): restore Metadata decorator with lazy collection mapping, remove processDocument
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>
2026-04-01 21:42:00 +13:00
Jake BarnbyandClaude Opus 4.6 088f1c2953 (fix): ensure float range min/max are cast to float with fallback
The formatOptions min/max may be integers after JSON decode or may be
on the attribute directly (from range filter). Use floatval() and check
both formatOptions and direct attribute keys.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 21:27:50 +13:00
Jake Barnby 2d093becc1 (chore): remove accidentally committed local files 2026-04-01 21:02:35 +13:00
Jake BarnbyandClaude Opus 4.6 311ca9377b (fix): add PHPStan type annotation for collectionsCache
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 21:02:24 +13:00
Jake BarnbyandClaude Opus 4.6 f69feec7ae (fix): increase Redis maxmemory from 512mb to 2048mb
The parallel E2E test load generates many queue messages across workers
(databases, webhooks, audits, functions, etc.). With 512mb, Redis runs
out of memory under CI load, causing worker crashes and cascading test
failures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 20:40:05 +13:00
Jake BarnbyandClaude Opus 4.6 09e8380328 (fix): relax processDocument collectionsCache PHPStan type annotation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 20:20:15 +13:00
Jake BarnbyandClaude Opus 4.6 1eb9c386f6 (fix): replace Metadata decorator with processDocument() in endpoint actions
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>
2026-04-01 19:58:07 +13:00
Jake BarnbyandClaude Opus 4.6 33b971a2c1 (fix): pre-compute collection mapping in getDatabasesDB, remove all queries from Metadata
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>
2026-04-01 18:56:14 +13:00
Jake BarnbyandClaude Opus 4.6 796179decd (fix): wrap collection mapping query in silent() to prevent hook events
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>
2026-04-01 17:36:04 +13:00
Jake BarnbyandClaude Opus 4.6 21e6715bd1 (fix): remove debug logging that breaks HTTP context
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>
2026-04-01 17:03:15 +13:00
Jake BarnbyandClaude Opus 4.6 b3ed5032cf (fix): add debug logging for collection mapping failures
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 16:35:57 +13:00
Jake BarnbyandClaude Opus 4.6 f4cb632dbc (fix): add debug logging for collection mapping failures
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>
2026-04-01 15:05:03 +13:00
Jake BarnbyandClaude Opus 4.6 7dca267727 (fix): strip database prefix from internal collection name before mapping lookup
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>
2026-04-01 14:40:31 +13:00
Jake BarnbyandClaude Opus 4.6 1c19cf5323 (fix): remove silent/skipValidation from Metadata mapping query
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>
2026-04-01 14:17:55 +13:00
Jake BarnbyandClaude Opus 4.6 234fd8da2f (fix): update utopia-php/database with tenant normalization and relationship validation
Includes:
- Tenant type normalization at boundary (restore strict comparison)
- Validate relationship $id in Document constructor
- Convert associative arrays to Documents in relationship hooks
- PHPStan type fixes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 13:54:15 +13:00
Jake BarnbyandClaude Opus 4.6 124002f419 (fix): lazy-load collection mapping with silent/skipValidation to reduce Redis pressure
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>
2026-04-01 13:19:52 +13:00
Jake BarnbyandClaude Opus 4.6 b05489bbc8 (fix): inject dbForProject into getDatabasesDB resource for collection mapping
The collection ID mapping query needs dbForProject which was missing
from the getDatabasesDB resource's dependency injection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 12:22:20 +13:00
Jake BarnbyandClaude Opus 4.6 dca59528df (fix): load collection ID mapping in getDatabasesDB instead of decorator
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>
2026-04-01 06:27:34 +13:00
Jake BarnbyandClaude Opus 4.6 f8b3e687a0 (fix): batch-load collection ID mapping instead of per-document queries
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>
2026-04-01 05:19:59 +13:00
Jake BarnbyandClaude Opus 4.6 ca99508c55 (fix): resolve internal collection names to user-facing IDs in Metadata decorator
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>
2026-04-01 04:55:52 +13:00
Jake BarnbyandClaude Opus 4.6 cf71c104e8 (fix): update utopia-php/database with loose tenant comparison and PHPStan fixes
Includes loose comparison (!=) for tenant checks in Collections.php to
handle int/string tenant value mismatches, and PHPStan type assertions
for Document constructor calls.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 04:16:48 +13:00
Jake BarnbyandClaude Opus 4.6 a097f5a987 (fix): fix inline relationship auto-ID, increase test timeouts
Updates utopia-php/database with fix for inline relationship data
without $id not being auto-converted to Document objects. The
Relationships hook now converts associative arrays to Documents before
type-checking, allowing auto-ID generation to work.

Also increases testEventTrigger timeout to 120s and Realtime concurrent
test timeout to 90s for CI resilience.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 03:54:11 +13:00
Jake BarnbyandClaude Opus 4.6 4cb70799ab (fix): register floatRange format against ColumnType::Double
Float attributes created via API use type 'double' (ColumnType::Double)
since the Float/Create endpoint was updated. The format registration
must match to pass format validation during attribute creation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 02:54:13 +13:00
Jake BarnbyandClaude Opus 4.6 e214a154b3 (chore): regenerate composer.lock timestamp
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 02:51:24 +13:00
Jake BarnbyandClaude Opus 4.6 5f4b91c350 (fix): correct ForeignKeyAction import namespace
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>
2026-04-01 02:03:21 +13:00
Jake BarnbyandClaude Opus 4.6 7520234e4a (fix): increase testEventTrigger timeout and harden Realtime coroutine test
- Functions testEventTrigger: increase assertEventually from 20s to 60s
  to accommodate shared mode queue latency and cold starts
- Realtime testConcurrentRealtimeTrafficCoroutines: already has 45s
  WebSocket timeout and TimeoutException handling for CI resilience

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 01:51:45 +13:00
Jake BarnbyandClaude Opus 4.6 e8f4137249 (fix): convert onDelete string to ForeignKeyAction enum in updateRelationship
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>
2026-04-01 01:35:13 +13:00
Jake BarnbyandClaude Opus 4.6 1b1b1dfc2b (fix): fix Metadata relatedCollection lookup, aggregate test aliases, and decorator silencing
- 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>
2026-04-01 00:56:19 +13:00
Jake BarnbyandClaude Opus 4.6 7fc153d344 (fix): set project on UserEvents, stop removing $collection, update migration lib
- 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>
2026-04-01 00:23:09 +13:00
Jake BarnbyandClaude Opus 4.6 44d7591dd1 (fix): use ColumnType::Double for float attributes and restore databaseId response key
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>
2026-03-31 23:30:09 +13:00
Jake BarnbyandClaude Opus 4.6 1f20a18587 (fix): update database with castingAfter fix and remove debug project filter
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>
2026-03-31 22:17:26 +13:00
Jake BarnbyandClaude Opus 4.6 2cf04e4b0f (fix): skip Metadata decorator for internal documents, add Tenancy hook, fix Mongo tenant null
- 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>
2026-03-31 21:43:50 +13:00
Jake BarnbyandClaude Opus 4.6 3a2e61224d (fix): update V17 test expectations to include query attributes and values
The V17 filter now correctly parses cursorAfter/search/isNotNull queries
with their full parameters (attribute, values) instead of stripping them
to method-only objects.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 21:02:12 +13:00
Jake BarnbyandClaude Opus 4.6 348750b6d1 (fix): handle Method enum in V17 str_contains and fix Proxy cleanup order
V17 filter's str_contains($method, '.') throws when $method is a Method
enum. Guard with is_string() since enum cases never contain dots.

Proxy test: delete rule before site to avoid cascade-delete race.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 20:49:07 +13:00
Jake BarnbyandClaude Opus 4.6 ead8e37699 (fix): convert V17 filter method string to Method enum and update database
V17 request filter switch compared raw strings against Method enum
cases which never matched in PHP 8.1+. Use Method::tryFrom() to convert
the parsed method string to the enum before the switch.

Also updates utopia-php/database with skipValidation for internal
metadata document updates.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 20:32:02 +13:00
Jake BarnbyandClaude Opus 4.6 a9fd6c6282 (fix): resolve PermissionType enum, Method enum, VectorsDB and database library issues
- Use PermissionType->value when constructing Permission objects (Documents/Create,
  Upsert, Transactions/Create, Storage/Files/Create)
- Convert Method enum to string in Realtime adapter implode and interpolation
- Use Attribute/Index object property access in VectorsDB Collections/Create
- Update utopia-php/database with Structure Float case, TenantFilter alias fix,
  and Document::getTenant() type cast removal

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 19:47:55 +13:00
Jake BarnbyandClaude Opus 4.6 7de44f16b1 (fix): update utopia-php/database with Mongo datetime casting fix
Handles numeric string (millisecond timestamp) values in castingBefore
by passing them directly to UTCDateTime instead of NativeDateTime.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 18:56:20 +13:00
Jake BarnbyandClaude Opus 4.6 6c5f028d24 (fix): use property access on GroupedQueries return type
Query::groupByType() now returns a GroupedQueries readonly class instead
of an associative array. All bracket access ($grouped['filters'], etc.)
must use arrow syntax ($grouped->filters).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 18:53:11 +13:00
Jake BarnbyandClaude Opus 4.6 3ce82ab29f (fix): update utopia-php/database with Pool write hook propagation fix
The Pool adapter was only syncing write hooks (Permissions, Tenancy) for
plural document operations (createDocuments, updateDocuments) but not
singular ones (createDocument, updateDocument). This caused permission
rows to never be inserted for single-document creates, making newly
created users invisible to subsequent queries on SQL databases.

Also removes stale PHPStan baseline entries and fixes redundant null
coalesce in FunctionCache.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 18:35:45 +13:00
Jake Barnby 26f70357c8 Merge remote-tracking branch 'origin/1.9.x' into feat-query-lib
# Conflicts:
#	app/config/collections/projects.php
#	src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Indexes/Create.php
#	src/Appwrite/Platform/Modules/Storage/Http/Buckets/Files/Update.php
2026-03-31 17:09:24 +13:00
Jake Barnby 0321ec5192 (fix): update library with Pool DML-only hook sync 2026-03-27 15:00:19 +13:00
Jake Barnby 5156aae427 (fix): update library with Pool write hook delegation 2026-03-27 14:40:37 +13:00
Jake Barnby 4b3de66e19 (fix): register Permissions hook on all DB instances in resources.php, worker.php, cli.php 2026-03-27 14:19:57 +13:00
Jake Barnby fd3df71660 (fix): update library with Permissions auto-registration 2026-03-27 14:09:50 +13:00
Jake Barnby 1f5ac9e323 (fix): filter Usage hook to only handle create/delete events, not reads 2026-03-27 13:50:41 +13:00
Jake Barnby bc1c1718b8 (fix): update V17 filter test expectation for new Query format 2026-03-27 12:54:17 +13:00
Jake Barnby 79afbdddf7 (fix): replace last Query::TYPE_SELECT in test 2026-03-27 12:35:50 +13:00
Jake Barnby 29390a6b34 (fix): update RuntimeQuery::compile -> prepare in test file 2026-03-27 12:19:21 +13:00
Jake Barnby 1e3b7c8d63 (fix): revert to empty syncWriteHooks, hooks only on getDatabasesDB 2026-03-27 12:02:57 +13:00
Jake Barnby 0755bf8e2c (fix): update library with Permissions hook type fix 2026-03-27 11:45:10 +13:00
Jake Barnby 8280bdaf9e (fix): remove manual Permissions/Tenancy from non-user-data DB instances 2026-03-27 11:31:47 +13:00
Jake Barnby 6ec07af3db (fix): rename RuntimeQuery::compile to prepare to avoid parent method conflict 2026-03-27 11:16:51 +13:00
Jake Barnby 8bab4b162f (fix): update library with Float type handling in all SQL adapters 2026-03-27 05:06:37 +13:00
Jake Barnby 9d4475526a (fix): update library with Float type handling 2026-03-27 04:54:11 +13:00
Jake Barnby 0ff9426d08 (fix): replace remaining array access on config Attribute objects 2026-03-27 04:42:05 +13:00
Jake Barnby 6869bef2ea (fix): update utopia-php/audit to fix Database::VAR_STRING constant removal 2026-03-27 04:29:55 +13:00
Jake Barnby 1f4a66a5e9 (fix): update library with Input constructor fix 2026-03-27 04:15:49 +13:00
Jake Barnby 6e797a60f5 (style): fix import ordering 2026-03-27 04:03:36 +13:00
Jake Barnby 135c337d08 (fix): update library with getPermissionsByType type fix 2026-03-27 03:52:16 +13:00
Jake Barnby dbe79bca27 (fix): update query library with missing Builder methods 2026-03-27 03:40:28 +13:00
Jake Barnby 8f4f739690 (fix): update library with getPDO return type fix 2026-03-27 03:25:54 +13:00
Jake Barnby 4a77a1ea57 (fix): update library with PDO object type fix 2026-03-27 03:14:00 +13:00
Jake Barnby a2a3e8fe27 (fix): update library with PDO compatibility fix, add missing SetType import 2026-03-27 03:01:37 +13:00
Jake Barnby 84d16aaa73 (fix): add missing Method import and fix double QueryQueryMethod replacement 2026-03-27 02:48:29 +13:00
Jake Barnby efa7ccc8fc (fix): alias Query\Method as QueryMethod to avoid SDK\Method collision 2026-03-27 02:37:41 +13:00
Jake Barnby 3e2504e75a (fix): replace all remaining Query::TYPE_* with Method enum across entire codebase 2026-03-27 02:26:51 +13:00
Jake Barnby 91727a4d11 (fix): replace remaining Document::SET_TYPE_* and Query::TYPE_* with enums 2026-03-27 02:15:24 +13:00
Jake Barnby 03e8e79424 (fix): remove dbForProject dependency from getDatabasesDB to prevent pool exhaustion 2026-03-27 02:02:19 +13:00
Jake Barnby 9d199ec0e5 (fix): pass ColumnType enum to Structure::addFormat and hasFormat 2026-03-27 01:51:07 +13:00
Jake Barnby 9cb517081a (fix): update database library with parse error fix 2026-03-27 01:31:43 +13:00
Jake Barnby 0aff210cab (chore): update composer.lock with latest database library 2026-03-27 01:22:02 +13:00
Jake Barnby 5101db4172 (fix): pass ColumnType string values to Structure::addFormat and hasFormat 2026-03-27 01:13:10 +13:00
Jake Barnby 85467e3d7e (fix): alias Spatial feature import to avoid name collision with Validator\Spatial 2026-03-27 01:05:12 +13:00
Jake Barnby a3b83a5d29 (test): add e2e tests for aggregations, joins, groupBy, and join security 2026-03-27 00:51:16 +13:00
Jake Barnby c791012bf7 (refactor): convert collection configs to Attribute/Index value objects 2026-03-27 00:51:08 +13:00
Jake Barnby 1230f8201f (feat): add hook pipeline and move usage/permissions/metadata to hooks 2026-03-27 00:50:59 +13:00
Jake Barnby 82c9c5322e (refactor): replace Database constants with enums and Capability checks 2026-03-27 00:50:49 +13:00
Jake Barnby e4dc9bcd86 (chore): update utopia-php/database to query-lib branch 2026-03-27 00:50:37 +13:00
179 changed files with 6718 additions and 9099 deletions
+7
View File
@@ -23,6 +23,7 @@ use Utopia\Console;
use Utopia\Database\Adapter\Pool as DatabasePool;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Hook\Permissions;
use Utopia\Database\Validator\Authorization;
use Utopia\DI\Dependency;
use Utopia\DSN\DSN;
@@ -132,6 +133,8 @@ $setResource('dbForPlatform', function ($pools, $cache, $authorization) {
throw new Exception('Console is not ready yet. Please try again later.');
}
$dbForPlatform->addHook(new Permissions());
return $dbForPlatform;
}, ['pools', 'cache', 'authorization']);
@@ -203,6 +206,8 @@ $setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $c
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId());
$database->addHook(new Permissions());
return $database;
};
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
@@ -227,6 +232,8 @@ $setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $a
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_TASK)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
$database->addHook(new Permissions());
// set tenant
if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant($project->getSequence());
File diff suppressed because it is too large Load Diff
+76 -114
View File
@@ -1,126 +1,88 @@
<?php
use Utopia\Database\Attribute;
use Utopia\Database\Database;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Index;
use Utopia\Query\Schema\ColumnType;
use Utopia\Query\Schema\IndexType;
return [
'collections' => [
'$collection' => ID::custom('databases'),
'$id' => ID::custom('collections'),
'$collection' => 'databases',
'$id' => 'collections',
'name' => 'Collections',
'attributes' => [
[
'$id' => ID::custom('databaseInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('databaseId'),
'type' => Database::VAR_STRING,
'signed' => true,
'size' => Database::LENGTH_KEY,
'format' => '',
'filters' => [],
'required' => true,
'default' => null,
'array' => false,
],
[
'$id' => ID::custom('name'),
'type' => Database::VAR_STRING,
'size' => 256,
'required' => true,
'signed' => true,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('enabled'),
'type' => Database::VAR_BOOLEAN,
'signed' => true,
'size' => 0,
'format' => '',
'filters' => [],
'required' => true,
'default' => null,
'array' => false,
],
[
'$id' => ID::custom('documentSecurity'),
'type' => Database::VAR_BOOLEAN,
'signed' => true,
'size' => 0,
'format' => '',
'filters' => [],
'required' => true,
'default' => null,
'array' => false,
],
[
'$id' => ID::custom('attributes'),
'type' => Database::VAR_STRING,
'size' => 1000000,
'required' => false,
'signed' => true,
'array' => false,
'filters' => ['subQueryAttributes'],
],
[
'$id' => ID::custom('indexes'),
'type' => Database::VAR_STRING,
'size' => 1000000,
'required' => false,
'signed' => true,
'array' => false,
'filters' => ['subQueryIndexes'],
],
[
'$id' => ID::custom('search'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
new Attribute(
key: 'databaseInternalId',
type: ColumnType::String,
size: Database::LENGTH_KEY,
required: true,
),
new Attribute(
key: 'databaseId',
type: ColumnType::String,
size: Database::LENGTH_KEY,
required: true,
),
new Attribute(
key: 'name',
type: ColumnType::String,
size: 256,
required: true,
),
new Attribute(
key: 'enabled',
type: ColumnType::Boolean,
required: true,
),
new Attribute(
key: 'documentSecurity',
type: ColumnType::Boolean,
required: true,
),
new Attribute(
key: 'attributes',
type: ColumnType::String,
size: 1000000,
filters: ['subQueryAttributes'],
),
new Attribute(
key: 'indexes',
type: ColumnType::String,
size: 1000000,
filters: ['subQueryIndexes'],
),
new Attribute(
key: 'search',
type: ColumnType::String,
size: 16384,
),
],
'indexes' => [
[
'$id' => ID::custom('_fulltext_search'),
'type' => Database::INDEX_FULLTEXT,
'attributes' => ['search'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_name'),
'type' => Database::INDEX_KEY,
'attributes' => ['name'],
'lengths' => [256],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_enabled'),
'type' => Database::INDEX_KEY,
'attributes' => ['enabled'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_documentSecurity'),
'type' => Database::INDEX_KEY,
'attributes' => ['documentSecurity'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
new Index(
key: '_fulltext_search',
type: IndexType::Fulltext,
attributes: ['search'],
),
new Index(
key: '_key_name',
type: IndexType::Key,
attributes: ['name'],
lengths: [256],
orders: ['ASC'],
),
new Index(
key: '_key_enabled',
type: IndexType::Key,
attributes: ['enabled'],
orders: ['ASC'],
),
new Index(
key: '_key_documentSecurity',
type: IndexType::Key,
attributes: ['documentSecurity'],
orders: ['ASC'],
),
],
]
],
];
+59 -86
View File
@@ -1,94 +1,67 @@
<?php
use Utopia\Database\Attribute;
use Utopia\Database\Database;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Index;
use Utopia\Query\Schema\ColumnType;
use Utopia\Query\Schema\IndexType;
$logsCollection = [];
$logsCollection['stats'] = [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('stats'),
'name' => 'stats',
'attributes' => [
[
'$id' => ID::custom('metric'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 255,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
return [
'stats' => [
'$collection' => '_metadata',
'$id' => 'stats',
'name' => 'stats',
'attributes' => [
new Attribute(
key: 'metric',
type: ColumnType::String,
size: Database::LENGTH_KEY,
required: true,
),
new Attribute(
key: 'region',
type: ColumnType::String,
size: Database::LENGTH_KEY,
required: true,
),
new Attribute(
key: 'value',
type: ColumnType::Integer,
size: 8,
required: true,
),
new Attribute(
key: 'time',
type: ColumnType::Datetime,
signed: false,
filters: ['datetime'],
),
new Attribute(
key: 'period',
type: ColumnType::String,
size: 4,
required: true,
),
],
[
'$id' => ID::custom('region'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 255,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('value'),
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 8,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('time'),
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['datetime'],
],
[
'$id' => ID::custom('period'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 4,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => ID::custom('_key_time'),
'type' => Database::INDEX_KEY,
'attributes' => ['time'],
'lengths' => [],
'orders' => [Database::ORDER_DESC],
],
[
'$id' => ID::custom('_key_period_time'),
'type' => Database::INDEX_KEY,
'attributes' => ['period', 'time'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_metric_period_time'),
'type' => Database::INDEX_UNIQUE,
'attributes' => ['metric', 'period', 'time'],
'lengths' => [],
'orders' => [Database::ORDER_DESC],
'indexes' => [
new Index(
key: '_key_time',
type: IndexType::Key,
attributes: ['time'],
orders: ['DESC'],
),
new Index(
key: '_key_period_time',
type: IndexType::Key,
attributes: ['period', 'time'],
orders: ['ASC'],
),
new Index(
key: '_key_metric_period_time',
type: IndexType::Unique,
attributes: ['metric', 'period', 'time'],
orders: ['DESC'],
),
],
],
];
return $logsCollection;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+100 -150
View File
@@ -1,165 +1,115 @@
<?php
use Utopia\Database\Attribute;
use Utopia\Database\Database;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Index;
use Utopia\Query\Schema\ColumnType;
use Utopia\Query\Schema\IndexType;
return [
'collections' => [
'$collection' => ID::custom('databases'),
'$id' => ID::custom('collections'),
'$collection' => 'databases',
'$id' => 'collections',
'name' => 'Collections',
'attributes' => [
[
'$id' => ID::custom('databaseInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('databaseId'),
'type' => Database::VAR_STRING,
'signed' => true,
'size' => Database::LENGTH_KEY,
'format' => '',
'filters' => [],
'required' => true,
'default' => null,
'array' => false,
],
[
'$id' => ID::custom('name'),
'type' => Database::VAR_STRING,
'size' => 256,
'required' => true,
'signed' => true,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('dimension'),
'type' => Database::VAR_INTEGER,
'size' => 0,
'required' => true,
'signed' => false,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('enabled'),
'type' => Database::VAR_BOOLEAN,
'signed' => true,
'size' => 0,
'format' => '',
'filters' => [],
'required' => true,
'default' => null,
'array' => false,
],
[
'$id' => ID::custom('documentSecurity'),
'type' => Database::VAR_BOOLEAN,
'signed' => true,
'size' => 0,
'format' => '',
'filters' => [],
'required' => true,
'default' => null,
'array' => false,
],
[
'$id' => ID::custom('attributes'),
'type' => Database::VAR_STRING,
'size' => 1000000,
'required' => false,
'signed' => true,
'array' => false,
'filters' => ['subQueryAttributes'],
],
[
'$id' => ID::custom('indexes'),
'type' => Database::VAR_STRING,
'size' => 1000000,
'required' => false,
'signed' => true,
'array' => false,
'filters' => ['subQueryIndexes'],
],
[
'$id' => ID::custom('search'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
new Attribute(
key: 'databaseInternalId',
type: ColumnType::String,
size: Database::LENGTH_KEY,
required: true,
),
new Attribute(
key: 'databaseId',
type: ColumnType::String,
size: Database::LENGTH_KEY,
required: true,
),
new Attribute(
key: 'name',
type: ColumnType::String,
size: 256,
required: true,
),
new Attribute(
key: 'dimension',
type: ColumnType::Integer,
required: true,
signed: false,
),
new Attribute(
key: 'enabled',
type: ColumnType::Boolean,
required: true,
),
new Attribute(
key: 'documentSecurity',
type: ColumnType::Boolean,
required: true,
),
new Attribute(
key: 'attributes',
type: ColumnType::String,
size: 1000000,
filters: ['subQueryAttributes'],
),
new Attribute(
key: 'indexes',
type: ColumnType::String,
size: 1000000,
filters: ['subQueryIndexes'],
),
new Attribute(
key: 'search',
type: ColumnType::String,
size: 16384,
),
],
'defaultAttributes' => [
[
'$id' => ID::custom('embeddings'),
'type' => Database::VAR_VECTOR,
'required' => true,
'signed' => false,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('metadata'),
'type' => Database::VAR_OBJECT,
'default' => [],
'required' => false,
'size' => 0,
'signed' => false,
'array' => false,
'filters' => [],
],
new Attribute(
key: 'embeddings',
type: ColumnType::Vector,
required: true,
signed: false,
),
new Attribute(
key: 'metadata',
type: ColumnType::Object,
default: [],
signed: false,
),
],
'indexes' => [
[
'$id' => ID::custom('_fulltext_search'),
'type' => Database::INDEX_FULLTEXT,
'attributes' => ['search'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_name'),
'type' => Database::INDEX_KEY,
'attributes' => ['name'],
'lengths' => [256],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_enabled'),
'type' => Database::INDEX_KEY,
'attributes' => ['enabled'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_documentSecurity'),
'type' => Database::INDEX_KEY,
'attributes' => ['documentSecurity'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
new Index(
key: '_fulltext_search',
type: IndexType::Fulltext,
attributes: ['search'],
),
new Index(
key: '_key_name',
type: IndexType::Key,
attributes: ['name'],
lengths: [256],
orders: ['ASC'],
),
new Index(
key: '_key_enabled',
type: IndexType::Key,
attributes: ['enabled'],
orders: ['ASC'],
),
new Index(
key: '_key_documentSecurity',
type: IndexType::Key,
attributes: ['documentSecurity'],
orders: ['ASC'],
),
],
'defaultIndexes' => [
// not creating default indexes on the embeddings as it depends on the type of query users using the most
[
'$id' => ID::custom('_key_metadata'),
'type' => Database::INDEX_OBJECT,
'attributes' => ['metadata'],
'lengths' => [],
'orders' => [],
],
]
]
new Index(
key: '_key_metadata',
type: IndexType::Object,
attributes: ['metadata'],
),
],
],
];
+6 -5
View File
@@ -52,6 +52,7 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\SetType;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Queries;
use Utopia\Database\Validator\Query\Cursor;
@@ -514,7 +515,7 @@ Http::post('/v1/account')
Query::equal('identifier', [$email]),
]);
if (!$existingTarget->isEmpty()) {
$user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND);
$user->setAttribute('targets', $existingTarget, SetType::Append);
}
}
@@ -2520,7 +2521,7 @@ Http::post('/v1/account/tokens/email')
Query::equal('identifier', [$email]),
]);
if (!$existingTarget->isEmpty()) {
$user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND);
$user->setAttribute('targets', $existingTarget, SetType::Append);
}
}
@@ -3116,8 +3117,8 @@ Http::get('/v1/account/logs')
}
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$limit = $grouped->limit ?? 25;
$offset = $grouped->offset ?? 0;
$logs = $audit->getLogsByUser($user->getSequence(), offset: $offset, limit: $limit);
$output = [];
@@ -4660,7 +4661,7 @@ Http::get('/v1/account/identities')
$cursor->setValue($cursorDocument);
}
$filterQueries = Query::groupByType($queries)['filters'];
$filterQueries = Query::groupByType($queries)->filters;
try {
$results = $dbForProject->find('identities', $queries);
} catch (OrderException $e) {
+8 -8
View File
@@ -1157,8 +1157,8 @@ Http::get('/v1/messaging/providers/:providerId/logs')
}
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$limit = $grouped->limit ?? 25;
$offset = $grouped->offset ?? 0;
$resource = 'provider/' . $providerId;
$logs = $audit->getLogsByResource($resource, offset: $offset, limit: $limit);
@@ -2561,8 +2561,8 @@ Http::get('/v1/messaging/topics/:topicId/logs')
}
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$limit = $grouped->limit ?? 25;
$offset = $grouped->offset ?? 0;
$resource = 'topic/' . $topicId;
$logs = $audit->getLogsByResource($resource, offset: $offset, limit: $limit);
@@ -2976,8 +2976,8 @@ Http::get('/v1/messaging/subscribers/:subscriberId/logs')
}
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$limit = $grouped->limit ?? 25;
$offset = $grouped->offset ?? 0;
$resource = 'subscriber/' . $subscriberId;
$logs = $audit->getLogsByResource($resource, limit: $limit, offset: $offset);
@@ -3789,8 +3789,8 @@ Http::get('/v1/messaging/messages/:messageId/logs')
}
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$limit = $grouped->limit ?? 25;
$offset = $grouped->offset ?? 0;
$resource = 'message/' . $messageId;
$logs = $audit->getLogsByResource($resource, limit: $limit, offset: $offset);
+1 -1
View File
@@ -978,7 +978,7 @@ Http::get('/v1/migrations')
$cursor->setValue($cursorDocument);
}
$filterQueries = Query::groupByType($queries)['filters'];
$filterQueries = Query::groupByType($queries)->filters;
try {
$migrations = $dbForProject->find('migrations', $queries);
$total = $includeTotal ? $dbForProject->count('migrations', $filterQueries, APP_LIMIT_COUNT) : 0;
+3 -2
View File
@@ -31,6 +31,7 @@ use Utopia\Database\Validator\UID;
use Utopia\Emails\Validator\Email;
use Utopia\Http\Http;
use Utopia\Locale\Locale;
use Utopia\Query\Method as QueryMethod;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
@@ -872,7 +873,7 @@ Http::get('/v1/projects/:projectId/keys')
}
// Backwards compatibility
if (\count(Query::getByType($queries, [Query::TYPE_LIMIT])) === 0) {
if (\count(Query::getByType($queries, [QueryMethod::Limit])) === 0) {
$queries[] = Query::limit(5000);
}
@@ -898,7 +899,7 @@ Http::get('/v1/projects/:projectId/keys')
$cursor->setValue($cursorDocument);
}
$filterQueries = Query::groupByType($queries)['filters'];
$filterQueries = Query::groupByType($queries)->filters;
$keys = $dbForPlatform->find('keys', $queries);
+5 -4
View File
@@ -52,6 +52,7 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\SetType;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Queries;
use Utopia\Database\Validator\Query\Cursor;
@@ -191,7 +192,7 @@ function createUser(Hash $hash, string $userId, ?string $email, ?string $passwor
Query::equal('identifier', [$email]),
]);
if (!$existingTarget->isEmpty()) {
$user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND);
$user->setAttribute('targets', $existingTarget, SetType::Append);
}
}
}
@@ -215,7 +216,7 @@ function createUser(Hash $hash, string $userId, ?string $email, ?string $passwor
Query::equal('identifier', [$phone]),
]);
if (!$existingTarget->isEmpty()) {
$user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND);
$user->setAttribute('targets', $existingTarget, SetType::Append);
}
}
}
@@ -955,8 +956,8 @@ Http::get('/v1/users/:userId/logs')
}
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$limit = $grouped->limit ?? 25;
$offset = $grouped->offset ?? 0;
$logs = $audit->getLogsByUser($user->getSequence(), limit: $limit, offset: $offset);
$output = [];
+10 -9
View File
@@ -56,6 +56,7 @@ use Utopia\Logger\Log;
use Utopia\Logger\Log\User;
use Utopia\Logger\Logger;
use Utopia\Platform\Service;
use Utopia\Query\Method as QueryMethod;
use Utopia\Span\Span;
use Utopia\System\System;
use Utopia\Validator;
@@ -1351,7 +1352,7 @@ Http::error()
}
// logical queries - recursively format nested queries
if (in_array($method, [Query::TYPE_AND, Query::TYPE_OR], true)) {
if (in_array($method, [QueryMethod::And, QueryMethod::Or], true)) {
$nested = [];
foreach ($values as $nestedArray) {
if (is_array($nestedArray)) {
@@ -1365,26 +1366,26 @@ Http::error()
}
// select - show selected attributes
if ($method === Query::TYPE_SELECT) {
if ($method === QueryMethod::Select) {
$attributes = array_values(array_filter($values, 'is_string'));
return [$method => $attributes];
}
// pagination
if (in_array($method, [
Query::TYPE_LIMIT,
Query::TYPE_OFFSET,
Query::TYPE_CURSOR_AFTER,
Query::TYPE_CURSOR_BEFORE
QueryMethod::Limit,
QueryMethod::Offset,
QueryMethod::CursorAfter,
QueryMethod::CursorBefore
], true)) {
return [$method => []];
}
// orders
if (in_array($method, [
Query::TYPE_ORDER_DESC,
Query::TYPE_ORDER_ASC,
Query::TYPE_ORDER_RANDOM
QueryMethod::OrderDesc,
QueryMethod::OrderAsc,
QueryMethod::OrderRandom
], true)) {
return [$method => !empty($attribute) ? [$attribute] : []];
}
+3 -2
View File
@@ -33,6 +33,7 @@ use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate as DuplicateException;
use Utopia\Database\Helpers\Role;
use Utopia\Database\PermissionType;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Authorization\Input;
use Utopia\Database\Validator\Roles;
@@ -362,7 +363,7 @@ Http::init()
* whether the admin user has necessary permission on the project (sites, functions, etc. don't have permissions associated to them).
*/
if (empty($apiKey) && ! $user->isEmpty() && $project->getId() !== 'console' && $mode === APP_MODE_ADMIN) {
$input = new Input(Database::PERMISSION_READ, $project->getPermissionsByType(Database::PERMISSION_READ));
$input = new Input(PermissionType::Read, $project->getPermissionsByType(PermissionType::Read));
$initialStatus = $authorization->getStatus();
$authorization->enable();
if (! $authorization->isValid($input)) {
@@ -644,7 +645,7 @@ Http::init()
}
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
$valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
$valid = $authorization->isValid(new Input(PermissionType::Read, $bucket->getRead()));
if (! $fileSecurity && ! $valid && ! $isToken) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
+8 -59
View File
@@ -255,25 +255,8 @@ function createDatabase(Http $app, string $resourceKey, string $dbName, array $c
continue;
}
$attributes = array_map(fn ($attr) => new Document([
'$id' => ID::custom($attr['$id']),
'type' => $attr['type'],
'size' => $attr['size'],
'required' => $attr['required'],
'signed' => $attr['signed'],
'array' => $attr['array'],
'filters' => $attr['filters'],
'default' => $attr['default'] ?? null,
'format' => $attr['format'] ?? ''
]), $collection['attributes']);
$indexes = array_map(fn ($index) => new Document([
'$id' => ID::custom($index['$id']),
'type' => $index['type'],
'attributes' => $index['attributes'],
'lengths' => $index['lengths'],
'orders' => $index['orders'],
]), $collection['indexes']);
$attributes = $collection['attributes'];
$indexes = $collection['indexes'];
$database->createCollection($key, $attributes, $indexes);
$collectionsCreated++;
@@ -340,25 +323,8 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $tot
throw new Exception('Files collection is not configured.');
}
$attributes = array_map(fn ($attr) => new Document([
'$id' => ID::custom($attr['$id']),
'type' => $attr['type'],
'size' => $attr['size'],
'required' => $attr['required'],
'signed' => $attr['signed'],
'array' => $attr['array'],
'filters' => $attr['filters'],
'default' => $attr['default'] ?? null,
'format' => $attr['format'] ?? ''
]), $files['attributes']);
$indexes = array_map(fn ($index) => new Document([
'$id' => ID::custom($index['$id']),
'type' => $index['type'],
'attributes' => $index['attributes'],
'lengths' => $index['lengths'],
'orders' => $index['orders'],
]), $files['indexes']);
$attributes = $files['attributes'];
$indexes = $files['indexes'];
$dbForPlatform->createCollection('bucket_' . $bucket->getSequence(), $attributes, $indexes);
}
@@ -386,25 +352,8 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $tot
throw new Exception('Files collection is not configured.');
}
$attributes = array_map(fn ($attr) => new Document([
'$id' => ID::custom($attr['$id']),
'type' => $attr['type'],
'size' => $attr['size'],
'required' => $attr['required'],
'signed' => $attr['signed'],
'array' => $attr['array'],
'filters' => $attr['filters'],
'default' => $attr['default'] ?? null,
'format' => $attr['format'] ?? ''
]), $files['attributes']);
$indexes = array_map(fn ($index) => new Document([
'$id' => ID::custom($index['$id']),
'type' => $index['type'],
'attributes' => $index['attributes'],
'lengths' => $index['lengths'],
'orders' => $index['orders'],
]), $files['indexes']);
$attributes = $files['attributes'];
$indexes = $files['indexes'];
$authorization->skip(fn () => $dbForPlatform->createCollection('bucket_' . $bucket->getSequence(), $attributes, $indexes));
}
@@ -481,8 +430,8 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $tot
continue;
}
$attributes = \array_map(fn ($attribute) => new Document($attribute), $collection['attributes']);
$indexes = \array_map(fn (array $index) => new Document($index), $collection['indexes']);
$attributes = $collection['attributes'];
$indexes = $collection['indexes'];
$dbForProject->createCollection($key, $attributes, $indexes);
$collectionsCreated++;
+7 -6
View File
@@ -4,6 +4,7 @@ use Appwrite\OpenSSL\OpenSSL;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Query\Schema\ColumnType;
use Utopia\System\System;
Database::addFilter(
@@ -79,7 +80,7 @@ Database::addFilter(
$attributeType = $attribute->getAttribute('type');
switch ($attributeType) {
case Database::VAR_RELATIONSHIP:
case ColumnType::Relationship->value:
$options = $attribute->getAttribute('options');
foreach ($options as $key => $value) {
$attribute->setAttribute($key, $value);
@@ -87,11 +88,11 @@ Database::addFilter(
$attribute->removeAttribute('options');
break;
case Database::VAR_STRING:
case Database::VAR_VARCHAR:
case Database::VAR_TEXT:
case Database::VAR_MEDIUMTEXT:
case Database::VAR_LONGTEXT:
case ColumnType::String->value:
case ColumnType::Varchar->value:
case ColumnType::Text->value:
case ColumnType::MediumText->value:
case ColumnType::LongText->value:
$filters = $attribute->getAttribute('filters', []);
$attribute->setAttribute('encrypt', in_array('encrypt', $filters));
break;
+10 -10
View File
@@ -1,9 +1,9 @@
<?php
use Utopia\Database\Database;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\Structure;
use Utopia\Emails\Validator\Email;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\IP;
use Utopia\Validator\Range;
use Utopia\Validator\URL;
@@ -11,33 +11,33 @@ use Utopia\Validator\WhiteList;
Structure::addFormat(APP_DATABASE_ATTRIBUTE_EMAIL, function () {
return new Email();
}, Database::VAR_STRING);
}, ColumnType::String);
Structure::addFormat(APP_DATABASE_ATTRIBUTE_DATETIME, function () {
return new DatetimeValidator();
}, Database::VAR_DATETIME);
}, ColumnType::Datetime);
Structure::addFormat(APP_DATABASE_ATTRIBUTE_ENUM, function ($attribute) {
$elements = $attribute['formatOptions']['elements'] ?? [];
return new WhiteList($elements, true);
}, Database::VAR_STRING);
}, ColumnType::String);
Structure::addFormat(APP_DATABASE_ATTRIBUTE_IP, function () {
return new IP();
}, Database::VAR_STRING);
}, ColumnType::String);
Structure::addFormat(APP_DATABASE_ATTRIBUTE_URL, function () {
return new URL();
}, Database::VAR_STRING);
}, ColumnType::String);
Structure::addFormat(APP_DATABASE_ATTRIBUTE_INT_RANGE, function ($attribute) {
$min = $attribute['formatOptions']['min'] ?? -INF;
$max = $attribute['formatOptions']['max'] ?? INF;
return new Range($min, $max, Range::TYPE_INTEGER);
}, Database::VAR_INTEGER);
}, ColumnType::Integer);
Structure::addFormat(APP_DATABASE_ATTRIBUTE_FLOAT_RANGE, function ($attribute) {
$min = $attribute['formatOptions']['min'] ?? -INF;
$max = $attribute['formatOptions']['max'] ?? INF;
$min = \floatval($attribute['formatOptions']['min'] ?? $attribute['min'] ?? -INF);
$max = \floatval($attribute['formatOptions']['max'] ?? $attribute['max'] ?? INF);
return new Range($min, $max, Range::TYPE_FLOAT);
}, Database::VAR_FLOAT);
}, ColumnType::Double);
+50 -273
View File
@@ -28,6 +28,11 @@ use Appwrite\Network\Validator\Origin;
use Appwrite\Network\Validator\Redirect;
use Appwrite\Usage\Context as UsageContext;
use Appwrite\Utopia\Database\Documents\User;
use Appwrite\Utopia\Database\Hooks\DocumentUsage;
use Appwrite\Utopia\Database\Hooks\FunctionCache;
use Appwrite\Utopia\Database\Hooks\Metadata;
use Appwrite\Utopia\Database\Hooks\Usage;
use Appwrite\Utopia\Database\Hooks\UserEvents;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Executor\Executor;
@@ -51,6 +56,9 @@ use Utopia\Database\Adapter\Pool as DatabasePool;
use Utopia\Database\Database;
use Utopia\Database\DateTime as DatabaseDateTime;
use Utopia\Database\Document;
use Utopia\Database\Hook\Permissions;
use Utopia\Database\Hook\Relationships;
use Utopia\Database\Hook\Tenancy;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\DSN\DSN;
@@ -58,6 +66,7 @@ use Utopia\Http\Http;
use Utopia\Locale\Locale;
use Utopia\Logger\Log;
use Utopia\Pools\Group;
use Utopia\Query\Method;
use Utopia\Queue\Broker\Pool as BrokerPool;
use Utopia\Queue\Publisher;
use Utopia\Queue\Queue;
@@ -642,86 +651,7 @@ Http::setResource('dbForProject', function (Group $pools, Database $dbForPlatfor
->setNamespace('_' . $project->getSequence());
}
/**
* This isolated event handling for `users.*.create` which is based on a `Database::EVENT_DOCUMENT_CREATE` listener may look odd, but it is **intentional**.
*
* Accounts can be created in many ways beyond `createAccount`
* (anonymous, OAuth, phone, etc.), and those flows are probably not covered in event tests; so we handle this here.
*/
$eventDatabaseListener = function (Document $project, Document $document, Response $response, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime) {
// Only trigger events for user creation with the database listener.
if ($document->getCollection() !== 'users') {
return;
}
$queueForEvents
->setEvent('users.[userId].create')
->setParam('userId', $document->getId())
->setPayload($response->output($document, Response::MODEL_USER));
// Trigger functions, webhooks, and realtime events
$queueForFunctions
->from($queueForEvents)
->trigger();
/** Trigger webhooks events only if a project has them enabled */
if (! empty($project->getAttribute('webhooks'))) {
$queueForWebhooks
->from($queueForEvents)
->trigger();
}
/** Trigger realtime events only for non console events */
if ($queueForEvents->getProject()->getId() !== 'console') {
$queueForRealtime
->from($queueForEvents)
->trigger();
}
};
/**
* Purge function events cache when functions are created, updated or deleted.
*/
$functionsEventsCacheListener = function (string $event, Document $document, Document $project, Database $dbForProject) {
if ($document->getCollection() !== 'functions') {
return;
}
if ($project->isEmpty() || $project->getId() === 'console') {
return;
}
$hostname = $dbForProject->getAdapter()->getHostname();
$cacheKey = \sprintf(
'%s-cache-%s:%s:%s:project:%s:functions:events',
$dbForProject->getCacheName(),
$hostname ?? '',
$dbForProject->getNamespace(),
$dbForProject->getTenant(),
$project->getId()
);
$dbForProject->getCache()->purge($cacheKey);
};
/**
* Prefix metrics with database type when applicable.
* Avoids prefixing for legacy and tablesdb types to preserve historical metrics.
*/
$getDatabaseTypePrefixedMetric = function (string $databaseType, string $metric): string {
if (
$databaseType === '' ||
$databaseType === DATABASE_TYPE_LEGACY ||
$databaseType === DATABASE_TYPE_TABLESDB
) {
return $metric;
}
return $databaseType . '.' . $metric;
};
// Determine database type from request path, similar to api.php
// Determine database type from request path
$path = $request->getURI();
$databaseType = match (true) {
str_contains($path, '/documentsdb') => DATABASE_TYPE_DOCUMENTSDB,
@@ -729,115 +659,6 @@ Http::setResource('dbForProject', function (Group $pools, Database $dbForPlatfor
default => '',
};
$usageDatabaseListener = function (string $event, Document $document, UsageContext $usage) use ($getDatabaseTypePrefixedMetric, $databaseType) {
$value = 1;
switch ($event) {
case Database::EVENT_DOCUMENT_DELETE:
$value = -1;
break;
case Database::EVENT_DOCUMENTS_DELETE:
$value = -1 * $document->getAttribute('modified', 0);
break;
case Database::EVENT_DOCUMENTS_CREATE:
$value = $document->getAttribute('modified', 0);
break;
case Database::EVENT_DOCUMENTS_UPSERT:
$value = $document->getAttribute('created', 0);
break;
}
switch (true) {
case $document->getCollection() === 'teams':
$usage->addMetric(METRIC_TEAMS, $value); // per project
break;
case $document->getCollection() === 'users':
$usage->addMetric(METRIC_USERS, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$usage->addReduce($document);
}
break;
case $document->getCollection() === 'sessions': // sessions
$usage->addMetric(METRIC_SESSIONS, $value); // per project
break;
case $document->getCollection() === 'databases': // databases
$metric = $getDatabaseTypePrefixedMetric($databaseType, METRIC_DATABASES);
$usage->addMetric($metric, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$usage->addReduce($document);
}
break;
case str_starts_with($document->getCollection(), 'database_') && ! str_contains($document->getCollection(), 'collection'): // collections
$parts = explode('_', $document->getCollection());
$databaseInternalId = $parts[1] ?? 0;
$collectionMetric = $getDatabaseTypePrefixedMetric($databaseType, METRIC_COLLECTIONS);
$databaseIdCollectionMetric = $getDatabaseTypePrefixedMetric($databaseType, METRIC_DATABASE_ID_COLLECTIONS);
$usage
->addMetric($collectionMetric, $value) // per project
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, $databaseIdCollectionMetric), $value);
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$usage->addReduce($document);
}
break;
case str_starts_with($document->getCollection(), 'database_') && str_contains($document->getCollection(), '_collection_'): // documents
$parts = explode('_', $document->getCollection());
$databaseInternalId = $parts[1] ?? 0;
$collectionInternalId = $parts[3] ?? 0;
$documentsMetric = $getDatabaseTypePrefixedMetric($databaseType, METRIC_DOCUMENTS);
$databaseIdDocumentsMetric = $getDatabaseTypePrefixedMetric($databaseType, METRIC_DATABASE_ID_DOCUMENTS);
$databaseIdCollectionIdDocumentsMetric = $getDatabaseTypePrefixedMetric($databaseType, METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS);
$usage
->addMetric($documentsMetric, $value) // per project
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, $databaseIdDocumentsMetric), $value) // per database
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$databaseInternalId, $collectionInternalId], $databaseIdCollectionIdDocumentsMetric), $value); // per collection
break;
case $document->getCollection() === 'buckets': // buckets
$usage->addMetric(METRIC_BUCKETS, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$usage
->addReduce($document);
}
break;
case str_starts_with($document->getCollection(), 'bucket_'): // files
$parts = explode('_', $document->getCollection());
$bucketInternalId = $parts[1];
$usage
->addMetric(METRIC_FILES, $value) // per project
->addMetric(METRIC_FILES_STORAGE, $document->getAttribute('sizeOriginal') * $value) // per project
->addMetric(str_replace('{bucketInternalId}', $bucketInternalId, METRIC_BUCKET_ID_FILES), $value) // per bucket
->addMetric(str_replace('{bucketInternalId}', $bucketInternalId, METRIC_BUCKET_ID_FILES_STORAGE), $document->getAttribute('sizeOriginal') * $value); // per bucket
break;
case $document->getCollection() === 'functions':
$usage->addMetric(METRIC_FUNCTIONS, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$usage
->addReduce($document);
}
break;
case $document->getCollection() === 'sites':
$usage->addMetric(METRIC_SITES, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$usage
->addReduce($document);
}
break;
case $document->getCollection() === 'deployments':
$usage
->addMetric(METRIC_DEPLOYMENTS, $value) // per project
->addMetric(METRIC_DEPLOYMENTS_STORAGE, $document->getAttribute('size') * $value) // per project
->addMetric(str_replace(['{resourceType}'], [$document->getAttribute('resourceType')], METRIC_RESOURCE_TYPE_DEPLOYMENTS), $value) // per function
->addMetric(str_replace(['{resourceType}'], [$document->getAttribute('resourceType')], METRIC_RESOURCE_TYPE_DEPLOYMENTS_STORAGE), $document->getAttribute('size') * $value)
->addMetric(str_replace(['{resourceType}', '{resourceInternalId}'], [$document->getAttribute('resourceType'), $document->getAttribute('resourceInternalId')], METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS), $value) // per function
->addMetric(str_replace(['{resourceType}', '{resourceInternalId}'], [$document->getAttribute('resourceType'), $document->getAttribute('resourceInternalId')], METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS_STORAGE), $document->getAttribute('size') * $value);
break;
default:
break;
}
};
// Clone the queues, to prevent events triggered by the database listener
// from overwriting the events that are supposed to be triggered in the shutdown hook.
@@ -847,23 +668,22 @@ Http::setResource('dbForProject', function (Group $pools, Database $dbForPlatfor
$queueForRealtime = new Realtime();
$database
->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $usage))
->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $usage))
->on(Database::EVENT_DOCUMENTS_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $usage))
->on(Database::EVENT_DOCUMENTS_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $usage))
->on(Database::EVENT_DOCUMENTS_UPSERT, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $usage))
->on(Database::EVENT_DOCUMENT_CREATE, 'create-trigger-events', fn ($event, $document) => $eventDatabaseListener(
->addHook(new Usage($usage, $databaseType))
->addHook(new UserEvents(
$project,
$document,
$response,
$queueForEventsClone->from($queueForEvents),
$queueForFunctions->from($queueForEvents),
$queueForWebhooks->from($queueForEvents),
$queueForRealtime->from($queueForEvents)
$queueForEvents,
$queueForEventsClone,
$queueForFunctions,
$queueForWebhooks,
$queueForRealtime,
))
->on(Database::EVENT_DOCUMENT_CREATE, 'purge-function-events-cache', fn ($event, $document) => $functionsEventsCacheListener($event, $document, $project, $database))
->on(Database::EVENT_DOCUMENT_UPDATE, 'purge-function-events-cache', fn ($event, $document) => $functionsEventsCacheListener($event, $document, $project, $database))
->on(Database::EVENT_DOCUMENT_DELETE, 'purge-function-events-cache', fn ($event, $document) => $functionsEventsCacheListener($event, $document, $project, $database));
->addHook(new FunctionCache($project, $database))
->addHook(new Permissions());
if ($database->getSharedTables() && ($database->getTenant() !== null)) {
$database->addHook(new Tenancy($database->getTenant()));
}
return $database;
}, ['pools', 'dbForPlatform', 'cache', 'project', 'response', 'publisher', 'publisherFunctions', 'publisherWebhooks', 'queueForEvents', 'queueForFunctions', 'queueForWebhooks', 'queueForRealtime', 'usage', 'authorization', 'request']);
@@ -883,13 +703,16 @@ Http::setResource('dbForPlatform', function (Group $pools, Cache $cache, Authori
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
$database->setDocumentType('users', User::class);
$database->addHook(new Permissions());
return $database;
}, ['pools', 'cache', 'authorization']);
Http::setResource('getDatabasesDB', function (Group $pools, Cache $cache, Document $project, Request $request, UsageContext $usage, Authorization $authorization) {
return function (Document $database) use ($pools, $cache, $project, $request, $usage, $authorization): Database {
return function (Document $database, ?Document $collection = null) use ($pools, $cache, $project, $request, $usage, $authorization): Database {
$originalDatabase = $database;
$context = str_contains($request->getURI(), '/tablesdb/') ? 'table' : 'collection';
$databaseDSN = $database->getAttribute('database', $project->getAttribute('database', ''));
$databaseType = $database->getAttribute('type', '');
@@ -938,81 +761,32 @@ Http::setResource('getDatabasesDB', function (Group $pools, Cache $cache, Docume
$database->setTimeout($timeout);
}
// Register database event listeners for usage stats collection
$documentsMetric = METRIC_DOCUMENTS;
$databaseIdDocumentsMetric = METRIC_DATABASE_ID_DOCUMENTS;
$databaseIdCollectionIdDocumentsMetric = METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS;
if ($databaseType !== DATABASE_TYPE_LEGACY && $databaseType !== DATABASE_TYPE_TABLESDB) {
$documentsMetric = $databaseType. '.' .$documentsMetric;
$databaseIdDocumentsMetric = $databaseType. '.' .$databaseIdDocumentsMetric;
$databaseIdCollectionIdDocumentsMetric = $databaseType . '.' .$databaseIdCollectionIdDocumentsMetric;
$documentsMetric = $databaseType . '.' . $documentsMetric;
$databaseIdDocumentsMetric = $databaseType . '.' . $databaseIdDocumentsMetric;
$databaseIdCollectionIdDocumentsMetric = $databaseType . '.' . $databaseIdCollectionIdDocumentsMetric;
}
$database
->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', function ($event, $document) use ($usage, $documentsMetric, $databaseIdDocumentsMetric, $databaseIdCollectionIdDocumentsMetric) {
$value = 1;
->addHook(new DocumentUsage(
$usage,
$documentsMetric,
$databaseIdDocumentsMetric,
$databaseIdCollectionIdDocumentsMetric,
))
->addHook(new Permissions())
->addHook(new Relationships($database))
->addHook(new Metadata(
database: $originalDatabase,
context: $context,
));
if (str_starts_with($document->getCollection(), 'database_') && str_contains($document->getCollection(), '_collection_')) {
$parts = explode('_', $document->getCollection());
$databaseInternalId = $parts[1] ?? 0;
$collectionInternalId = $parts[3] ?? 0;
$usage
->addMetric($documentsMetric, $value) // per project
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, $databaseIdDocumentsMetric), $value) // per database
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$databaseInternalId, $collectionInternalId], $databaseIdCollectionIdDocumentsMetric), $value); // per collection
}
})
->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', function ($event, $document) use ($usage, $documentsMetric, $databaseIdDocumentsMetric, $databaseIdCollectionIdDocumentsMetric) {
$value = -1;
if (str_starts_with($document->getCollection(), 'database_') && str_contains($document->getCollection(), '_collection_')) {
$parts = explode('_', $document->getCollection());
$databaseInternalId = $parts[1] ?? 0;
$collectionInternalId = $parts[3] ?? 0;
$usage
->addMetric($documentsMetric, $value) // per project
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, $databaseIdDocumentsMetric), $value) // per database
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$databaseInternalId, $collectionInternalId], $databaseIdCollectionIdDocumentsMetric), $value); // per collection
}
})
->on(Database::EVENT_DOCUMENTS_CREATE, 'calculate-usage', function ($event, $document) use ($usage, $documentsMetric, $databaseIdDocumentsMetric, $databaseIdCollectionIdDocumentsMetric) {
$value = $document->getAttribute('modified', 0);
if (str_starts_with($document->getCollection(), 'database_') && str_contains($document->getCollection(), '_collection_')) {
$parts = explode('_', $document->getCollection());
$databaseInternalId = $parts[1] ?? 0;
$collectionInternalId = $parts[3] ?? 0;
$usage
->addMetric($documentsMetric, $value) // per project
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, $databaseIdDocumentsMetric), $value) // per database
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$databaseInternalId, $collectionInternalId], $databaseIdCollectionIdDocumentsMetric), $value); // per collection
}
})
->on(Database::EVENT_DOCUMENTS_DELETE, 'calculate-usage', function ($event, $document) use ($usage, $documentsMetric, $databaseIdDocumentsMetric, $databaseIdCollectionIdDocumentsMetric) {
$value = -1 * $document->getAttribute('modified', 0);
if (str_starts_with($document->getCollection(), 'database_') && str_contains($document->getCollection(), '_collection_')) {
$parts = explode('_', $document->getCollection());
$databaseInternalId = $parts[1] ?? 0;
$collectionInternalId = $parts[3] ?? 0;
$usage
->addMetric($documentsMetric, $value) // per project
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, $databaseIdDocumentsMetric), $value) // per database
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$databaseInternalId, $collectionInternalId], $databaseIdCollectionIdDocumentsMetric), $value); // per collection
}
})
->on(Database::EVENT_DOCUMENTS_UPSERT, 'calculate-usage', function ($event, $document) use ($usage, $documentsMetric, $databaseIdDocumentsMetric, $databaseIdCollectionIdDocumentsMetric) {
$value = $document->getAttribute('created', 0);
if (str_starts_with($document->getCollection(), 'database_') && str_contains($document->getCollection(), '_collection_')) {
$parts = explode('_', $document->getCollection());
$databaseInternalId = $parts[1] ?? 0;
$collectionInternalId = $parts[3] ?? 0;
$usage
->addMetric($documentsMetric, $value) // per project
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, $databaseIdDocumentsMetric), $value) // per database
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$databaseInternalId, $collectionInternalId], $databaseIdCollectionIdDocumentsMetric), $value); // per collection
}
});
if ($database->getSharedTables() && ($database->getTenant() !== null)) {
$database->addHook(new Tenancy($database->getTenant()));
}
return $database;
};
@@ -1073,6 +847,7 @@ Http::setResource('getProjectDB', function (Group $pools, Database $dbForPlatfor
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$database = new Database($adapter, $cache);
$database->addHook(new Permissions());
$databases[$dsn->getHost()] = $database;
$configure($database);
@@ -1100,6 +875,8 @@ Http::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorizati
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
$database->addHook(new Permissions());
// set tenant
if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant($project->getSequence());
@@ -1323,7 +1100,7 @@ Http::setResource('schema', function ($utopia, $dbForProject, $authorization) {
$complexity = function (int $complexity, array $args) {
$queries = Query::parseQueries($args['queries'] ?? []);
$query = Query::getByType($queries, [Query::TYPE_LIMIT])[0] ?? null;
$query = Query::getByType($queries, [Method::Limit])[0] ?? null;
$limit = $query ? $query->getValue() : APP_LIMIT_LIST_DEFAULT;
return $complexity * $limit;
+9
View File
@@ -34,6 +34,7 @@ use Utopia\Database\Adapter\Pool as DatabasePool;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Hook\Permissions;
use Utopia\Database\Validator\Authorization;
use Utopia\DSN\DSN;
use Utopia\Logger\Log;
@@ -75,6 +76,7 @@ Server::setResource('dbForPlatform', function (Cache $cache, Registry $register,
->setNamespace('_console')
->setDocumentType('users', User::class);
$dbForPlatform->addHook(new Permissions());
return $dbForPlatform;
}, ['cache', 'register', 'authorization']);
@@ -126,6 +128,8 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register,
->setAuthorization($authorization)
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
$database->addHook(new Permissions());
return $database;
}, ['cache', 'register', 'message', 'project', 'dbForPlatform', 'authorization']);
@@ -188,6 +192,8 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatf
->setAuthorization($authorization)
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
$database->addHook(new Permissions());
return $database;
};
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
@@ -212,6 +218,8 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache, Authoriza
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES_WORKER);
$database->addHook(new Permissions());
if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant($project->getSequence());
}
@@ -270,6 +278,7 @@ Server::setResource('getDatabasesDB', function (Cache $cache, Registry $register
}
$database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
$database->addHook(new Permissions());
return $database;
};
}, ['cache', 'register', 'project', 'authorization']);
+13 -3
View File
@@ -53,14 +53,14 @@
"appwrite/php-clamav": "2.0.*",
"utopia-php/abuse": "1.2.*",
"utopia-php/analytics": "0.15.*",
"utopia-php/audit": "2.2.*",
"utopia-php/audit": "dev-feat-query-lib as 2.2.0",
"utopia-php/auth": "0.5.*",
"utopia-php/cache": "1.0.*",
"utopia-php/cli": "0.22.*",
"utopia-php/compression": "0.1.*",
"utopia-php/config": "1.*",
"utopia-php/console": "0.1.*",
"utopia-php/database": "5.*",
"utopia-php/database": "dev-feat-query-lib as 5.3.17",
"utopia-php/agents": "1.*",
"utopia-php/detector": "0.2.*",
"utopia-php/domains": "1.*",
@@ -73,7 +73,7 @@
"utopia-php/locale": "0.8.*",
"utopia-php/logger": "0.6.*",
"utopia-php/messaging": "0.20.*",
"utopia-php/migration": "1.9.*",
"utopia-php/migration": "dev-feat-query-lib as 1.8.0",
"utopia-php/platform": "0.7.*",
"utopia-php/pools": "1.*",
"utopia-php/span": "1.1.*",
@@ -111,6 +111,16 @@
"provide": {
"ext-phpiredis": "*"
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/utopia-php/async.git"
},
{
"type": "vcs",
"url": "https://github.com/utopia-php/migration.git"
}
],
"config": {
"platform": {
},
Generated
+354 -96
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "b5261855586680e467168f527e0634ae",
"content-hash": "9d66f869fc3ff5abc5873a31e6976911",
"packages": [
{
"name": "adhocore/jwt",
@@ -1634,6 +1634,71 @@
},
"time": "2026-01-21T04:14:03+00:00"
},
{
"name": "opis/closure",
"version": "4.5.0",
"source": {
"type": "git",
"url": "https://github.com/opis/closure.git",
"reference": "b97e42b95bb72d87507f5e2d137ceb239aea8d6b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/opis/closure/zipball/b97e42b95bb72d87507f5e2d137ceb239aea8d6b",
"reference": "b97e42b95bb72d87507f5e2d137ceb239aea8d6b",
"shasum": ""
},
"require": {
"php": "^8.0"
},
"require-dev": {
"phpunit/phpunit": "^9.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.x-dev"
}
},
"autoload": {
"files": [
"src/functions.php"
],
"psr-4": {
"Opis\\Closure\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Marius Sarca",
"email": "marius.sarca@gmail.com"
},
{
"name": "Sorin Sarca",
"email": "sarca_sorin@hotmail.com"
}
],
"description": "A library that can be used to serialize closures (anonymous functions) and arbitrary data.",
"homepage": "https://opis.io/closure",
"keywords": [
"anonymous classes",
"anonymous functions",
"closure",
"function",
"serializable",
"serialization",
"serialize"
],
"support": {
"issues": "https://github.com/opis/closure/issues",
"source": "https://github.com/opis/closure/tree/4.5.0"
},
"time": "2026-03-05T13:32:42+00:00"
},
{
"name": "paragonie/constant_time_encoding",
"version": "v3.1.3",
@@ -2708,16 +2773,16 @@
},
{
"name": "symfony/http-client",
"version": "v7.4.7",
"version": "v7.4.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client.git",
"reference": "1010624285470eb60e88ed10035102c75b4ea6af"
"reference": "01933e626c3de76bea1e22641e205e78f6a34342"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-client/zipball/1010624285470eb60e88ed10035102c75b4ea6af",
"reference": "1010624285470eb60e88ed10035102c75b4ea6af",
"url": "https://api.github.com/repos/symfony/http-client/zipball/01933e626c3de76bea1e22641e205e78f6a34342",
"reference": "01933e626c3de76bea1e22641e205e78f6a34342",
"shasum": ""
},
"require": {
@@ -2785,7 +2850,7 @@
"http"
],
"support": {
"source": "https://github.com/symfony/http-client/tree/v7.4.7"
"source": "https://github.com/symfony/http-client/tree/v7.4.8"
},
"funding": [
{
@@ -2805,7 +2870,7 @@
"type": "tidelift"
}
],
"time": "2026-03-05T11:16:58+00:00"
"time": "2026-03-30T12:55:43+00:00"
},
{
"name": "symfony/http-client-contracts",
@@ -3501,24 +3566,143 @@
"time": "2026-02-09T12:46:39+00:00"
},
{
"name": "utopia-php/audit",
"version": "2.2.1",
"name": "utopia-php/async",
"version": "dev-main",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/audit.git",
"reference": "e3e2d6ad5c7f6377d9237df296a12eb7943892fd"
"url": "https://github.com/utopia-php/async.git",
"reference": "7a0c6957b41731a5c999382ad26a0b2fdbd19812"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/audit/zipball/e3e2d6ad5c7f6377d9237df296a12eb7943892fd",
"reference": "e3e2d6ad5c7f6377d9237df296a12eb7943892fd",
"url": "https://api.github.com/repos/utopia-php/async/zipball/7a0c6957b41731a5c999382ad26a0b2fdbd19812",
"reference": "7a0c6957b41731a5c999382ad26a0b2fdbd19812",
"shasum": ""
},
"require": {
"opis/closure": "4.*",
"php": ">=8.1"
},
"require-dev": {
"amphp/amp": "3.*",
"amphp/parallel": "2.*",
"amphp/process": "^2.0",
"laravel/pint": "1.*",
"phpstan/phpstan": "2.*",
"phpunit/phpunit": "11.5.45",
"react/child-process": "0.*",
"react/event-loop": "1.*",
"swoole/ide-helper": "*"
},
"suggest": {
"amphp/amp": "Required for Amp promise adapter",
"amphp/parallel": "Required for Amp parallel adapter",
"ext-ev": "Required for ReactPHP event loop (recommended for best performance)",
"ext-parallel": "Required for parallel adapter (requires PHP ZTS build)",
"ext-sockets": "Required for Swoole Process adapter",
"ext-swoole": "Required for Swoole Thread and Process adapters (recommended for best performance)",
"react/child-process": "Required for ReactPHP parallel adapter",
"react/event-loop": "Required for ReactPHP promise and parallel adapters"
},
"default-branch": true,
"type": "library",
"autoload": {
"psr-4": {
"Utopia\\Async\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Utopia\\Tests\\": "tests/"
}
},
"scripts": {
"test-unit": [
"vendor/bin/phpunit tests/Unit --exclude-group no-swoole"
],
"test-promise-sync": [
"vendor/bin/phpunit tests/E2e/Promise/SyncTest.php"
],
"test-promise-swoole": [
"vendor/bin/phpunit tests/E2e/Promise/Swoole"
],
"test-promise-amp": [
"vendor/bin/phpunit tests/E2e/Promise/Amp"
],
"test-promise-react": [
"vendor/bin/phpunit tests/E2e/Promise/React"
],
"test-parallel-sync": [
"vendor/bin/phpunit tests/E2e/Parallel/Sync"
],
"test-parallel-swoole-thread": [
"vendor/bin/phpunit tests/E2e/Parallel/Swoole/ThreadTest.php"
],
"test-parallel-swoole-process": [
"vendor/bin/phpunit tests/E2e/Parallel/Swoole/ProcessTest.php"
],
"test-parallel-amp": [
"vendor/bin/phpunit tests/E2e/Parallel/Amp"
],
"test-parallel-react": [
"vendor/bin/phpunit tests/E2e/Parallel/React"
],
"test-parallel-ext": [
"php -n -d extension=parallel.so -d extension=sockets.so vendor/bin/phpunit tests/E2e/Parallel/Parallel"
],
"test-e2e": [
"vendor/bin/phpunit tests/E2e --exclude-group ext-parallel"
],
"test": [
"@test-unit",
"@test-e2e",
"@test-parallel-ext"
],
"lint": [
"vendor/bin/pint"
],
"format": [
"php -d memory_limit=4G vendor/bin/pint"
],
"check": [
"vendor/bin/phpstan analyse src tests --level=max --memory-limit=4G"
]
},
"license": [
"MIT"
],
"authors": [
{
"name": "Appwrite Team",
"email": "team@appwrite.io"
}
],
"description": "High-performance concurrent + parallel library with Promise and Parallel execution support for PHP.",
"support": {
"source": "https://github.com/utopia-php/async/tree/main",
"issues": "https://github.com/utopia-php/async/issues"
},
"time": "2026-01-09T06:16:09+00:00"
},
{
"name": "utopia-php/audit",
"version": "dev-feat-query-lib",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/audit.git",
"reference": "ae866f6a6115bd9d6e6e7037978d5c5fb5b132d1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/audit/zipball/ae866f6a6115bd9d6e6e7037978d5c5fb5b132d1",
"reference": "ae866f6a6115bd9d6e6e7037978d5c5fb5b132d1",
"shasum": ""
},
"require": {
"php": ">=8.0",
"utopia-php/database": "5.*",
"utopia-php/fetch": "0.5.*",
"utopia-php/validators": "0.2.*"
"utopia-php/validators": "*"
},
"require-dev": {
"laravel/pint": "1.*",
@@ -3545,9 +3729,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/audit/issues",
"source": "https://github.com/utopia-php/audit/tree/2.2.1"
"source": "https://github.com/utopia-php/audit/tree/feat-query-lib"
},
"time": "2026-02-02T10:39:25+00:00"
"time": "2026-03-26T15:29:23+00:00"
},
{
"name": "utopia-php/auth",
@@ -3850,16 +4034,16 @@
},
{
"name": "utopia-php/database",
"version": "5.3.17",
"version": "dev-feat-query-lib",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "cff2b6ed63d3291b74110d086e16ff089fe05993"
"reference": "2c49e169d47d00d03b485853e052ea83875da9de"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/cff2b6ed63d3291b74110d086e16ff089fe05993",
"reference": "cff2b6ed63d3291b74110d086e16ff089fe05993",
"url": "https://api.github.com/repos/utopia-php/database/zipball/2c49e169d47d00d03b485853e052ea83875da9de",
"reference": "2c49e169d47d00d03b485853e052ea83875da9de",
"shasum": ""
},
"require": {
@@ -3867,18 +4051,20 @@
"ext-mongodb": "*",
"ext-pdo": "*",
"php": ">=8.4",
"utopia-php/async": "@dev",
"utopia-php/cache": "1.*",
"utopia-php/console": "0.1.*",
"utopia-php/mongo": "1.*",
"utopia-php/pools": "1.*",
"utopia-php/query": "dev-feat-builder",
"utopia-php/validators": "0.2.*"
},
"require-dev": {
"brianium/paratest": "^7.7",
"fakerphp/faker": "1.23.*",
"laravel/pint": "*",
"pcov/clobber": "2.*",
"phpstan/phpstan": "1.*",
"phpunit/phpunit": "9.*",
"phpstan/phpstan": "^2.0",
"phpunit/phpunit": "^12.0",
"rregeer/phpunit-coverage-check": "0.3.*",
"swoole/ide-helper": "5.1.3",
"utopia-php/cli": "0.22.*"
@@ -3903,9 +4089,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/5.3.17"
"source": "https://github.com/utopia-php/database/tree/feat-query-lib"
},
"time": "2026-03-20T01:18:52+00:00"
"time": "2026-04-02T11:40:25+00:00"
},
{
"name": "utopia-php/detector",
@@ -4518,16 +4704,16 @@
},
{
"name": "utopia-php/migration",
"version": "1.9.1",
"version": "dev-feat-query-lib",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/migration.git",
"reference": "7a86aeadf182b63a9f4ceba7e137588b31c5d2e2"
"reference": "1bc59979ee7a1af5f98333f277cad27785ef30c1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/7a86aeadf182b63a9f4ceba7e137588b31c5d2e2",
"reference": "7a86aeadf182b63a9f4ceba7e137588b31c5d2e2",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/1bc59979ee7a1af5f98333f277cad27785ef30c1",
"reference": "1bc59979ee7a1af5f98333f277cad27785ef30c1",
"shasum": ""
},
"require": {
@@ -4536,7 +4722,7 @@
"ext-openssl": "*",
"halaxa/json-machine": "^1.2",
"php": ">=8.1",
"utopia-php/database": "5.*",
"utopia-php/database": "dev-feat-query-lib as 5.3.17",
"utopia-php/dsn": "0.2.*",
"utopia-php/storage": "1.0.*"
},
@@ -4553,7 +4739,25 @@
"Utopia\\Migration\\": "src/Migration"
}
},
"notification-url": "https://packagist.org/downloads/",
"autoload-dev": {
"psr-4": {
"Utopia\\Tests\\": "tests/Migration"
}
},
"scripts": {
"test": [
"./vendor/bin/phpunit"
],
"lint": [
"./vendor/bin/pint --test"
],
"format": [
"./vendor/bin/pint"
],
"check": [
"./vendor/bin/phpstan analyse --level 3 src tests --memory-limit 2G"
]
},
"license": [
"MIT"
],
@@ -4566,10 +4770,10 @@
"utopia"
],
"support": {
"issues": "https://github.com/utopia-php/migration/issues",
"source": "https://github.com/utopia-php/migration/tree/1.9.1"
"source": "https://github.com/utopia-php/migration/tree/feat-query-lib",
"issues": "https://github.com/utopia-php/migration/issues"
},
"time": "2026-03-25T07:05:27+00:00"
"time": "2026-04-01T14:01:14+00:00"
},
{
"name": "utopia-php/mongo",
@@ -4789,6 +4993,53 @@
},
"time": "2020-10-24T07:04:59+00:00"
},
{
"name": "utopia-php/query",
"version": "dev-feat-builder",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/query.git",
"reference": "d40bf14fed0a5a92146d89cc6549518a2a559c8c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/query/zipball/d40bf14fed0a5a92146d89cc6549518a2a559c8c",
"reference": "d40bf14fed0a5a92146d89cc6549518a2a559c8c",
"shasum": ""
},
"require": {
"php": ">=8.4"
},
"require-dev": {
"laravel/pint": "*",
"mongodb/mongodb": "^2.0",
"phpstan/phpstan": "*",
"phpunit/phpunit": "^12.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Utopia\\Query\\": "src/Query"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "A simple library providing a query abstraction for filtering, ordering, and pagination",
"keywords": [
"framework",
"php",
"query",
"upf",
"utopia"
],
"support": {
"issues": "https://github.com/utopia-php/query/issues",
"source": "https://github.com/utopia-php/query/tree/feat-builder"
},
"time": "2026-03-31T02:21:12+00:00"
},
{
"name": "utopia-php/queue",
"version": "0.15.6",
@@ -5439,16 +5690,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
"version": "1.14.0",
"version": "1.16.5",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "7e7e257b10a8c1384a237e7d8d73452e2108901e"
"reference": "d2a93863ec907cdcae283c3062d9a24192b909fc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/7e7e257b10a8c1384a237e7d8d73452e2108901e",
"reference": "7e7e257b10a8c1384a237e7d8d73452e2108901e",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/d2a93863ec907cdcae283c3062d9a24192b909fc",
"reference": "d2a93863ec907cdcae283c3062d9a24192b909fc",
"shasum": ""
},
"require": {
@@ -5484,22 +5735,22 @@
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
"support": {
"issues": "https://github.com/appwrite/sdk-generator/issues",
"source": "https://github.com/appwrite/sdk-generator/tree/1.14.0"
"source": "https://github.com/appwrite/sdk-generator/tree/1.16.5"
},
"time": "2026-03-26T12:50:11+00:00"
"time": "2026-04-01T03:01:19+00:00"
},
{
"name": "brianium/paratest",
"version": "v7.19.2",
"version": "v7.20.0",
"source": {
"type": "git",
"url": "https://github.com/paratestphp/paratest.git",
"reference": "66e4f7910cecf67736bccf2b8bd53a2e3eb98bd9"
"reference": "81c80677c9ec0ed4ef16b246167f11dec81a6e3d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paratestphp/paratest/zipball/66e4f7910cecf67736bccf2b8bd53a2e3eb98bd9",
"reference": "66e4f7910cecf67736bccf2b8bd53a2e3eb98bd9",
"url": "https://api.github.com/repos/paratestphp/paratest/zipball/81c80677c9ec0ed4ef16b246167f11dec81a6e3d",
"reference": "81c80677c9ec0ed4ef16b246167f11dec81a6e3d",
"shasum": ""
},
"require": {
@@ -5523,7 +5774,7 @@
"ext-pcntl": "*",
"ext-pcov": "*",
"ext-posix": "*",
"phpstan/phpstan": "^2.1.40",
"phpstan/phpstan": "^2.1.44",
"phpstan/phpstan-deprecation-rules": "^2.0.4",
"phpstan/phpstan-phpunit": "^2.0.16",
"phpstan/phpstan-strict-rules": "^2.0.10",
@@ -5567,7 +5818,7 @@
],
"support": {
"issues": "https://github.com/paratestphp/paratest/issues",
"source": "https://github.com/paratestphp/paratest/tree/v7.19.2"
"source": "https://github.com/paratestphp/paratest/tree/v7.20.0"
},
"funding": [
{
@@ -5579,7 +5830,7 @@
"type": "paypal"
}
],
"time": "2026-03-09T14:33:17+00:00"
"time": "2026-03-29T15:46:14+00:00"
},
{
"name": "czproject/git-php",
@@ -6195,11 +6446,11 @@
},
{
"name": "phpstan/phpstan",
"version": "2.1.44",
"version": "2.1.46",
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/4a88c083c668b2c364a425c9b3171b2d9ea5d218",
"reference": "4a88c083c668b2c364a425c9b3171b2d9ea5d218",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/a193923fc2d6325ef4e741cf3af8c3e8f54dbf25",
"reference": "a193923fc2d6325ef4e741cf3af8c3e8f54dbf25",
"shasum": ""
},
"require": {
@@ -6244,7 +6495,7 @@
"type": "github"
}
],
"time": "2026-03-25T17:34:21+00:00"
"time": "2026-04-01T09:25:14+00:00"
},
{
"name": "phpunit/php-code-coverage",
@@ -6594,16 +6845,16 @@
},
{
"name": "phpunit/phpunit",
"version": "12.5.14",
"version": "12.5.15",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "47283cfd98d553edcb1353591f4e255dc1bb61f0"
"reference": "aeb6899ffdbbf4b4ff5e6b6ebb77b35c51bb6d9a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/47283cfd98d553edcb1353591f4e255dc1bb61f0",
"reference": "47283cfd98d553edcb1353591f4e255dc1bb61f0",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/aeb6899ffdbbf4b4ff5e6b6ebb77b35c51bb6d9a",
"reference": "aeb6899ffdbbf4b4ff5e6b6ebb77b35c51bb6d9a",
"shasum": ""
},
"require": {
@@ -6625,7 +6876,7 @@
"sebastian/cli-parser": "^4.2.0",
"sebastian/comparator": "^7.1.4",
"sebastian/diff": "^7.0.0",
"sebastian/environment": "^8.0.3",
"sebastian/environment": "^8.0.4",
"sebastian/exporter": "^7.0.2",
"sebastian/global-state": "^8.0.2",
"sebastian/object-enumerator": "^7.0.0",
@@ -6672,31 +6923,15 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.14"
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.15"
},
"funding": [
{
"url": "https://phpunit.de/sponsors.html",
"type": "custom"
},
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
"type": "tidelift"
"url": "https://phpunit.de/sponsoring.html",
"type": "other"
}
],
"time": "2026-02-18T12:38:40+00:00"
"time": "2026-03-31T06:41:33+00:00"
},
{
"name": "sebastian/cli-parser",
@@ -7681,16 +7916,16 @@
},
{
"name": "symfony/console",
"version": "v8.0.7",
"version": "v8.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a"
"reference": "5b66d385dc58f69652e56f78a4184615e3f2b7f7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a",
"reference": "15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a",
"url": "https://api.github.com/repos/symfony/console/zipball/5b66d385dc58f69652e56f78a4184615e3f2b7f7",
"reference": "5b66d385dc58f69652e56f78a4184615e3f2b7f7",
"shasum": ""
},
"require": {
@@ -7747,7 +7982,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v8.0.7"
"source": "https://github.com/symfony/console/tree/v8.0.8"
},
"funding": [
{
@@ -7767,7 +8002,7 @@
"type": "tidelift"
}
],
"time": "2026-03-06T14:06:22+00:00"
"time": "2026-03-30T15:14:47+00:00"
},
{
"name": "symfony/polyfill-ctype",
@@ -8101,16 +8336,16 @@
},
{
"name": "symfony/process",
"version": "v8.0.5",
"version": "v8.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674"
"reference": "cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674",
"reference": "b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674",
"url": "https://api.github.com/repos/symfony/process/zipball/cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc",
"reference": "cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc",
"shasum": ""
},
"require": {
@@ -8142,7 +8377,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v8.0.5"
"source": "https://github.com/symfony/process/tree/v8.0.8"
},
"funding": [
{
@@ -8162,20 +8397,20 @@
"type": "tidelift"
}
],
"time": "2026-01-26T15:08:38+00:00"
"time": "2026-03-30T15:14:47+00:00"
},
{
"name": "symfony/string",
"version": "v8.0.6",
"version": "v8.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4"
"reference": "ae9488f874d7603f9d2dfbf120203882b645d963"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/6c9e1108041b5dce21a9a4984b531c4923aa9ec4",
"reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4",
"url": "https://api.github.com/repos/symfony/string/zipball/ae9488f874d7603f9d2dfbf120203882b645d963",
"reference": "ae9488f874d7603f9d2dfbf120203882b645d963",
"shasum": ""
},
"require": {
@@ -8232,7 +8467,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v8.0.6"
"source": "https://github.com/symfony/string/tree/v8.0.8"
},
"funding": [
{
@@ -8252,7 +8487,7 @@
"type": "tidelift"
}
],
"time": "2026-02-09T10:14:57+00:00"
"time": "2026-03-30T15:14:47+00:00"
},
{
"name": "textalk/websocket",
@@ -8433,9 +8668,32 @@
"time": "2024-11-07T12:36:22+00:00"
}
],
"aliases": [],
"aliases": [
{
"package": "utopia-php/audit",
"version": "dev-feat-query-lib",
"alias": "2.2.0",
"alias_normalized": "2.2.0.0"
},
{
"package": "utopia-php/database",
"version": "dev-feat-query-lib",
"alias": "5.3.17",
"alias_normalized": "5.3.17.0"
},
{
"package": "utopia-php/migration",
"version": "dev-feat-query-lib",
"alias": "1.8.0",
"alias_normalized": "1.8.0.0"
}
],
"minimum-stability": "dev",
"stability-flags": {},
"stability-flags": {
"utopia-php/audit": 20,
"utopia-php/database": 20,
"utopia-php/migration": 20
},
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
+1 -1
View File
@@ -1362,7 +1362,7 @@ services:
container_name: appwrite-redis
command: >
redis-server
--maxmemory 512mb
--maxmemory 2048mb
--maxmemory-policy allkeys-lru
--maxmemory-samples 5
ports:
-12
View File
@@ -156,12 +156,6 @@ parameters:
count: 1
path: app/init/registers.php
-
message: '#^Variable \$hostname on left side of \?\? always exists and is not nullable\.$#'
identifier: nullCoalesce.variable
count: 1
path: app/init/resources.php
-
message: '#^Anonymous function has an unused use \$register\.$#'
identifier: closure.unusedUse
@@ -264,12 +258,6 @@ parameters:
count: 1
path: src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php
-
message: '#^Variable \$relations in empty\(\) always exists and is not falsy\.$#'
identifier: empty.variable
count: 1
path: src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php
-
message: '#^Anonymous function has an unused use \$dbForProject\.$#'
identifier: closure.unusedUse
+21 -20
View File
@@ -8,6 +8,7 @@ use Utopia\Database\Exception;
use Utopia\Database\Exception\Timeout;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Query\Method;
/**
* Service for managing transaction state and providing transaction-aware document operations
@@ -578,7 +579,7 @@ class TransactionState
$selections = [];
foreach ($queries as $query) {
if ($query->getMethod() === Query::TYPE_SELECT) {
if ($query->getMethod() === Method::Select) {
$values = $query->getValues();
foreach ($values as $value) {
// Skip relationship selections (containing '.')
@@ -627,13 +628,13 @@ class TransactionState
foreach ($queries as $query) {
$method = $query->getMethod();
if (!\in_array($method, [
Query::TYPE_LIMIT,
Query::TYPE_OFFSET,
Query::TYPE_CURSOR_AFTER,
Query::TYPE_CURSOR_BEFORE,
Query::TYPE_SELECT,
Query::TYPE_ORDER_ASC,
Query::TYPE_ORDER_DESC
Method::Limit,
Method::Offset,
Method::CursorAfter,
Method::CursorBefore,
Method::Select,
Method::OrderAsc,
Method::OrderDesc
])) {
$filters[] = $query;
}
@@ -660,19 +661,19 @@ class TransactionState
$docValue = $doc->getAttribute($attribute);
switch ($filter->getMethod()) {
case Query::TYPE_EQUAL:
case Method::Equal:
if (!\in_array($docValue, $values)) {
return false;
}
break;
case Query::TYPE_NOT_EQUAL:
case Method::NotEqual:
if (\in_array($docValue, $values)) {
return false;
}
break;
case Query::TYPE_CONTAINS:
case Method::Contains:
$matches = false;
foreach ($values as $value) {
if (\is_array($docValue) && \in_array($value, $docValue)) {
@@ -685,7 +686,7 @@ class TransactionState
}
break;
case Query::TYPE_STARTS_WITH:
case Method::StartsWith:
$matches = false;
foreach ($values as $value) {
if (\is_string($docValue) && \str_starts_with($docValue, $value)) {
@@ -698,7 +699,7 @@ class TransactionState
}
break;
case Query::TYPE_ENDS_WITH:
case Method::EndsWith:
$matches = false;
foreach ($values as $value) {
if (\is_string($docValue) && \str_ends_with($docValue, $value)) {
@@ -711,43 +712,43 @@ class TransactionState
}
break;
case Query::TYPE_GREATER:
case Method::GreaterThan:
if (!($docValue > $values[0])) {
return false;
}
break;
case Query::TYPE_GREATER_EQUAL:
case Method::GreaterThanEqual:
if (!($docValue >= $values[0])) {
return false;
}
break;
case Query::TYPE_LESSER:
case Method::LessThan:
if (!($docValue < $values[0])) {
return false;
}
break;
case Query::TYPE_LESSER_EQUAL:
case Method::LessThanEqual:
if (!($docValue <= $values[0])) {
return false;
}
break;
case Query::TYPE_IS_NULL:
case Method::IsNull:
if (!\is_null($docValue)) {
return false;
}
break;
case Query::TYPE_IS_NOT_NULL:
case Method::IsNotNull:
if (\is_null($docValue)) {
return false;
}
break;
case Query::TYPE_BETWEEN:
case Method::Between:
if (!($docValue >= $values[0] && $docValue <= $values[1])) {
return false;
}
+6 -5
View File
@@ -11,6 +11,7 @@ use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Query\Method;
class Realtime extends MessagingAdapter
{
@@ -84,7 +85,7 @@ class Realtime extends MessagingAdapter
$data = [
'strings' => $strings,
'compiled' => RuntimeQuery::compile($queryGroup),
'compiled' => RuntimeQuery::prepare($queryGroup),
];
foreach ($roles as $role) {
@@ -414,7 +415,7 @@ class Realtime extends MessagingAdapter
{
$queries = Query::parseQueries($queries);
$stack = $queries;
$allowed = implode(', ', RuntimeQuery::ALLOWED_QUERIES);
$allowed = implode(', ', array_map(fn (Method $m) => $m->value, RuntimeQuery::ALLOWED_QUERIES));
while (!empty($stack)) {
$query = array_pop($stack);
@@ -422,15 +423,15 @@ class Realtime extends MessagingAdapter
if (!in_array($method, RuntimeQuery::ALLOWED_QUERIES, true)) {
throw new QueryException(
"Query method '{$method}' is not supported in Realtime queries. Allowed: {$allowed}"
"Query method '{$method->value}' is not supported in Realtime queries. Allowed: {$allowed}"
);
}
if ($method === Query::TYPE_SELECT) {
if ($method === Method::Select) {
RuntimeQuery::validateSelectQuery($query);
}
if (in_array($method, [Query::TYPE_AND, Query::TYPE_OR], true)) {
if (in_array($method, [Method::And, Method::Or], true)) {
\array_push($stack, ...$query->getValues());
}
}
+18 -38
View File
@@ -5,6 +5,7 @@ namespace Appwrite\Migration;
use Exception;
use Utopia\Config\Config;
use Utopia\Console;
use Utopia\Database\Attribute;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Conflict;
@@ -252,15 +253,8 @@ abstract class Migration
$collection = $this->collections[$collectionType][$id];
$attributes = [];
foreach ($collection['attributes'] as $attribute) {
$attributes[] = new Document($attribute);
}
$indexes = [];
foreach ($collection['indexes'] as $index) {
$indexes[] = new Document($index);
}
$attributes = $collection['attributes'];
$indexes = $collection['indexes'];
try {
$this->dbForProject->createCollection($name, $attributes, $indexes);
@@ -309,7 +303,7 @@ abstract class Migration
$attributesToCreate = [];
$attributes = $collection['attributes'];
$attributeKeys = \array_column($collection['attributes'], '$id');
$attributeKeys = \array_map(fn ($a) => $a->key, $collection['attributes']);
foreach ($attributeIds as $attributeId) {
$attributeKey = \array_search($attributeId, $attributeKeys);
@@ -318,12 +312,11 @@ abstract class Migration
throw new Exception("Attribute {$attributeId} not found");
}
$attribute = $attributes[$attributeKey];
$attribute['filters'] ??= [];
$attribute['default'] ??= null;
$attribute['default'] = \in_array('json', $attribute['filters'])
? \json_encode($attribute['default'])
: $attribute['default'];
$attribute = clone $attributes[$attributeKey];
if (\in_array('json', $attribute->filters) && $attribute->default !== null) {
$attribute->default = \json_encode($attribute->default);
}
$attributesToCreate[] = $attribute;
}
@@ -374,28 +367,21 @@ abstract class Migration
$attributes = $collection['attributes'];
$attributeKey = \array_search($attributeId, \array_column($attributes, '$id'));
$attributeKey = \array_search($attributeId, \array_map(fn ($a) => $a->key, $attributes));
if ($attributeKey === false) {
throw new Exception("Attribute {$attributeId} not found");
}
$attribute = $attributes[$attributeKey];
$filters = $attribute['filters'] ?? [];
$default = $attribute['default'] ?? null;
$attribute = clone $attributes[$attributeKey];
if (\in_array('json', $attribute->filters) && $attribute->default !== null) {
$attribute->default = \json_encode($attribute->default);
}
$database->createAttribute(
collection: $collectionId,
id: $attributeId,
type: $attribute['type'],
size: $attribute['size'],
required: $attribute['required'],
default: \in_array('json', $filters) ? \json_encode($default) : $default,
signed: $attribute['signed'] ?? true,
array: $attribute['array'] ?? false,
format: $attribute['format'] ?? '',
formatOptions: $attribute['formatOptions'] ?? [],
filters: $filters,
attribute: $attribute,
);
}
@@ -432,21 +418,15 @@ abstract class Migration
$indexes = $collection['indexes'];
$indexKey = \array_search($indexId, \array_column($indexes, '$id'));
$indexKey = \array_search($indexId, \array_map(fn ($i) => $i->key, $indexes));
if ($indexKey === false) {
throw new Exception("Index {$indexId} not found");
}
$index = $indexes[$indexKey];
$database->createIndex(
collection: $collectionId,
id: $indexId,
type: $index['type'],
attributes: $index['attributes'],
lengths: $index['lengths'] ?? [],
orders: $index['orders'] ?? []
index: $indexes[$indexKey],
);
}
+2 -1
View File
@@ -11,6 +11,7 @@ use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Query\Schema\ColumnType;
use Utopia\System\System;
class V15 extends Migration
@@ -362,7 +363,7 @@ class V15 extends Migration
$this->dbForProject->updateAttribute(
collection: $table,
id: $attribute,
type: Database::VAR_DATETIME,
type: ColumnType::Datetime->value,
signed: false
);
} catch (\Throwable $th) {
+3 -2
View File
@@ -7,6 +7,7 @@ use Utopia\Auth\Proofs\Password;
use Utopia\Console;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Query\Schema\ColumnType;
class V17 extends Migration
{
@@ -47,7 +48,7 @@ class V17 extends Migration
$id = "bucket_{$bucket->getSequence()}";
try {
$this->dbForProject->updateAttribute($id, 'mimeType', Database::VAR_STRING, 255, true, false);
$this->dbForProject->updateAttribute($id, 'mimeType', ColumnType::String->value, 255, true, false);
$this->dbForProject->purgeCachedCollection($id);
} catch (\Throwable $th) {
Console::warning("'mimeType' from {$id}: {$th->getMessage()}");
@@ -87,7 +88,7 @@ class V17 extends Migration
/**
* Update 'mimeType' attribute size (127->255)
*/
$this->dbForProject->updateAttribute($id, 'mimeType', Database::VAR_STRING, 255, true, false);
$this->dbForProject->updateAttribute($id, 'mimeType', ColumnType::String->value, 255, true, false);
$this->dbForProject->purgeCachedCollection($id);
} catch (\Throwable $th) {
Console::warning("'mimeType' from {$id}: {$th->getMessage()}");
+3 -2
View File
@@ -8,6 +8,7 @@ use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Query\Schema\ColumnType;
class V18 extends Migration
{
@@ -56,7 +57,7 @@ class V18 extends Migration
$collectionTable = "{$databaseTable}_collection_{$collection->getSequence()}";
foreach ($collection['attributes'] ?? [] as $attribute) {
if ($attribute['type'] !== Database::VAR_FLOAT) {
if ($attribute['type'] !== ColumnType::Float->value) {
continue;
}
$this->changeAttributeInternalType($collectionTable, $attribute['key'], 'DOUBLE');
@@ -87,7 +88,7 @@ class V18 extends Migration
Console::log("Migrating Collection \"{$id}\"");
foreach ($collection['attributes'] ?? [] as $attribute) {
if ($attribute['type'] !== Database::VAR_FLOAT) {
if ($attribute['type'] !== ColumnType::Float->value) {
continue;
}
$this->changeAttributeInternalType($id, $attribute['$id'], 'DOUBLE');
@@ -4,6 +4,7 @@ namespace Appwrite\Platform\Installer\Http\Installer;
use Appwrite\Platform\Installer\Runtime\State;
use Appwrite\Platform\Installer\Server;
use Swoole\Coroutine;
use Utopia\Http\Adapter\Swoole\Request;
use Utopia\Http\Adapter\Swoole\Response;
use Utopia\Platform\Action;
@@ -50,12 +51,17 @@ class Complete extends Action
$progressData = ($installId !== '') ? $state->readProgressFile($installId) : [];
if (!$sessionSecret) {
$details = $progressData['details'][Server::STEP_ACCOUNT_SETUP] ?? [];
if (!empty($details['sessionSecret'])) {
$sessionSecret = $details['sessionSecret'];
$sessionId = $sessionId ?: ($details['sessionId'] ?? '');
$sessionExpire = $sessionExpire ?: ($details['sessionExpire'] ?? '');
if (!$sessionSecret && $installId !== '') {
for ($attempt = 0; $attempt < 10; $attempt++) {
$progressData = $state->readProgressFile($installId);
$details = $progressData['details'][Server::STEP_ACCOUNT_SETUP] ?? [];
if (!empty($details['sessionSecret'])) {
$sessionSecret = $details['sessionSecret'];
$sessionId = $sessionId ?: ($details['sessionId'] ?? '');
$sessionExpire = $sessionExpire ?: ($details['sessionExpire'] ?? '');
break;
}
Coroutine::getCid() !== -1 ? Coroutine::sleep(0.5) : usleep(500_000);
}
}
@@ -7,6 +7,9 @@ use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Adapter\Feature\Relationships as FeatureRelationships;
use Utopia\Database\Adapter\Feature\Spatial;
use Utopia\Database\Capability;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Domains\Domain;
@@ -93,14 +96,14 @@ class Get extends Action
'_APP_OPTIONS_FORCE_HTTPS' => System::getEnv('_APP_OPTIONS_FORCE_HTTPS'),
'_APP_DOMAINS_NAMESERVERS' => System::getEnv('_APP_DOMAINS_NAMESERVERS'),
'_APP_DB_ADAPTER' => System::getEnv('_APP_DB_ADAPTER', 'mariadb'),
'supportForRelationships' => $adapter->getSupportForRelationships(),
'supportForOperators' => $adapter->getSupportForOperators(),
'supportForSpatials' => $adapter->getSupportForSpatialAttributes(),
'supportForSpatialIndexNull' => $adapter->getSupportForSpatialIndexNull(),
'supportForFulltextWildcard' => $adapter->getSupportForFulltextWildcardIndex(),
'supportForMultipleFulltextIndexes' => $adapter->getSupportForMultipleFulltextIndexes(),
'supportForAttributeResizing' => $adapter->getSupportForAttributeResizing(),
'supportForSchemas' => $adapter->getSupportForSchemas(),
'supportForRelationships' => $adapter instanceof FeatureRelationships,
'supportForOperators' => $adapter->supports(Capability::Operators),
'supportForSpatials' => $adapter instanceof Spatial,
'supportForSpatialIndexNull' => $adapter->supports(Capability::SpatialIndexNull),
'supportForFulltextWildcard' => $adapter->supports(Capability::FulltextWildcard),
'supportForMultipleFulltextIndexes' => $adapter->supports(Capability::MultipleFulltextIndexes),
'supportForAttributeResizing' => $adapter->supports(Capability::AttributeResizing),
'supportForSchemas' => $adapter->supports(Capability::Schemas),
'maxIndexLength' => $adapter->getMaxIndexLength(),
'supportForIntegerIds' => $adapter->getIdAttributeType() === 'integer',
]);
@@ -4,9 +4,9 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action as AppwriteAction;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Operator;
use Utopia\Query\Schema\ColumnType;
class Action extends AppwriteAction
{
@@ -43,7 +43,7 @@ class Action extends AppwriteAction
{
$relationshipKeys = [];
foreach ($collection->getAttribute('attributes', []) as $attribute) {
if ($attribute->getAttribute('type') === Database::VAR_RELATIONSHIP) {
if ($attribute->getAttribute('type') === ColumnType::Relationship->value) {
$relationshipKeys[$attribute->getAttribute('key')] = true;
}
}
@@ -8,6 +8,8 @@ use Appwrite\Extend\Exception;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response as UtopiaResponse;
use Throwable;
use Utopia\Database\Adapter\Feature\Spatial;
use Utopia\Database\Capability;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate as DuplicateException;
@@ -17,10 +19,13 @@ use Utopia\Database\Exception\Relationship as RelationshipException;
use Utopia\Database\Exception\Structure as StructureException;
use Utopia\Database\Exception\Truncate as TruncateException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\RelationSide;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Structure;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Platform\Action as UtopiaAction;
use Utopia\Query\Schema\ColumnType;
use Utopia\Query\Schema\ForeignKeyAction;
use Utopia\Validator\Range;
abstract class Action extends UtopiaAction
@@ -233,55 +238,55 @@ abstract class Action extends UtopiaAction
$isCollections = $this->isCollectionsAPI();
return match ($type) {
Database::VAR_BOOLEAN => $isCollections
ColumnType::Boolean->value => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_BOOLEAN
: UtopiaResponse::MODEL_COLUMN_BOOLEAN,
Database::VAR_INTEGER => $isCollections
ColumnType::Integer->value => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_INTEGER
: UtopiaResponse::MODEL_COLUMN_INTEGER,
Database::VAR_FLOAT => $isCollections
ColumnType::Double->value => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_FLOAT
: UtopiaResponse::MODEL_COLUMN_FLOAT,
Database::VAR_DATETIME => $isCollections
ColumnType::Datetime->value => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_DATETIME
: UtopiaResponse::MODEL_COLUMN_DATETIME,
Database::VAR_RELATIONSHIP => $isCollections
ColumnType::Relationship->value => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_RELATIONSHIP
: UtopiaResponse::MODEL_COLUMN_RELATIONSHIP,
Database::VAR_POINT => $isCollections
ColumnType::Point->value => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_POINT
: UtopiaResponse::MODEL_COLUMN_POINT,
Database::VAR_LINESTRING => $isCollections
ColumnType::Linestring->value => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_LINE
: UtopiaResponse::MODEL_COLUMN_LINE,
Database::VAR_POLYGON => $isCollections
ColumnType::Polygon->value => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_POLYGON
: UtopiaResponse::MODEL_COLUMN_POLYGON,
Database::VAR_VARCHAR => $isCollections
ColumnType::Varchar->value => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_VARCHAR
: UtopiaResponse::MODEL_COLUMN_VARCHAR,
Database::VAR_TEXT => $isCollections
ColumnType::Text->value => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_TEXT
: UtopiaResponse::MODEL_COLUMN_TEXT,
Database::VAR_MEDIUMTEXT => $isCollections
ColumnType::MediumText->value => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_MEDIUMTEXT
: UtopiaResponse::MODEL_COLUMN_MEDIUMTEXT,
Database::VAR_LONGTEXT => $isCollections
ColumnType::LongText->value => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_LONGTEXT
: UtopiaResponse::MODEL_COLUMN_LONGTEXT,
Database::VAR_STRING => match ($format) {
ColumnType::String->value => match ($format) {
APP_DATABASE_ATTRIBUTE_EMAIL => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_EMAIL
: UtopiaResponse::MODEL_COLUMN_EMAIL,
@@ -322,7 +327,7 @@ abstract class Action extends UtopiaAction
$default = $attribute->getAttribute('default');
$options = $attribute->getAttribute('options', []);
if (in_array($type, Database::SPATIAL_TYPES) && !$dbForProject->getAdapter()->getSupportForSpatialAttributes()) {
if (in_array($type, [ColumnType::Point->value, ColumnType::Linestring->value, ColumnType::Polygon->value]) && !$dbForProject->getAdapter() instanceof Spatial) {
throw new Exception($this->getSpatialTypeNotSupportedException(), params: [$type]);
}
@@ -339,7 +344,7 @@ abstract class Action extends UtopiaAction
}
if (!empty($format)) {
if (!Structure::hasFormat($format, $type)) {
if (!Structure::hasFormat($format, ColumnType::from($type))) {
throw new Exception($this->getFormatUnsupportedException(), "Format $format not available for $type columns.");
}
}
@@ -353,8 +358,8 @@ abstract class Action extends UtopiaAction
throw new Exception($this->getDefaultUnsupportedException(), 'Cannot set default value for array ' . $this->getContext() . 's');
}
if ($type === Database::VAR_RELATIONSHIP) {
$options['side'] = Database::RELATION_SIDE_PARENT;
if ($type === ColumnType::Relationship->value) {
$options['side'] = RelationSide::Parent->value;
$relatedCollection = $dbForProject->getDocument('database_' . $db->getSequence(), $options['relatedCollection'] ?? '');
if ($relatedCollection->isEmpty()) {
$parent = $this->isCollectionsAPI() ? 'collection' : 'table';
@@ -384,8 +389,8 @@ abstract class Action extends UtopiaAction
]);
if (
!$dbForProject->getAdapter()->getSupportForSpatialIndexNull() &&
\in_array($attribute->getAttribute('type'), Database::SPATIAL_TYPES) &&
!$dbForProject->getAdapter()->supports(Capability::SpatialIndexNull) &&
\in_array($attribute->getAttribute('type'), [ColumnType::Point->value, ColumnType::Linestring->value, ColumnType::Polygon->value]) &&
$attribute->getAttribute('required')
) {
$hasData = $authorization->skip(fn () => $dbForProject
@@ -412,11 +417,11 @@ abstract class Action extends UtopiaAction
$dbForProject->purgeCachedDocument('database_' . $db->getSequence(), $collectionId);
$dbForProject->purgeCachedCollection('database_' . $db->getSequence() . '_collection_' . $collection->getSequence());
if ($type === Database::VAR_RELATIONSHIP && $options['twoWay']) {
if ($type === ColumnType::Relationship->value && $options['twoWay']) {
$twoWayKey = $options['twoWayKey'];
$options['relatedCollection'] = $collection->getId();
$options['twoWayKey'] = $key;
$options['side'] = Database::RELATION_SIDE_CHILD;
$options['side'] = RelationSide::Child->value;
try {
$twoWayAttribute = new Document([
@@ -516,7 +521,7 @@ abstract class Action extends UtopiaAction
throw new Exception($this->getTypeInvalidException());
}
if ($attribute->getAttribute('type') === Database::VAR_STRING && $attribute->getAttribute(('filter') !== $filter)) {
if ($attribute->getAttribute('type') === ColumnType::String->value && $attribute->getAttribute(('filter') !== $filter)) {
throw new Exception($this->getTypeInvalidException());
}
@@ -549,9 +554,9 @@ abstract class Action extends UtopiaAction
}
if ($attribute->getAttribute('format') === APP_DATABASE_ATTRIBUTE_INT_RANGE) {
$validator = new Range($min, $max, Database::VAR_INTEGER);
$validator = new Range($min, $max, ColumnType::Integer->value);
} else {
$validator = new Range($min, $max, Database::VAR_FLOAT);
$validator = new Range($min, $max, ColumnType::Double->value);
if (!is_null($default)) {
$default = \floatval($default);
@@ -593,7 +598,7 @@ abstract class Action extends UtopiaAction
break;
}
if ($type === Database::VAR_RELATIONSHIP) {
if ($type === ColumnType::Relationship->value) {
$primaryDocumentOptions = \array_merge($attribute->getAttribute('options', []), $options);
$attribute->setAttribute('options', $primaryDocumentOptions);
try {
@@ -601,7 +606,7 @@ abstract class Action extends UtopiaAction
collection: $collectionId,
id: $key,
newKey: $newKey,
onDelete: $primaryDocumentOptions['onDelete'],
onDelete: ForeignKeyAction::from($primaryDocumentOptions['onDelete']),
);
} catch (IndexException) {
throw new Exception(Exception::INDEX_INVALID);
@@ -16,6 +16,7 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
@@ -78,7 +79,7 @@ class Create extends Action
{
$attribute = $this->createAttribute($databaseId, $collectionId, new Document([
'key' => $key,
'type' => Database::VAR_BOOLEAN,
'type' => ColumnType::Boolean->value,
'size' => 0,
'required' => $required,
'default' => $default,
@@ -15,6 +15,7 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
@@ -82,7 +83,7 @@ class Update extends Action
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
authorization: $authorization,
type: Database::VAR_BOOLEAN,
type: ColumnType::Boolean->value,
default: $default,
required: $required,
newKey: $newKey
@@ -17,6 +17,7 @@ use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
@@ -82,7 +83,7 @@ class Create extends Action
$collectionId,
new Document([
'key' => $key,
'type' => Database::VAR_DATETIME,
'type' => ColumnType::Datetime->value,
'size' => 0,
'required' => $required,
'default' => $default,
@@ -16,6 +16,7 @@ use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
@@ -83,7 +84,7 @@ class Update extends Action
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
authorization: $authorization,
type: Database::VAR_DATETIME,
type: ColumnType::Datetime->value,
default: $default,
required: $required,
newKey: $newKey
@@ -11,6 +11,7 @@ use Appwrite\SDK\Deprecated;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Capability;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
@@ -18,6 +19,7 @@ use Utopia\Database\Validator\IndexDependency as IndexDependencyValidator;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
class Delete extends Action
{
@@ -91,7 +93,7 @@ class Delete extends Action
$validator = new IndexDependencyValidator(
$collection->getAttribute('indexes'),
$dbForProject->getAdapter()->getSupportForCastIndexArray(),
$dbForProject->getAdapter()->supports(Capability::CastIndexArray),
);
if (!$validator->isValid($attribute)) {
@@ -106,7 +108,7 @@ class Delete extends Action
$dbForProject->purgeCachedDocument('database_' . $db->getSequence(), $collectionId);
$dbForProject->purgeCachedCollection('database_' . $db->getSequence() . '_collection_' . $collection->getSequence());
if ($attribute->getAttribute('type') === Database::VAR_RELATIONSHIP) {
if ($attribute->getAttribute('type') === ColumnType::Relationship->value) {
$options = $attribute->getAttribute('options');
if ($options['twoWay']) {
$relatedCollection = $dbForProject->getDocument('database_' . $db->getSequence(), $options['relatedCollection']);
@@ -17,6 +17,7 @@ use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Emails\Validator\Email;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
@@ -82,7 +83,7 @@ class Create extends Action
$collectionId,
new Document([
'key' => $key,
'type' => Database::VAR_STRING,
'type' => ColumnType::String->value,
'size' => 254,
'required' => $required,
'default' => $default,
@@ -16,6 +16,7 @@ use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Emails\Validator\Email;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
@@ -83,7 +84,7 @@ class Update extends Action
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
authorization: $authorization,
type: Database::VAR_STRING,
type: ColumnType::String->value,
filter: APP_DATABASE_ATTRIBUTE_EMAIL,
default: $default,
required: $required,
@@ -17,6 +17,7 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
@@ -89,7 +90,7 @@ class Create extends Action
$collectionId,
new Document([
'key' => $key,
'type' => Database::VAR_STRING,
'type' => ColumnType::String->value,
'size' => Database::LENGTH_KEY,
'required' => $required,
'default' => $default,
@@ -15,6 +15,7 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
@@ -85,7 +86,7 @@ class Update extends Action
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
authorization: $authorization,
type: Database::VAR_STRING,
type: ColumnType::String->value,
filter: APP_DATABASE_ATTRIBUTE_ENUM,
default: $default,
required: $required,
@@ -17,6 +17,7 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\FloatValidator;
use Utopia\Validator\Nullable;
@@ -88,14 +89,14 @@ class Create extends Action
throw new Exception($this->getInvalidValueException(), 'Minimum value must be lesser than maximum value');
}
$validator = new Range($min, $max, Database::VAR_FLOAT);
$validator = new Range($min, $max, ColumnType::Double->value);
if (!\is_null($default) && !$validator->isValid($default)) {
throw new Exception($this->getInvalidValueException(), $validator->getDescription());
}
$attribute = $this->createAttribute($databaseId, $collectionId, new Document([
'key' => $key,
'type' => Database::VAR_FLOAT,
'type' => ColumnType::Double->value,
'size' => 0,
'required' => $required,
'default' => $default,
@@ -15,6 +15,7 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\FloatValidator;
use Utopia\Validator\Nullable;
@@ -85,7 +86,7 @@ class Update extends Action
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
authorization: $authorization,
type: Database::VAR_FLOAT,
type: ColumnType::Double->value,
default: $default,
required: $required,
min: $min,
@@ -16,6 +16,7 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\IP;
use Utopia\Validator\Nullable;
@@ -82,7 +83,7 @@ class Create extends Action
$collectionId,
new Document([
'key' => $key,
'type' => Database::VAR_STRING,
'type' => ColumnType::String->value,
'size' => 39,
'required' => $required,
'default' => $default,
@@ -15,6 +15,7 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\IP;
use Utopia\Validator\Nullable;
@@ -83,7 +84,7 @@ class Update extends Action
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
authorization: $authorization,
type: Database::VAR_STRING,
type: ColumnType::String->value,
filter: APP_DATABASE_ATTRIBUTE_IP,
default: $default,
required: $required,
@@ -17,6 +17,7 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\Integer;
use Utopia\Validator\Nullable;
@@ -88,7 +89,7 @@ class Create extends Action
throw new Exception($this->getInvalidValueException(), 'Minimum value must be lesser than maximum value');
}
$validator = new Range($min, $max, Database::VAR_INTEGER);
$validator = new Range($min, $max, ColumnType::Integer->value);
if (!\is_null($default) && !$validator->isValid($default)) {
throw new Exception($this->getInvalidValueException(), $validator->getDescription());
}
@@ -97,7 +98,7 @@ class Create extends Action
$attribute = $this->createAttribute($databaseId, $collectionId, new Document([
'key' => $key,
'type' => Database::VAR_INTEGER,
'type' => ColumnType::Integer->value,
'size' => $size,
'required' => $required,
'default' => $default,
@@ -15,6 +15,7 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\Integer;
use Utopia\Validator\Nullable;
@@ -85,7 +86,7 @@ class Update extends Action
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
authorization: $authorization,
type: Database::VAR_INTEGER,
type: ColumnType::Integer->value,
default: $default,
required: $required,
min: $min,
@@ -11,6 +11,7 @@ use Appwrite\SDK\Deprecated;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Adapter\Feature\Spatial as SpatialFeature;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
@@ -18,6 +19,7 @@ use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\Spatial;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
@@ -66,7 +68,7 @@ class Create extends Action
->param('collectionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).', false, ['dbForProject'])
->param('key', '', fn (Database $dbForProject) => new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Attribute Key.', false, ['dbForProject'])
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Nullable(new Spatial(Database::VAR_LINESTRING)), 'Default value for attribute when not provided, two-dimensional array of coordinate pairs, [[longitude, latitude], [longitude, latitude], …], listing the vertices of the line in order. Cannot be set when attribute is required.', true)
->param('default', null, new Nullable(new Spatial(ColumnType::Linestring->value)), 'Default value for attribute when not provided, two-dimensional array of coordinate pairs, [[longitude, latitude], [longitude, latitude], …], listing the vertices of the line in order. Cannot be set when attribute is required.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForDatabase')
@@ -77,13 +79,13 @@ class Create extends Action
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?array $default, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
{
if (!$dbForProject->getAdapter()->getSupportForSpatialAttributes()) {
if (!$dbForProject->getAdapter() instanceof SpatialFeature) {
throw new Exception(Exception::GENERAL_FEATURE_UNSUPPORTED, 'Spatial columns are not supported by this database.');
}
$attribute = $this->createAttribute($databaseId, $collectionId, new Document([
'key' => $key,
'type' => Database::VAR_LINESTRING,
'type' => ColumnType::Linestring->value,
'required' => $required,
'default' => $default
]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
@@ -11,12 +11,14 @@ use Appwrite\SDK\Deprecated;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Adapter\Feature\Spatial as SpatialFeature;
use Utopia\Database\Database;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\Spatial;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
@@ -66,7 +68,7 @@ class Update extends Action
->param('collectionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#createCollection).', false, ['dbForProject'])
->param('key', '', fn (Database $dbForProject) => new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Attribute Key.', false, ['dbForProject'])
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Nullable(new Spatial(Database::VAR_LINESTRING)), 'Default value for attribute when not provided, two-dimensional array of coordinate pairs, [[longitude, latitude], [longitude, latitude], …], listing the vertices of the line in order. Cannot be set when attribute is required.', true)
->param('default', null, new Nullable(new Spatial(ColumnType::Linestring->value)), 'Default value for attribute when not provided, two-dimensional array of coordinate pairs, [[longitude, latitude], [longitude, latitude], …], listing the vertices of the line in order. Cannot be set when attribute is required.', true)
->param('newKey', null, fn (Database $dbForProject) => new Nullable(new Key(false, $dbForProject->getAdapter()->getMaxUIDLength())), 'New attribute key.', true, ['dbForProject'])
->inject('response')
->inject('dbForProject')
@@ -77,7 +79,7 @@ class Update extends Action
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?array $default, ?string $newKey, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization): void
{
if (!$dbForProject->getAdapter()->getSupportForSpatialAttributes()) {
if (!$dbForProject->getAdapter() instanceof SpatialFeature) {
throw new Exception(Exception::GENERAL_FEATURE_UNSUPPORTED, 'Spatial columns are not supported by this database.');
}
@@ -88,7 +90,7 @@ class Update extends Action
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
authorization: $authorization,
type: Database::VAR_LINESTRING,
type: ColumnType::Linestring->value,
default: $default,
required: $required,
newKey: $newKey
@@ -17,6 +17,7 @@ use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Http\Http;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
@@ -103,7 +104,7 @@ class Create extends Action
$collectionId,
new Document([
'key' => $key,
'type' => Database::VAR_LONGTEXT,
'type' => ColumnType::LongText->value,
'size' => 2147483647,
'required' => $required,
'default' => $default,
@@ -14,6 +14,7 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
@@ -88,7 +89,7 @@ class Update extends Action
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
authorization: $authorization,
type: Database::VAR_LONGTEXT,
type: ColumnType::LongText->value,
default: $default,
required: $required,
newKey: $newKey
@@ -17,6 +17,7 @@ use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Http\Http;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
@@ -103,7 +104,7 @@ class Create extends Action
$collectionId,
new Document([
'key' => $key,
'type' => Database::VAR_MEDIUMTEXT,
'type' => ColumnType::MediumText->value,
'size' => 16777215,
'required' => $required,
'default' => $default,
@@ -14,6 +14,7 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
@@ -88,7 +89,7 @@ class Update extends Action
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
authorization: $authorization,
type: Database::VAR_MEDIUMTEXT,
type: ColumnType::MediumText->value,
default: $default,
required: $required,
newKey: $newKey
@@ -11,6 +11,7 @@ use Appwrite\SDK\Deprecated;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Adapter\Feature\Spatial as SpatialFeature;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
@@ -18,6 +19,7 @@ use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\Spatial;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
@@ -66,7 +68,7 @@ class Create extends Action
->param('collectionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).', false, ['dbForProject'])
->param('key', '', fn (Database $dbForProject) => new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Attribute Key.', false, ['dbForProject'])
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Nullable(new Spatial(Database::VAR_POINT)), 'Default value for attribute when not provided, array of two numbers [longitude, latitude], representing a single coordinate. Cannot be set when attribute is required.', true)
->param('default', null, new Nullable(new Spatial(ColumnType::Point->value)), 'Default value for attribute when not provided, array of two numbers [longitude, latitude], representing a single coordinate. Cannot be set when attribute is required.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForDatabase')
@@ -77,13 +79,13 @@ class Create extends Action
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?array $default, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
{
if (!$dbForProject->getAdapter()->getSupportForSpatialAttributes()) {
if (!$dbForProject->getAdapter() instanceof SpatialFeature) {
throw new Exception(Exception::GENERAL_FEATURE_UNSUPPORTED, 'Spatial columns are not supported by this database.');
}
$attribute = $this->createAttribute($databaseId, $collectionId, new Document([
'key' => $key,
'type' => Database::VAR_POINT,
'type' => ColumnType::Point->value,
'required' => $required,
'default' => $default,
]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
@@ -11,12 +11,14 @@ use Appwrite\SDK\Deprecated;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Adapter\Feature\Spatial as SpatialFeature;
use Utopia\Database\Database;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\Spatial;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
@@ -66,7 +68,7 @@ class Update extends Action
->param('collectionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#createCollection).', false, ['dbForProject'])
->param('key', '', fn (Database $dbForProject) => new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Attribute Key.', false, ['dbForProject'])
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Nullable(new Spatial(Database::VAR_POINT)), 'Default value for attribute when not provided, array of two numbers [longitude, latitude], representing a single coordinate. Cannot be set when attribute is required.', true)
->param('default', null, new Nullable(new Spatial(ColumnType::Point->value)), 'Default value for attribute when not provided, array of two numbers [longitude, latitude], representing a single coordinate. Cannot be set when attribute is required.', true)
->param('newKey', null, fn (Database $dbForProject) => new Nullable(new Key(false, $dbForProject->getAdapter()->getMaxUIDLength())), 'New attribute key.', true, ['dbForProject'])
->inject('response')
->inject('dbForProject')
@@ -77,7 +79,7 @@ class Update extends Action
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?array $default, ?string $newKey, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization): void
{
if (!$dbForProject->getAdapter()->getSupportForSpatialAttributes()) {
if (!$dbForProject->getAdapter() instanceof SpatialFeature) {
throw new Exception(Exception::GENERAL_FEATURE_UNSUPPORTED, 'Spatial columns are not supported by this database.');
}
@@ -88,7 +90,7 @@ class Update extends Action
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
authorization: $authorization,
type: Database::VAR_POINT,
type: ColumnType::Point->value,
default: $default,
required: $required,
newKey: $newKey
@@ -11,6 +11,7 @@ use Appwrite\SDK\Deprecated;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Adapter\Feature\Spatial as SpatialFeature;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
@@ -18,6 +19,7 @@ use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\Spatial;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
@@ -66,7 +68,7 @@ class Create extends Action
->param('collectionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).', false, ['dbForProject'])
->param('key', '', fn (Database $dbForProject) => new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Attribute Key.', false, ['dbForProject'])
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Nullable(new Spatial(Database::VAR_POLYGON)), 'Default value for attribute when not provided, three-dimensional array where the outer array holds one or more linear rings, [[[longitude, latitude], …], …], the first ring is the exterior boundary, any additional rings are interior holes, and each ring must start and end with the same coordinate pair. Cannot be set when attribute is required.', true)
->param('default', null, new Nullable(new Spatial(ColumnType::Polygon->value)), 'Default value for attribute when not provided, three-dimensional array where the outer array holds one or more linear rings, [[[longitude, latitude], …], …], the first ring is the exterior boundary, any additional rings are interior holes, and each ring must start and end with the same coordinate pair. Cannot be set when attribute is required.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForDatabase')
@@ -77,13 +79,13 @@ class Create extends Action
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?array $default, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
{
if (!$dbForProject->getAdapter()->getSupportForSpatialAttributes()) {
if (!$dbForProject->getAdapter() instanceof SpatialFeature) {
throw new Exception(Exception::GENERAL_FEATURE_UNSUPPORTED, 'Spatial columns are not supported by this database.');
}
$attribute = $this->createAttribute($databaseId, $collectionId, new Document([
'key' => $key,
'type' => Database::VAR_POLYGON,
'type' => ColumnType::Polygon->value,
'required' => $required,
'default' => $default,
]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
@@ -11,12 +11,14 @@ use Appwrite\SDK\Deprecated;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Adapter\Feature\Spatial as SpatialFeature;
use Utopia\Database\Database;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\Spatial;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
@@ -66,7 +68,7 @@ class Update extends Action
->param('collectionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#createCollection).', false, ['dbForProject'])
->param('key', '', fn (Database $dbForProject) => new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Attribute Key.', false, ['dbForProject'])
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Nullable(new Spatial(Database::VAR_POLYGON)), 'Default value for attribute when not provided, three-dimensional array where the outer array holds one or more linear rings, [[[longitude, latitude], …], …], the first ring is the exterior boundary, any additional rings are interior holes, and each ring must start and end with the same coordinate pair. Cannot be set when attribute is required.', true)
->param('default', null, new Nullable(new Spatial(ColumnType::Polygon->value)), 'Default value for attribute when not provided, three-dimensional array where the outer array holds one or more linear rings, [[[longitude, latitude], …], …], the first ring is the exterior boundary, any additional rings are interior holes, and each ring must start and end with the same coordinate pair. Cannot be set when attribute is required.', true)
->param('newKey', null, fn (Database $dbForProject) => new Nullable(new Key(false, $dbForProject->getAdapter()->getMaxUIDLength())), 'New attribute key.', true, ['dbForProject'])
->inject('response')
->inject('dbForProject')
@@ -77,7 +79,7 @@ class Update extends Action
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?array $default, ?string $newKey, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization): void
{
if (!$dbForProject->getAdapter()->getSupportForSpatialAttributes()) {
if (!$dbForProject->getAdapter() instanceof SpatialFeature) {
throw new Exception(Exception::GENERAL_FEATURE_UNSUPPORTED, 'Spatial columns are not supported by this database.');
}
@@ -88,7 +90,7 @@ class Update extends Action
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
authorization: $authorization,
type: Database::VAR_POLYGON,
type: ColumnType::Polygon->value,
default: $default,
required: $required,
newKey: $newKey
@@ -11,12 +11,16 @@ use Appwrite\SDK\Deprecated;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Adapter\Feature\Relationships as FeatureRelationships;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\RelationType;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Query\Schema\ForeignKeyAction;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\WhiteList;
@@ -66,18 +70,18 @@ class Create extends Action
->param('collectionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Collection ID.', false, ['dbForProject'])
->param('relatedCollectionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Related Collection ID.', false, ['dbForProject'])
->param('type', '', new WhiteList([
Database::RELATION_ONE_TO_ONE,
Database::RELATION_MANY_TO_ONE,
Database::RELATION_MANY_TO_MANY,
Database::RELATION_ONE_TO_MANY
RelationType::OneToOne->value,
RelationType::ManyToOne->value,
RelationType::ManyToMany->value,
RelationType::OneToMany->value
], true), 'Relation type')
->param('twoWay', false, new Boolean(), 'Is Two Way?', true)
->param('key', null, fn (Database $dbForProject) => new Nullable(new Key(false, $dbForProject->getAdapter()->getMaxUIDLength())), 'Attribute Key.', true, ['dbForProject'])
->param('twoWayKey', null, fn (Database $dbForProject) => new Nullable(new Key(false, $dbForProject->getAdapter()->getMaxUIDLength())), 'Two Way Attribute Key.', true, ['dbForProject'])
->param('onDelete', Database::RELATION_MUTATE_RESTRICT, new WhiteList([
Database::RELATION_MUTATE_CASCADE,
Database::RELATION_MUTATE_RESTRICT,
Database::RELATION_MUTATE_SET_NULL
->param('onDelete', ForeignKeyAction::Restrict->value, new WhiteList([
ForeignKeyAction::Cascade->value,
ForeignKeyAction::Restrict->value,
ForeignKeyAction::SetNull->value
], true), 'Constraints option', true)
->inject('response')
->inject('dbForProject')
@@ -89,7 +93,7 @@ class Create extends Action
public function action(string $databaseId, string $collectionId, string $relatedCollectionId, string $type, bool $twoWay, ?string $key, ?string $twoWayKey, string $onDelete, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
{
if (!$dbForProject->getAdapter()->getSupportForRelationships()) {
if (!$dbForProject->getAdapter() instanceof FeatureRelationships) {
throw new Exception(Exception::GENERAL_FEATURE_UNSUPPORTED, 'Relationships are not supported by this database.');
}
@@ -116,7 +120,7 @@ class Create extends Action
$attributes = $collection->getAttribute('attributes', []);
foreach ($attributes as $attribute) {
if ($attribute->getAttribute('type') !== Database::VAR_RELATIONSHIP) {
if ($attribute->getAttribute('type') !== ColumnType::Relationship->value) {
continue;
}
@@ -135,8 +139,8 @@ class Create extends Action
}
if (
$type === Database::RELATION_MANY_TO_MANY &&
$attribute->getAttribute('options')['relationType'] === Database::RELATION_MANY_TO_MANY &&
$type === RelationType::ManyToMany->value &&
$attribute->getAttribute('options')['relationType'] === RelationType::ManyToMany->value &&
$attribute->getAttribute('options')['relatedCollection'] === $relatedCollection->getId()
) {
$parentType = $this->isCollectionsAPI() ? 'collection' : 'table';
@@ -146,7 +150,7 @@ class Create extends Action
$attribute = $this->createAttribute($databaseId, $collectionId, new Document([
'key' => $key,
'type' => Database::VAR_RELATIONSHIP,
'type' => ColumnType::Relationship->value,
'size' => 0,
'required' => false,
'default' => null,
@@ -11,11 +11,14 @@ use Appwrite\SDK\Deprecated;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Adapter\Feature\Relationships as FeatureRelationships;
use Utopia\Database\Database;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Query\Schema\ForeignKeyAction;
use Utopia\Validator\Nullable;
use Utopia\Validator\WhiteList;
@@ -66,9 +69,9 @@ class Update extends Action
->param('collectionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Collection ID.', false, ['dbForProject'])
->param('key', '', fn (Database $dbForProject) => new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Attribute Key.', false, ['dbForProject'])
->param('onDelete', null, new WhiteList([
Database::RELATION_MUTATE_CASCADE,
Database::RELATION_MUTATE_RESTRICT,
Database::RELATION_MUTATE_SET_NULL
ForeignKeyAction::Cascade->value,
ForeignKeyAction::Restrict->value,
ForeignKeyAction::SetNull->value
], true), 'Constraints option', true)
->param('newKey', null, fn (Database $dbForProject) => new Nullable(new Key(false, $dbForProject->getAdapter()->getMaxUIDLength())), 'New Attribute Key.', true, ['dbForProject'])
->inject('response')
@@ -89,7 +92,7 @@ class Update extends Action
Event $queueForEvents,
Authorization $authorization
): void {
if (!$dbForProject->getAdapter()->getSupportForRelationships()) {
if (!$dbForProject->getAdapter() instanceof FeatureRelationships) {
throw new Exception(Exception::GENERAL_FEATURE_UNSUPPORTED, 'Relationships are not supported by this database.');
}
@@ -100,7 +103,7 @@ class Update extends Action
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
authorization: $authorization,
type: Database::VAR_RELATIONSHIP,
type: ColumnType::Relationship->value,
required: false,
options: [
'onDelete' => $onDelete
@@ -18,6 +18,7 @@ use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Http\Http;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
@@ -125,7 +126,7 @@ class Create extends Action
$collectionId,
new Document([
'key' => $key,
'type' => Database::VAR_STRING,
'type' => ColumnType::String->value,
'size' => $size,
'required' => $required,
'default' => $default,
@@ -15,6 +15,7 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
@@ -97,7 +98,7 @@ class Update extends Action
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
authorization: $authorization,
type: Database::VAR_STRING,
type: ColumnType::String->value,
size: $size,
default: $default,
required: $required,
@@ -17,6 +17,7 @@ use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Http\Http;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
@@ -103,7 +104,7 @@ class Create extends Action
$collectionId,
new Document([
'key' => $key,
'type' => Database::VAR_TEXT,
'type' => ColumnType::Text->value,
'size' => 65535,
'required' => $required,
'default' => $default,
@@ -14,6 +14,7 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
@@ -88,7 +89,7 @@ class Update extends Action
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
authorization: $authorization,
type: Database::VAR_TEXT,
type: ColumnType::Text->value,
default: $default,
required: $required,
newKey: $newKey
@@ -16,6 +16,7 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\URL;
@@ -90,7 +91,7 @@ class Create extends Action
): void {
$attribute = $this->createAttribute($databaseId, $collectionId, new Document([
'key' => $key,
'type' => Database::VAR_STRING,
'type' => ColumnType::String->value,
'size' => 2000,
'required' => $required,
'default' => $default,
@@ -15,6 +15,7 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\URL;
@@ -93,7 +94,7 @@ class Update extends Action
$dbForProject,
$queueForEvents,
$authorization,
type: Database::VAR_STRING,
type: ColumnType::String->value,
filter: APP_DATABASE_ATTRIBUTE_URL,
default: $default,
required: $required,
@@ -17,6 +17,7 @@ use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Http\Http;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
@@ -120,7 +121,7 @@ class Create extends Action
$collectionId,
new Document([
'key' => $key,
'type' => Database::VAR_VARCHAR,
'type' => ColumnType::Varchar->value,
'size' => $size,
'required' => $required,
'default' => $default,
@@ -14,6 +14,7 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
@@ -92,7 +93,7 @@ class Update extends Action
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
authorization: $authorization,
type: Database::VAR_VARCHAR,
type: ColumnType::Varchar->value,
size: $size,
default: $default,
required: $required,
@@ -18,6 +18,8 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Method as QueryMethod;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
class XList extends Action
@@ -94,7 +96,7 @@ class XList extends Action
$cursor = \array_filter(
$queries,
fn ($query) => \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE])
fn ($query) => \in_array($query->getMethod(), [QueryMethod::CursorAfter, QueryMethod::CursorBefore])
);
$cursor = \reset($cursor);
@@ -136,7 +138,7 @@ class XList extends Action
}
foreach ($attributes as $attribute) {
if ($attribute->getAttribute('type') === Database::VAR_STRING) {
if ($attribute->getAttribute('type') === ColumnType::String->value) {
$filters = $attribute->getAttribute('filters', []);
$attribute->setAttribute('encrypt', in_array('encrypt', $filters));
}
@@ -13,6 +13,9 @@ use Appwrite\Utopia\Database\Validator\Attributes as AttributesValidator;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Indexes as IndexesValidator;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Adapter\Feature\SchemaAttributes;
use Utopia\Database\Adapter\Feature\Spatial;
use Utopia\Database\Capability;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate as DuplicateException;
@@ -26,6 +29,7 @@ use Utopia\Database\Validator\Index as IndexValidator;
use Utopia\Database\Validator\Permissions;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
use Utopia\Validator\JSON;
@@ -125,15 +129,15 @@ class Create extends Action
/**
* @var Database $dbForDatabases
*/
$dbForDatabases = $getDatabasesDB($database);
$dbForDatabases = $getDatabasesDB($database, $collection);
$collectionKey = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence();
$databaseKey = 'database_' . $database->getSequence();
$attributesValidator = new AttributesValidator(
APP_LIMIT_ARRAY_PARAMS_SIZE,
$dbForDatabases->getAdapter()->getSupportForSpatialAttributes(),
$dbForDatabases->getAdapter()->getSupportForAttributes()
$dbForDatabases->getAdapter() instanceof Spatial,
$dbForDatabases->getAdapter() instanceof SchemaAttributes
);
if (!$attributesValidator->isValid($attributes)) {
@@ -142,7 +146,7 @@ class Create extends Action
}
foreach ($attributes as $attribute) {
if (($attribute['type'] ?? '') === Database::VAR_RELATIONSHIP) {
if (($attribute['type'] ?? '') === ColumnType::Relationship->value) {
$dbForProject->deleteDocument($databaseKey, $collection->getId());
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Relationship attributes cannot be created inline. Use the create relationship endpoint instead.');
}
@@ -187,21 +191,21 @@ class Create extends Action
[],
$dbForDatabases->getAdapter()->getMaxIndexLength(),
$dbForDatabases->getAdapter()->getInternalIndexesKeys(),
$dbForDatabases->getAdapter()->getSupportForIndexArray(),
$dbForDatabases->getAdapter()->getSupportForSpatialIndexNull(),
$dbForDatabases->getAdapter()->getSupportForSpatialIndexOrder(),
$dbForDatabases->getAdapter()->getSupportForVectors(),
$dbForDatabases->getAdapter()->getSupportForAttributes(),
$dbForDatabases->getAdapter()->getSupportForMultipleFulltextIndexes(),
$dbForDatabases->getAdapter()->getSupportForIdenticalIndexes(),
$dbForDatabases->getAdapter()->getSupportForObjectIndexes(),
$dbForDatabases->getAdapter()->getSupportForTrigramIndex(),
$dbForDatabases->getAdapter()->getSupportForSpatialAttributes(),
$dbForDatabases->getAdapter()->getSupportForIndex(),
$dbForDatabases->getAdapter()->getSupportForUniqueIndex(),
$dbForDatabases->getAdapter()->getSupportForFulltextIndex(),
$dbForDatabases->getAdapter()->getSupportForTTLIndexes(),
$dbForDatabases->getAdapter()->getSupportForObject(),
$dbForDatabases->getAdapter()->supports(Capability::IndexArray),
$dbForDatabases->getAdapter()->supports(Capability::SpatialIndexNull),
$dbForDatabases->getAdapter()->supports(Capability::SpatialIndexOrder),
$dbForDatabases->getAdapter()->supports(Capability::Vectors),
$dbForDatabases->getAdapter() instanceof SchemaAttributes,
$dbForDatabases->getAdapter()->supports(Capability::MultipleFulltextIndexes),
$dbForDatabases->getAdapter()->supports(Capability::IdenticalIndexes),
$dbForDatabases->getAdapter()->supports(Capability::ObjectIndexes),
$dbForDatabases->getAdapter()->supports(Capability::TrigramIndex),
$dbForDatabases->getAdapter() instanceof Spatial,
$dbForDatabases->getAdapter()->supports(Capability::Index),
$dbForDatabases->getAdapter()->supports(Capability::UniqueIndex),
$dbForDatabases->getAdapter()->supports(Capability::Fulltext),
$dbForDatabases->getAdapter()->supports(Capability::TTLIndexes),
$dbForDatabases->getAdapter()->supports(Capability::Objects),
);
foreach ($collectionIndexes as $indexDoc) {
@@ -217,7 +221,8 @@ class Create extends Action
attributes: $collectionAttributes,
indexes: $collectionIndexes,
permissions: $permissions,
documentSecurity: $documentSecurity
documentSecurity: $documentSecurity,
metadata: ['externalId' => $collectionId],
);
} catch (DuplicateException) {
$dbForProject->deleteDocument($databaseKey, $collection->getId());
@@ -275,7 +280,9 @@ class Create extends Action
array $attribute,
): array {
$key = $attribute['key'];
$type = $attribute['type'];
$type = $attribute['type'] === ColumnType::Float->value
? ColumnType::Double->value
: $attribute['type'];
$size = $attribute['size'] ?? 0;
$required = $attribute['required'] ?? false;
$signed = $attribute['signed'] ?? true;
@@ -290,13 +297,13 @@ class Create extends Action
}
if (isset($attribute['min']) || isset($attribute['max'])) {
$format = $type === Database::VAR_INTEGER
$format = $type === ColumnType::Integer->value
? APP_DATABASE_ATTRIBUTE_INT_RANGE
: APP_DATABASE_ATTRIBUTE_FLOAT_RANGE;
$formatOptions = [
'min' => $attribute['min'] ?? ($type === Database::VAR_INTEGER ? \PHP_INT_MIN : -\PHP_FLOAT_MAX),
'max' => $attribute['max'] ?? ($type === Database::VAR_INTEGER ? \PHP_INT_MAX : \PHP_FLOAT_MAX),
'min' => $attribute['min'] ?? ($type === ColumnType::Integer->value ? \PHP_INT_MIN : -\PHP_FLOAT_MAX),
'max' => $attribute['max'] ?? ($type === ColumnType::Integer->value ? \PHP_INT_MAX : \PHP_FLOAT_MAX),
];
}
@@ -86,7 +86,7 @@ class Delete extends Action
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Failed to remove $type from DB");
}
$dbForDatabases = $getDatabasesDB($database);
$dbForDatabases = $getDatabasesDB($database, $collection);
$dbForDatabases->purgeCachedCollection('database_' . $database->getSequence() . '_collection_' . $collection->getSequence());
$queueForDatabase
@@ -9,7 +9,6 @@ use Appwrite\Platform\Modules\Databases\Http\Databases\Action as DatabasesAction
use Appwrite\Utopia\Database\Validator\CustomId;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
abstract class Action extends DatabasesAction
{
@@ -318,102 +317,6 @@ abstract class Action extends DatabasesAction
}
}
/**
* Resolves relationships in a document and attaches metadata.
*/
protected function processDocument(
/* database */
Document $database,
Document $collection,
Document $document,
Database $dbForProject,
/* options */
array &$collectionsCache,
Authorization $authorization,
?int &$operations = null,
int $depth = 0,
): bool {
if ($operations !== null && $document->isEmpty()) {
return false;
}
if ($operations !== null) {
$operations++;
}
$collectionId = $collection->getId();
$document->removeAttribute('$collection');
$document->setAttribute('$databaseId', $database->getId());
$document->setAttribute('$' . $this->getCollectionsEventsContext() . 'Id', $collectionId);
// Stop processing relationships if max depth reached
if ($depth >= Database::RELATION_MAX_DEPTH) {
return true;
}
$relationships = $collectionsCache[$collectionId] ??= \array_filter(
$collection->getAttribute('attributes', []),
fn ($attr) => $attr->getAttribute('type') === Database::VAR_RELATIONSHIP
);
foreach ($relationships as $relationship) {
$key = $relationship->getAttribute('key');
$related = $document->getAttribute($key);
if (empty($related)) {
if (\in_array(\gettype($related), ['array', 'object']) && $operations !== null) {
$operations++;
}
continue;
}
$relations = \is_array($related) ? $related : [$related];
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
if (!isset($collectionsCache[$relatedCollectionId])) {
$relatedCollectionDoc = $authorization->skip(
fn () => $dbForProject->getDocument(
'database_' . $database->getSequence(),
$relatedCollectionId
)
);
$collectionsCache[$relatedCollectionId] = \array_filter(
$relatedCollectionDoc->getAttribute('attributes', []),
fn ($attr) => $attr->getAttribute('type') === Database::VAR_RELATIONSHIP
);
}
foreach ($relations as $relation) {
if ($relation instanceof Document) {
$relatedCollection = new Document([
'$id' => $relatedCollectionId,
'attributes' => $collectionsCache[$relatedCollectionId],
]);
$this->processDocument(
database: $database,
collection: $relatedCollection,
document: $relation,
dbForProject: $dbForProject,
collectionsCache: $collectionsCache,
authorization: $authorization,
operations: $operations,
depth: $depth + 1
);
}
}
if (\is_array($related)) {
$document->setAttribute($relationship->getAttribute('key'), \array_values($relations));
} elseif (empty($relations)) {
$document->setAttribute($relationship->getAttribute('key'), null);
}
}
return true;
}
/**
* For triggering different queues for each document for a bulk documents
* @param string $event
@@ -498,4 +401,5 @@ abstract class Action extends DatabasesAction
$queueForFunctions->reset();
$queueForWebhooks->reset();
}
}
@@ -25,6 +25,7 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Nullable;
use Utopia\Validator\Numeric;
@@ -172,7 +173,7 @@ class Decrement extends Action
return;
}
$dbForDatabases = $getDatabasesDB($database);
$dbForDatabases = $getDatabasesDB($database, $collection);
try {
$document = $dbForDatabases->decreaseDocumentAttribute(
collection: 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
@@ -199,7 +200,7 @@ class Decrement extends Action
fn ($document) => $document->getAttribute('key'),
\array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
fn ($attribute) => $attribute->getAttribute('type') === ColumnType::Relationship->value
)
);
@@ -25,6 +25,7 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Nullable;
use Utopia\Validator\Numeric;
@@ -172,7 +173,7 @@ class Increment extends Action
return;
}
$dbForDatabases = $getDatabasesDB($database);
$dbForDatabases = $getDatabasesDB($database, $collection);
try {
$document = $dbForDatabases->increaseDocumentAttribute(
collection: 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
@@ -199,7 +200,7 @@ class Increment extends Action
fn ($document) => $document->getAttribute('key'),
\array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
fn ($attribute) => $attribute->getAttribute('type') === ColumnType::Relationship->value
)
);
@@ -22,6 +22,7 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
@@ -101,7 +102,7 @@ class Delete extends Action
$hasRelationships = \array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
fn ($attribute) => $attribute->getAttribute('type') === ColumnType::Relationship->value
);
if ($hasRelationships) {
@@ -164,7 +165,7 @@ class Delete extends Action
return;
}
$dbForDatabases = $getDatabasesDB($database);
$dbForDatabases = $getDatabasesDB($database, $collection);
$documents = [];
try {
@@ -24,6 +24,7 @@ use Utopia\Database\Query;
use Utopia\Database\Validator\Permissions;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\ArrayList;
use Utopia\Validator\JSON;
use Utopia\Validator\Nullable;
@@ -117,7 +118,7 @@ class Update extends Action
$hasRelationships = \array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
fn ($attribute) => $attribute->getAttribute('type') === ColumnType::Relationship->value
);
if ($hasRelationships) {
@@ -190,7 +191,7 @@ class Update extends Action
return;
}
$dbForDatabases = $getDatabasesDB($database);
$dbForDatabases = $getDatabasesDB($database, $collection);
$documents = [];
try {
@@ -22,6 +22,7 @@ use Utopia\Database\Exception\Structure as StructureException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\ArrayList;
use Utopia\Validator\JSON;
use Utopia\Validator\Nullable;
@@ -103,7 +104,7 @@ class Upsert extends Action
$hasRelationships = \array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
fn ($attribute) => $attribute->getAttribute('type') === ColumnType::Relationship->value
);
if ($hasRelationships) {
@@ -166,7 +167,7 @@ class Upsert extends Action
return;
}
$dbForDatabases = $getDatabasesDB($database);
$dbForDatabases = $getDatabasesDB($database, $collection);
$upserted = [];
try {
@@ -24,11 +24,12 @@ use Utopia\Database\Exception\Structure as StructureException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\PermissionType;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Authorization\Input;
use Utopia\Database\Validator\Permissions;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\ArrayList;
use Utopia\Validator\JSON;
use Utopia\Validator\Nullable;
@@ -122,7 +123,7 @@ class Create extends Action
->param('documentId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Document ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', true, ['dbForProject'])
->param('collectionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define attributes before creating documents.', false, ['dbForProject'])
->param('data', [], new JSON(), 'Document data as JSON object.', true, example: '{"username":"walter.obrien","email":"walter.obrien@example.com","fullName":"Walter O\'Brien","age":30,"isAdmin":false}')
->param('permissions', null, new Nullable(new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE])), 'An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->param('permissions', null, new Nullable(new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [PermissionType::Read, PermissionType::Update, PermissionType::Delete, PermissionType::Write])), 'An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->param('documents', [], fn (array $plan) => new ArrayList(new JSON(), $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH), 'Array of documents data as JSON objects.', true, ['plan'])
->param('transactionId', null, fn (Database $dbForProject) => new Nullable(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'Transaction ID for staging the operation.', true, ['dbForProject'])
->inject('response')
@@ -202,7 +203,7 @@ class Create extends Action
$hasRelationships = \array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
fn ($attribute) => $attribute->getAttribute('type') === ColumnType::Relationship->value
);
if ($isBulk && $hasRelationships) {
@@ -211,9 +212,9 @@ class Create extends Action
$setPermissions = function (Document $document, ?array $permissions) use ($user, $isAPIKey, $isPrivilegedUser, $isBulk, $dbForProject, $authorization) {
$allowedPermissions = [
Database::PERMISSION_READ,
Database::PERMISSION_UPDATE,
Database::PERMISSION_DELETE,
PermissionType::Read,
PermissionType::Update,
PermissionType::Delete,
];
// If bulk, we need to validate permissions explicitly per document
@@ -234,17 +235,17 @@ class Create extends Action
$permissions = [];
if (!empty($user->getId()) && !$isPrivilegedUser) {
foreach ($allowedPermissions as $permission) {
$permissions[] = (new Permission($permission, 'user', $user->getId()))->toString();
$permissions[] = (new Permission($permission->value, 'user', $user->getId()))->toString();
}
}
}
// Users can only manage their own roles, API keys and Admin users can manage any
if (!$isAPIKey && !$isPrivilegedUser) {
foreach (Database::PERMISSIONS as $type) {
foreach ([PermissionType::Read, PermissionType::Create, PermissionType::Update, PermissionType::Delete] as $type) {
foreach ($permissions as $permission) {
$permission = Permission::parse($permission);
if ($permission->getPermission() != $type) {
if ($permission->getPermission() != $type->value) {
continue;
}
$role = (new Role(
@@ -262,98 +263,7 @@ class Create extends Action
$document->setAttribute('$permissions', $permissions);
};
$operations = 0;
$checkPermissions = function (Document $collection, Document $document, string $permission) use ($isAPIKey, $isPrivilegedUser, &$checkPermissions, $dbForProject, $database, &$operations, $authorization) {
$operations++;
$documentSecurity = $collection->getAttribute('documentSecurity', false);
$validCollection = $authorization->isValid(
new Input($permission, $collection->getPermissionsByType($permission))
);
if (($permission === Database::PERMISSION_UPDATE && !$documentSecurity) || !$validCollection) {
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
}
if ($permission === Database::PERMISSION_UPDATE) {
$validDocument = $authorization->isValid(
new Input($permission, $document->getUpdate())
);
$valid = $validCollection || $validDocument;
if ($documentSecurity && !$valid) {
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
}
}
$relationships = \array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
);
foreach ($relationships as $relationship) {
$related = $document->getAttribute($relationship->getAttribute('key'));
if (empty($related)) {
continue;
}
$isList = \is_array($related) && \array_values($related) === $related;
if ($isList) {
$relations = $related;
} else {
$relations = [$related];
}
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
$relatedCollection = $authorization->skip(
fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $relatedCollectionId)
);
foreach ($relations as &$relation) {
if (
\is_array($relation)
&& \array_values($relation) !== $relation
&& !isset($relation['$id'])
) {
$relation['$id'] = ID::unique();
$relation = new Document($relation);
}
$this->validateRelationship($relation);
if ($relation instanceof Document) {
$relation = $this->removeReadonlyAttributes($relation, $isAPIKey || $isPrivilegedUser);
$current = $authorization->skip(
fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(), $relation->getId())
);
if ($current->isEmpty()) {
$type = Database::PERMISSION_CREATE;
if (isset($relation['$id']) && $relation['$id'] === 'unique()') {
$relation['$id'] = ID::unique();
}
} else {
$relation->setAttribute('$collection', $relatedCollection->getId());
$type = Database::PERMISSION_UPDATE;
}
$checkPermissions($relatedCollection, $relation, $type);
}
}
if ($isList) {
$document->setAttribute($relationship->getAttribute('key'), \array_values($relations));
} else {
$document->setAttribute($relationship->getAttribute('key'), \reset($relations));
}
}
};
$documents = \array_map(function ($document) use ($collection, $permissions, $checkPermissions, $isBulk, $documentId, $setPermissions, $isAPIKey, $isPrivilegedUser) {
$documents = \array_map(function ($document) use ($collection, $permissions, $isBulk, $documentId, $setPermissions, $isAPIKey, $isPrivilegedUser) {
$document['$collection'] = $collection->getId();
// Determine the source ID depending on whether it's a bulk operation.
@@ -374,7 +284,7 @@ class Create extends Action
$document = $this->removeReadonlyAttributes($document, $isAPIKey || $isPrivilegedUser);
$document = new Document($document);
$setPermissions($document, $permissions);
$checkPermissions($collection, $document, Database::PERMISSION_CREATE);
return $document;
}, $documents);
@@ -448,7 +358,7 @@ class Create extends Action
return;
}
$dbForDatabases = $getDatabasesDB($database);
$dbForDatabases = $getDatabasesDB($database, $collection);
try {
$created = [];
$dbForDatabases->withPreserveDates(
@@ -479,21 +389,9 @@ class Create extends Action
->setParam('tableId', $collection->getId())
->setContext($this->getCollectionsEventsContext(), $collection);
$collectionsCache = [];
foreach ($created as $document) {
$this->processDocument(
database: $database,
collection: $collection,
document: $document,
dbForProject: $dbForProject,
collectionsCache: $collectionsCache,
authorization: $authorization
);
}
$usage
->addMetric($this->getDatabasesOperationWriteMetric(), \max(1, $operations))
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), $this->getDatabasesIdOperationWriteMetric()), \max(1, $operations)); // per collection
->addMetric($this->getDatabasesOperationWriteMetric(), 1)
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), $this->getDatabasesIdOperationWriteMetric()), 1); // per collection
$response->setStatusCode(SwooleResponse::STATUS_CODE_CREATED);
@@ -21,6 +21,7 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Nullable;
class Delete extends Action
@@ -120,7 +121,7 @@ class Delete extends Action
throw new Exception($this->getParentNotFoundException(), params: [$collectionId]);
}
$dbForDatabases = $getDatabasesDB($database);
$dbForDatabases = $getDatabasesDB($database, $collection);
// Read permission should not be required for delete
$collectionTableId = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence();
@@ -204,17 +205,6 @@ class Delete extends Action
throw new Exception($this->getRestrictedException());
}
$collectionsCache = [];
$this->processDocument(
database: $database,
collection: $collection,
document: $document,
dbForProject: $dbForProject,
collectionsCache: $collectionsCache,
authorization: $authorization
);
$usage
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, 1)
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), 1); // per collection
@@ -225,7 +215,7 @@ class Delete extends Action
fn ($document) => $document->getAttribute('key'),
\array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
fn ($attribute) => $attribute->getAttribute('type') === ColumnType::Relationship->value
)
);
@@ -88,7 +88,7 @@ class Get extends Action
$collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
$dbForDatabases = $getDatabasesDB($database);
$dbForDatabases = $getDatabasesDB($database, $collection);
if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception($this->getParentNotFoundException(), params: [$collectionId]);
}
@@ -100,7 +100,7 @@ class Get extends Action
}
try {
$selects = Query::groupByType($queries)['selections'] ?? [];
$selects = Query::groupByType($queries)->selections ?? [];
$collectionTableId = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence();
$collectionTableId = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence();
@@ -122,23 +122,9 @@ class Get extends Action
throw new Exception($this->getNotFoundException(), params: [$documentId]);
}
$operations = 0;
$collectionsCache = [];
$this->processDocument(
database: $database,
collection: $collection,
document: $document,
dbForProject: $dbForProject,
collectionsCache: $collectionsCache,
authorization: $authorization,
operations: $operations
);
$usage
->addMetric($this->getDatabasesOperationReadMetric(), max($operations, 1))
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), $this->getDatabasesIdOperationReadMetric()), $operations);
$response->addHeader('X-Debug-Operations', $operations);
->addMetric($this->getDatabasesOperationReadMetric(), 1)
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), $this->getDatabasesIdOperationReadMetric()), 1);
$response->dynamic($document, $this->getResponseModel());
}
@@ -90,7 +90,7 @@ class XList extends Action
throw new Exception($this->getParentNotFoundException(), params: [$collectionId]);
}
$dbForDatabases = $getDatabasesDB($database);
$dbForDatabases = $getDatabasesDB($database, $collection);
$document = $dbForDatabases->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId);
if ($document->isEmpty()) {
throw new Exception($this->getNotFoundException(), params: [$documentId]);
@@ -108,8 +108,8 @@ class XList extends Action
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$limit = $grouped->limit ?? 25;
$offset = $grouped->offset ?? 0;
$logs = $audit->getLogsByResource($resource, limit: $limit, offset: $offset);
@@ -22,10 +22,12 @@ use Utopia\Database\Exception\Structure as StructureException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\PermissionType;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Permissions;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\JSON;
use Utopia\Validator\Nullable;
@@ -78,7 +80,7 @@ class Update extends Action
->param('collectionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Collection ID.', false, ['dbForProject'])
->param('documentId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Document ID.', false, ['dbForProject'])
->param('data', [], new JSON(), 'Document data as JSON object. Include only attribute and value pairs to be updated.', true, example: '{"username":"walter.obrien","email":"walter.obrien@example.com","fullName":"Walter O\'Brien","age":33,"isAdmin":false}')
->param('permissions', null, new Nullable(new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE])), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->param('permissions', null, new Nullable(new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [PermissionType::Read, PermissionType::Update, PermissionType::Delete, PermissionType::Write])), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->param('transactionId', null, fn (Database $dbForProject) => new Nullable(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'Transaction ID for staging the operation.', true, ['dbForProject'])
->inject('requestTimestamp')
->inject('response')
@@ -120,7 +122,7 @@ class Update extends Action
$data = $this->parseOperators($data, $collection);
}
$dbForDatabases = $getDatabasesDB($database);
$dbForDatabases = $getDatabasesDB($database, $collection);
// Read permission should not be required for update
/** @var Document $document */
$collectionTableId = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence();
@@ -138,18 +140,18 @@ class Update extends Action
// Map aggregate permissions into the multiple permissions they represent.
$permissions = Permission::aggregate($permissions, [
Database::PERMISSION_READ,
Database::PERMISSION_UPDATE,
Database::PERMISSION_DELETE,
PermissionType::Read,
PermissionType::Update,
PermissionType::Delete,
]);
// Users can only manage their own roles, API keys and Admin users can manage any
$roles = $authorization->getRoles();
if (!$isAPIKey && !$isPrivilegedUser && !\is_null($permissions)) {
foreach (Database::PERMISSIONS as $type) {
foreach ([PermissionType::Read, PermissionType::Create, PermissionType::Update, PermissionType::Delete] as $type) {
foreach ($permissions as $permission) {
$permission = Permission::parse($permission);
if ($permission->getPermission() != $type) {
if ($permission->getPermission() != $type->value) {
continue;
}
$role = (new Role(
@@ -173,86 +175,6 @@ class Update extends Action
$data = $this->removeReadonlyAttributes($data, $isAPIKey || $isPrivilegedUser);
$newDocument = new Document($data);
$operations = 0;
$setCollection = (function (Document $collection, Document $document) use ($isAPIKey, $isPrivilegedUser, &$setCollection, $dbForProject, $database, &$operations, $authorization) {
$operations++;
$relationships = \array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
);
foreach ($relationships as $relationship) {
$related = $document->getAttribute($relationship->getAttribute('key'));
if (empty($related)) {
continue;
}
$isList = \is_array($related) && \array_values($related) === $related;
if ($isList) {
$relations = $related;
} else {
$relations = [$related];
}
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
$relatedCollection = $authorization->skip(
fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $relatedCollectionId)
);
foreach ($relations as &$relation) {
// If the relation is an array it can be either update or create a child document.
if (
\is_array($relation)
&& \array_values($relation) !== $relation
&& !isset($relation['$id'])
) {
$relation['$id'] = ID::unique();
$relation = new Document($relation);
}
$this->validateRelationship($relation);
if ($relation instanceof Document) {
$relation = $this->removeReadonlyAttributes($relation, $isAPIKey || $isPrivilegedUser);
$oldDocument = $authorization->skip(fn () => $dbForProject->getDocument(
'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(),
$relation->getId()
));
// Attribute $collection is required for Utopia.
$relation->setAttribute(
'$collection',
'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence()
);
if ($oldDocument->isEmpty()) {
if (isset($relation['$id']) && $relation['$id'] === 'unique()') {
$relation['$id'] = ID::unique();
}
}
$setCollection($relatedCollection, $relation);
}
}
if ($isList) {
$document->setAttribute($relationship->getAttribute('key'), \array_values($relations));
} else {
$document->setAttribute($relationship->getAttribute('key'), \reset($relations));
}
}
});
$setCollection($collection, $newDocument);
$usage
->addMetric($this->getDatabasesOperationWriteMetric(), max($operations, 1))
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), $this->getDatabasesIdOperationWriteMetric()), $operations);
// Handle transaction staging
if ($transactionId !== null) {
$transaction = ($isAPIKey || $isPrivilegedUser)
@@ -340,15 +262,9 @@ class Update extends Action
throw new Exception($this->getStructureException(), $e->getMessage());
}
$collectionsCache = [];
$this->processDocument(
database: $database,
collection: $collection,
document: $document,
dbForProject: $dbForProject,
collectionsCache: $collectionsCache,
authorization: $authorization,
);
$usage
->addMetric($this->getDatabasesOperationWriteMetric(), 1)
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), $this->getDatabasesIdOperationWriteMetric()), 1);
$response->dynamic($document, $this->getResponseModel());
@@ -356,7 +272,7 @@ class Update extends Action
fn ($document) => $document->getAttribute('key'),
\array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
fn ($attribute) => $attribute->getAttribute('type') === ColumnType::Relationship->value
)
);
@@ -23,10 +23,12 @@ use Utopia\Database\Exception\Structure as StructureException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\PermissionType;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Permissions;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\JSON;
use Utopia\Validator\Nullable;
@@ -81,7 +83,7 @@ class Upsert extends Action
->param('collectionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Collection ID.', false, ['dbForProject'])
->param('documentId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Document ID.', false, ['dbForProject'])
->param('data', [], new JSON(), 'Document data as JSON object. Include all required attributes of the document to be created or updated.', true, example: '{"username":"walter.obrien","email":"walter.obrien@example.com","fullName":"Walter O\'Brien","age":30,"isAdmin":false}')
->param('permissions', null, new Nullable(new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE])), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->param('permissions', null, new Nullable(new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [PermissionType::Read, PermissionType::Update, PermissionType::Delete, PermissionType::Write])), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->param('transactionId', null, fn (Database $dbForProject) => new Nullable(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'Transaction ID for staging the operation.', true, ['dbForProject'])
->inject('requestTimestamp')
->inject('response')
@@ -125,11 +127,11 @@ class Upsert extends Action
$data = $this->parseOperators($data, $collection);
}
$dbForDatabases = $getDatabasesDB($database);
$dbForDatabases = $getDatabasesDB($database, $collection);
$allowedPermissions = [
Database::PERMISSION_READ,
Database::PERMISSION_UPDATE,
Database::PERMISSION_DELETE,
PermissionType::Read,
PermissionType::Update,
PermissionType::Delete,
];
$permissions = Permission::aggregate($permissions, $allowedPermissions);
@@ -150,7 +152,7 @@ class Upsert extends Action
if (!empty($user->getId())) {
$defaultPermissions = [];
foreach ($allowedPermissions as $permission) {
$defaultPermissions[] = (new Permission($permission, 'user', $user->getId()))->toString();
$defaultPermissions[] = (new Permission($permission->value, 'user', $user->getId()))->toString();
}
$permissions = $defaultPermissions;
}
@@ -162,10 +164,10 @@ class Upsert extends Action
// Users can only manage their own roles, API keys and Admin users can manage any
$roles = $authorization->getRoles();
if (!$isAPIKey && !$isPrivilegedUser && !\is_null($permissions)) {
foreach (Database::PERMISSIONS as $type) {
foreach ([PermissionType::Read, PermissionType::Create, PermissionType::Update, PermissionType::Delete] as $type) {
foreach ($permissions as $permission) {
$permission = Permission::parse($permission);
if ($permission->getPermission() != $type) {
if ($permission->getPermission() != $type->value) {
continue;
}
$role = (new Role(
@@ -184,85 +186,6 @@ class Upsert extends Action
$data['$permissions'] = $permissions ?? [];
$data = $this->removeReadonlyAttributes($data, $isAPIKey || $isPrivilegedUser);
$newDocument = new Document($data);
$operations = 0;
$setCollection = (function (Document $collection, Document $document) use ($isAPIKey, $isPrivilegedUser, &$setCollection, $dbForProject, $dbForDatabases, $database, &$operations, $authorization) {
$operations++;
$relationships = \array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
);
foreach ($relationships as $relationship) {
$related = $document->getAttribute($relationship->getAttribute('key'));
if (empty($related)) {
continue;
}
$isList = \is_array($related) && \array_values($related) === $related;
if ($isList) {
$relations = $related;
} else {
$relations = [$related];
}
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
$relatedCollection = $authorization->skip(
fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $relatedCollectionId)
);
foreach ($relations as &$relation) {
// If the relation is an array it can be either update or create a child document.
if (
\is_array($relation)
&& \array_values($relation) !== $relation
&& !isset($relation['$id'])
) {
$relation['$id'] = ID::unique();
$relation = new Document($relation);
}
$this->validateRelationship($relation);
if ($relation instanceof Document) {
$relation = $this->removeReadonlyAttributes($relation, $isAPIKey || $isPrivilegedUser);
$oldDocument = $authorization->skip(fn () => $dbForDatabases->getDocument(
'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(),
$relation->getId()
));
// Attribute $collection is required for Utopia.
$relation->setAttribute(
'$collection',
'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence()
);
if ($oldDocument->isEmpty()) {
if (isset($relation['$id']) && $relation['$id'] === 'unique()') {
$relation['$id'] = ID::unique();
}
}
$setCollection($relatedCollection, $relation);
}
}
if ($isList) {
$document->setAttribute($relationship->getAttribute('key'), \array_values($relations));
} else {
$document->setAttribute($relationship->getAttribute('key'), \reset($relations));
}
}
});
$setCollection($collection, $newDocument);
$usage
->addMetric($this->getDatabasesOperationWriteMetric(), \max(1, $operations))
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), $this->getDatabasesIdOperationWriteMetric()), \max(1, $operations));
// Handle transaction staging
if ($transactionId !== null) {
@@ -350,11 +273,8 @@ class Upsert extends Action
throw new Exception($this->getStructureException(), $e->getMessage());
}
$collectionsCache = [];
if (empty($upserted[0])) {
if ($transactionId !== null) {
// For transactions, get the document with transaction changes applied
$upserted[0] = $transactionState->getDocument($database, $collectionTableId, $documentId, $transactionId);
} else {
$upserted[0] = $dbForDatabases->getDocument($collectionTableId, $documentId);
@@ -363,20 +283,15 @@ class Upsert extends Action
$document = $upserted[0];
$this->processDocument(
database: $database,
collection: $collection,
document: $document,
dbForProject: $dbForProject,
collectionsCache: $collectionsCache,
authorization: $authorization
);
$usage
->addMetric($this->getDatabasesOperationWriteMetric(), 1)
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), $this->getDatabasesIdOperationWriteMetric()), 1);
$relationships = \array_map(
fn ($document) => $document->getAttribute('key'),
\array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
fn ($attribute) => $attribute->getAttribute('type') === ColumnType::Relationship->value
)
);
@@ -104,7 +104,7 @@ class XList extends Action
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$dbForDatabases = $getDatabasesDB($database);
$dbForDatabases = $getDatabasesDB($database, $collection);
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
@@ -127,7 +127,7 @@ class XList extends Action
}
try {
$selectQueries = Query::groupByType($queries)['selections'] ?? [];
$selectQueries = Query::groupByType($queries)->selections ?? [];
$collectionTableId = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence();
// Use transaction-aware document retrieval if transactionId is provided
if ($transactionId !== null) {
@@ -218,20 +218,7 @@ class XList extends Action
throw new Exception(Exception::DATABASE_TIMEOUT);
}
$operations = 0;
$collectionsCache = [];
foreach ($documents as $document) {
$this->processDocument(
database: $database,
collection: $collection,
document: $document,
dbForProject: $dbForProject,
collectionsCache: $collectionsCache,
authorization: $authorization,
operations: $operations
);
}
$operations = \count($documents);
$usage
->addMetric($this->getDatabasesOperationReadMetric(), max($operations, 1))
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), $this->getDatabasesIdOperationReadMetric()), $operations);
@@ -11,6 +11,9 @@ use Appwrite\SDK\Deprecated;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Adapter\Feature\SchemaAttributes;
use Utopia\Database\Adapter\Feature\Spatial;
use Utopia\Database\Capability;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate as DuplicateException;
@@ -21,6 +24,8 @@ use Utopia\Database\Validator\Index as IndexValidator;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Query\Schema\IndexType;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Integer;
use Utopia\Validator\Nullable;
@@ -71,9 +76,9 @@ class Create extends Action
->param('databaseId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Database ID.', false, ['dbForProject'])
->param('collectionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).', false, ['dbForProject'])
->param('key', null, fn (Database $dbForProject) => new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Index Key.', false, ['dbForProject'])
->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE, Database::INDEX_SPATIAL]), 'Index type.')
->param('type', null, new WhiteList([IndexType::Key->value, IndexType::Fulltext->value, IndexType::Unique->value, IndexType::Spatial->value]), 'Index type.')
->param('attributes', null, fn (Database $dbForProject) => new ArrayList(new Key(true, $dbForProject->getAdapter()->getMaxUIDLength()), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of attributes to index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' attributes are allowed, each 32 characters long.', false, ['dbForProject'])
->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'], false, Database::VAR_STRING), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of index orders. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' orders are allowed.', true)
->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'], false, ColumnType::String->value), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of index orders. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' orders are allowed.', true)
->param('lengths', [], new ArrayList(new Nullable(new Integer()), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Length of index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE, optional: true)
->inject('response')
->inject('dbForProject')
@@ -104,7 +109,7 @@ class Create extends Action
Query::equal('databaseInternalId', [$db->getSequence()])
], 61);
$dbForDatabases = $getDatabasesDB($db);
$dbForDatabases = $getDatabasesDB($db, $collection);
$limit = $dbForDatabases->getLimitForIndexes();
@@ -119,7 +124,7 @@ class Create extends Action
$oldAttributes[] = [
'key' => '$id',
'type' => Database::VAR_STRING,
'type' => ColumnType::String->value,
'status' => 'available',
'required' => true,
'array' => false,
@@ -128,7 +133,7 @@ class Create extends Action
];
$oldAttributes[] = [
'key' => '$createdAt',
'type' => Database::VAR_DATETIME,
'type' => ColumnType::Datetime->value,
'status' => 'available',
'signed' => false,
'required' => false,
@@ -138,7 +143,7 @@ class Create extends Action
];
$oldAttributes[] = [
'key' => '$updatedAt',
'type' => Database::VAR_DATETIME,
'type' => ColumnType::Datetime->value,
'status' => 'available',
'signed' => false,
'required' => false,
@@ -148,7 +153,7 @@ class Create extends Action
];
$contextType = $this->getParentContext();
if ($dbForDatabases->getAdapter()->getSupportForAttributes()) {
if ($dbForDatabases->getAdapter() instanceof SchemaAttributes) {
foreach ($attributes as $i => $attribute) {
// find attribute metadata in collection document
$attributeIndex = \array_search($attribute, array_column($oldAttributes, 'key'));
@@ -161,7 +166,7 @@ class Create extends Action
$attributeType = $oldAttributes[$attributeIndex]['type'];
$attributeArray = $oldAttributes[$attributeIndex]['array'] ?? false;
if ($attributeType === Database::VAR_RELATIONSHIP) {
if ($attributeType === ColumnType::Relationship->value) {
throw new Exception($this->getParentInvalidTypeException(), "Cannot create an index for a relationship $contextType: " . $oldAttributes[$attributeIndex]['key']);
}
@@ -199,21 +204,21 @@ class Create extends Action
$collection->getAttribute('indexes'),
$dbForDatabases->getAdapter()->getMaxIndexLength(),
$dbForDatabases->getAdapter()->getInternalIndexesKeys(),
$dbForDatabases->getAdapter()->getSupportForIndexArray(),
$dbForDatabases->getAdapter()->getSupportForSpatialIndexNull(),
$dbForDatabases->getAdapter()->getSupportForSpatialIndexOrder(),
$dbForDatabases->getAdapter()->getSupportForVectors(),
$dbForDatabases->getAdapter()->getSupportForAttributes(),
$dbForDatabases->getAdapter()->getSupportForMultipleFulltextIndexes(),
$dbForDatabases->getAdapter()->getSupportForIdenticalIndexes(),
$dbForDatabases->getAdapter()->getSupportForObjectIndexes(),
$dbForDatabases->getAdapter()->getSupportForTrigramIndex(),
$dbForDatabases->getAdapter()->getSupportForSpatialAttributes(),
$dbForDatabases->getAdapter()->getSupportForIndex(),
$dbForDatabases->getAdapter()->getSupportForUniqueIndex(),
$dbForDatabases->getAdapter()->getSupportForFulltextIndex(),
$dbForDatabases->getAdapter()->getSupportForTTLIndexes(),
$dbForDatabases->getAdapter()->getSupportForObject()
$dbForDatabases->getAdapter()->supports(Capability::IndexArray),
$dbForDatabases->getAdapter()->supports(Capability::SpatialIndexNull),
$dbForDatabases->getAdapter()->supports(Capability::SpatialIndexOrder),
$dbForDatabases->getAdapter()->supports(Capability::Vectors),
$dbForDatabases->getAdapter() instanceof SchemaAttributes,
$dbForDatabases->getAdapter()->supports(Capability::MultipleFulltextIndexes),
$dbForDatabases->getAdapter()->supports(Capability::IdenticalIndexes),
$dbForDatabases->getAdapter()->supports(Capability::ObjectIndexes),
$dbForDatabases->getAdapter()->supports(Capability::TrigramIndex),
$dbForDatabases->getAdapter() instanceof Spatial,
$dbForDatabases->getAdapter()->supports(Capability::Index),
$dbForDatabases->getAdapter()->supports(Capability::UniqueIndex),
$dbForDatabases->getAdapter()->supports(Capability::Fulltext),
$dbForDatabases->getAdapter()->supports(Capability::TTLIndexes),
$dbForDatabases->getAdapter()->supports(Capability::Objects)
);
if (!$validator->isValid($index)) {
@@ -98,8 +98,8 @@ class XList extends Action
}
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$limit = $grouped->limit ?? 25;
$offset = $grouped->offset ?? 0;
$context = $this->getContext();
$resource = "database/$databaseId/$context/$collectionId";
@@ -111,7 +111,7 @@ class Update extends Action
->setAttribute('search', \implode(' ', [$collectionId, $searchName]))
);
$dbForDatabases = $getDatabasesDB($database);
$dbForDatabases = $getDatabasesDB($database, $collection);
$dbForDatabases->updateCollection('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $permissions, $documentSecurity);
$queueForEvents
@@ -77,7 +77,7 @@ class Get extends Action
{
$database = $dbForProject->getDocument('databases', $databaseId);
$collectionDocument = $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId);
$dbForDatabases = $getDatabasesDB($database);
$dbForDatabases = $getDatabasesDB($database, $collectionDocument);
$collection = $dbForDatabases->getCollection('database_' . $database->getSequence() . '_collection_' . $collectionDocument->getSequence());
if ($collection->isEmpty()) {
@@ -216,15 +216,8 @@ class Create extends Action
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'The "collections" collection is not configured.');
}
$attributes = [];
foreach ($collections['attributes'] as $attribute) {
$attributes[] = new Document($attribute);
}
$indexes = [];
foreach ($collections['indexes'] as $index) {
$indexes[] = new Document($index);
}
$attributes = $collections['attributes'];
$indexes = $collections['indexes'];
try {
$dbForProject->createCollection('database_' . $database->getSequence(), $attributes, $indexes);
@@ -87,8 +87,8 @@ class XList extends Action
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$limit = $grouped->limit ?? 25;
$offset = $grouped->offset ?? 0;
$resource = 'database/' . $databaseId;
$logs = $audit->getLogsByResource($resource, limit: $limit, offset: $offset);
@@ -12,6 +12,7 @@ use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\PermissionType;
use Utopia\Database\Validator\Authorization;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Validator\Range;
@@ -64,13 +65,13 @@ class Create extends Action
$permissions = [];
if (!empty($user->getId())) {
$allowedPermissions = [
Database::PERMISSION_READ,
Database::PERMISSION_UPDATE,
Database::PERMISSION_DELETE,
PermissionType::Read,
PermissionType::Update,
PermissionType::Delete,
];
foreach ($allowedPermissions as $permission) {
$permissions[] = (new Permission($permission, 'user', $user->getId()))->toString();
$permissions[] = (new Permission($permission->value, 'user', $user->getId()))->toString();
}
}
@@ -17,10 +17,12 @@ use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\PermissionType;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Authorization\Input;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\ArrayList;
class Create extends Action
@@ -131,7 +133,7 @@ class Create extends Action
if (\in_array($operation['action'], ['bulkCreate', 'bulkUpdate', 'bulkUpsert', 'bulkDelete'])) {
$hasRelationships = \array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
fn ($attribute) => $attribute->getAttribute('type') === ColumnType::Relationship->value
);
if ($hasRelationships) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk operations are not supported for ' . $this->getGroupId() . ' with relationship attributes');
@@ -158,10 +160,10 @@ class Create extends Action
// Bulk operations skip permission validation entirely (API key/admin only, already checked above)
if (!\in_array($operation['action'], ['bulkCreate', 'bulkUpdate', 'bulkUpsert', 'bulkDelete'])) {
$permissionType = match ($operation['action']) {
'create' => Database::PERMISSION_CREATE,
'update', 'increment', 'decrement' => Database::PERMISSION_UPDATE,
'delete' => Database::PERMISSION_DELETE,
'upsert' => ($document && !$document->isEmpty()) ? Database::PERMISSION_UPDATE : Database::PERMISSION_CREATE,
'create' => PermissionType::Create,
'update', 'increment', 'decrement' => PermissionType::Update,
'delete' => PermissionType::Delete,
'upsert' => ($document && !$document->isEmpty()) ? PermissionType::Update : PermissionType::Create,
default => throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid action: ' . $operation['action'])
};
@@ -174,18 +176,18 @@ class Create extends Action
);
$documentValid = false;
if ($document !== null && !$document->isEmpty() && $documentSecurity) {
if ($permissionType === Database::PERMISSION_UPDATE) {
if ($permissionType === PermissionType::Update) {
$documentValid = $authorization->isValid(
new Input(Database::PERMISSION_UPDATE, $document->getUpdate())
new Input(PermissionType::Update, $document->getUpdate())
);
} elseif ($permissionType === Database::PERMISSION_DELETE) {
} elseif ($permissionType === PermissionType::Delete) {
$documentValid = $authorization->isValid(
new Input(Database::PERMISSION_DELETE, $document->getDelete())
new Input(PermissionType::Delete, $document->getDelete())
);
}
}
if ($permissionType === Database::PERMISSION_CREATE || !$documentSecurity) {
if ($permissionType === PermissionType::Create || !$documentSecurity) {
if (!$collectionValid) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
@@ -199,10 +201,10 @@ class Create extends Action
if (isset($operation['data']['$permissions'])) {
$permissions = $operation['data']['$permissions'];
$roles = $authorization->getRoles();
foreach (Database::PERMISSIONS as $type) {
foreach ([PermissionType::Read, PermissionType::Create, PermissionType::Update, PermissionType::Delete] as $type) {
foreach ($permissions as $permission) {
$permission = Permission::parse($permission);
if ($permission->getPermission() != $type) {
if ($permission->getPermission() != $type->value) {
continue;
}
$role = (new Role(
@@ -10,7 +10,7 @@ use Appwrite\SDK\Parameter;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Database;
use Utopia\Database\PermissionType;
use Utopia\Database\Validator\Permissions;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
@@ -96,7 +96,7 @@ class Create extends DocumentCreate
->param('documentId', '', new CustomId(), 'Document ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', true)
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define attributes before creating documents.')
->param('data', [], new JSON(), 'Document data as JSON object.', true, example: '{"username":"walter.obrien","email":"walter.obrien@example.com","fullName":"Walter O\'Brien","age":30,"isAdmin":false}')
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [PermissionType::Read, PermissionType::Update, PermissionType::Delete, PermissionType::Write]), 'An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->param('documents', [], fn (array $plan) => new ArrayList(new JSON(), $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH), 'Array of documents data as JSON objects.', true, ['plan'])
->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true)
->inject('response')
@@ -8,7 +8,7 @@ use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Database;
use Utopia\Database\PermissionType;
use Utopia\Database\Validator\Permissions;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
@@ -59,7 +59,7 @@ class Update extends DocumentUpdate
->param('collectionId', '', new UID(), 'Collection ID.')
->param('documentId', '', new UID(), 'Document ID.')
->param('data', [], new JSON(), 'Document data as JSON object. Include only fields and value pairs to be updated.', true)
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [PermissionType::Read, PermissionType::Update, PermissionType::Delete, PermissionType::Write]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true)
->inject('requestTimestamp')
->inject('response')
@@ -8,7 +8,7 @@ use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Database;
use Utopia\Database\PermissionType;
use Utopia\Database\Validator\Permissions;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
@@ -61,7 +61,7 @@ class Upsert extends DocumentUpsert
->param('collectionId', '', new UID(), 'Collection ID.')
->param('documentId', '', new UID(), 'Document ID.')
->param('data', [], new JSON(), 'Document data as JSON object. Include all required fields of the document to be created or updated.', true)
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [PermissionType::Read, PermissionType::Update, PermissionType::Delete, PermissionType::Write]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->param('transactionId', null, new UID(), 'Transaction ID for staging the operation.', true)
->inject('requestTimestamp')
->inject('response')
@@ -12,6 +12,8 @@ use Utopia\Database\Database;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Query\Schema\IndexType;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Integer;
use Utopia\Validator\Nullable;
@@ -58,9 +60,9 @@ class Create extends IndexCreate
->param('databaseId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Database ID.', false, ['dbForProject'])
->param('collectionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).', false, ['dbForProject'])
->param('key', null, fn (Database $dbForProject) => new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Index Key.', false, ['dbForProject'])
->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE]), 'Index type.')
->param('type', null, new WhiteList([IndexType::Key->value, IndexType::Fulltext->value, IndexType::Unique->value]), 'Index type.')
->param('attributes', null, fn (Database $dbForProject) => new ArrayList(new Key(true, $dbForProject->getAdapter()->getMaxUIDLength()), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of attributes to index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' attributes are allowed, each 32 characters long.', false, ['dbForProject'])
->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'], false, Database::VAR_STRING), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of index orders. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' orders are allowed.', true)
->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'], false, ColumnType::String->value), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of index orders. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' orders are allowed.', true)
->param('lengths', [], new ArrayList(new Nullable(new Integer()), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Length of index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE, optional: true)
->inject('response')
->inject('dbForProject')
@@ -81,8 +81,8 @@ class XList extends Action
}
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$limit = $grouped->limit ?? 25;
$offset = $grouped->offset ?? 0;
$resource = 'database/' . $databaseId;
$logs = $audit->getLogsByResource($resource, limit: $limit, offset: $offset);
@@ -12,6 +12,7 @@ use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\Spatial;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
@@ -56,7 +57,7 @@ class Create extends LineCreate
->param('tableId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Table ID. You can create a new table using the TablesDB service [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable).', false, ['dbForProject'])
->param('key', '', fn (Database $dbForProject) => new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Column Key.', false, ['dbForProject'])
->param('required', null, new Boolean(), 'Is column required?')
->param('default', null, new Nullable(new Spatial(Database::VAR_LINESTRING)), 'Default value for column when not provided, two-dimensional array of coordinate pairs, [[longitude, latitude], [longitude, latitude], …], listing the vertices of the line in order. Cannot be set when column is required.', true)
->param('default', null, new Nullable(new Spatial(ColumnType::Linestring->value)), 'Default value for column when not provided, two-dimensional array of coordinate pairs, [[longitude, latitude], [longitude, latitude], …], listing the vertices of the line in order. Cannot be set when column is required.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForDatabase')
@@ -13,6 +13,7 @@ use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\Spatial;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
@@ -58,7 +59,7 @@ class Update extends LineUpdate
->param('tableId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Table ID. You can create a new table using the TablesDB service [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable).', false, ['dbForProject'])
->param('key', '', fn (Database $dbForProject) => new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Column Key.', false, ['dbForProject'])
->param('required', null, new Boolean(), 'Is column required?')
->param('default', null, new Nullable(new Spatial(Database::VAR_LINESTRING)), 'Default value for column when not provided, two-dimensional array of coordinate pairs, [[longitude, latitude], [longitude, latitude], …], listing the vertices of the line in order. Cannot be set when column is required.', true)
->param('default', null, new Nullable(new Spatial(ColumnType::Linestring->value)), 'Default value for column when not provided, two-dimensional array of coordinate pairs, [[longitude, latitude], [longitude, latitude], …], listing the vertices of the line in order. Cannot be set when column is required.', true)
->param('newKey', null, fn (Database $dbForProject) => new Nullable(new Key(false, $dbForProject->getAdapter()->getMaxUIDLength())), 'New Column Key.', true, ['dbForProject'])
->inject('response')
->inject('dbForProject')
@@ -12,6 +12,7 @@ use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\Spatial;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Query\Schema\ColumnType;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
@@ -56,7 +57,7 @@ class Create extends PointCreate
->param('tableId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Table ID. You can create a new table using the TablesDB service [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable).', false, ['dbForProject'])
->param('key', '', fn (Database $dbForProject) => new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Column Key.', false, ['dbForProject'])
->param('required', null, new Boolean(), 'Is column required?')
->param('default', null, new Nullable(new Spatial(Database::VAR_POINT)), 'Default value for column when not provided, array of two numbers [longitude, latitude], representing a single coordinate. Cannot be set when column is required.', true)
->param('default', null, new Nullable(new Spatial(ColumnType::Point->value)), 'Default value for column when not provided, array of two numbers [longitude, latitude], representing a single coordinate. Cannot be set when column is required.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForDatabase')

Some files were not shown because too many files have changed in this diff Show More