mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b46fc77053 |
@@ -630,11 +630,6 @@ return [
|
||||
'description' => 'Site with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::SITE_ALREADY_EXISTS => [
|
||||
'name' => Exception::SITE_ALREADY_EXISTS,
|
||||
'description' => 'Site with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::SITE_TEMPLATE_NOT_FOUND => [
|
||||
'name' => Exception::SITE_TEMPLATE_NOT_FOUND,
|
||||
'description' => 'Site Template with the requested ID could not be found.',
|
||||
@@ -1296,11 +1291,6 @@ return [
|
||||
'description' => 'Message with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::MESSAGE_ALREADY_EXISTS => [
|
||||
'name' => Exception::MESSAGE_ALREADY_EXISTS,
|
||||
'description' => 'Message with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::MESSAGE_MISSING_TARGET => [
|
||||
'name' => Exception::MESSAGE_MISSING_TARGET,
|
||||
'description' => 'Message with the requested ID has no recipients (topics or users or targets).',
|
||||
|
||||
@@ -3251,7 +3251,7 @@ Http::post('/v1/messaging/messages/email')
|
||||
}
|
||||
}
|
||||
|
||||
$message = new Document([
|
||||
$message = $dbForProject->createDocument('messages', new Document([
|
||||
'$id' => $messageId,
|
||||
'providerType' => MESSAGE_TYPE_EMAIL,
|
||||
'topics' => $topics,
|
||||
@@ -3267,12 +3267,7 @@ Http::post('/v1/messaging/messages/email')
|
||||
'attachments' => $attachments,
|
||||
],
|
||||
'status' => $status,
|
||||
]);
|
||||
try {
|
||||
$message = $dbForProject->createDocument('messages', $message);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::MESSAGE_ALREADY_EXISTS);
|
||||
}
|
||||
]));
|
||||
|
||||
switch ($status) {
|
||||
case MessageStatus::PROCESSING:
|
||||
@@ -3405,7 +3400,7 @@ Http::post('/v1/messaging/messages/sms')
|
||||
}
|
||||
}
|
||||
|
||||
$message = new Document([
|
||||
$message = $dbForProject->createDocument('messages', new Document([
|
||||
'$id' => $messageId,
|
||||
'providerType' => MESSAGE_TYPE_SMS,
|
||||
'topics' => $topics,
|
||||
@@ -3415,12 +3410,7 @@ Http::post('/v1/messaging/messages/sms')
|
||||
'content' => $content,
|
||||
],
|
||||
'status' => $status,
|
||||
]);
|
||||
try {
|
||||
$message = $dbForProject->createDocument('messages', $message);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::MESSAGE_ALREADY_EXISTS);
|
||||
}
|
||||
]));
|
||||
|
||||
switch ($status) {
|
||||
case MessageStatus::PROCESSING:
|
||||
@@ -3630,7 +3620,7 @@ Http::post('/v1/messaging/messages/push')
|
||||
$pushData['priority'] = $priority;
|
||||
}
|
||||
|
||||
$message = new Document([
|
||||
$message = $dbForProject->createDocument('messages', new Document([
|
||||
'$id' => $messageId,
|
||||
'providerType' => MESSAGE_TYPE_PUSH,
|
||||
'topics' => $topics,
|
||||
@@ -3639,12 +3629,7 @@ Http::post('/v1/messaging/messages/push')
|
||||
'scheduledAt' => $scheduledAt,
|
||||
'data' => $pushData,
|
||||
'status' => $status,
|
||||
]);
|
||||
try {
|
||||
$message = $dbForProject->createDocument('messages', $message);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::MESSAGE_ALREADY_EXISTS);
|
||||
}
|
||||
]));
|
||||
|
||||
switch ($status) {
|
||||
case MessageStatus::PROCESSING:
|
||||
|
||||
@@ -560,29 +560,6 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
|
||||
'site' => '',
|
||||
};
|
||||
|
||||
$streamingDetected = false;
|
||||
$streamCallback = function (?string $streamData, ?array $streamHeaders) use ($response, &$streamingDetected, $execution, $deployment): void {
|
||||
if ($streamHeaders !== null) {
|
||||
// Headers signal — fired once before body, only when SSE detected
|
||||
$streamingDetected = true;
|
||||
$statusCode = \intval($streamHeaders['x-open-runtimes-status-code'] ?? 200);
|
||||
$response->setStatusCode($statusCode);
|
||||
foreach ($streamHeaders as $key => $value) {
|
||||
if (\in_array(\strtolower($key), FUNCTION_ALLOWLIST_HEADERS_RESPONSE)) {
|
||||
$response->addHeader($key, \is_array($value) ? \implode(', ', $value) : $value);
|
||||
}
|
||||
}
|
||||
if ($deployment->getAttribute('resourceType') === 'functions') {
|
||||
$response->addHeader('x-appwrite-execution-id', $execution->getId());
|
||||
} elseif ($deployment->getAttribute('resourceType') === 'sites') {
|
||||
$response->addHeader('x-appwrite-log-id', $execution->getId());
|
||||
}
|
||||
}
|
||||
if ($streamData !== null) {
|
||||
$response->chunk($streamData);
|
||||
}
|
||||
};
|
||||
|
||||
$executionResponse = $executor->createExecution(
|
||||
projectId: $project->getId(),
|
||||
deploymentId: $deployment->getId(),
|
||||
@@ -601,26 +578,9 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
|
||||
memory: $spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT,
|
||||
logging: $resource->getAttribute('logging', true),
|
||||
requestTimeout: 30,
|
||||
responseFormat: Executor::RESPONSE_FORMAT_ARRAY_HEADERS,
|
||||
streamCallback: $streamCallback,
|
||||
responseFormat: Executor::RESPONSE_FORMAT_ARRAY_HEADERS
|
||||
);
|
||||
|
||||
// If SSE streaming was detected, body was already forwarded chunk by chunk.
|
||||
// Close the response and populate execution metadata from what we have.
|
||||
// Note: logs/errors are not available for streaming responses (the executor
|
||||
// streams the body and does not send metadata back over the same channel).
|
||||
if ($streamingDetected) {
|
||||
$response->chunk('', true);
|
||||
|
||||
$execution->setAttribute('status', 'completed');
|
||||
$execution->setAttribute('logs', '');
|
||||
$execution->setAttribute('errors', '');
|
||||
$execution->setAttribute('responseStatusCode', 200);
|
||||
$execution->setAttribute('responseHeaders', []);
|
||||
$execution->setAttribute('duration', \microtime(true) - $durationStart);
|
||||
return true;
|
||||
}
|
||||
|
||||
$headerOverrides = [];
|
||||
|
||||
// Branded 404 override
|
||||
|
||||
@@ -27,7 +27,6 @@ use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Authorization\Input;
|
||||
@@ -930,19 +929,14 @@ Http::shutdown()
|
||||
$accessedAt = $cacheLog->getAttribute('accessedAt', 0);
|
||||
$now = DateTime::now();
|
||||
if ($cacheLog->isEmpty()) {
|
||||
try {
|
||||
$authorization->skip(fn () => $dbForProject->createDocument('cache', new Document([
|
||||
'$id' => $key,
|
||||
'resource' => $resource,
|
||||
'resourceType' => $resourceType,
|
||||
'mimeType' => $response->getContentType(),
|
||||
'accessedAt' => $now,
|
||||
'signature' => $signature,
|
||||
])));
|
||||
} catch (DuplicateException) {
|
||||
// Race condition: another concurrent request already created the cache document
|
||||
$cacheLog = $authorization->skip(fn () => $dbForProject->getDocument('cache', $key));
|
||||
}
|
||||
$authorization->skip(fn () => $dbForProject->createDocument('cache', new Document([
|
||||
'$id' => $key,
|
||||
'resource' => $resource,
|
||||
'resourceType' => $resourceType,
|
||||
'mimeType' => $response->getContentType(),
|
||||
'accessedAt' => $now,
|
||||
'signature' => $signature,
|
||||
])));
|
||||
} elseif (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_CACHE_UPDATE)) > $accessedAt) {
|
||||
$cacheLog->setAttribute('accessedAt', $now);
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), $cacheLog));
|
||||
|
||||
@@ -562,6 +562,11 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
|
||||
|
||||
$log->addTag('method', $route?->getMethod() ?? $request->getMethod());
|
||||
$log->addTag('url', $route?->getPath() ?? $request->getURI());
|
||||
|
||||
if (str_contains($th->getMessage(), 'FTS_TERM or FTS_NUMB')) {
|
||||
$log->addTag('paramQueries', json_encode($request->getParam('queries')));
|
||||
}
|
||||
|
||||
$log->addTag('verboseType', get_class($th));
|
||||
$log->addTag('code', $th->getCode());
|
||||
// $log->addTag('projectId', $project->getId()); // TODO: Figure out how to get ProjectID, if it becomes relevant
|
||||
|
||||
Generated
+24
-24
@@ -4517,16 +4517,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/migration",
|
||||
"version": "1.6.2",
|
||||
"version": "1.6.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/migration.git",
|
||||
"reference": "037bf4b3813d44f1b0990bc124e35b501ed27fca"
|
||||
"reference": "c5c7544d02d2418536d41050794050132f247d62"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/037bf4b3813d44f1b0990bc124e35b501ed27fca",
|
||||
"reference": "037bf4b3813d44f1b0990bc124e35b501ed27fca",
|
||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/c5c7544d02d2418536d41050794050132f247d62",
|
||||
"reference": "c5c7544d02d2418536d41050794050132f247d62",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4566,9 +4566,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/migration/issues",
|
||||
"source": "https://github.com/utopia-php/migration/tree/1.6.2"
|
||||
"source": "https://github.com/utopia-php/migration/tree/1.6.1"
|
||||
},
|
||||
"time": "2026-02-25T12:00:11+00:00"
|
||||
"time": "2026-02-17T05:49:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/mongo",
|
||||
@@ -4684,16 +4684,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/pools",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/pools.git",
|
||||
"reference": "74de7c5457a2c447f27e7ec4d72e8412a7d68c10"
|
||||
"reference": "b7d8dd00306cdd8bf3ff6f1dc90caeaf27dabeb1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/pools/zipball/74de7c5457a2c447f27e7ec4d72e8412a7d68c10",
|
||||
"reference": "74de7c5457a2c447f27e7ec4d72e8412a7d68c10",
|
||||
"url": "https://api.github.com/repos/utopia-php/pools/zipball/b7d8dd00306cdd8bf3ff6f1dc90caeaf27dabeb1",
|
||||
"reference": "b7d8dd00306cdd8bf3ff6f1dc90caeaf27dabeb1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4731,9 +4731,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/pools/issues",
|
||||
"source": "https://github.com/utopia-php/pools/tree/1.0.3"
|
||||
"source": "https://github.com/utopia-php/pools/tree/1.0.2"
|
||||
},
|
||||
"time": "2026-02-26T08:42:40+00:00"
|
||||
"time": "2026-01-28T13:12:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/preloader",
|
||||
@@ -5489,16 +5489,16 @@
|
||||
},
|
||||
{
|
||||
"name": "brianium/paratest",
|
||||
"version": "v7.19.1",
|
||||
"version": "v7.19.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paratestphp/paratest.git",
|
||||
"reference": "95b03194f4cdf5c83175ceead673e21cb66465e7"
|
||||
"reference": "7c6c29af7c4b406b49ce0c6b0a3a81d3684474e6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/paratestphp/paratest/zipball/95b03194f4cdf5c83175ceead673e21cb66465e7",
|
||||
"reference": "95b03194f4cdf5c83175ceead673e21cb66465e7",
|
||||
"url": "https://api.github.com/repos/paratestphp/paratest/zipball/7c6c29af7c4b406b49ce0c6b0a3a81d3684474e6",
|
||||
"reference": "7c6c29af7c4b406b49ce0c6b0a3a81d3684474e6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5512,7 +5512,7 @@
|
||||
"phpunit/php-code-coverage": "^12.5.3 || ^13.0.1",
|
||||
"phpunit/php-file-iterator": "^6.0.1 || ^7",
|
||||
"phpunit/php-timer": "^8 || ^9",
|
||||
"phpunit/phpunit": "^12.5.14 || ^13.0.5",
|
||||
"phpunit/phpunit": "^12.5.9 || ^13",
|
||||
"sebastian/environment": "^8.0.3 || ^9",
|
||||
"symfony/console": "^7.4.4 || ^8.0.4",
|
||||
"symfony/process": "^7.4.5 || ^8.0.5"
|
||||
@@ -5522,10 +5522,10 @@
|
||||
"ext-pcntl": "*",
|
||||
"ext-pcov": "*",
|
||||
"ext-posix": "*",
|
||||
"phpstan/phpstan": "^2.1.40",
|
||||
"phpstan/phpstan-deprecation-rules": "^2.0.4",
|
||||
"phpstan/phpstan-phpunit": "^2.0.16",
|
||||
"phpstan/phpstan-strict-rules": "^2.0.10",
|
||||
"phpstan/phpstan": "^2.1.38",
|
||||
"phpstan/phpstan-deprecation-rules": "^2.0.3",
|
||||
"phpstan/phpstan-phpunit": "^2.0.12",
|
||||
"phpstan/phpstan-strict-rules": "^2.0.8",
|
||||
"symfony/filesystem": "^7.4.0 || ^8.0.1"
|
||||
},
|
||||
"bin": [
|
||||
@@ -5566,7 +5566,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/paratestphp/paratest/issues",
|
||||
"source": "https://github.com/paratestphp/paratest/tree/v7.19.1"
|
||||
"source": "https://github.com/paratestphp/paratest/tree/v7.19.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -5578,7 +5578,7 @@
|
||||
"type": "paypal"
|
||||
}
|
||||
],
|
||||
"time": "2026-02-25T14:53:45+00:00"
|
||||
"time": "2026-02-06T10:53:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/annotations",
|
||||
@@ -9067,5 +9067,5 @@
|
||||
"platform-overrides": {
|
||||
"php": "8.3"
|
||||
},
|
||||
"plugin-api-version": "2.9.0"
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
|
||||
@@ -166,7 +166,6 @@ class Exception extends \Exception
|
||||
|
||||
/** Sites */
|
||||
public const string SITE_NOT_FOUND = 'site_not_found';
|
||||
public const string SITE_ALREADY_EXISTS = 'site_already_exists';
|
||||
public const string SITE_TEMPLATE_NOT_FOUND = 'site_template_not_found';
|
||||
|
||||
/** Functions */
|
||||
@@ -366,7 +365,6 @@ class Exception extends \Exception
|
||||
|
||||
/** Message */
|
||||
public const string MESSAGE_NOT_FOUND = 'message_not_found';
|
||||
public const string MESSAGE_ALREADY_EXISTS = 'message_already_exists';
|
||||
public const string MESSAGE_MISSING_TARGET = 'message_missing_target';
|
||||
public const string MESSAGE_ALREADY_SENT = 'message_already_sent';
|
||||
public const string MESSAGE_ALREADY_PROCESSING = 'message_already_processing';
|
||||
|
||||
+4
-71
@@ -24,7 +24,6 @@ use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class XList extends Action
|
||||
@@ -71,17 +70,15 @@ class XList extends Action
|
||||
->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
|
||||
->param('transactionId', null, fn (Database $dbForProject) => new Nullable(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'Transaction ID to read uncommitted changes within the transaction.', true, ['dbForProject'])
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->param('ttl', 0, new Range(min: 0, max: 86400), 'TTL (seconds) for cached responses when caching is enabled for select queries. Must be between 0 and 86400 (24 hours).', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('user')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('transactionState')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, array $queries, ?string $transactionId, bool $includeTotal, int $ttl, UtopiaResponse $response, Database $dbForProject, Document $user, StatsUsage $queueForStatsUsage, TransactionState $transactionState, Authorization $authorization): void
|
||||
public function action(string $databaseId, string $collectionId, array $queries, ?string $transactionId, bool $includeTotal, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, TransactionState $transactionState, Authorization $authorization): void
|
||||
{
|
||||
$isAPIKey = User::isApp($authorization->getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
|
||||
@@ -132,73 +129,9 @@ class XList extends Action
|
||||
$documents = $transactionState->listDocuments($collectionTableId, $transactionId, $queries);
|
||||
$total = $includeTotal ? $transactionState->countDocuments($collectionTableId, $transactionId, $queries) : 0;
|
||||
} elseif (! empty($selectQueries)) {
|
||||
|
||||
if ((int)$ttl > 0) {
|
||||
$serializedQueries = [];
|
||||
foreach ($queries as $query) {
|
||||
$serializedQueries[] = $query instanceof Query ? $query->toArray() : $query;
|
||||
}
|
||||
|
||||
$hostname = $dbForProject->getAdapter()->getHostname();
|
||||
$roles = $dbForProject->getAuthorization()->getRoles();
|
||||
$schemaHash = \md5(\json_encode($collection->getAttribute('attributes', [])) . \json_encode($collection->getAttribute('indexes', [])));
|
||||
$cacheKeyBase = \sprintf(
|
||||
'%s-cache-%s:%s:%s:collection:%s:%s:user:%s:%s',
|
||||
$dbForProject->getCacheName(),
|
||||
$hostname ?? '',
|
||||
$dbForProject->getNamespace(),
|
||||
$dbForProject->getTenant(),
|
||||
$collectionId,
|
||||
$schemaHash,
|
||||
\md5(\json_encode($roles)),
|
||||
\md5(\json_encode($serializedQueries))
|
||||
);
|
||||
|
||||
$documentsCacheKey = $cacheKeyBase . ':documents';
|
||||
$totalCacheKey = $cacheKeyBase . ':total';
|
||||
|
||||
$documentsCacheHit = $totalDocumentsCacheHit = false;
|
||||
|
||||
$cachedDocuments = $dbForProject->getCache()->load($documentsCacheKey, $ttl);
|
||||
|
||||
if ($cachedDocuments !== null &&
|
||||
$cachedDocuments !== false &&
|
||||
\is_array($cachedDocuments)) {
|
||||
$documents = \array_map(function ($doc) {
|
||||
return new Document($doc);
|
||||
}, $cachedDocuments);
|
||||
$documentsCacheHit = true;
|
||||
} else {
|
||||
$documents = $dbForProject->find($collectionTableId, $queries);
|
||||
|
||||
// Convert Document objects to arrays for caching
|
||||
$documentsArray = \array_map(function ($doc) {
|
||||
return $doc->getArrayCopy();
|
||||
}, $documents);
|
||||
$dbForProject->getCache()->save($documentsCacheKey, $documentsArray);
|
||||
}
|
||||
|
||||
if ($includeTotal) {
|
||||
$cachedTotal = $dbForProject->getCache()->load($totalCacheKey, $ttl);
|
||||
if ($cachedTotal !== null && $cachedTotal !== false) {
|
||||
$total = $cachedTotal;
|
||||
$totalDocumentsCacheHit = true;
|
||||
} else {
|
||||
$total = $dbForProject->count($collectionTableId, $queries, APP_LIMIT_COUNT);
|
||||
$dbForProject->getCache()->save($totalCacheKey, $total);
|
||||
}
|
||||
} else {
|
||||
$total = 0;
|
||||
}
|
||||
|
||||
$response->addHeader('X-Appwrite-Cache', $documentsCacheHit ? 'hit' : 'miss');
|
||||
|
||||
} else {
|
||||
// has selects, allow relationship on documents
|
||||
$documents = $dbForProject->find($collectionTableId, $queries);
|
||||
$total = $includeTotal ? $dbForProject->count($collectionTableId, $queries, APP_LIMIT_COUNT) : 0;
|
||||
}
|
||||
|
||||
// has selects, allow relationship on documents
|
||||
$documents = $dbForProject->find($collectionTableId, $queries);
|
||||
$total = $includeTotal ? $dbForProject->count($collectionTableId, $queries, APP_LIMIT_COUNT) : 0;
|
||||
} else {
|
||||
// has no selects, disable relationship loading on documents
|
||||
/* @type Document[] $documents */
|
||||
|
||||
@@ -14,7 +14,6 @@ use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class XList extends DocumentXList
|
||||
@@ -57,10 +56,8 @@ class XList extends DocumentXList
|
||||
->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
|
||||
->param('transactionId', null, fn (Database $dbForProject) => new Nullable(new UID($dbForProject->getAdapter()->getMaxUIDLength())), 'Transaction ID to read uncommitted changes within the transaction.', true, ['dbForProject'])
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->param('ttl', 0, new Range(min: 0, max: 86400), 'TTL (seconds) for cached responses when caching is enabled for select queries. Must be between 0 and 86400 (24 hours).', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('user')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('transactionState')
|
||||
->inject('authorization')
|
||||
|
||||
@@ -90,15 +90,6 @@ class Delete extends Base
|
||||
}
|
||||
$status = $execution->getAttribute('status');
|
||||
|
||||
// Treat timed-out executions as failed so they can be deleted.
|
||||
if ($status === 'waiting' || $status === 'processing') {
|
||||
$timeout = $function->getAttribute('timeout', 900);
|
||||
$elapsed = \time() - \strtotime($execution->getCreatedAt());
|
||||
if ($elapsed >= $timeout) {
|
||||
$status = 'failed';
|
||||
}
|
||||
}
|
||||
|
||||
if (!in_array($status, ['completed', 'failed', 'scheduled'])) {
|
||||
throw new Exception(Exception::EXECUTION_IN_PROGRESS);
|
||||
}
|
||||
|
||||
@@ -82,16 +82,6 @@ class Get extends Base
|
||||
throw new Exception(Exception::EXECUTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Override status in response if the execution is stuck in waiting/processing beyond the function timeout.
|
||||
$status = $execution->getAttribute('status', '');
|
||||
if ($status === 'waiting' || $status === 'processing') {
|
||||
$timeout = $function->getAttribute('timeout', 900);
|
||||
$elapsed = \time() - \strtotime($execution->getCreatedAt());
|
||||
if ($elapsed >= $timeout) {
|
||||
$execution->setAttribute('status', 'failed');
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic($execution, Response::MODEL_EXECUTION);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ use Appwrite\Utopia\Database\Documents\User;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Executions;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Order as OrderException;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
@@ -111,35 +110,6 @@ class XList extends Base
|
||||
$cursor->setValue($cursorDocument);
|
||||
}
|
||||
|
||||
// Calculate the cutoff datetime before which a waiting/processing execution is considered timed out.
|
||||
$timeout = $function->getAttribute('timeout', 900);
|
||||
$thresholdDate = new \DateTime("-{$timeout} seconds");
|
||||
$threshold = DateTime::format($thresholdDate);
|
||||
|
||||
// Capture what statuses the caller explicitly requested, before we mutate the query.
|
||||
$requestedStatuses = [];
|
||||
foreach ($queries as $query) {
|
||||
if ($query->getMethod() === Query::TYPE_EQUAL && $query->getAttribute() === 'status') {
|
||||
$requestedStatuses = [...$requestedStatuses, ...$query->getValues()];
|
||||
}
|
||||
}
|
||||
|
||||
// If the caller is filtering by 'failed', expand the DB query to also return
|
||||
// waiting/processing executions created before the timeout threshold, so timed-out
|
||||
// executions that were never marked failed in the DB are included in the results.
|
||||
foreach ($queries as $index => $query) {
|
||||
if ($query->getMethod() === Query::TYPE_EQUAL && $query->getAttribute() === 'status' && \in_array('failed', $query->getValues())) {
|
||||
$queries[$index] = Query::or([
|
||||
$query,
|
||||
Query::and([
|
||||
Query::equal('status', ['waiting', 'processing']),
|
||||
Query::createdBefore($threshold),
|
||||
]),
|
||||
]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
|
||||
try {
|
||||
@@ -149,20 +119,6 @@ class XList extends Base
|
||||
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
|
||||
}
|
||||
|
||||
// Override status in response for timed-out executions, but only when the caller
|
||||
// did not explicitly request a non-failed status (e.g. waiting/processing).
|
||||
if (empty(\array_diff($requestedStatuses, ['failed']))) {
|
||||
foreach ($results as $execution) {
|
||||
$status = $execution->getAttribute('status', '');
|
||||
if ($status === 'waiting' || $status === 'processing') {
|
||||
$elapsed = \time() - \strtotime($execution->getCreatedAt());
|
||||
if ($elapsed >= $timeout) {
|
||||
$execution->setAttribute('status', 'failed');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'executions' => $results,
|
||||
'total' => $total,
|
||||
|
||||
@@ -71,16 +71,6 @@ class Get extends Base
|
||||
throw new Exception(Exception::LOG_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Override status in response if the log is stuck in waiting/processing beyond the site timeout.
|
||||
$status = $log->getAttribute('status', '');
|
||||
if ($status === 'waiting' || $status === 'processing') {
|
||||
$timeout = $site->getAttribute('timeout', 30);
|
||||
$elapsed = \time() - \strtotime($log->getCreatedAt());
|
||||
if ($elapsed >= $timeout) {
|
||||
$log->setAttribute('status', 'failed');
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic($log, Response::MODEL_EXECUTION); //TODO: Change to model log, but model log already exists - decide what to do
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ use Appwrite\Utopia\Database\Validator\Queries\Executions;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Logs;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Order as OrderException;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
@@ -100,35 +99,6 @@ class XList extends Base
|
||||
$cursor->setValue($cursorDocument);
|
||||
}
|
||||
|
||||
// Calculate the cutoff datetime before which a waiting/processing log is considered timed out.
|
||||
$timeout = $site->getAttribute('timeout', 30);
|
||||
$thresholdDate = new \DateTime("-{$timeout} seconds");
|
||||
$threshold = DateTime::format($thresholdDate);
|
||||
|
||||
// Capture what statuses the caller explicitly requested, before we mutate the query.
|
||||
$requestedStatuses = [];
|
||||
foreach ($queries as $query) {
|
||||
if ($query->getMethod() === Query::TYPE_EQUAL && $query->getAttribute() === 'status') {
|
||||
$requestedStatuses = [...$requestedStatuses, ...$query->getValues()];
|
||||
}
|
||||
}
|
||||
|
||||
// If the caller is filtering by 'failed', expand the DB query to also return
|
||||
// waiting/processing logs created before the timeout threshold, so timed-out
|
||||
// logs that were never marked failed in the DB are included in the results.
|
||||
foreach ($queries as $index => $query) {
|
||||
if ($query->getMethod() === Query::TYPE_EQUAL && $query->getAttribute() === 'status' && \in_array('failed', $query->getValues())) {
|
||||
$queries[$index] = Query::or([
|
||||
$query,
|
||||
Query::and([
|
||||
Query::equal('status', ['waiting', 'processing']),
|
||||
Query::createdBefore($threshold),
|
||||
]),
|
||||
]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
|
||||
try {
|
||||
@@ -138,20 +108,6 @@ class XList extends Base
|
||||
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
|
||||
}
|
||||
|
||||
// Override status in response for timed-out logs, but only when the caller
|
||||
// did not explicitly request a non-failed status (e.g. waiting/processing).
|
||||
if (empty(\array_diff($requestedStatuses, ['failed']))) {
|
||||
foreach ($results as $log) {
|
||||
$status = $log->getAttribute('status', '');
|
||||
if ($status === 'waiting' || $status === 'processing') {
|
||||
$elapsed = \time() - \strtotime($log->getCreatedAt());
|
||||
if ($elapsed >= $timeout) {
|
||||
$log->setAttribute('status', 'failed');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'executions' => $results,
|
||||
'total' => $total,
|
||||
|
||||
@@ -14,7 +14,6 @@ use Appwrite\Utopia\Response;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
@@ -137,7 +136,7 @@ class Create extends Base
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'When connecting to VCS (Version Control System), you need to provide "installationId" and "providerBranch".');
|
||||
}
|
||||
|
||||
$site = new Document([
|
||||
$site = $dbForProject->createDocument('sites', new Document([
|
||||
'$id' => $siteId,
|
||||
'enabled' => $enabled,
|
||||
'live' => true,
|
||||
@@ -167,17 +166,13 @@ class Create extends Base
|
||||
'runtimeSpecification' => $specification,
|
||||
'buildRuntime' => $buildRuntime,
|
||||
'adapter' => $adapter,
|
||||
]);
|
||||
|
||||
try {
|
||||
$site = $dbForProject->createDocument('sites', $site);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::SITE_ALREADY_EXISTS);
|
||||
}
|
||||
]));
|
||||
|
||||
// Git connect logic
|
||||
if (!empty($providerRepositoryId)) {
|
||||
$teamId = $project->getAttribute('teamId', '');
|
||||
$repository = new Document([
|
||||
|
||||
$repository = $dbForPlatform->createDocument('repositories', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => $this->getPermissions($teamId, $project->getId()),
|
||||
'installationId' => $installation->getId(),
|
||||
@@ -189,8 +184,8 @@ class Create extends Base
|
||||
'resourceInternalId' => $site->getSequence(),
|
||||
'resourceType' => 'site',
|
||||
'providerPullRequestIds' => []
|
||||
]);
|
||||
$repository = $dbForPlatform->createDocument('repositories', $repository);
|
||||
]));
|
||||
|
||||
$site->setAttribute('repositoryId', $repository->getId());
|
||||
$site->setAttribute('repositoryInternalId', $repository->getSequence());
|
||||
}
|
||||
|
||||
@@ -190,9 +190,11 @@ class Update extends Base
|
||||
$repositoryInternalId = '';
|
||||
}
|
||||
|
||||
// Git connect logic
|
||||
if (!$isConnected && !empty($providerRepositoryId)) {
|
||||
$teamId = $project->getAttribute('teamId', '');
|
||||
$repository = new Document([
|
||||
|
||||
$repository = $dbForPlatform->createDocument('repositories', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => $this->getPermissions($teamId, $project->getId()),
|
||||
'installationId' => $installation->getId(),
|
||||
@@ -204,8 +206,8 @@ class Update extends Base
|
||||
'resourceInternalId' => $site->getSequence(),
|
||||
'resourceType' => 'site',
|
||||
'providerPullRequestIds' => []
|
||||
]);
|
||||
$repository = $dbForPlatform->createDocument('repositories', $repository);
|
||||
]));
|
||||
|
||||
$repositoryId = $repository->getId();
|
||||
$repositoryInternalId = $repository->getSequence();
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Event\Webhook;
|
||||
use Appwrite\Extend\Exception as AppwriteException;
|
||||
use Appwrite\Utopia\Response\Model\Execution;
|
||||
use Exception;
|
||||
use Executor\Executor;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Console;
|
||||
@@ -72,10 +73,7 @@ class Functions extends Action
|
||||
$payload = $message->getPayload() ?? [];
|
||||
|
||||
if (empty($payload)) {
|
||||
throw new AppwriteException(
|
||||
AppwriteException::GENERAL_ARGUMENT_INVALID,
|
||||
'Functions worker: missing payload in schedule execution'
|
||||
);
|
||||
throw new Exception('Missing payload');
|
||||
}
|
||||
|
||||
$type = $payload['type'] ?? '';
|
||||
@@ -394,10 +392,7 @@ class Functions extends Action
|
||||
$runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []);
|
||||
|
||||
if (!\array_key_exists($function->getAttribute('runtime'), $runtimes)) {
|
||||
throw new AppwriteException(
|
||||
AppwriteException::FUNCTION_RUNTIME_UNSUPPORTED,
|
||||
\sprintf('Runtime "%s" is not supported', $function->getAttribute('runtime', '')),
|
||||
);
|
||||
throw new Exception('Runtime "' . $function->getAttribute('runtime', '') . '" is not supported');
|
||||
}
|
||||
|
||||
$runtime = $runtimes[$function->getAttribute('runtime')];
|
||||
@@ -645,7 +640,7 @@ class Functions extends Action
|
||||
if (!empty($error)) {
|
||||
throw new AppwriteException(
|
||||
AppwriteException::GENERAL_SERVER_ERROR,
|
||||
'Function execution failed: ' . ($error ?: 'No error message provided'),
|
||||
$error ?: 'Function execution failed with no error message',
|
||||
$errorCode
|
||||
);
|
||||
}
|
||||
|
||||
@@ -129,15 +129,7 @@ class Executor
|
||||
'timeout' => $timeout
|
||||
];
|
||||
|
||||
// Wrap callback to match two-arg signature (?string $data, ?array $headers)
|
||||
// getLogs only cares about data chunks, not headers signal
|
||||
$wrappedCallback = function (?string $data, ?array $headers) use ($callback): void {
|
||||
if ($data !== null) {
|
||||
$callback($data);
|
||||
}
|
||||
};
|
||||
|
||||
$this->call($this->endpoint, self::METHOD_GET, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout, $wrappedCallback);
|
||||
$this->call($this->endpoint, self::METHOD_GET, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -212,8 +204,7 @@ class Executor
|
||||
bool $logging,
|
||||
string $runtimeEntrypoint = '',
|
||||
?int $requestTimeout = null,
|
||||
string $responseFormat = self::RESPONSE_FORMAT_OBJECT_HEADERS,
|
||||
?callable $streamCallback = null,
|
||||
string $responseFormat = self::RESPONSE_FORMAT_OBJECT_HEADERS
|
||||
) {
|
||||
$runtimeId = "$projectId-$deploymentId";
|
||||
$route = '/runtimes/' . $runtimeId . '/executions';
|
||||
@@ -251,13 +242,6 @@ class Executor
|
||||
$requestTimeout = $timeout + 15;
|
||||
}
|
||||
|
||||
// Streaming path: tell executor to stream and forward chunks via callback
|
||||
if ($streamCallback !== null) {
|
||||
$params['stream'] = 'true';
|
||||
$this->call($this->endpoint, self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId, 'content-type' => 'multipart/form-data', 'x-executor-response-format' => $responseFormat ], $params, false, $requestTimeout, $streamCallback);
|
||||
return [];
|
||||
}
|
||||
|
||||
$response = $this->call($this->endpoint, self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId, 'content-type' => 'multipart/form-data', 'accept' => 'multipart/form-data', 'x-executor-response-format' => $responseFormat ], $params, true, $requestTimeout);
|
||||
|
||||
$status = $response['headers']['status-code'];
|
||||
@@ -353,14 +337,8 @@ class Executor
|
||||
if (isset($callback)) {
|
||||
$headers[] = 'accept: text/event-stream';
|
||||
|
||||
$callbackHeadersFired = false;
|
||||
$handleEvent = function ($ch, $data) use ($callback, &$callbackHeadersFired, &$responseHeaders) {
|
||||
if (!$callbackHeadersFired) {
|
||||
// Fire headers signal once before the first data chunk
|
||||
$callback(null, $responseHeaders);
|
||||
$callbackHeadersFired = true;
|
||||
}
|
||||
$callback($data, null);
|
||||
$handleEvent = function ($ch, $data) use ($callback) {
|
||||
$callback($data);
|
||||
return \strlen($data);
|
||||
};
|
||||
|
||||
|
||||
@@ -3267,205 +3267,6 @@ trait DatabasesBase
|
||||
], $this->getHeaders()));
|
||||
}
|
||||
|
||||
public function testListDocumentsWithCache(): void
|
||||
{
|
||||
$data = $this->setupDocuments();
|
||||
$databaseId = $data['databaseId'];
|
||||
$docIds = $data['documentIds'];
|
||||
|
||||
// Filter to setup documents only, since other tests may have created additional docs in this collection.
|
||||
$baseQueries = [
|
||||
Query::equal('$id', $docIds)->toString(),
|
||||
Query::select(['title', 'releaseYear', '$id'])->toString(),
|
||||
Query::orderAsc('releaseYear')->toString(),
|
||||
];
|
||||
|
||||
// 1. Using cache with select queries, first request should miss cache.
|
||||
$documents1 = $this->client->call(Client::METHOD_GET, $this->getRecordUrl($databaseId, $data['moviesId']), array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => $baseQueries,
|
||||
'ttl' => 30,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $documents1['headers']['status-code']);
|
||||
$this->assertEquals(3, $documents1['body']['total']);
|
||||
$this->assertCount(3, $documents1['body'][$this->getRecordResource()]);
|
||||
$this->assertEquals(1944, $documents1['body'][$this->getRecordResource()][0]['releaseYear']);
|
||||
$this->assertEquals(2017, $documents1['body'][$this->getRecordResource()][1]['releaseYear']);
|
||||
$this->assertEquals(2019, $documents1['body'][$this->getRecordResource()][2]['releaseYear']);
|
||||
$this->assertArrayHasKey('title', $documents1['body'][$this->getRecordResource()][0]);
|
||||
$this->assertArrayHasKey('releaseYear', $documents1['body'][$this->getRecordResource()][0]);
|
||||
$this->assertArrayHasKey('$id', $documents1['body'][$this->getRecordResource()][0]);
|
||||
$this->assertArrayHasKey('x-appwrite-cache', $documents1['headers']);
|
||||
$this->assertEquals('miss', $documents1['headers']['x-appwrite-cache']);
|
||||
|
||||
// 2. Using cache with same select queries, should return cached results.
|
||||
$documents2 = $this->client->call(Client::METHOD_GET, $this->getRecordUrl($databaseId, $data['moviesId']), array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => $baseQueries,
|
||||
'ttl' => 30,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $documents2['headers']['status-code']);
|
||||
$this->assertEquals(3, $documents2['body']['total']);
|
||||
$this->assertCount(3, $documents2['body'][$this->getRecordResource()]);
|
||||
$this->assertEquals($documents1['body'][$this->getRecordResource()][0]['$id'], $documents2['body'][$this->getRecordResource()][0]['$id']);
|
||||
$this->assertEquals($documents1['body'][$this->getRecordResource()][0]['title'], $documents2['body'][$this->getRecordResource()][0]['title']);
|
||||
$this->assertEquals($documents1['body'][$this->getRecordResource()][0]['releaseYear'], $documents2['body'][$this->getRecordResource()][0]['releaseYear']);
|
||||
$this->assertArrayHasKey('x-appwrite-cache', $documents2['headers']);
|
||||
$this->assertEquals('hit', $documents2['headers']['x-appwrite-cache']);
|
||||
|
||||
// 3. Using cache with same select queries but total is false, should return cached results just for documents.
|
||||
$documents3 = $this->client->call(Client::METHOD_GET, $this->getRecordUrl($databaseId, $data['moviesId']), array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => $baseQueries,
|
||||
'ttl' => 30,
|
||||
'total' => false,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $documents3['headers']['status-code']);
|
||||
$this->assertCount(3, $documents3['body'][$this->getRecordResource()]);
|
||||
$this->assertEquals($documents3['body'][$this->getRecordResource()][0]['$id'], $documents1['body'][$this->getRecordResource()][0]['$id']);
|
||||
$this->assertEquals($documents3['body'][$this->getRecordResource()][0]['title'], $documents1['body'][$this->getRecordResource()][0]['title']);
|
||||
$this->assertEquals($documents3['body'][$this->getRecordResource()][0]['releaseYear'], $documents1['body'][$this->getRecordResource()][0]['releaseYear']);
|
||||
$this->assertEquals(0, $documents3['body']['total']);
|
||||
$this->assertArrayHasKey('x-appwrite-cache', $documents3['headers']);
|
||||
$this->assertEquals('hit', $documents3['headers']['x-appwrite-cache']);
|
||||
|
||||
// 4. Using cache with different select queries, should miss cache.
|
||||
$documents4 = $this->client->call(Client::METHOD_GET, $this->getRecordUrl($databaseId, $data['moviesId']), array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::equal('$id', $docIds)->toString(),
|
||||
Query::select(['title'])->toString(),
|
||||
Query::orderAsc('releaseYear')->toString(),
|
||||
],
|
||||
'ttl' => 10,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $documents4['headers']['status-code']);
|
||||
$this->assertEquals(3, $documents4['body']['total']);
|
||||
$this->assertCount(3, $documents4['body'][$this->getRecordResource()]);
|
||||
$this->assertEquals($documents4['body'][$this->getRecordResource()][0]['title'], $documents1['body'][$this->getRecordResource()][0]['title']);
|
||||
$this->assertEquals($documents4['body'][$this->getRecordResource()][1]['title'], $documents1['body'][$this->getRecordResource()][1]['title']);
|
||||
$this->assertEquals($documents4['body'][$this->getRecordResource()][2]['title'], $documents1['body'][$this->getRecordResource()][2]['title']);
|
||||
$this->assertArrayHasKey('x-appwrite-cache', $documents4['headers']);
|
||||
$this->assertEquals('miss', $documents4['headers']['x-appwrite-cache']);
|
||||
|
||||
// 5. Not using cache at all
|
||||
$documents5 = $this->client->call(Client::METHOD_GET, $this->getRecordUrl($databaseId, $data['moviesId']), array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::equal('$id', $docIds)->toString(),
|
||||
Query::select(['title', 'releaseYear', '$id'])->toString(),
|
||||
Query::orderAsc('releaseYear')->toString(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $documents5['headers']['status-code']);
|
||||
$this->assertCount(3, $documents5['body'][$this->getRecordResource()]);
|
||||
$this->assertEquals(1944, $documents5['body'][$this->getRecordResource()][0]['releaseYear']);
|
||||
$this->assertArrayNotHasKey('x-appwrite-cache', $documents5['headers']);
|
||||
|
||||
sleep(10);
|
||||
|
||||
// 6. Using cache with same select queries but passed ttl time, should miss cache.
|
||||
$documents6 = $this->client->call(Client::METHOD_GET, $this->getRecordUrl($databaseId, $data['moviesId']), array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => $baseQueries,
|
||||
'ttl' => 10,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $documents6['headers']['status-code']);
|
||||
$this->assertCount(3, $documents6['body'][$this->getRecordResource()]);
|
||||
$this->assertArrayHasKey('title', $documents6['body'][$this->getRecordResource()][0]);
|
||||
$this->assertArrayHasKey('releaseYear', $documents6['body'][$this->getRecordResource()][0]);
|
||||
$this->assertArrayHasKey('$id', $documents6['body'][$this->getRecordResource()][0]);
|
||||
$this->assertArrayHasKey('x-appwrite-cache', $documents6['headers']);
|
||||
$this->assertEquals('miss', $documents6['headers']['x-appwrite-cache']);
|
||||
}
|
||||
|
||||
public function testListDocumentsCacheBustedByAttributeChange(): void
|
||||
{
|
||||
$data = $this->setupDocuments();
|
||||
$databaseId = $data['databaseId'];
|
||||
$docIds = $data['documentIds'];
|
||||
|
||||
// Use different select queries from testListDocumentsWithCache to avoid cache key collision.
|
||||
$queries = [
|
||||
Query::equal('$id', $docIds)->toString(),
|
||||
Query::select(['title', '$id'])->toString(),
|
||||
Query::orderAsc('$createdAt')->toString(),
|
||||
];
|
||||
|
||||
// 1. First request should miss cache.
|
||||
$documents1 = $this->client->call(Client::METHOD_GET, $this->getRecordUrl($databaseId, $data['moviesId']), array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => $queries,
|
||||
'ttl' => 300,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $documents1['headers']['status-code']);
|
||||
$this->assertArrayHasKey('x-appwrite-cache', $documents1['headers']);
|
||||
$this->assertEquals('miss', $documents1['headers']['x-appwrite-cache']);
|
||||
|
||||
// 2. Same request should hit cache.
|
||||
$documents2 = $this->client->call(Client::METHOD_GET, $this->getRecordUrl($databaseId, $data['moviesId']), array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => $queries,
|
||||
'ttl' => 300,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $documents2['headers']['status-code']);
|
||||
$this->assertArrayHasKey('x-appwrite-cache', $documents2['headers']);
|
||||
$this->assertEquals('hit', $documents2['headers']['x-appwrite-cache']);
|
||||
|
||||
// 3. Add a new attribute to the collection, which updates the collection's $updatedAt.
|
||||
$attribute = $this->client->call(Client::METHOD_POST, $this->getSchemaUrl($databaseId, $data['moviesId']) . '/string', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'key' => 'cacheTestAttr',
|
||||
'size' => 64,
|
||||
'required' => false,
|
||||
]);
|
||||
|
||||
$this->assertEquals(202, $attribute['headers']['status-code']);
|
||||
|
||||
// Wait for the attribute to be ready
|
||||
$this->waitForAttribute($databaseId, $data['moviesId'], 'cacheTestAttr');
|
||||
|
||||
// 4. Same request should now miss cache because collection $updatedAt changed.
|
||||
$documents3 = $this->client->call(Client::METHOD_GET, $this->getRecordUrl($databaseId, $data['moviesId']), array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => $queries,
|
||||
'ttl' => 300,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $documents3['headers']['status-code']);
|
||||
$this->assertArrayHasKey('x-appwrite-cache', $documents3['headers']);
|
||||
$this->assertEquals('miss', $documents3['headers']['x-appwrite-cache']);
|
||||
}
|
||||
|
||||
public function testGetDocument(): void
|
||||
{
|
||||
$data = $this->getDocumentsList();
|
||||
|
||||
Reference in New Issue
Block a user