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
Chirag AggarwalandGitHub a5b0378138 Merge pull request #11737 from appwrite/codex/phpstan-baseline-part-2
[codex] Fix PHPStan baseline cleanup issues (part 2)
2026-04-01 13:23:15 +05:30
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
Chirag AggarwalandGitHub 320068a576 Merge pull request #11740 from appwrite/speed-up-phpstan
Speed up PHPStan analysis with result caching
2026-04-01 13:01:07 +05:30
Chirag Aggarwal 3cd90ae629 fix analyze 2026-04-01 12:59:51 +05:30
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
Chirag Aggarwal 44f33473bd Use composer analyze in CI to stay in sync with local workflow 2026-04-01 12:04:31 +05:30
Chirag Aggarwal 358f1b78a8 Speed up PHPStan analysis with result caching
Configure a project-local result cache directory so PHPStan only
re-analyses files that changed. In CI, persist the cache across
runs with actions/cache and suppress progress output.
2026-04-01 12:00:32 +05:30
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 BarnbyandGitHub 1f6b9d94bf Merge pull request #11739 from appwrite/readme-update-1.9.0-installation 2026-04-01 05:53:38 +00:00
Aditya OberaiandGitHub a734c8cd46 Update installation commands in readme for 1.9.0 to include self-hosted wizard 2026-04-01 11:20:45 +05:30
Chirag Aggarwal 1788e1bd6c Address PR review feedback 2026-04-01 11:15:59 +05:30
Chirag Aggarwal 983adf3ffd Fix analyze regressions in PHPStan cleanup 2026-04-01 11:00:26 +05:30
Chirag Aggarwal f2ea0b9b48 Fix PHPStan baseline cleanup issues (part 2) 2026-04-01 10:20:20 +05:30
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 BarnbyandGitHub 44610462b3 Merge pull request #11735 from appwrite/lohanidamodar-patch-2 2026-04-01 03:57:20 +00:00
Damodar LohaniandGitHub 28ece7de02 Change Usage class from final to non-final 2026-04-01 09:36:26 +05:45
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 BarnbyandGitHub 8c6d4d8b36 Merge pull request #11734 from appwrite/fix-defaults 2026-04-01 02:50:09 +00:00
Jake Barnby 2ebc6f70ef (fix): param default 2026-04-01 15:49:40 +13:00
Damodar LohaniandGitHub 2b7690d6db Merge pull request #11732 from appwrite/claude/add-deployment-hook-method-prkpM
Add beforeCreateGitDeployment hook for deployment validation
2026-04-01 08:27:50 +05:45
Damodar LohaniandGitHub d9af799cc7 Merge branch '1.9.x' into claude/add-deployment-hook-method-prkpM 2026-04-01 08:09:46 +05:45
Damodar LohaniandGitHub 3ed1ca736d Merge pull request #11731 from appwrite/claude/update-php-runtimes-hNh1r
Update dependencies
2026-04-01 07:57:25 +05:45
Jake BarnbyandGitHub 9fa35db838 Merge pull request #11733 from appwrite/fix-defaults 2026-04-01 02:08:45 +00:00
Claude b6e020389b Remove docblock from beforeCreateGitDeployment hook
https://claude.ai/code/session_01HP1N9hHbqMzxm5QmaoGhyZ
2026-04-01 02:07:36 +00: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 Barnby ccc0cfbfdc (fix): migrate default 2026-04-01 15:04:52 +13:00
Claude b91506fc2d Rename hook to beforeCreateGitDeployment
https://claude.ai/code/session_01HP1N9hHbqMzxm5QmaoGhyZ
2026-04-01 02:03:59 +00:00
Claude 9ffc23946c Add validateGitDeployment hook method to Deployment trait
Add a no-op protected method that Cloud can override to enforce
billing/block checks before processing git deployments. The hook
is called inside the foreach loop after project validation, so any
exception it throws is caught and logged as an error.

https://claude.ai/code/session_01HP1N9hHbqMzxm5QmaoGhyZ
2026-04-01 02:02:18 +00:00
Jake BarnbyandGitHub 8bb6b5cd2a Merge pull request #11646 from appwrite/feat/import-export-json 2026-04-01 01:56:47 +00:00
Claude afea4ca57b Update appwrite/php-runtimes to 0.19.5
https://claude.ai/code/session_01KXrbPzuXNzRhn38xm9zqwJ
2026-04-01 01:44:07 +00: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 BarnbyandGitHub b30b89ade9 Merge pull request #11730 from appwrite/fix-v24 2026-04-01 01:11:54 +00:00
Jake Barnby 829cf887dc (fix): missing users case 2026-04-01 14:07:59 +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
premtsd-codeandGitHub d862a64874 Merge branch '1.9.x' into feat/import-export-json 2026-03-31 22:54:12 +01: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
premtsd-codeandGitHub 7dcfd3ff3d Update description for get-queue-audits endpoint 2026-03-31 12:38:48 +01:00
Prem Palanisamy 4dfdfb5e59 Merge remote-tracking branch 'origin/1.9.x' into feat/import-export-json 2026-03-31 12:37:17 +01:00
Prem Palanisamy 5d1009b324 fix: correct resourceType routing, schemaless validation, and E2E tests for migrations
- Add getDatabaseResourceType() helper to map database types to resource constants
- Use database-specific resourceType for CSV/JSON import/export instead of hardcoded TYPE_DATABASE
- Skip attribute validation for schemaless databases (DocumentsDB/VectorsDB) in exports
- Parse JSON export queries in migration worker
- Restore MigrationsBase from 1.9.x and append VectorsDB/DocumentsDB E2E tests
2026-03-31 12:35:18 +01: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
Prem Palanisamy a80ecd0cb6 fix: alphabetize imports, update phpstan baseline count for migrations tests 2026-03-30 17:24:58 +01:00
Prem Palanisamy 8be7c6182e fix: update composer.lock for utopia-php/database branch 2026-03-30 17:16:05 +01:00
premtsd-codeandGitHub 3bb6a8bcc8 Merge branch '1.9.x' into feat/import-export-json 2026-03-30 16:15:16 +01:00
Prem Palanisamy 0085d93aeb fix: add version alias for database branch 2026-03-30 15:48:05 +01:00
Prem Palanisamy 3b9b0c96c4 use utopia-php/database fix/mongo-order-case branch for order case normalization 2026-03-30 15:46:20 +01:00
Prem Palanisamy 2611bf4af1 fix: add setPlatform to JSON import trigger for consistency 2026-03-30 15:43:21 +01:00
Prem Palanisamy de219de31d fix: restore original databasetype block in CSV export 2026-03-30 15:26:09 +01:00
Prem Palanisamy d8bbd82556 fix: remove duplicate database fetch, add null-safe queries fallback, add schemaless comment 2026-03-30 14:55:05 +01:00
Prem Palanisamy aaebeec61e fix: remove spatial from documentsdb indexes, parse JSON export queries, skip schema validation for schemaless exports 2026-03-30 09:09:50 +01:00
premtsd-codeandGitHub 53d0e14f97 Merge branch '1.9.x' into feat/import-export-json 2026-03-28 04:23:07 +00: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
Prem Palanisamy 741267c6c5 fix: rename locale keys to dataExport, use {{type}} placeholder for CSV/JSON 2026-03-26 12:09:10 +00:00
Jake Barnby 85467e3d7e (fix): alias Spatial feature import to avoid name collision with Validator\Spatial 2026-03-27 01:05:12 +13:00
Prem Palanisamy e85080499a fix: generalize export handler for CSV and JSON — download URL, email, dynamic file extension 2026-03-26 11:54:54 +00: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
Prem Palanisamy ee1ca5ace6 fix: remove email verification from vectorsdb export test (tested separately) 2026-03-26 11:36:50 +00:00
Prem Palanisamy b36472f0da add E2E tests for vectorsdb and documentsdb JSON import/export 2026-03-26 11:24:14 +00:00
Prem Palanisamy 52ae8b3880 fix: use type-specific resources for JSON endpoints, add JSON source/destination to worker 2026-03-26 09:23:45 +00:00
Prem Palanisamy 8f09e74462 fix: bump migration to 1.9.*, fix dataExportType property 2026-03-26 07:41:23 +00:00
Prem Palanisamy 30907d716f cleanup: remove duplicate setProject, remove stale spec files 2026-03-26 06:55:28 +00:00
DarshanandPrem Palanisamy eb46855e72 regen: specs. 2026-03-26 06:44:53 +00:00
DarshanandPrem Palanisamy 45557e5929 add: missing doc. 2026-03-26 06:44:36 +00:00
DarshanandPrem Palanisamy 6ad6a5dea3 specs. 2026-03-26 06:44:22 +00:00
DarshanandPrem Palanisamy 098f7aa3e3 update: comment. 2026-03-26 06:43:49 +00:00
DarshanandPrem Palanisamy f8c8c17757 add: tests;
fix: tests.
2026-03-26 06:43:49 +00:00
DarshanandPrem Palanisamy 5b1ee93927 fix: endpoint. 2026-03-26 06:43:04 +00:00
222 changed files with 10284 additions and 9906 deletions
+9 -1
View File
@@ -150,8 +150,16 @@ jobs:
- name: Install dependencies
run: composer install --prefer-dist --no-progress --ignore-platform-reqs
- name: Cache PHPStan result cache
uses: actions/cache@v4
with:
path: .phpstan-cache
key: phpstan-${{ github.sha }}
restore-keys: |
phpstan-
- name: Run PHPStan
run: composer analyze
run: composer analyze -- --no-progress
locale:
name: Checks / Locale
+1
View File
@@ -21,6 +21,7 @@ appwrite.config.json
/app/config/specs/
/docs/examples/
.phpunit.cache
.phpstan-cache
playwright-report
test-results
docker-compose.web-installer.yml
+3
View File
@@ -72,6 +72,7 @@ Before running the installation command, make sure you have [Docker](https://www
```bash
docker run -it --rm \
--publish 20080:20080 \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
@@ -84,6 +85,7 @@ docker run -it --rm \
```cmd
docker run -it --rm ^
--publish 20080:20080 ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
@@ -94,6 +96,7 @@ docker run -it --rm ^
```powershell
docker run -it --rm `
--publish 20080:20080 `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
+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'],
),
],
],
];
+15 -15
View File
@@ -57,21 +57,21 @@
"emails.recovery.thanks": "Thanks,",
"emails.recovery.buttonText": "Reset password",
"emails.recovery.signature": "{{project}} team",
"emails.csvExport.success.subject": "Your CSV export is ready",
"emails.csvExport.success.preview": "Your data export has been completed successfully.",
"emails.csvExport.success.hello": "Hello {{user}},",
"emails.csvExport.success.body": "Your CSV export is ready to download. Click the button below to download your data export.",
"emails.csvExport.success.footer": "This download link will expire in 1 hour.",
"emails.csvExport.success.thanks": "Thanks,",
"emails.csvExport.success.buttonText": "Download CSV",
"emails.csvExport.success.signature": "Appwrite team",
"emails.csvExport.failure.subject": "Your CSV export failed - file too large",
"emails.csvExport.failure.preview": "Your data export failed because the file size exceeds your plan limit.",
"emails.csvExport.failure.hello": "Hello {{user}},",
"emails.csvExport.failure.body": "Your CSV export could not be completed because the export file size ({{size}}MB) exceeds your plan limit. Please consider upgrading your plan or exporting a smaller dataset.",
"emails.csvExport.failure.footer": "If you have any questions, please contact our support team.",
"emails.csvExport.failure.thanks": "Thanks,",
"emails.csvExport.failure.signature": "{{project}} team",
"emails.dataExport.success.subject": "Your {{type}} export is ready",
"emails.dataExport.success.preview": "Your data export has been completed successfully.",
"emails.dataExport.success.hello": "Hello {{user}},",
"emails.dataExport.success.body": "Your {{type}} export is ready to download. Click the button below to download your data export.",
"emails.dataExport.success.footer": "This download link will expire in 1 hour.",
"emails.dataExport.success.thanks": "Thanks,",
"emails.dataExport.success.buttonText": "Download {{type}}",
"emails.dataExport.success.signature": "Appwrite team",
"emails.dataExport.failure.subject": "Your {{type}} export failed - file too large",
"emails.dataExport.failure.preview": "Your data export failed because the file size exceeds your plan limit.",
"emails.dataExport.failure.hello": "Hello {{user}},",
"emails.dataExport.failure.body": "Your {{type}} export could not be completed because the export file size ({{size}}MB) exceeds your plan limit. Please consider upgrading your plan or exporting a smaller dataset.",
"emails.dataExport.failure.footer": "If you have any questions, please contact our support team.",
"emails.dataExport.failure.thanks": "Thanks,",
"emails.dataExport.failure.signature": "{{project}} team",
"emails.invitation.subject": "Invitation to {{team}} Team at {{project}}",
"emails.invitation.preview": "{{owner}} invited you to join {{team}} at {{project}}",
"emails.invitation.hello": "Hello {{user}},",
+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);
+315 -13
View File
@@ -29,6 +29,7 @@ use Utopia\Migration\Resource;
use Utopia\Migration\Sources\Appwrite;
use Utopia\Migration\Sources\CSV;
use Utopia\Migration\Sources\Firebase;
use Utopia\Migration\Sources\JSON;
use Utopia\Migration\Sources\NHost;
use Utopia\Migration\Sources\Supabase;
use Utopia\Migration\Transfer;
@@ -53,6 +54,15 @@ function getDatabaseTransferResourceServices(string $databaseType)
};
}
function getDatabaseResourceType(string $databaseType): string
{
return match($databaseType) {
DATABASE_TYPE_VECTORSDB => Resource::TYPE_DATABASE_VECTORSDB,
DATABASE_TYPE_DOCUMENTSDB => Resource::TYPE_DATABASE_DOCUMENTSDB,
default => Resource::TYPE_DATABASE,
};
}
Http::post('/v1/migrations/appwrite')
->groups(['api', 'migrations'])
->desc('Create Appwrite migration')
@@ -447,6 +457,7 @@ Http::post('/v1/migrations/csv/imports')
}
$fileSize = $deviceForMigrations->getFileSize($newPath);
$resources = Transfer::extractServices([getDatabaseTransferResourceServices($databaseType)]);
$resourceType = getDatabaseResourceType($databaseType);
$migration = $dbForProject->createDocument('migrations', new Document([
'$id' => $migrationId,
@@ -456,7 +467,7 @@ Http::post('/v1/migrations/csv/imports')
'destination' => Appwrite::getName(),
'resources' => $resources,
'resourceId' => $resourceId,
'resourceType' => Resource::TYPE_DATABASE,
'resourceType' => $resourceType,
'statusCounters' => '{}',
'resourceData' => '{}',
'errors' => [],
@@ -565,16 +576,6 @@ Http::post('/v1/migrations/csv/exports')
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$validator = new Documents(
attributes: $collection->getAttribute('attributes', []),
indexes: $collection->getAttribute('indexes', []),
idAttributeType: $dbForProject->getAdapter()->getIdAttributeType(),
);
if (!$validator->isValid($parsedQueries)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
}
// getting databasetype
$resources = explode(':', $resourceId);
$databaseId = $resources[0];
@@ -583,7 +584,23 @@ Http::post('/v1/migrations/csv/exports')
if (!in_array($databaseType, CSV_ALLOWED_DATABASE_TYPES)) {
throw new Exception(Exception::MIGRATION_DATABASE_TYPE_UNSUPPORTED, 'Database type not supported for csv');
}
// Schemaless databases (DocumentsDB, VectorsDB) allow queries on dynamic fields
$isSchemaless = in_array($databaseType, [DATABASE_TYPE_DOCUMENTSDB, DATABASE_TYPE_VECTORSDB]);
$validator = new Documents(
attributes: $collection->getAttribute('attributes', []),
indexes: $collection->getAttribute('indexes', []),
idAttributeType: $dbForProject->getAdapter()->getIdAttributeType(),
supportForAttributes: !$isSchemaless,
);
if (!$validator->isValid($parsedQueries)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
}
$resources = Transfer::extractServices([getDatabaseTransferResourceServices($databaseType)]);
$resourceType = getDatabaseResourceType($databaseType);
$migration = $dbForProject->createDocument('migrations', new Document([
'$id' => ID::unique(),
@@ -593,7 +610,7 @@ Http::post('/v1/migrations/csv/exports')
'destination' => CSV::getName(),
'resources' => $resources,
'resourceId' => $resourceId,
'resourceType' => Resource::TYPE_DATABASE,
'resourceType' => $resourceType,
'statusCounters' => '{}',
'resourceData' => '{}',
'errors' => [],
@@ -624,6 +641,291 @@ Http::post('/v1/migrations/csv/exports')
->dynamic($migration, Response::MODEL_MIGRATION);
});
Http::post('/v1/migrations/json/imports')
->groups(['api', 'migrations'])
->desc('Import documents from a JSON')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].create')
->label('audits.event', 'migration.create')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'createJSONImport',
description: '/docs/references/migrations/migration-json-import.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_ACCEPTED,
model: Response::MODEL_MIGRATION,
)
]
))
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File ID.')
->param('resourceId', null, new CompoundUID(), 'Composite ID in the format {databaseId:collectionId}, identifying a collection within a database.')
->param('internalFile', false, new Boolean(), 'Is the file stored in an internal bucket?', true)
->inject('response')
->inject('dbForProject')
->inject('dbForPlatform')
->inject('authorization')
->inject('project')
->inject('platform')
->inject('deviceForFiles')
->inject('deviceForMigrations')
->inject('queueForEvents')
->inject('queueForMigrations')
->action(function (
string $bucketId,
string $fileId,
string $resourceId,
bool $internalFile,
Response $response,
Database $dbForProject,
Database $dbForPlatform,
Authorization $authorization,
Document $project,
array $platform,
Device $deviceForFiles,
Device $deviceForMigrations,
Event $queueForEvents,
Migration $queueForMigrations
) {
$bucket = $authorization->skip(function () use ($internalFile, $dbForPlatform, $dbForProject, $bucketId) {
if ($internalFile) {
return $dbForPlatform->getDocument('buckets', 'default');
}
return $dbForProject->getDocument('buckets', $bucketId);
});
if ($bucket->isEmpty()) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$file = $authorization->skip(fn () => $internalFile ? $dbForPlatform->getDocument('bucket_' . $bucket->getSequence(), $fileId) : $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
$path = $file->getAttribute('path', '');
if (!$deviceForFiles->exists($path)) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND, 'File not found in ' . $path);
}
// No encryption or compression on files above 20MB.
$hasEncryption = !empty($file->getAttribute('openSSLCipher'));
$compression = $file->getAttribute('algorithm', Compression::NONE);
$hasCompression = $compression !== Compression::NONE;
$migrationId = ID::unique();
$newPath = $deviceForMigrations->getPath($migrationId . '_' . $fileId . '.json');
if ($hasEncryption || $hasCompression) {
$source = $deviceForFiles->read($path);
if ($hasEncryption) {
$source = OpenSSL::decrypt(
$source,
$file->getAttribute('openSSLCipher'),
System::getEnv('_APP_OPENSSL_KEY_V' . $file->getAttribute('openSSLVersion')),
0,
hex2bin($file->getAttribute('openSSLIV')),
hex2bin($file->getAttribute('openSSLTag'))
);
}
if ($hasCompression) {
switch ($compression) {
case Compression::ZSTD:
$source = (new Zstd())->decompress($source);
break;
case Compression::GZIP:
$source = (new GZIP())->decompress($source);
break;
}
}
// Manual write after decryption and/or decompression
if (!$deviceForMigrations->write($newPath, $source, 'application/json')) {
throw new \Exception('Unable to copy file');
}
} elseif (!$deviceForFiles->transfer($path, $newPath, $deviceForMigrations)) {
throw new \Exception('Unable to copy file');
}
$fileSize = $deviceForMigrations->getFileSize($newPath);
[$databaseId] = \explode(':', $resourceId, 2);
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$databaseType = $database->getAttribute('type');
$resources = Transfer::extractServices([getDatabaseTransferResourceServices($databaseType)]);
$resourceType = getDatabaseResourceType($databaseType);
$migration = $dbForProject->createDocument('migrations', new Document([
'$id' => $migrationId,
'status' => 'pending',
'stage' => 'init',
'source' => JSON::getName(),
'destination' => Appwrite::getName(),
'resources' => $resources,
'resourceId' => $resourceId,
'resourceType' => $resourceType,
'statusCounters' => '{}',
'resourceData' => '{}',
'errors' => [],
'options' => [
'path' => $newPath,
'size' => $fileSize,
],
]));
$queueForEvents->setParam('migrationId', $migration->getId());
$queueForMigrations
->setMigration($migration)
->setProject($project)
->setPlatform($platform)
->trigger();
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($migration, Response::MODEL_MIGRATION);
});
Http::post('/v1/migrations/json/exports')
->groups(['api', 'migrations'])
->desc('Export documents to JSON')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].create')
->label('audits.event', 'migration.create')
->label('sdk', new Method(
namespace: 'migrations',
group: null,
name: 'createJSONExport',
description: '/docs/references/migrations/migration-json-export.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_ACCEPTED,
model: Response::MODEL_MIGRATION,
)
]
))
->param('resourceId', null, new CompoundUID(), 'Composite ID in the format {databaseId:collectionId}, identifying a collection within a database to export.')
->param('filename', '', new Text(255), 'The name of the file to be created for the export, excluding the .json extension.')
->param('columns', [], new ArrayList(new Text(Database::LENGTH_KEY)), 'List of attributes to export. If empty, all attributes will be exported. You can use the `*` wildcard to export all attributes from the collection.', true)
->param('queries', [], new ArrayList(new Text(0)), 'Array of query strings generated using the Query class provided by the SDK to filter documents to export. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
->param('notify', true, new Boolean(), 'Set to true to receive an email when the export is complete. Default is true.', true)
->inject('user')
->inject('response')
->inject('dbForProject')
->inject('dbForPlatform')
->inject('authorization')
->inject('project')
->inject('platform')
->inject('queueForEvents')
->inject('queueForMigrations')
->action(function (
string $resourceId,
string $filename,
array $columns,
array $queries,
bool $notify,
Document $user,
Response $response,
Database $dbForProject,
Database $dbForPlatform,
Authorization $authorization,
Document $project,
array $platform,
Event $queueForEvents,
Migration $queueForMigrations
) {
try {
$parsedQueries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$bucket = $authorization->skip(fn () => $dbForPlatform->getDocument('buckets', 'default'));
if ($bucket->isEmpty()) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
[$databaseId, $collectionId] = \explode(':', $resourceId, 2);
if (empty($databaseId)) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
if (empty($collectionId)) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
if ($collection->isEmpty()) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$databaseType = $database->getAttribute('type');
// Schemaless databases (DocumentsDB, VectorsDB) allow queries on dynamic fields
$isSchemaless = in_array($databaseType, [DATABASE_TYPE_DOCUMENTSDB, DATABASE_TYPE_VECTORSDB]);
$validator = new Documents(
attributes: $collection->getAttribute('attributes', []),
indexes: $collection->getAttribute('indexes', []),
idAttributeType: $dbForProject->getAdapter()->getIdAttributeType(),
supportForAttributes: !$isSchemaless,
);
if (!$validator->isValid($parsedQueries)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
}
$resources = Transfer::extractServices([getDatabaseTransferResourceServices($databaseType)]);
$resourceType = getDatabaseResourceType($databaseType);
$migration = $dbForProject->createDocument('migrations', new Document([
'$id' => ID::unique(),
'status' => 'pending',
'stage' => 'init',
'source' => Appwrite::getName(),
'destination' => JSON::getName(),
'resources' => $resources,
'resourceId' => $resourceId,
'resourceType' => $resourceType,
'statusCounters' => '{}',
'resourceData' => '{}',
'errors' => [],
'options' => [
'bucketId' => 'default', // Always use internal bucket
'filename' => $filename,
'columns' => $columns,
'queries' => $queries,
'notify' => $notify,
'userInternalId' => $user->getSequence(),
],
]));
$queueForEvents->setParam('migrationId', $migration->getId());
$queueForMigrations
->setMigration($migration)
->setProject($project)
->setPlatform($platform)
->trigger();
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($migration, Response::MODEL_MIGRATION);
});
Http::get('/v1/migrations')
->groups(['api', 'migrations'])
->desc('List migrations')
@@ -676,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.8.*",
"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
+360 -102
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": "f9225f2b580de0ccb796b2fb8c881384",
"content-hash": "9d66f869fc3ff5abc5873a31e6976911",
"packages": [
{
"name": "adhocore/jwt",
@@ -161,16 +161,16 @@
},
{
"name": "appwrite/php-runtimes",
"version": "0.19.4",
"version": "0.19.5",
"source": {
"type": "git",
"url": "https://github.com/appwrite/runtimes.git",
"reference": "eea9d1b3ca2540eab623b419c8afde09ef406c0b"
"reference": "aa2f7760cd0493c0880209b92df812c9386b3546"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/runtimes/zipball/eea9d1b3ca2540eab623b419c8afde09ef406c0b",
"reference": "eea9d1b3ca2540eab623b419c8afde09ef406c0b",
"url": "https://api.github.com/repos/appwrite/runtimes/zipball/aa2f7760cd0493c0880209b92df812c9386b3546",
"reference": "aa2f7760cd0493c0880209b92df812c9386b3546",
"shasum": ""
},
"require": {
@@ -210,9 +210,9 @@
],
"support": {
"issues": "https://github.com/appwrite/runtimes/issues",
"source": "https://github.com/appwrite/runtimes/tree/0.19.4"
"source": "https://github.com/appwrite/runtimes/tree/0.19.5"
},
"time": "2026-02-17T10:04:39+00:00"
"time": "2026-04-01T01:39:23+00:00"
},
{
"name": "brick/math",
@@ -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.8.3",
"version": "dev-feat-query-lib",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/migration.git",
"reference": "8633523b3343d492427331b6eec53f020f6ab7a7"
"reference": "1bc59979ee7a1af5f98333f277cad27785ef30c1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/8633523b3343d492427331b6eec53f020f6ab7a7",
"reference": "8633523b3343d492427331b6eec53f020f6ab7a7",
"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.8.3"
"source": "https://github.com/utopia-php/migration/tree/feat-query-lib",
"issues": "https://github.com/utopia-php/migration/issues"
},
"time": "2026-03-19T09:18:47+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:
+1 -1
View File
@@ -1 +1 @@
Get the number of audit logs that are waiting to be processed in the Appwrite internal queue server.
Get the number of audit logs that are waiting to be processed in the Appwrite internal queue server.
-520
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
@@ -216,102 +210,6 @@ parameters:
count: 1
path: src/Appwrite/GraphQL/Resolvers.php
-
message: '#^Variable \$databaseId might not be defined\.$#'
identifier: variable.undefined
count: 5
path: src/Appwrite/GraphQL/Schema.php
-
message: '#^Variable \$sdk in empty\(\) always exists and is not falsy\.$#'
identifier: empty.variable
count: 1
path: src/Appwrite/GraphQL/Schema.php
-
message: '#^Method Appwrite\\GraphQL\\Types\:\:assoc\(\) should return Appwrite\\GraphQL\\Types\\Json but returns GraphQL\\Type\\Definition\\Type\.$#'
identifier: return.type
count: 1
path: src/Appwrite/GraphQL/Types.php
-
message: '#^Method Appwrite\\GraphQL\\Types\:\:inputFile\(\) should return Appwrite\\GraphQL\\Types\\InputFile but returns GraphQL\\Type\\Definition\\Type\.$#'
identifier: return.type
count: 1
path: src/Appwrite/GraphQL/Types.php
-
message: '#^Method Appwrite\\GraphQL\\Types\:\:json\(\) should return Appwrite\\GraphQL\\Types\\Json but returns GraphQL\\Type\\Definition\\Type\.$#'
identifier: return.type
count: 1
path: src/Appwrite/GraphQL/Types.php
-
message: '#^Class Appwrite\\Network\\Validator\\CNAME not found\.$#'
identifier: class.notFound
count: 1
path: src/Appwrite/GraphQL/Types/Mapper.php
-
message: '#^Class Utopia\\Validator\\Origin not found\.$#'
identifier: class.notFound
count: 1
path: src/Appwrite/GraphQL/Types/Mapper.php
-
message: '#^Call to an undefined method Appwrite\\Migration\\Version\\V15\:\:documentsIterator\(\)\.$#'
identifier: method.notFound
count: 7
path: src/Appwrite/Migration/Version/V15.php
-
message: '#^Method Appwrite\\Migration\\Version\\V15\:\:fixDocument\(\) should return Utopia\\Database\\Document but empty return statement found\.$#'
identifier: return.empty
count: 1
path: src/Appwrite/Migration/Version/V15.php
-
message: '#^PHPDoc tag @return with type string\|false is not subtype of native type string\.$#'
identifier: return.phpDocType
count: 1
path: src/Appwrite/Migration/Version/V15.php
-
message: '#^Variable \$tag on left side of \?\? always exists and is always null\.$#'
identifier: nullCoalesce.variable
count: 1
path: src/Appwrite/Migration/Version/V15.php
-
message: '#^Call to an undefined method Appwrite\\Migration\\Version\\V17\:\:documentsIterator\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Appwrite/Migration/Version/V17.php
-
message: '#^Call to an undefined method Appwrite\\Migration\\Version\\V18\:\:documentsIterator\(\)\.$#'
identifier: method.notFound
count: 2
path: src/Appwrite/Migration/Version/V18.php
-
message: '#^Call to an undefined method Appwrite\\Migration\\Version\\V19\:\:documentsIterator\(\)\.$#'
identifier: method.notFound
count: 4
path: src/Appwrite/Migration/Version/V19.php
-
message: '#^Call to an undefined method Appwrite\\Migration\\Version\\V20\:\:documentsIterator\(\)\.$#'
identifier: method.notFound
count: 6
path: src/Appwrite/Migration/Version/V20.php
-
message: '#^Variable \$query on left side of \?\? always exists and is not nullable\.$#'
identifier: nullCoalesce.variable
count: 1
path: src/Appwrite/Migration/Version/V20.php
-
message: '#^Method Appwrite\\Network\\Cors\:\:headers\(\) should return array\<string, string\> but returns array\<string, int\|string\>\.$#'
identifier: return.type
@@ -360,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
@@ -474,18 +366,6 @@ parameters:
count: 1
path: src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Logs/XList.php
-
message: '#^Variable \$device might not be defined\.$#'
identifier: variable.undefined
count: 5
path: src/Appwrite/Platform/Modules/Functions/Http/Deployments/Download/Get.php
-
message: '#^Variable \$path might not be defined\.$#'
identifier: variable.undefined
count: 5
path: src/Appwrite/Platform/Modules/Functions/Http/Deployments/Download/Get.php
-
message: '#^Call to method getAttribute\(\) on an unknown class Appwrite\\Platform\\Modules\\Functions\\Http\\Executions\\Utopia\\Database\\Document\.$#'
identifier: class.notFound
@@ -546,12 +426,6 @@ parameters:
count: 1
path: src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php
-
message: '#^Variable \$enabled on left side of \?\?\= always exists and is not nullable\.$#'
identifier: nullCoalesce.variable
count: 1
path: src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php
-
message: '#^Undefined variable\: \$cpus$#'
identifier: variable.undefined
@@ -600,24 +474,6 @@ parameters:
count: 1
path: src/Appwrite/Platform/Modules/Proxy/Http/Rules/Verification/Update.php
-
message: '#^Variable \$device might not be defined\.$#'
identifier: variable.undefined
count: 5
path: src/Appwrite/Platform/Modules/Sites/Http/Deployments/Download/Get.php
-
message: '#^Variable \$path might not be defined\.$#'
identifier: variable.undefined
count: 5
path: src/Appwrite/Platform/Modules/Sites/Http/Deployments/Download/Get.php
-
message: '#^Variable \$enabled on left side of \?\?\= always exists and is not nullable\.$#'
identifier: nullCoalesce.variable
count: 1
path: src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php
-
message: '#^Variable \$iv might not be defined\.$#'
identifier: variable.undefined
@@ -630,30 +486,6 @@ parameters:
count: 1
path: src/Appwrite/Platform/Modules/Storage/Http/Buckets/Files/Create.php
-
message: '#^Variable \$allowedFileExtensions on left side of \?\?\= always exists and is not nullable\.$#'
identifier: nullCoalesce.variable
count: 1
path: src/Appwrite/Platform/Modules/Storage/Http/Buckets/Update.php
-
message: '#^Variable \$antivirus on left side of \?\?\= always exists and is not nullable\.$#'
identifier: nullCoalesce.variable
count: 1
path: src/Appwrite/Platform/Modules/Storage/Http/Buckets/Update.php
-
message: '#^Variable \$enabled on left side of \?\?\= always exists and is not nullable\.$#'
identifier: nullCoalesce.variable
count: 1
path: src/Appwrite/Platform/Modules/Storage/Http/Buckets/Update.php
-
message: '#^Variable \$transformations on left side of \?\?\= always exists and is not nullable\.$#'
identifier: nullCoalesce.variable
count: 1
path: src/Appwrite/Platform/Modules/Storage/Http/Buckets/Update.php
-
message: '#^Caught class Appwrite\\Platform\\Modules\\Teams\\Http\\Memberships\\Throwable not found\.$#'
identifier: class.notFound
@@ -678,78 +510,6 @@ parameters:
count: 14
path: src/Appwrite/Platform/Modules/Teams/Http/Memberships/Create.php
-
message: '#^Variable \$logBase might not be defined\.$#'
identifier: variable.undefined
count: 1
path: src/Appwrite/Platform/Modules/VCS/Http/GitHub/Authorize/External/Update.php
-
message: '#^Variable \$previewUrl in empty\(\) always exists and is not falsy\.$#'
identifier: empty.variable
count: 1
path: src/Appwrite/Platform/Modules/VCS/Http/GitHub/Authorize/External/Update.php
-
message: '#^Variable \$repositoryName in empty\(\) always exists and is not falsy\.$#'
identifier: empty.variable
count: 1
path: src/Appwrite/Platform/Modules/VCS/Http/GitHub/Authorize/External/Update.php
-
message: '#^Variable \$rule in empty\(\) always exists and is not falsy\.$#'
identifier: empty.variable
count: 1
path: src/Appwrite/Platform/Modules/VCS/Http/GitHub/Authorize/External/Update.php
-
message: '#^Result of method Utopia\\Http\\Response\:\:redirect\(\) \(void\) is used\.$#'
identifier: method.void
count: 2
path: src/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.php
-
message: '#^Undefined variable\: \$redirectFailure$#'
identifier: variable.undefined
count: 2
path: src/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.php
-
message: '#^Variable \$redirectFailure in empty\(\) is never defined\.$#'
identifier: empty.variable
count: 1
path: src/Appwrite/Platform/Modules/VCS/Http/GitHub/Callback/Get.php
-
message: '#^Result of method Appwrite\\Utopia\\Response\:\:json\(\) \(void\) is used\.$#'
identifier: method.void
count: 1
path: src/Appwrite/Platform/Modules/VCS/Http/GitHub/Events/Create.php
-
message: '#^Variable \$logBase might not be defined\.$#'
identifier: variable.undefined
count: 1
path: src/Appwrite/Platform/Modules/VCS/Http/GitHub/Events/Create.php
-
message: '#^Variable \$previewUrl in empty\(\) always exists and is not falsy\.$#'
identifier: empty.variable
count: 1
path: src/Appwrite/Platform/Modules/VCS/Http/GitHub/Events/Create.php
-
message: '#^Variable \$repositoryName in empty\(\) always exists and is not falsy\.$#'
identifier: empty.variable
count: 1
path: src/Appwrite/Platform/Modules/VCS/Http/GitHub/Events/Create.php
-
message: '#^Variable \$rule in empty\(\) always exists and is not falsy\.$#'
identifier: empty.variable
count: 1
path: src/Appwrite/Platform/Modules/VCS/Http/GitHub/Events/Create.php
-
message: '#^Variable \$providerConfig on left side of \?\? always exists and is not nullable\.$#'
identifier: nullCoalesce.variable
@@ -918,108 +678,6 @@ parameters:
count: 1
path: src/Appwrite/SDK/Method.php
-
message: '#^PHPDoc tag @param references unknown parameter\: \$services$#'
identifier: parameter.notFound
count: 1
path: src/Appwrite/SDK/Specification/Format.php
-
message: '#^PHPDoc tag @return with type Appwrite\\SDK\\Specification\\Format is incompatible with native type array\.$#'
identifier: return.phpDocType
count: 1
path: src/Appwrite/SDK/Specification/Format.php
-
message: '#^Cannot unset offset ''schema'' on array\{description\: ''No content'', content\?\: non\-empty\-array\<''''\|''\*/\*''\|''application/json''\|''image/\*''\|''image/png''\|''multipart/form\-data''\|''text/html''\|''text/plain'', array\{schema\: array\{''\$ref''\: non\-falsy\-string\}\}\|array\{schema\: array\{oneOf\: array\<array\{''\$ref''\: non\-falsy\-string\}\>\}\}\>\}\.$#'
identifier: unset.offset
count: 1
path: src/Appwrite/SDK/Specification/Format/OpenAPI3.php
-
message: '#^Class Utopia\\Database\\Validator\\DatetimeValidator not found\.$#'
identifier: class.notFound
count: 1
path: src/Appwrite/SDK/Specification/Format/OpenAPI3.php
-
message: '#^Class Utopia\\Validator\\Length not found\.$#'
identifier: class.notFound
count: 1
path: src/Appwrite/SDK/Specification/Format/OpenAPI3.php
-
message: '#^Class Utopia\\Validator\\Mock not found\.$#'
identifier: class.notFound
count: 1
path: src/Appwrite/SDK/Specification/Format/OpenAPI3.php
-
message: '#^Offset ''securityDefinitions'' on array\{openapi\: ''3\.0\.0'', info\: array\{version\: string, title\: string, description\: string, termsOfService\: string, contact\: array\{name\: string, url\: string, email\: string\}, license\: array\{name\: ''BSD\-3\-Clause'', url\: ''https\://raw…''\}\}, servers\: array\{array\{url\: string\}, array\{url\: string\}\}, paths\: array\{\}, tags\: array, components\: array\{schemas\: array\{\}, securitySchemes\: array\}, externalDocs\: array\{description\: string, url\: string\}\} in isset\(\) does not exist\.$#'
identifier: isset.offset
count: 1
path: src/Appwrite/SDK/Specification/Format/OpenAPI3.php
-
message: '#^Variable \$desc on left side of \?\?\= always exists and is not nullable\.$#'
identifier: nullCoalesce.variable
count: 1
path: src/Appwrite/SDK/Specification/Format/OpenAPI3.php
-
message: '#^Variable \$validator in empty\(\) always exists and is not falsy\.$#'
identifier: empty.variable
count: 1
path: src/Appwrite/SDK/Specification/Format/OpenAPI3.php
-
message: '#^Class Utopia\\Database\\Validator\\DatetimeValidator not found\.$#'
identifier: class.notFound
count: 1
path: src/Appwrite/SDK/Specification/Format/Swagger2.php
-
message: '#^Class Utopia\\Validator\\Length not found\.$#'
identifier: class.notFound
count: 1
path: src/Appwrite/SDK/Specification/Format/Swagger2.php
-
message: '#^Class Utopia\\Validator\\Mock not found\.$#'
identifier: class.notFound
count: 1
path: src/Appwrite/SDK/Specification/Format/Swagger2.php
-
message: '#^PHPDoc tag @var with type Appwrite\\SDK\\Method is not subtype of native type \*NEVER\*\.$#'
identifier: varTag.nativeType
count: 1
path: src/Appwrite/SDK/Specification/Format/Swagger2.php
-
message: '#^Variable \$additionalMethods in empty\(\) always exists and is always falsy\.$#'
identifier: empty.variable
count: 1
path: src/Appwrite/SDK/Specification/Format/Swagger2.php
-
message: '#^Variable \$desc on left side of \?\?\= always exists and is not nullable\.$#'
identifier: nullCoalesce.variable
count: 1
path: src/Appwrite/SDK/Specification/Format/Swagger2.php
-
message: '#^Variable \$sdk in empty\(\) always exists and is not falsy\.$#'
identifier: empty.variable
count: 1
path: src/Appwrite/SDK/Specification/Format/Swagger2.php
-
message: '#^Variable \$validator in empty\(\) always exists and is not falsy\.$#'
identifier: empty.variable
count: 1
path: src/Appwrite/SDK/Specification/Format/Swagger2.php
-
message: '#^PHPDoc tag @param has invalid value \(Document \$this\)\: Unexpected token "\$this", expected variable at offset 69 on line 4$#'
identifier: phpDoc.parseError
@@ -1032,196 +690,18 @@ parameters:
count: 1
path: src/Appwrite/Utopia/Database/Documents/User.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\Databases\\Legacy\\DatabasesStringTypesTest\:\:\$setupCache through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 4
path: tests/e2e/Services/Databases/Legacy/DatabasesStringTypesTest.php
-
message: '#^Call to an undefined method Tests\\E2E\\Services\\Databases\\Permissions\\LegacyPermissionsGuestTest\:\:getIndexUrl\(\)\.$#'
identifier: method.notFound
count: 1
path: tests/e2e/Services/Databases/Permissions/LegacyPermissionsGuestTest.php
-
message: '#^Call to an undefined method Tests\\E2E\\Services\\Databases\\Permissions\\LegacyPermissionsMemberTest\:\:getIndexUrl\(\)\.$#'
identifier: method.notFound
count: 1
path: tests/e2e/Services/Databases/Permissions/LegacyPermissionsMemberTest.php
-
message: '#^Call to an undefined method Tests\\E2E\\Services\\Databases\\Permissions\\LegacyPermissionsTeamTest\:\:getIndexUrl\(\)\.$#'
identifier: method.notFound
count: 1
path: tests/e2e/Services/Databases/Permissions/LegacyPermissionsTeamTest.php
-
message: '#^Method PHPUnit\\Framework\\TestCase\:\:addToAssertionCount\(\) invoked with 2 parameters, 1 required\.$#'
identifier: arguments.count
count: 1
path: tests/e2e/Services/Functions/FunctionsCustomClientTest.php
-
message: '#^Variable \$largeTag might not be defined\.$#'
identifier: variable.undefined
count: 8
path: tests/e2e/Services/Functions/FunctionsCustomServerTest.php
-
message: '#^Binary operation "\+" between string and 1 results in an error\.$#'
identifier: binaryOp.invalid
count: 1
path: tests/e2e/Services/GraphQL/Legacy/AbuseTest.php
-
message: '#^Variable \$from in empty\(\) is never defined\.$#'
identifier: empty.variable
count: 1
path: tests/e2e/Services/GraphQL/MessagingTest.php
-
message: '#^Method Tests\\E2E\\Services\\GraphQL\\StorageClientTest\:\:testGetFileDownload\(\) should return array but return statement is missing\.$#'
identifier: return.missing
count: 1
path: tests/e2e/Services/GraphQL/StorageClientTest.php
-
message: '#^Method Tests\\E2E\\Services\\GraphQL\\StorageServerTest\:\:testGetFileDownload\(\) should return array but return statement is missing\.$#'
identifier: return.missing
count: 1
path: tests/e2e/Services/GraphQL/StorageServerTest.php
-
message: '#^Binary operation "\+" between string and 1 results in an error\.$#'
identifier: binaryOp.invalid
count: 1
path: tests/e2e/Services/GraphQL/TablesDB/AbuseTest.php
-
message: '#^Variable \$from in empty\(\) is never defined\.$#'
identifier: empty.variable
count: 1
path: tests/e2e/Services/Messaging/MessagingConsoleClientTest.php
-
message: '#^Variable \$from in empty\(\) is never defined\.$#'
identifier: empty.variable
count: 1
path: tests/e2e/Services/Messaging/MessagingCustomClientTest.php
-
message: '#^Variable \$from in empty\(\) is never defined\.$#'
identifier: empty.variable
count: 1
path: tests/e2e/Services/Messaging/MessagingCustomServerTest.php
-
message: '#^Anonymous function has an unused use \$databaseId\.$#'
identifier: closure.unusedUse
count: 5
path: tests/e2e/Services/Migrations/MigrationsConsoleClientTest.php
-
message: '#^Anonymous function has an unused use \$tableId\.$#'
identifier: closure.unusedUse
count: 5
path: tests/e2e/Services/Migrations/MigrationsConsoleClientTest.php
-
message: '#^Variable \$largeFile might not be defined\.$#'
identifier: variable.undefined
count: 8
path: tests/e2e/Services/Storage/StorageConsoleClientTest.php
-
message: '#^Variable \$largeFile might not be defined\.$#'
identifier: variable.undefined
count: 8
path: tests/e2e/Services/Storage/StorageCustomClientTest.php
-
message: '#^Variable \$largeFile might not be defined\.$#'
identifier: variable.undefined
count: 8
path: tests/e2e/Services/Storage/StorageCustomServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\TablesDB\\DatabasesStringTypesTest\:\:\$setupCache through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 4
path: tests/e2e/Services/TablesDB/DatabasesStringTypesTest.php
-
message: '#^Call to an undefined method Tests\\E2E\\Services\\TablesDB\\Permissions\\TablesDBPermissionsGuestTest\:\:getIndexUrl\(\)\.$#'
identifier: method.notFound
count: 1
path: tests/e2e/Services/TablesDB/Permissions/TablesDBPermissionsGuestTest.php
-
message: '#^Call to an undefined method Tests\\E2E\\Services\\TablesDB\\Permissions\\TablesDBPermissionsMemberTest\:\:getIndexUrl\(\)\.$#'
identifier: method.notFound
count: 1
path: tests/e2e/Services/TablesDB/Permissions/TablesDBPermissionsMemberTest.php
-
message: '#^Call to an undefined method Tests\\E2E\\Services\\TablesDB\\Permissions\\TablesDBPermissionsTeamTest\:\:getIndexUrl\(\)\.$#'
identifier: method.notFound
count: 1
path: tests/e2e/Services/TablesDB/Permissions/TablesDBPermissionsTeamTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\Users\\UsersCustomServerTest\:\:\$cachedHashedPasswordUsers through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 2
path: tests/e2e/Services/Users/UsersCustomServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\Users\\UsersCustomServerTest\:\:\$cachedUser through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 7
path: tests/e2e/Services/Users/UsersCustomServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\Users\\UsersCustomServerTest\:\:\$cachedUserTarget through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 7
path: tests/e2e/Services/Users/UsersCustomServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\Users\\UsersCustomServerTest\:\:\$userEmailUpdated through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 3
path: tests/e2e/Services/Users/UsersCustomServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\Users\\UsersCustomServerTest\:\:\$userNameUpdated through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 3
path: tests/e2e/Services/Users/UsersCustomServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\Users\\UsersCustomServerTest\:\:\$userNumberUpdated through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 3
path: tests/e2e/Services/Users/UsersCustomServerTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\VCS\\VCSConsoleClientTest\:\:\$cachedFunctionData through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 4
path: tests/e2e/Services/VCS/VCSConsoleClientTest.php
-
message: '#^Unsafe access to private property Tests\\E2E\\Services\\VCS\\VCSConsoleClientTest\:\:\$cachedInstallationId through static\:\:\.$#'
identifier: staticClassAccess.privateProperty
count: 4
path: tests/e2e/Services/VCS/VCSConsoleClientTest.php
-
message: '#^Unsafe call to private method Tests\\Unit\\Auth\\KeyTest\:\:generateKey\(\) through static\:\:\.$#'
identifier: staticClassAccess.privateMethod
count: 3
path: tests/unit/Auth/KeyTest.php
-
message: '#^Call to an undefined method Utopia\\Queue\\Publisher\:\:getEvents\(\)\.$#'
identifier: method.notFound
+1
View File
@@ -3,6 +3,7 @@ includes:
parameters:
level: 3
tmpDir: .phpstan-cache
paths:
- src
- app
+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;
}
+1 -1
View File
@@ -40,7 +40,7 @@ final class Usage extends Base
*/
public static function fromArray(array $data): static
{
return new static(
return new self(
project: new Document($data['project'] ?? []),
metrics: $data['metrics'] ?? [],
reduce: array_map(fn (array $doc) => new Document($doc), $data['reduce'] ?? []),
+76 -75
View File
@@ -98,10 +98,9 @@ class Schema
foreach ($routes as $route) {
/** @var Route $route */
/** @var \Appwrite\SDK\Method $sdk */
$sdk = $route->getLabel('sdk', false);
if (empty($sdk)) {
if ($sdk === false) {
continue;
}
@@ -177,7 +176,7 @@ class Schema
$required = $attr['required'];
$default = $attr['default'];
$escapedKey = str_replace('$', '', $key);
$collections[$collectionId][$escapedKey] = [
$collections[$databaseId][$collectionId][$escapedKey] = [
'type' => Mapper::attribute(
$type,
$array,
@@ -187,80 +186,82 @@ class Schema
];
}
foreach ($collections as $collectionId => $attributes) {
$objectType = new ObjectType([
'name' => $collectionId,
'fields' => \array_merge(
["_id" => ['type' => Type::string()]],
$attributes
),
]);
$attributes = \array_merge(
$attributes,
Mapper::args('mutate')
);
$queryFields[$collectionId . 'Get'] = [
'type' => $objectType,
'args' => Mapper::args('id'),
'resolve' => Resolvers::documentGet(
$utopia,
$databaseId,
$collectionId,
$urls['get'],
)
];
$queryFields[$collectionId . 'List'] = [
'type' => Type::listOf($objectType),
'args' => Mapper::args('list'),
'resolve' => Resolvers::documentList(
$utopia,
$databaseId,
$collectionId,
$urls['list'],
$params['list'],
),
'complexity' => $complexity,
];
$mutationFields[$collectionId . 'Create'] = [
'type' => $objectType,
'args' => $attributes,
'resolve' => Resolvers::documentCreate(
$utopia,
$databaseId,
$collectionId,
$urls['create'],
$params['create'],
)
];
$mutationFields[$collectionId . 'Update'] = [
'type' => $objectType,
'args' => \array_merge(
Mapper::args('id'),
\array_map(
fn ($attr) => $attr['type'] = Type::getNullableType($attr['type']),
foreach ($collections as $databaseId => $databaseCollections) {
foreach ($databaseCollections as $collectionId => $attributes) {
$objectType = new ObjectType([
'name' => $collectionId,
'fields' => \array_merge(
["_id" => ['type' => Type::string()]],
$attributes
),
]);
$attributes = \array_merge(
$attributes,
Mapper::args('mutate')
);
$queryFields[$collectionId . 'Get'] = [
'type' => $objectType,
'args' => Mapper::args('id'),
'resolve' => Resolvers::documentGet(
$utopia,
$databaseId,
$collectionId,
$urls['get'],
)
),
'resolve' => Resolvers::documentUpdate(
$utopia,
$databaseId,
$collectionId,
$urls['update'],
$params['update'],
)
];
$mutationFields[$collectionId . 'Delete'] = [
'type' => Mapper::model('none'),
'args' => Mapper::args('id'),
'resolve' => Resolvers::documentDelete(
$utopia,
$databaseId,
$collectionId,
$urls['delete'],
)
];
];
$queryFields[$collectionId . 'List'] = [
'type' => Type::listOf($objectType),
'args' => Mapper::args('list'),
'resolve' => Resolvers::documentList(
$utopia,
$databaseId,
$collectionId,
$urls['list'],
$params['list'],
),
'complexity' => $complexity,
];
$mutationFields[$collectionId . 'Create'] = [
'type' => $objectType,
'args' => $attributes,
'resolve' => Resolvers::documentCreate(
$utopia,
$databaseId,
$collectionId,
$urls['create'],
$params['create'],
)
];
$mutationFields[$collectionId . 'Update'] = [
'type' => $objectType,
'args' => \array_merge(
Mapper::args('id'),
\array_map(
fn ($attr) => $attr['type'] = Type::getNullableType($attr['type']),
$attributes
)
),
'resolve' => Resolvers::documentUpdate(
$utopia,
$databaseId,
$collectionId,
$urls['update'],
$params['update'],
)
];
$mutationFields[$collectionId . 'Delete'] = [
'type' => Mapper::model('none'),
'args' => Mapper::args('id'),
'resolve' => Resolvers::documentDelete(
$utopia,
$databaseId,
$collectionId,
$urls['delete'],
)
];
}
}
$offset += $limit;
}
+16 -7
View File
@@ -15,10 +15,13 @@ class Types
*
* @return Json
*/
public static function json(): Type
public static function json(): Json
{
if (Registry::has(Json::class)) {
return Registry::get(Json::class);
$type = Registry::get(Json::class);
if ($type instanceof Json) {
return $type;
}
}
$type = new Json();
Registry::set(Json::class, $type);
@@ -28,12 +31,15 @@ class Types
/**
* Get the JSON type.
*
* @return Json
* @return Assoc
*/
public static function assoc(): Type
public static function assoc(): Assoc
{
if (Registry::has(Assoc::class)) {
return Registry::get(Assoc::class);
$type = Registry::get(Assoc::class);
if ($type instanceof Assoc) {
return $type;
}
}
$type = new Assoc();
Registry::set(Assoc::class, $type);
@@ -45,10 +51,13 @@ class Types
*
* @return InputFile
*/
public static function inputFile(): Type
public static function inputFile(): InputFile
{
if (Registry::has(InputFile::class)) {
return Registry::get(InputFile::class);
$type = Registry::get(InputFile::class);
if ($type instanceof InputFile) {
return $type;
}
}
$type = new InputFile();
Registry::set(InputFile::class, $type);
+1 -3
View File
@@ -273,11 +273,9 @@ class Mapper
case \Appwrite\Auth\Validator\Password::class:
case \Appwrite\Event\Validator\Event::class:
case \Appwrite\Event\Validator\FunctionEvent::class:
case \Appwrite\Network\Validator\CNAME::class:
case \Utopia\Emails\Validator\Email::class:
case \Appwrite\Network\Validator\Redirect::class:
case \Appwrite\Network\Validator\DNS::class:
case \Appwrite\Network\Validator\Origin::class:
case \Appwrite\Task\Validator\Cron::class:
case \Appwrite\Utopia\Database\Validator\CustomId::class:
case \Utopia\Database\Validator\Key::class:
@@ -286,7 +284,7 @@ class Mapper
case \Utopia\Validator\HexColor::class:
case \Utopia\Validator\Host::class:
case \Utopia\Validator\IP::class:
case \Utopia\Validator\Origin::class:
case \Appwrite\Network\Validator\Origin::class:
case \Utopia\Validator\Text::class:
case \Utopia\Validator\URL::class:
case \Utopia\Validator\WhiteList::class:
+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());
}
}
+43 -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;
@@ -13,6 +14,7 @@ use Utopia\Database\Exception\Limit;
use Utopia\Database\Exception\Structure;
use Utopia\Database\Helpers\ID;
use Utopia\Database\PDO;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
abstract class Migration
@@ -204,6 +206,30 @@ abstract class Migration
}
}
/**
* @param array<Query> $queries
* @return \Generator<int, Document>
* @throws Exception
*/
protected function documentsIterator(string $collection, array $queries = []): \Generator
{
$offset = 0;
do {
$documents = $this->dbForProject->find($collection, [
...$queries,
Query::limit($this->limit),
Query::offset($offset),
]);
foreach ($documents as $document) {
yield $document;
}
$offset += \count($documents);
} while (\count($documents) === $this->limit);
}
/**
* Creates collection from the config collection.
*
@@ -227,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);
@@ -284,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);
@@ -293,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;
}
@@ -349,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,
);
}
@@ -407,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],
);
}
+6 -6
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) {
@@ -1224,7 +1225,7 @@ class V15 extends Migration
* @param \Utopia\Database\Document $document
* @return \Utopia\Database\Document
*/
protected function fixDocument(Document $document)
protected function fixDocument(Document $document): Document
{
switch ($document->getCollection()) {
case 'cache':
@@ -1234,7 +1235,7 @@ class V15 extends Migration
* skipping migration for 'cache' and 'variables'.
* 'users' already migrated.
*/
return;
return $document;
case '_metadata':
/**
@@ -1480,7 +1481,6 @@ class V15 extends Migration
* Filter from the 'encrypt' filter.
*
* @param string $value
* @return string|false
*/
protected function encryptFilter(string $value): string
{
@@ -1492,8 +1492,8 @@ class V15 extends Migration
'data' => OpenSSL::encrypt($value, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag),
'method' => OpenSSL::CIPHER_AES_128_GCM,
'iv' => \bin2hex($iv),
'tag' => \bin2hex($tag ?? ''),
'tag' => \bin2hex($tag),
'version' => '1',
]);
]) ?: '';
}
}
+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');
+1 -1
View File
@@ -452,7 +452,7 @@ class V20 extends Migration
Query::equal('period', ['1d']),
]);
$value = $query ?? 0;
$value = $query;
$this->createInfMetric($to, $value);
}
+14
View File
@@ -187,6 +187,20 @@ class V24 extends Migration
$this->dbForProject->purgeCachedCollection($id);
break;
case 'users':
try {
$this->createAttributeFromCollection($this->dbForProject, $id, 'impersonator');
} catch (Throwable $th) {
Console::warning("Failed to create attribute \"impersonator\" in collection {$id}: {$th->getMessage()}");
}
try {
$this->createIndexFromCollection($this->dbForProject, $id, 'impersonator');
} catch (Throwable $th) {
Console::warning("Failed to create index \"impersonator\" from {$id}: {$th->getMessage()}");
}
$this->dbForProject->purgeCachedCollection($id);
break;
case 'teams':
try {
$this->createAttributeFromCollection($this->dbForProject, $id, 'labels');
@@ -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()) {

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